Files
BlackboxBook/book/20_llm_application_design_patterns.md
2026-05-20 20:55:03 +03:00

53 KiB
Raw Permalink Blame History

ГЛАВА 20. ПАТТЕРНЫ ПРОЕКТИРОВАНИЯ LLM-ПРИЛОЖЕНИЙ


В 1994 году «Банда четырёх» (Gang of Four) выпустила книгу Design Patterns. Она не изобрела паттерны — паттерны уже существовали в коде тысяч проектов. Книга дала им имена. Когда у решения есть имя, инженеры перестают объяснять его заново на каждом совещании. «Используем Observer» — и комната понимает. Не потому что Observer сложен, а потому что имя заменяет десять минут рисования на доске.

LLM-инженерия к 2026 году находится в похожей точке. В главах 619 книги десятки паттернов — от структуры промпта до агентной архитектуры — разбросаны по контексту, в котором они впервые понадобились. Chain-of-Thought объяснялся в главе 9, ReAct — в главе 10, CoVe — в главе 13. Каждый раз паттерн раскрывался «изнутри» — механизм, формулы, когда работает и почему. Но инженеру, проектирующему новую систему, нужен другой вид: каталог. Одно место, где видны все паттерны, их назначение и критерии выбора.

Эта глава — каталог. Она не повторяет механику (за ней — в исходную главу), а даёт обзорную карту и добавляет паттерны, которые ранее не были формализованы: Router, Human-in-the-Loop, Fallback Chain, Retrieval-Gated Generation, MapReduce.

Формат каждого паттерна: Имя → Проблема → Решение → Когда использовать → Когда НЕ использовать → Ссылка на детальное объяснение.


20.1. Каталог паттернов: обзорная таблица

Двадцать паттернов, отсортированных от атомарных (уровень одного вызова) к составным (уровень системы):

# Паттерн Проблема Решение (одно предложение) Глава
1 Chain-of-Thought Модель ошибается в рассуждениях «Думай шаг за шагом» выводит промежуточную логику Гл. 9
2 Constrained Decoding Выход должен соответствовать схеме JSON Schema / grammar-based generation ограничивает формат Гл. 6
3 Semantic Exoskeleton Секции промпта интерферируют XML-теги разделяют блоки промпта Гл. 7
4 Self-Consistency Один ответ может быть ошибочным Генерируй N ответов, голосуй за консенсус Гл. 8
5 Best-of-N Качество варьируется между генерациями Генерируй N, оцени судьёй, выбери лучший Гл. 8
6 Generator-Verifier Один вызов LLM может галлюцинировать Второй вызов/проверка валидирует выход Гл. 13
7 CoVe Факты требуют верификации Генерируй → сформулируй вопросы → ответь независимо → исправь Гл. 13
8 RAG Модель не знает доменную информацию Найди контекст → дополни промпт → генерируй Гл. 12
9 Self-RAG Не каждый запрос требует retrieval Модель сама решает, когда извлекать Гл. 12
10 GraphRAG Нужно рассуждение по связям между сущностями Графовый retrieval с entity relationships Гл. 12
11 Retrieval-Gated Generation Модель должна отвечать только при наличии evidence Проверь confidence retrieval перед генерацией Новый
12 Router Разные задачи требуют разных моделей/промптов Классификатор маршрутизирует вход к нужному обработчику Новый
13 Planner-Executor Сложная задача требует декомпозиции Планировщик создаёт план, исполнитель выполняет шаги Гл. 9, 10
14 ReAct Агенту нужны и рассуждение, и действие Чередуй Thought / Action / Observation Гл. 10
15 Reflexion Агент повторяет одни и те же ошибки Агент рефлексирует над неудачами и корректирует стратегию Гл. 10
16 Multi-Agent Задача требует нескольких специализированных ролей Оркестратор делегирует агентам-специалистам Гл. 10
17 MapReduce Вход слишком велик для одного context window Разбей → обработай фрагменты → объедини результаты Новый
18 Human-in-the-Loop Некоторые решения требуют одобрения человека Автоматизируй безопасный путь, блокируй рискованный Новый
19 Fallback Chain Основная модель/промпт может отказать Цепочка от основного к запасному варианту Новый
20 ColPali Document retrieval теряет визуальный контекст Visual embeddings из скриншотов страниц Гл. 18

Паттерны в таблице пронумерованы для ссылок внутри главы; нумерация не означает приоритет.

Далее — детальные карточки. Паттерны, подробно разобранные ранее (Self-Consistency, Best-of-N, CoVe, ReAct, Reflexion, Multi-Agent, RAG, Self-RAG, GraphRAG), даны в сжатой форме со ссылкой на главу. Новые паттерны (Router, Human-in-the-Loop, Fallback Chain, Retrieval-Gated Generation, MapReduce) раскрыты полностью.


20.2. Router

Проблема

Ваша система обрабатывает разнородные входы: простые справочные вопросы, сложный анализ, генерация кода, работа с изображениями. Один промпт и одна модель не подходят для всех случаев — frontier-модель избыточна для FAQ, а лёгкая модель не справится с многошаговым рассуждением. Без маршрутизации вы платите за overkill на простых задачах или получаете провал на сложных.

Решение

Лёгкий классификатор на входе определяет тип задачи и направляет запрос к подходящему обработчику:

Input → Router (быстрый классификатор)
           ├── Handler A: frontier model + полный промпт (сложный анализ)
           ├── Handler B: лёгкая модель (FAQ, справки)
           ├── Handler C: rule-based (шаблонные ответы)
           └── Handler D: специализированный агент (код, данные)

Реализация

Три подхода к построению Router, от простого к сложному:

1. Rule-based Router — ключевые слова, regex, длина запроса. Самый предсказуемый вариант: если условия можно описать правилами, LLM не нужен. Маршрутизация по ключевым словам, длине запроса, наличию вложений.

2. Embedding-based Router — embed запрос, найти ближайший кластер. Для каждого маршрута формулируется текстовое описание (например, «написать код, исправить баг, рефакторинг» для code-handler). Запрос embed-ится той же моделью, cosine similarity определяет лучший маршрут. Плюс: не требует вызова LLM на каждый запрос. Минус: нужен набор описаний маршрутов.

3. LLM-based Router — дешёвая модель классифицирует запрос в одну из категорий. Самый гибкий, но самый дорогой из трёх. Оправдан, когда категории сложно описать правилами или embedding'ами.

Промпт для генерации Router: Напиши три варианта Router на Python с OpenAI SDK: (1) rule-based — маршрутизация по ключевым словам и regex, (2) embedding-based — cosine similarity между запросом и описаниями маршрутов через text-embedding-3-small, (3) LLM-based — дешёвая модель (mini/nano-tier) классифицирует запрос в JSON {"category": "..."} через response_format={"type": "json_object"}. Категории: code, analysis, faq, creative. Каждый вариант — отдельная функция route(query: str) -> str. Добавь type hints и обработку ошибок.

Когда использовать

  • Система обслуживает несколько типов задач с разными требованиями к качеству, стоимости или latency.
  • Вы хотите снизить inference-расходы, отправляя простые запросы на дешёвую модель.
  • Разные задачи требуют разных tool sets или системных промптов.

Когда НЕ использовать

  • Вся система решает одну задачу — один промпт справляется.
  • Объём запросов мал, и экономия от маршрутизации не окупает сложность.

Связь с другими паттернами

Router часто стоит перед другими паттернами: Router → Planner-Executor для сложных задач, Router → RAG для вопросов с доменной спецификой, Router → rule-based для шаблонных ответов. В главе 24 (§24.9) dynamic model routing упоминается как тренд inference-экономики; здесь паттерн формализован как переиспользуемый компонент.


20.3. Planner-Executor (с Verifier)

Проблема

Задача из нескольких шагов, где каждый шаг может провалиться, а результат предыдущего определяет следующий.

Решение

Три роли: Planner формирует план, Executor выполняет каждый шаг, Verifier проверяет результат. Оркестратор управляет циклом.

[Цель] → Planner → [План: шаг 1, 2, ..., N]
              ↓
         Executor (шаг 1) → Verifier → OK? → Executor (шаг 2) → ...
              ↑                           ↓
              └──── Replan ◄──── FAIL ◄───┘

Подробное объяснение:

Варианты

Вариант Особенность Глава
DAG-планирование Шаги могут выполняться параллельно Гл. 9, §9.3
ReAct Мысль → действие → наблюдение в цикле Гл. 10, §10.2
Plan-and-Execute Полный план вперёд → последовательное исполнение Гл. 10, §10.2
Mode-Architect Архитектор задаёт стратегию, исполнитель реализует Гл. 9, §9.4

Когда использовать

  • Задача требует нескольких шагов, и ошибка на одном шаге ломает весь результат.
  • Нужна возможность перепланирования при неожиданном результате.

Когда НЕ использовать

  • Single-shot Q&A, где Chain-of-Thought достаточен.
  • Задача не допускает задержку, вносимую циклом plan → execute → verify.

20.4. Generator-Verifier (Critic)

Проблема

Один вызов LLM порождает fluent, но фактически неточный текст. Модель оптимизирована на гладкость, а не на достоверность.

Решение

Принцип «два набора глаз»: Generator создаёт кандидат, Verifier проверяет. Пилот и второй пилот в кабине (подробная аналогия — глава 13, §13.1).

Варианты:

Вариант Механизм Глава
Два вызова одной модели Разные системные промпты (generate vs critique) Гл. 13, §13.1
Разные модели Generator = creative, Verifier = analytical Гл. 13, §13.1
CoVe Generate → plan questions → answer independently → revise Гл. 13, §13.2
LLM-as-Judge Модель оценивает выход по rubric Гл. 14
Код/тесты как Verifier Linter, unit tests, schema validation Гл. 13, §13.3

Когда использовать

  • High-stakes выходы: медицина, финансы, юридические документы, клиентские коммуникации.
  • Задачи с объективно проверяемыми фактами.

Когда НЕ использовать

  • Low-stakes, high-throughput задачи, где latency двойного вызова неприемлема.
  • Творческая генерация, где «правильного ответа» нет.

20.5. Human-in-the-Loop

Проблема

Некоторые действия слишком рискованны для полной автоматизации: финансовые транзакции, удаление данных, отправка сообщений клиентам, изменение production-инфраструктуры. Даже лучший агент с Verifier может ошибиться. Цена ошибки — не «плохой ответ в чате», а деньги, репутация, безопасность.

Решение

Автоматизируй безопасный путь, блокируй рискованный до подтверждения человеком. Три режима:

1. Confirmation Gate — агент формирует действие, но не выполняет до явного OK. Компонент ConfirmationGate хранит список high-risk инструментов (delete_record, send_email, execute_payment, deploy) и пороги (например, amount > 10_000). Если действие попадает под gate — выполнение блокируется до подтверждения человека. При отказе — возвращается {"status": "blocked", "reason": "human rejected"}.

Промпт для генерации Confirmation Gate: Напиши класс ConfirmationGate на Python: метод should_gate(tool_name, args) проверяет, нужно ли одобрение (по списку high-risk инструментов и порогам в args). Метод request_approval(tool_name, args) запрашивает подтверждение (через CLI для прототипа, через webhook/API для production). Функция agent_step(tool_name, args, gate) — обёртка: если gate сработал и человек отклонил, возвращает блокировку; иначе выполняет инструмент. Добавь поддержку async-подтверждения через очередь.

2. Review Queue — агент выполняет задачу, результат попадает в очередь на ревью перед доставкой. Паттерн описан в главе 17 (§17.7) в контексте наблюдаемости: оператор просматривает выходы, ловит систематические ошибки, настраивает пороги.

3. Escalation — агент сам определяет неуверенность и маршрутизирует к человеку. Механизм: после генерации оценивается confidence (через logprobs, self-rating или внешний классификатор). Если confidence ниже порога (например, 0.7) — результат уходит человеку как черновик, а не как финальный ответ. Ключевой момент: передавайте черновик вместе с эскалацией, чтобы человек не начинал с нуля.

Промпт для генерации Escalation-обёртки: Напиши функцию agent_with_escalation(query, confidence_threshold=0.7) на Python: генерирует ответ через LLM, оценивает confidence (logprobs или self-rating), при низкой уверенности возвращает {"status": "escalated", "draft": ...}, иначе {"status": "completed", "result": ...}. Покажи два варианта оценки confidence: через logprobs API и через повторный LLM-вызов с rubric.

Критерии для решения «нужен ли gate»

Три вопроса:

Критерий Вопрос Примеры
Необратимость Можно ли отменить действие? DELETE без бэкапа, отправка email — нельзя
Цена ошибки Финансовые, репутационные, safety-последствия? Платёж клиенту, юрид. документ — высокая цена
Уверенность Достаточно ли информации у агента? Неоднозначный запрос, противоречивые данные — нет

Правило: если ответ на любой из трёх вопросов — «да, это рискованно» — ставьте gate.

Когда использовать

  • Действия с реальными последствиями (деньги, данные, инфраструктура).
  • Домены с регуляторными требованиями (финансы, медицина).
  • Начальный период внедрения, пока не накоплена статистика ошибок.

Когда НЕ использовать

  • Полностью обратимые, низкорискованные действия — gate убьёт throughput.
  • Задачи с latency-требованиями, где ожидание человека неприемлемо.

20.6. Fallback Chain

Проблема

Production-система не может позволить себе ответ «сервис недоступен». Основная модель может отказать: rate limit, timeout, content filter, неожиданный формат ответа. Основной промпт может не работать для edge-case. Один сбой не должен ронять весь пайплайн.

Решение

Упорядоченная цепочка стратегий от оптимальной к минимально приемлемой:

Primary:   frontier model + полный промпт
    ↓ fail
Fallback 1: та же модель + упрощённый промпт
    ↓ fail
Fallback 2: лёгкая модель (GPT-5.4-mini, Claude Haiku 4.5)
    ↓ fail
Fallback 3: кэшированный ответ / статический default
    ↓ fail
Error:     структурированный отказ с объяснением

Реализация

Логика проста: упорядоченный список шагов (FallbackStep), каждый со своим name и execute-функцией. Функция fallback_chain проходит по цепочке: первый успешный результат возвращается с указанием источника ({"status": "ok", "source": step.name}). Если все шаги провалились — возвращается структурированная ошибка со всеми накопленными errors. Типичная цепочка: frontier-модель с полным промптом → та же модель с упрощённым → лёгкая модель → кэш.

Промпт для генерации Fallback Chain: Напиши на Python класс FallbackChain с упорядоченным списком шагов (dataclass FallbackStep с name: str и execute: Callable[[str], str]). Метод run(query) последовательно пробует каждый шаг с try/except, накапливает ошибки, возвращает первый успешный результат с source. Добавь логирование каждого fallback-перехода и подсчёт метрик. Пример цепочки: Claude Opus 4.6 (full prompt) → Claude Opus 4.6 (simplified) → Claude Haiku 4.5 (simplified) → cache lookup. Каждый уровень должен возвращать ответ в одном формате.

Важный нюанс: каждый уровень должен возвращать результат в одном и том же формате. Downstream-код не должен знать, пришёл ответ от frontier-модели или из кэша. Это обеспечивает Constrained Decoding (§20.9) на каждом уровне.

Мониторинг

Fallback Chain — один из паттернов, который требует наблюдаемости (глава 17). Если система регулярно проваливается на fallback 2-3, это сигнал: основной промпт нужно чинить, а не мириться с деградацией.

Когда использовать

  • Production-системы, где uptime критичен.
  • Multi-provider setup, где один API-провайдер может быть недоступен.
  • Задачи с постоянным потоком запросов (customer support, data pipelines).

Когда НЕ использовать

  • Development и evaluation: вам нужно видеть все ошибки, а не маскировать их fallback'ами.
  • Задачи, где качество fallback-ответа хуже, чем отсутствие ответа.

20.7. Retrieval-Gated Generation

Проблема

Классический RAG извлекает документы и генерирует ответ, даже если извлечённый контекст нерелевантен. Результат: модель галлюцинирует, опираясь на слабый контекст, — хуже, чем если бы просто сказала «не знаю». Проблема особенно остра в fact-critical доменах: медицинские справочники, юридические базы, финансовая аналитика.

Решение

Добавь gate между retrieval и generation. Если confidence retrieval ниже порога — не генерируй, а верни «недостаточно информации» или запроси уточнение.

Query → Retriever → [top-K документов, scores]
                        ↓
                     Gate: score > threshold?
                     ├── YES → Generator (с контекстом) → Ответ
                     └── NO  → "Недостаточно информации для ответа"

Реализация

Логика: retriever возвращает top-K документов со score. Фильтруем по score_threshold. Если количество релевантных документов меньше min_relevant_docs — возвращаем {"status": "insufficient_evidence"} вместо генерации. Иначе — передаём контекст в generator и возвращаем {"status": "answered", "answer": ..., "sources": N}.

Промпт для генерации Retrieval-Gated Generation: Напиши функцию retrieval_gated_generate(query, retriever, generator, score_threshold=0.75, min_relevant_docs=1) на Python. Retriever имеет метод .search(query, top_k), возвращающий список RetrievalResult(text, score). Фильтруй по порогу, при недостаточном evidence возвращай отказ с top_score для диагностики. Иначе объединяй тексты релевантных документов как контекст для generator. Добавь type hints и логирование.

Настройка порогов

Два параметра: score_threshold и min_relevant_docs. Правильные значения зависят от домена и embeddings. Подход:

  1. Соберите eval-набор: 50+ запросов с known-answer + 50+ запросов без ответа в базе.
  2. Постройте precision-recall кривую для разных порогов.
  3. Выберите порог, при котором recall приемлем (не отклоняет слишком много хороших запросов), а precision высока (не пропускает «пустые» контексты).

Связь с Self-RAG

Self-RAG (глава 12, §12.7) — более сложный вариант: модель сама решает, нужен ли retrieval, и сама оценивает, полезен ли извлечённый контекст. Retrieval-Gated Generation — упрощённая версия для случаев, когда вы не хотите полагаться на суждения модели о качестве контекста и предпочитаете детерминированный порог.

Когда использовать

  • Fact-critical приложения, где галлюцинация хуже, чем отказ отвечать.
  • Системы, где пользователь понимает ответ «не знаю» и может переформулировать вопрос.
  • Доменные Q&A-системы с ограниченной knowledge base.

Когда НЕ использовать

  • Brainstorming и creative-задачи, где генерация «вдохновения» ценнее точности.
  • Случаи, где «не знаю» неприемлемо и любой ответ лучше молчания.

20.8. MapReduce для длинных входов

Проблема

Вход не помещается в context window. Или помещается, но слишком длинный для качественной обработки — внимание модели размывается на больших контекстах (глава 5 о lost-in-the-middle). Примеры: суммаризация 500-страничного документа, анализ кодовой базы из сотен файлов, обработка логов за месяц.

Решение

Разбей вход на фрагменты → обработай каждый независимо → объедини результаты. Название — по аналогии с MapReduce из распределённых вычислений: map-функция обрабатывает фрагменты параллельно, reduce-функция агрегирует результаты.

Три варианта

1. Map-Reduce (классический):

[Документ] → split → [Chunk 1] → LLM → [Summary 1]
                      [Chunk 2] → LLM → [Summary 2]  → Reduce LLM → [Final Summary]
                      [Chunk 3] → LLM → [Summary 3]

Плюс: фрагменты обрабатываются параллельно. Минус: reduce-шаг не видит оригинальные детали, только промежуточные результаты.

2. Map-Refine (последовательный):

[Chunk 1] → LLM → [Draft 1]
[Chunk 2] + [Draft 1] → LLM → [Draft 2]
[Chunk 3] + [Draft 2] → LLM → [Draft 3 = Final]

Плюс: каждый шаг учитывает накопленный контекст. Минус: полностью последовательный, нельзя распараллелить.

3. Hierarchical (рекурсивный):

Level 0: chunks → LLM → summaries
Level 1: summaries → group → LLM → meta-summaries
Level 2: meta-summaries → LLM → final

Плюс: масштабируется на очень большие входы (книги, кодовые базы). Минус: максимальная потеря деталей.

Реализация

Алгоритм: (1) split текста на чанки с перекрытием (overlap), чтобы не терять контекст на границах; (2) map — параллельная обработка каждого чанка через LLM (например, «суммаризуй в 35 ключевых пунктов»); (3) reduce — объединение промежуточных результатов в финальный сводный вывод.

Промпт для генерации MapReduce-суммаризатора: Напиши функцию map_reduce_summarize(text, llm_call, chunk_size=4000, overlap=200) на Python. Разбивает текст на чанки с перекрытием, обрабатывает каждый параллельно через ThreadPoolExecutor (шаг map), затем объединяет результаты одним вызовом LLM (шаг reduce). llm_call — функция (str) -> str. Добавь вариант map-refine (последовательный). Обработка ошибок: если один чанк провалился — продолжить без него, но залогировать.

Когда использовать

  • Суммаризация документов, превышающих context window.
  • Анализ больших кодовых баз (по файлам/модулям).
  • Extraction из массивов (логи, таблицы, отзывы).

Когда НЕ использовать

  • Задачи, требующие глобального контекста: «какова общая нарративная арка романа?» — map-reduce потеряет нити между фрагментами.
  • Если документ помещается в context window frontier-модели с длинным контекстом (1M+ токенов у Gemini 3.x, Claude Opus 4.6, GPT-5.4) — попробуйте сначала полный контекст. MapReduce — не единственный ответ на «длинный вход» (глава 5).

20.9. Constrained Decoding

Проблема

Выход LLM должен быть машиночитаемым: JSON для API, SQL для базы данных, YAML для конфигурации. Free-form generation порождает невалидные структуры, пропущенные поля, лишние комментарии.

Решение

Ограничь пространство генерации формальной грамматикой или схемой. Подробно — глава 6, где structured outputs рассмотрены как ключевой элемент промпт-протокола.

Механизмы:

  • JSON Schema через response_format (OpenAI, Anthropic) — модель гарантирует структуру.
  • Function calling / tool use — модель заполняет параметры заранее определённых функций.
  • Grammar-based decoding (Outlines, llama.cpp grammars) — на этапе сэмплирования допускаются только токены, валидные по грамматике.

Когда использовать

  • Любой выход, который парсится downstream-кодом.
  • Заполнение форм, extraction из текста, classification.

Когда НЕ использовать

  • Free-form текст (объяснения, эссе, творческий контент).

20.10. Краткие карточки ранее разобранных паттернов

Chain-of-Thought (глава 9)

Промпт «Think step by step» или structured reasoning template выводит промежуточные шаги, повышая точность на задачах с рассуждениями. Работает лучше всего на задачах с объективно проверяемым ответом (математика, логика, код). Подробно — глава 9, §9.1.

Self-Consistency (глава 8, §8.3)

Сгенерируй N ответов с повышенной температурой → выбери наиболее частый (majority vote). Повышает точность за счёт N-кратного увеличения расхода токенов.

Best-of-N (глава 8, §8.4)

Сгенерируй N ответов → оцени каждый моделью-судьёй или функцией качества → верни лучший. Отличие от Self-Consistency: выбор по качеству, а не по частоте.

CoVe (глава 13, §13.2)

Generate → plan verification questions → answer questions independently → revise original. Снижает hallucination rate на фактоидных задачах.

ReAct (глава 10, §10.2)

Thought → Action → Observation в цикле. «Рассуждай вслух, действуй, наблюдай результат». Стандартный agent loop для систем с tool use.

Reflexion (глава 10, §10.2)

Агент after failure записывает рефлексию в память и использует её при повторной попытке. Нужна persistent memory между итерациями.

Multi-Agent (глава 10, §10.4)

Оркестратор делегирует задачи специализированным агентам. Варианты: supervisor, hierarchy, peer-to-peer. Оправдан при сложных задачах с чётко разделяемыми подзадачами.

RAG (глава 12)

Retrieve relevant documents → augment prompt with context → generate answer. Подробно — глава 12 целиком.

Self-RAG (глава 12, §12.7)

Модель решает, нужен ли retrieval; оценивает, полезен ли контекст; генерирует с reflection-токенами.

GraphRAG (глава 12, §12.7)

Строит граф сущностей и связей, выполняет multi-hop reasoning по графу. Для задач, где ответ требует соединения фактов из разных источников.

Semantic Exoskeleton (глава 7, §7.4)

XML-теги разделяют смысловые блоки промпта, предотвращая интерференцию секций. Базовый паттерн структурирования сложных промптов.


20.11. Выбор паттерна: дерево решений

Перед системой стоит конкретная задача. Как выбрать паттерн? Пройдите по дереву:

Задача — single-shot или multi-step?
│
├── Single-shot:
│   ├── Нужен гарантированный формат?
│   │   └── ДА → Constrained Decoding (§20.9)
│   │
│   ├── High-stakes, нужна точность?
│   │   └── ДА → Generator-Verifier (§20.4) или CoVe
│   │
│   ├── Нужны разнообразные варианты?
│   │   └── ДА → Best-of-N или Self-Consistency
│   │
│   └── Простой ответ достаточен?
│       └── ДА → Chain-of-Thought
│
└── Multi-step:
    ├── Разные типы задач на входе?
    │   └── ДА → Router (§20.2) → далее по ветке
    │
    ├── Нужно одобрение человека?
    │   └── ДА → Human-in-the-Loop (§20.5)
    │
    ├── Нужны внешние знания?
    │   └── ДА → RAG + Retrieval-Gated (§20.7)
    │
    ├── Вход слишком длинный?
    │   └── ДА → MapReduce (§20.8)
    │
    ├── Требуется reliability в production?
    │   └── ДА → Fallback Chain (§20.6)
    │
    └── Сложное рассуждение?
        └── ДА → Planner-Executor / ReAct (§20.3)

Важно: это дерево — отправная точка, а не жёсткий алгоритм. В реальных системах паттерны комбинируются. Типичная цепочка для production-агента:

Router → Planner-Executor → [RAG + Retrieval-Gate] → Generator-Verifier
  → Constrained Decoding → Human-in-the-Loop (для рискованных действий)
    → Fallback Chain (обёртка всего пайплайна)

20.12. Decision tree выбора паттерна

Каталог из 20 паттернов полезен как справочник. Но когда вы проектируете новую систему, нужен обратный процесс: «у меня задача X — какой паттерн выбрать?». Это дерево решений для шести типовых сценариев.

Сценарий 1: «Модель должна отвечать в строгом формате (JSON)»

→ Паттерн: Constrained Decoding (#2). → Подробнее: Глава 6, §6.4.

Сценарий 2: «Модель должна использовать внешние данные (документы, БД)»

  1. Данные обновляются чаще, чем раз в месяц? → RAG (#8).
  2. Нужны связи между сущностями (X работает в Y, Y принадлежит Z)? → GraphRAG (#10).
  3. Модель сама должна решать, когда нужен retrieval? → Self-RAG (#9).
  4. Перед генерацией нужно проверить, что retrieval что-то нашёл? → Retrieval-Gated Generation (#11).

Сценарий 3: «Модель ошибается в фактах (галлюцинирует)»

  1. Факты можно проверить вторым вызовом LLM? → Generator-Verifier (#6).
  2. Нужна систематическая проверка каждого утверждения? → CoVe (#7).
  3. Нужна оценка качества целой системы? → Eval-дисциплина (Глава 14).

Сценарий 4: «Задача требует нескольких шагов»

  1. Шаги независимы и могут выполняться параллельно? → Planner-Executor (#13) с DAG-планированием (Глава 9).
  2. Каждый шаг зависит от результата предыдущего? → ReAct (#14).
  3. Агент повторяет одни и те же ошибки? → Reflexion (#15).
  4. Нужны разные роли (писатель, редактор, проверяющий)? → Multi-Agent (#16).

Сценарий 5: «Один ответ ненадёжен, нужно несколько»

  1. Нужно выбрать лучший из N вариантов (качество)? → Best-of-N (#5).
  2. Нужен консенсус нескольких генераций (стабильность)? → Self-Consistency (#4).

Сценарий 6: «Разные запросы требуют разной обработки»

  1. Простые запросы должны обрабатываться дёшево, сложные — качественно? → Router (#12).
  2. Основная модель может отказать, нужен запасной вариант? → Fallback Chain (#19).
  3. Некоторые действия слишком рискованны для автоматизации? → Human-in-the-Loop (#18).

Сценарий 7: «Вход слишком велик для одного контекстного окна»

MapReduce (#17): разбей → обработай фрагменты → объедини результаты.

Быстрый опросник

Ответьте на 4 вопроса — и получите комбинацию паттернов:

  1. Выход должен быть в строгом формате? [Да / Нет]
  2. Нужны внешние данные? [Да, часто обновляются / Да, статичные / Нет]
  3. Задача требует нескольких шагов? [Да, последовательных / Да, параллельных / Нет]
  4. Цена ошибки? [Высокая / Средняя / Низкая]

Пример:

  • Да, Да (часто обновляются), Да (последовательных), Высокая → Constrained Decoding + RAG + ReAct + Generator-Verifier.
  • Да, Нет, Нет, Средняя → Constrained Decoding + CoVe.
  • Нет, Нет, Нет, Низкая → Chain-of-Thought (базовый промпт).

Композиция паттернов

Паттерны не исключают друг друга. Типичная production-система использует 3-5 паттернов одновременно:

Input → Router (#12) → RAG (#8) → ReAct (#14) → Generator-Verifier (#6) → Output
                           ↑                          ↑
                      Self-RAG (#9)           Human-in-the-Loop (#18)

Главное правило: начинайте с одного паттерна (#1 Chain-of-Thought или #2 Constrained Decoding). Добавляйте следующий только когда текущий перестал справляться. 90% проблем решаются комбинацией из 2-3 базовых паттернов.


Практический вывод

  1. Начните с простого. Chain-of-Thought + Constrained Decoding покрывают ~80% задач. Не добавляйте Router, пока у вас одна задача, и Generator-Verifier, пока цена ошибки низка.

  2. Добавляйте верификацию для high-stakes. Generator-Verifier или CoVe — когда ошибка стоит дорого. Два вызова LLM дешевле одного инцидента.

  3. RAG — когда нужны знания, а не догадки. Добавьте Retrieval-Gated Generation, чтобы модель молчала, когда данных нет, вместо того чтобы галлюцинировать.

  4. Router — когда система мультизадачна. Не гоните все запросы через frontier-модель. Маршрутизация экономит деньги и повышает качество за счёт специализации.

  5. Fallback Chain — для production. Запасной план на rate limit, timeout, content filter. Каждый уровень возвращает ответ в одном формате.

  6. Human-in-the-Loop — для необратимых действий. Gate на удаление, платежи, отправку. Правило: если действие нельзя откатить — требуй подтверждение.

  7. Комбинируйте, но не переусложняйте. Каждый паттерн добавляет latency и стоимость. Добавляйте следующий слой только когда eval показывает, что текущий недостаточен.

  8. Мониторьте, какой паттерн срабатывает. Если Fallback Chain регулярно проваливается до уровня 3 — чините основной промпт, а не добавляйте уровень 4. Если Router отправляет 95% в один handler — он не нужен.

Задания

  1. Аудит паттернов в существующем пайплайне. Возьмите любой LLM-пайплайн, который вы используете или разрабатываете. Пройдите по дереву решений из §20.11 и определите, какие паттерны уже присутствуют (возможно, неявно), каких не хватает, и какие избыточны. Ожидаемый результат: таблица «паттерн — статус — действие».

  2. Router + Fallback Chain. Реализуйте для своего проекта Router (любой из трёх вариантов из §20.2) + Fallback Chain (§20.6). Подключите логирование каждого перехода. Прогоните 100 реальных запросов и измерьте: (a) распределение по маршрутам, (b) fallback rate, (c) latency по маршрутам. Ожидаемый результат: дашборд с метриками маршрутизации.

  3. Retrieval-Gated Generation. Возьмите существующий RAG-пайплайн (или соберите минимальный по главе 12). Добавьте gate между retrieval и generation (§20.7). Соберите eval-набор из 50 запросов с ответом в базе + 50 запросов без ответа. Постройте precision-recall кривую для разных score_threshold. Ожидаемый результат: выбранный порог и метрики до/после включения gate — hallucination rate должен снизиться.

  4. Спроектируйте систему через decision tree. Возьмите новую задачу из вашего проекта (или придумайте). Пройдите «Быстрый опросник» из 4 вопросов. Определите комбинацию паттернов. Нарисуйте диаграмму компонентов (текстовую или mermaid). Обоснуйте: почему именно эти паттерны, а не альтернативы? Ожидаемый результат: архитектурная схема с обоснованием выбора паттернов.


Источники

Паттерны, детально разобранные в книге:

  • Chain-of-Thought — глава 9; Wei, J., et al. (2022). "Chain-of-Thought Prompting Elicits Reasoning in Large Language Models." NeurIPS 2022. arXiv:2201.11903
  • Self-Consistency — глава 8; Wang, X., et al. (2022). "Self-Consistency Improves Chain of Thought Reasoning in Language Models." arXiv:2203.11171
  • ReAct — глава 10; Yao, S., et al. (2023). "ReAct: Synergizing Reasoning and Acting in Language Models." ICLR 2023. arXiv:2210.03629
  • Reflexion — глава 10; Shinn, N., et al. (2023). "Reflexion: Language Agents with Verbal Reinforcement Learning." NeurIPS 2023. arXiv:2303.11366
  • CoVe — глава 13; Dhuliawala, S., et al. (2023). "Chain-of-Verification Reduces Hallucination in Large Language Models." arXiv:2309.11495
  • RAG — глава 12; Lewis, P., et al. (2020). "Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks." NeurIPS 2020. arXiv:2005.11401
  • Self-RAG — глава 12; Asai, A., et al. (2023). "Self-RAG: Learning to Retrieve, Generate, and Critique through Self-Reflection." arXiv:2310.11511
  • GraphRAG — глава 12; Edge, D., et al. (2024). "From Local to Global: A Graph RAG Approach to Query-Focused Summarization." arXiv:2404.16130
  • Structured Outputs / Constrained Decoding — глава 6; Willard, B. T. & Louf, R. (2023). "Efficient Guided Generation for Large Language Models." arXiv:2307.09702

Общие архитектурные паттерны:


Навигация: