
LLM - мощный инструмент, но его эффективность в продакшене зависит не от одного «хитрого промпта», а от всей архитектуры: что мы даём модели, как управляем её рассуждением и как проверяем/обрабатываем результат. В этой статье - компактная карта паттернов, разбитая по этапам конвейера:
Input -> Reasoning -> Output.Введение
Статей про LLM - вагон, и у всех свои "трюки". Мне не хватало схемы, которая раскладывала бы эти "трюки" по полочкам.Это моя попытка такую схему составить. Я сгруппировал основные известные мне паттерны по их месту в конвейере обработки запроса. Это не истина в последней инстанции, а просто карта местности, какой ее вижу я.
С чем мы работаем? Наши инструменты.
В центре всего стоит LLM. Ее задача - предсказывать следующее слово на основе предыдущих. Мы даем ей текст, а она, токен за токеном, его продолжает.Но есть важный нюанс: LLM на выходе дает не сам токен, а распределение вероятностей по всему своему словарю. А уже отдельный алгоритм сэмплирования (т.е. выбора следующего токена) решает, какой токен выбрать из этого распределения. В простейшем случае мы всегда берем самый вероятный (greedy-подход), но чаще всего мы управляем процессом с помощью параметров:
temperature: Наша "ручка креативности". Низкая температура (близкая к 0) делает ответы более предсказуемыми и фактическими, высокая - более разнообразными и случайными.top_p / top_k: Ограничивают выборку только самыми вероятными токенами, отсекая "хвост" распределения.
Structured Outputs. Вместо того чтобы просить LLM вернуть JSON и потом молиться, чтобы он был валидным, мы вмешиваемся в сам процесс сэмплирования.На каждом шаге, перед выбором следующего токена, мы применяем "маску", которая на лету запрещает все токены, нарушающие нашу заранее определенную структуру. Модель хотела бы сгенерировать что-то не то, но мы ей просто не позволяем.
Например: мы описываем нужную нам структуру данных с помощью
Pydantic и передаем ее как параметр при генерации, а движок инференса превращает эту схему в набор правил. Семплирование LLM следует этим правилам и просто физически не может сгенерировать токен, который приведет к невалидному JSON, неправильному типу данных, неверной структуре или выходу за пределы заданного списка.Мы увидим дальше, как этот низкоуровневый механизм используется в сильных паттернах.
Еще один инструмент в нашем ящике это модели-эмбеддеры. В отличие от генераторов, они не пишут текст, а "понимают" его. Они превращают фрагмент текста в числовой вектор (эмбеддинг) так, чтобы похожие по смыслу тексты имели похожие векторы. Это позволяет нам сравнивать текст по смыслу.
Входные данные
Все начинается с контекста. Что мы "покажем" модели перед тем, как она начнет генерировать ответ? От этого зависит 90% успеха. На этом этапе мы формируем входные данные, которые получит LLM.Промптинг
Это базовый уровень. Тут мы ставим задачу для LLM. В коде промпт обычно выглядит как большая строка, часто с плейсхолдерами для переменных. Перед отправкой в модель мы форматируем этот шаблон, подставляем в него запрос пользователя, найденные через RAG документы или примеры хороших ответов (few-shot prompting).Именно здесь мы формулируем задачу для LLM, указываем стиль ответа, формат вывода.
Хороший материал на эту тему: Руководство по промпт-инжинирингу – Nextra
RAG
Retrieval Augmented Generation - генерация дополненная поиском. Выполняем поиск информации, добавляем ее в контекст, генерируем ответ на основе найденной информации.Тема очень популярная, различных подходов и хитростей тут много, можно набрать на еще одну статью.
Memory
Цель - дать LLM память, хотя бы в рамках одного диалога.Возможные подходы:
- Держать в контексте полную историю диалога - вычислительно дорого, длина ограничена.
- Использовать скользящее окно диалога с несколькими последними сообщениями.
- Сжатие диалога - иногда просим LLM сжать историю сообщений.
- Дополнительные системы - например, даем LLM возможность сохранять факты о пользователе, как в фиче "Память" у веб версии ChatGPT. Пример "дополнительной системы" для памяти - Zep: A Temporal Knowledge Graph Architecture for Agent Memory
Управление мышлением модели
На этом этапе мы управляем процессом ответа LLM. Это паттерны, которые заставляют модель не просто генерировать текст, а решать задачи.Декомпозиция задачи
Частая проблема при решении сложных задач - модель пытается ответить сходу и ошибается. Мы можем разбить задачу на небольшие шаги. Самый простой вариант -Chain of Thought prompting (CoT, цепочка мыслей) . Мы просто добавляем инструкцию вида "думай по шагам".Другой вариант - мы вручную прописываем для модели жесткий план, которому она должна следовать при рассуждении.
Агенты и инструменты
Модель следует не заданному плану решения, а строит его сама, в реальном времени. Агент - это LLM, у которой есть доступ к набору инструментов (Tools), например:- Поиск в интернете
- API вашей CRM
- Интерпретатор кода
- Вызов другой LLM системы Сам механизм, который позволяет модели "вызывать" различные инструменты называется
Function Calling. Например:ReActпромптинг - просим модель сначала порассуждать, а затем вызвать подходящую функцию. (Вызов функции может выглядеть как сигнатура python функции в ответе модели, или как особый токен при генерации) Несколько агентов можно объединять в одну мультиагентную систему для совместного решения задач.
Routing
Простой, но полезный паттерн. Просим LLM быть диспетчером, чтобы решить по какой ветке логики отправить запрос дальше.Например: у нас в RAG сценарии две разных базы знаний и нам нужно выбрать релевантную.
Schema Guided Reasoning
Это не столько отдельный паттерн, сколько дополнительный инструмент который прокачивает все остальные паттерны.Суть
Schema Guided Reasoning (SGR) - в использовании Structured Outputs не просто для форматирования финального ответа, а для управления самим процессом мышления модели. Мы описываем логику рассуждений в виде строгой схемы, например Pydantic-класса, и модель вынуждена заполнять схему шаг за шагом. Это гарантирует нам, что все этапы рассуждений будут пройдены, ничего не будет пропущено.Используя
Enum, вложенные классы и даже динамическое создание классов мы можем упаковывать сложные цепочки рассуждений в один запрос к LLM.Подробнее: https://abdullin.com/schema-guided-reasoning/patterns
Выход: работаем с результатом генерации
Форматирование и валидация
Просим LLM указывать результат работы в определенном формате, напримерXML или JSON. (Для JSON можно использовать Structured Outputs чтобы гарантировать корректность)В случае когда не получилось распарсить ответ, можно использовать исправляющий промпт вида
"твой ответ дал ошибку: {ошибка}, исправь форматирование своего ответа..."Guardrails
Ответ может быть идеальным по форме, но недопустимым по содержанию.Guardrails это фильтр безопасности, который может работать:- На входе - опасные запросы от пользователя
- На выходе - недопустимые ответы нашей системы Может быть реализовано разными способами:
- Обычные regex фильтры
- Использование специализированных моделей-классификаторов (например
Llama Guard) - Использование второй LLM для оценки ответа
Caching
Если мы получаем запрос на который уже отвечали ранее, то ответ можно просто достать из кеша. Это экономит время и вычисления. Кеширование может быть как на основе полного совпадения строки, так и на основе семантического сходства, для этого используются эмбеддинги запросов.Пример - GitHub - zilliztech/GPTCache: Semantic cache for LLMs. Fully integrated with LangChain and llama_index.
Жизненный цикл
Тут не про обработку одного запроса, а про принципы разработки системы вцелом.Observability (наблюдаемость)
Нельзя починить то, что невидно.Observability - возможность заглянуть под капот приложения. Не просто запрос и финальный ответ, а весь путь:- В какую подсистему
Routerотправил запрос? - Какие документы извлек RAG?
- Какие "мысли" думал агент когда выбирал инструмент?
- Сколько времени и токенов понадобилось на каждом шаге? Это основа для отладки и мониторинга, а также главный источник данных для
Data Flywheel. Инструмент: Home - Phoenix
Evaluation (Оценка)
"Как понять что мои изменения не сломали то, что уже работало?" - Вместо ручной проверки "на глазок", мы создаем наборы данных и прогоняем их через систему, замеряя метрики. Самый мощный подход здесь этоLLM-as-a-Judge. Для оценки качества ответа нашей системы мы используем более мощную LLM, которая сравнивает результат с эталоном или оценивает по заданным критериям.Fine-tuning (Дообучение)
Иногда промптов и RAG недостаточно, чтобы добиться нужного поведения системы.Fine-tuning это процесс дополнительного обучения LLM модели на вашем собственном датасете.Data Flywheel (Маховик данных)
Система должна самосовершенствоваться. Цикл выглядит так:- Пользовали взаимодействуют с системой
- Мы собираем запросы, ответы, обратную связь (лайки, дизлайки, копирование ответа, правки)
- Эти данные становятся "топливом" - мы используем их для создания тестовых и обучающих датасетов
- Обновленная, более умная система выкатывается в продакшен И так по кругу.
Заключение
Я попробовал описать паттерны работы с LLM так, как они выглядят с моей позиции. Я уверен, что далеко не все расписал идеально, это скорее приглашение к диалогу, чем готовая классификация.Какими паттернами пользуетесь вы? Чего не хватает/лишнее в моей схеме? Жду ваших мнений в комментариях.
Последнее редактирование: