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, указываем стиль ответа, формат вывода.
Хороший материал на эту тему: https://www.promptingguide.ai/
RAG
Retrieval Augmented Generation
- генерация дополненная поиском. Выполняем поиск информации, добавляем ее в контекст, генерируем ответ на основе найденной информации.
Тема очень популярная, различных подходов и хитростей тут много, можно набрать на еще одну статью.
Memory
Цель - дать LLM память, хотя бы в рамках одного диалога.
Возможные подходы:
Держать в контексте полную историю диалога - вычислительно дорого, длина ограничена.
Использовать скользящее окно диалога с несколькими последними сообщениями.
Сжатие диалога - иногда просим LLM сжать историю сообщений.
Дополнительные системы - например, даем LLM возможность сохранять факты о пользователе, как в фиче "Память" у веб версии ChatGPT. Пример "дополнительной системы" для памяти - https://arxiv.org/abs/2501.13956
Управление мышлением модели
На этом этапе мы управляем процессом ответа 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
Если мы получаем запрос на который уже отвечали ранее, то ответ можно просто достать из кеша. Это экономит время и вычисления. Кеширование может быть как на основе полного совпадения строки, так и на основе семантического сходства, для этого используются эмбеддинги запросов.
Пример - https://github.com/zilliztech/gptcache
Жизненный цикл
Тут не про обработку одного запроса, а про принципы разработки системы вцелом.
Observability (наблюдаемость)
Нельзя починить то, что невидно. Observability
- возможность заглянуть под капот приложения. Не просто запрос и финальный ответ, а весь путь:
В какую подсистему
Router
отправил запрос?Какие документы извлек RAG?
Какие "мысли" думал агент когда выбирал инструмент?
Сколько времени и токенов понадобилось на каждом шаге? Это основа для отладки и мониторинга, а также главный источник данных для
Data Flywheel
. Инструмент: https://phoenix.arize.com/
Evaluation (Оценка)
"Как понять что мои изменения не сломали то, что уже работало?" - Вместо ручной проверки "на глазок", мы создаем наборы данных и прогоняем их через систему, замеряя метрики. Самый мощный подход здесь это LLM-as-a-Judge
. Для оценки качества ответа нашей системы мы используем более мощную LLM, которая сравнивает результат с эталоном или оценивает по заданным критериям.
Fine-tuning (Дообучение)
Иногда промптов и RAG недостаточно, чтобы добиться нужного поведения системы. Fine-tuning
это процесс дополнительного обучения LLM модели на вашем собственном датасете.
Data Flywheel (Маховик данных)
Система должна самосовершенствоваться. Цикл выглядит так:
Пользовали взаимодействуют с системой
Мы собираем запросы, ответы, обратную связь (лайки, дизлайки, копирование ответа, правки)
Эти данные становятся "топливом" - мы используем их для создания тестовых и обучающих датасетов
Обновленная, более умная система выкатывается в продакшен И так по кругу.
Заключение
Я попробовал описать паттерны работы с LLM так, как они выглядят с моей позиции. Я уверен, что далеко не все расписал идеально, это скорее приглашение к диалогу, чем готовая классификация.
Какими паттернами пользуетесь вы? Чего не хватает/лишнее в моей схеме? Жду ваших мнений в комментариях.
zarfaz
Круто. Очень круто. Я бы с удовольствием почитал бы примеры/гайды по реализации подключения апи которые позволяют на ленту во время мыслительного процесса дозапрашивать информацию или корректировать структуру ответа