5 измерений для выбора паттерна
Прежде чем выбирать паттерн, нужно понять природу задачи по пяти ключевым измерениям. Каждое измерение — вопрос, ответ на который сужает выбор.
Задача требует вызовов внешних инструментов — поиска, API, файловой системы, кода — или ответ строится только из знаний модели и текста?
tools: yes/noШаги задачи можно перечислить заранее — или каждый следующий шаг зависит от результата предыдущего и неизвестен до его выполнения?
plannable: yes/noВажна ли высокая точность итогового ответа или текста — или первый корректный черновик достаточен? Есть ли чёткий критерий качества?
quality: high/okВопрос требует 2+ независимых факта, связанных в цепочку рассуждений — или достаточно одного шага поиска/вычисления?
multi_hop: yes/noLatency критична (пользователь ждёт ответ в реальном времени) — или задача фоновая и можно потратить несколько дополнительных секунд на качество?
latency: strict/okСамый частый паттерн на практике — прямой вызов LLM без агентского цикла. Если задача решается одним хорошим промптом — не нужен ни один из четырёх паттернов. Паттерны добавляют сложность, которая должна быть оправдана реальной проблемой.
Дерево решений
Следуй вопросам по порядку. Первый ответ «да» указывает на нужный паттерн. Только если все ответы «нет» — используй ReAct как универсальный инструментный цикл.
Порядок вопросов отражает приоритеты, а не жёсткие правила. Задача может быть одновременно multi-hop и требовать качества — тогда стоит рассмотреть комбинацию паттернов (см. раздел «Комбинации»).
Паттерны и их ниши
Каждый паттерн оптимально решает один класс задач. Понимание «ниши» помогает быстро отсеять неподходящие варианты.
- Задачи с инструментами, где следующий шаг непредсказуем
- Интерактивный поиск, браузер, файловая система
- Когда нужна адаптация по ходу выполнения
- Универсальный выбор, когда другие паттерны не подходят
- Длинные задачи: отчёты, миграции, конвейеры данных
- Параллельное исполнение независимых шагов
- Когда важна прозрачность: пользователь видит план
- Шаги заранее известны или легко предсказуемы
- Генерация текста с высокими требованиями к качеству
- Написание кода с верификацией результата
- Любые задачи, где есть чёткий критерий «хорошо/плохо»
- Когда первый черновик гарантированно не идеален
- Multi-hop QA: 2+ факта в цепочке
- Исследовательские задачи с поиском
- Сравнение нескольких объектов по критериям
- Когда важна прозрачность: видны все промежуточные ответы
Сравнительная таблица
Семь ключевых критериев для принятия решения. Зелёный — преимущество, жёлтый — приемлемо, красный — слабое место паттерна.
| Критерий | ReAct | Plan-and-Exec | Reflection | Self-Ask |
|---|---|---|---|---|
| Работает без инструментов | сложно | обычно | да | да |
| Адаптация к неожиданностям | высокая | средняя | нет | нет |
| Параллельное исполнение | нет | да | нет | да |
| Итеративное улучшение | нет | нет | да | нет |
| Multi-hop факты | частично | частично | нет | да |
| Прозрачность процесса | средняя | высокая | средняя | высокая |
| Стоимость токенов | средняя | средняя | высокая | средняя |
Типовые задачи и паттерны
Конкретные примеры задач и рекомендованный паттерн для каждого:
| Задача | Паттерн | Почему |
|---|---|---|
| Написать и отредактировать статью | Reflection | Есть чёткий критерий качества; нужно несколько итераций |
| Ответить на вопрос «кто старше — X или Y?» | Self-Ask | Multi-hop: нужны два факта (год рождения/смерти обоих) |
| Сгенерировать отчёт из 5 разделов | Plan-and-Execute | Структура известна заранее; разделы независимы → параллельно |
| Найти баг и исправить код | ReAct | Нужны инструменты (запуск, поиск по файлам); шаги непредсказуемы |
| Написать юнит-тест для функции | Reflection | Чёткий критерий: тест должен проходить и покрывать edge cases |
| Мониторинг и реакция на события | ReAct | Адаптивный цикл; следующий шаг зависит от наблюдения |
| Перевод + проверка качества перевода | Reflection | Сначала перевод, критик оценивает точность и стиль, ревизор правит |
| Сравнить 3 продукта по 5 характеристикам | Self-Ask | 15 независимых фактов → декомпозиция + параллельный сбор |
| Ответить на простой фактовый вопрос | прямой вызов | Один факт, один шаг — паттерны только добавят latency |
| Перенести данные из CSV в базу | Plan-and-Execute | Шаги (читать, трансформировать, писать) известны; длинный процесс |
Комбинации паттернов
Паттерны не взаимоисключающие. Для сложных задач правильная стратегия — комбинировать их в цепочку, где каждый паттерн решает свою часть задачи.
Self-Ask собирает все необходимые факты через декомпозицию и поиск. Reflection получает факты как контекст и итеративно улучшает итоговый текст до заданного уровня качества.
Plan-and-Execute строит структуру и исполняет шаги (параллельно, где возможно). Каждый шаг или финальный результат пропускается через Reflection для проверки и улучшения качества.
ReAct выполняет задачу с использованием инструментов. По завершении Reflection оценивает полноту и точность результата и при необходимости запускает ещё один цикл ReAct с уточнёнными инструкциями.
Self-Ask декомпозирует вопрос на подвопросы. Каждый подвопрос решается через ReAct с доступом к инструментам поиска — вместо единого поиска для всей задачи.
Комбинация паттернов умножает сложность и стоимость. Начинай с одного паттерна. Добавляй второй только тогда, когда конкретно видишь, какую проблему он решает. Два последовательных вызова LLM уже стоят дороже одного — убедись, что результат стоит этих денег.
Автоматический выбор паттерна
Для систем, где тип задачи непредсказуем (например, чат-бот с произвольными запросами), можно реализовать роутер — отдельный вызов LLM, который анализирует задачу и выбирает паттерн. Это позволяет одному агенту корректно обрабатывать разнородные запросы.
ROUTER_TOOL: схема выбора
import anthropic
client = anthropic.Anthropic()
ROUTER_TOOL = {
"name": "select_pattern",
"description": (
"Анализируй задачу и выбери наиболее подходящий паттерн агента. "
"Если задача простая — выбери 'direct' и не используй паттерны."
),
"input_schema": {
"type": "object",
"properties": {
"pattern": {
"type": "string",
"enum": ["direct", "react", "self_ask", "plan_execute", "reflection"],
"description": (
"direct — простой вызов без паттерна; "
"react — инструментный цикл с адаптацией; "
"self_ask — multi-hop вопросы, цепочка фактов; "
"plan_execute — длинная задача с предсказуемыми шагами; "
"reflection — генерация с итеративным улучшением качества"
)
},
"reasoning": {
"type": "string",
"description": "Краткое объяснение выбора — 1 предложение"
},
"use_reflection": {
"type": "boolean",
"description": "True — добавить Reflection поверх выбранного паттерна"
}
},
"required": ["pattern", "reasoning", "use_reflection"]
}
}
Реализация роутера
ROUTER_SYSTEM = """Ты — роутер AI-агента. Твоя задача — выбрать паттерн для задачи.
Правила выбора:
- direct: ответ известен без поиска и инструментов, один шаг
- reflection: нужно написать/улучшить текст, код, перевод — есть критерий качества
- self_ask: вопрос требует 2+ связанных фактов (multi-hop), сравнение объектов
- plan_execute: длинная задача, структура шагов ясна заранее, нужна параллельность
- react: нужны инструменты, шаги непредсказуемы, всё остальное
use_reflection=true — если любой паттерн должен финально пройти через критику."""
def route(task: str) -> tuple[str, bool]:
"""Возвращает (pattern_name, use_reflection)."""
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=256,
system=ROUTER_SYSTEM,
tools=[ROUTER_TOOL],
tool_choice={"type": "tool", "name": "select_pattern"},
messages=[{"role": "user", "content": f"Задача: {task}"}]
)
for block in response.content:
if block.type == "tool_use" and block.name == "select_pattern":
inp = block.input
print(f"[Router] pattern={inp['pattern']}, "
f"reflection={inp['use_reflection']}, "
f"reason={inp['reasoning']}")
return inp["pattern"], inp.get("use_reflection", False)
return "react", False # fallback
Диспетчер: запуск нужного агента
def adaptive_agent(task: str) -> str:
"""Автоматически выбирает паттерн и запускает нужного агента."""
pattern, use_reflection = route(task)
# Запускаем выбранный паттерн
if pattern == "direct":
result = direct_answer(task)
elif pattern == "self_ask":
result = self_ask(task)
elif pattern == "plan_execute":
result = plan_and_execute(task)
elif pattern == "reflection":
result = critique_and_revise(task)
else: # react — default
result = react_loop(task)
# Опционально: финальная критика поверх любого паттерна
if use_reflection and pattern != "reflection":
result = critique_and_revise(
task=task,
initial_draft=result, # не генерируем заново
max_rounds=2
)
return result
def direct_answer(task: str) -> str:
"""Прямой вызов без агентского цикла — для простых задач."""
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=512,
messages=[{"role": "user", "content": task}]
)
return response.content[0].text
# Использование
if __name__ == "__main__":
tasks = [
"Сколько будет 2 + 2?",
"Кто дольше прожил — Пушкин или Лермонтов?",
"Напиши отчёт о состоянии рынка ИИ в 2024 году",
"Найди баг в этом коде и исправь: def add(a, b): return a - b",
"Напиши введение для статьи про нейросети",
]
for task in tasks:
print(f"\n{'='*60}")
print(f"Задача: {task}")
result = adaptive_agent(task)
print(f"Результат: {result[:200]}...")
Роутер использует tool_choice: {"type": "tool", "name": "select_pattern"} —
это гарантирует, что модель всегда вернёт структурированный JSON с выбором.
Без этого модель может ответить текстом вместо вызова инструмента, и роутер
вернёт fallback «react».
Типичные ошибки выбора
Шпаргалка
- Прямой вызов — задача решается одним хорошим промптом
- Reflection — нужно улучшить качество; есть критерий «хорошо/плохо»
- Self-Ask — multi-hop вопрос: 2+ факта в цепочке или сравнение объектов
- Plan-and-Execute — длинная задача, шаги известны заранее, важна параллельность
- ReAct — нужны инструменты, шаги непредсказуемы → универсальный выбор
- Порядок проверки:
улучшение?→multi-hop?→предсказуем?→ReAct - Комбинируй осознанно:
Self-Ask + Reflection,Plan-Execute + Reflection,ReAct + Reflection - ROUTER_TOOL +
tool_choice: "tool"→ автоматический выбор паттерна - Каждый лишний паттерн = больше токенов + больше latency → должен быть оправдан
Практические задания
- Классификация задач. Возьми 10 реальных пользовательских запросов из любого продукта (или придумай). Для каждого определи: какой паттерн оптимален и почему. Оцени, в скольких случаях достаточно прямого вызова без агентских паттернов.
-
Реализуй роутер.
Реализуй
adaptive_agent(task)с ROUTER_TOOL. Подключи хотя бы два паттерна (например,directиreflection). Протестируй на 5 разных задачах и проверь, что роутер выбирает ожидаемый паттерн. Логируй reasoning — это поможет понять, на что смотрит модель. - Benchmark паттернов. Возьми одну сложную задачу (например, написать аналитическую статью на 500 слов по теме на ваш выбор). Реши её тремя способами: прямой вызов, Reflection (3 итерации), Self-Ask + Reflection. Сравни: качество результата, число токенов, время выполнения. Когда комбинация оправдана, а когда избыточна?