Раздел 2 — Агентные циклы и обработка stop_reason
Что покрывает этот раздел
Как строить центральный поток управления агента Claude: отправить запрос, проверить stop_reason, выполнить инструменты, которые запросил Claude, добавить результаты в историю и повторить. Каждый паттерн более высокого уровня (оркестратор–воркеры, субагенты, оценщик–оптимизатор, Agent SDK) строится поверх этого цикла.
Исходный материал (из официального руководства)
Требуемые знания
- Жизненный цикл агентного цикла: отправить запрос в Claude, проверить
stop_reason("tool_use"vs"end_turn"), выполнить запрошенные инструменты, вернуть результаты для следующей итерации. - Как результаты инструментов добавляются в историю диалога, чтобы модель могла рассуждать о следующем действии.
- Различие между принятием решений моделью (Claude рассуждает, какой инструмент вызвать дальше, исходя из контекста) и предварительно сконфигурированными деревьями решений (разработчик жёстко прописывает последовательность инструментов).
Требуемые навыки
- Реализовать поток управления агентного цикла, который продолжается, пока
stop_reason == "tool_use", и завершается, когдаstop_reason == "end_turn". - Добавлять результаты инструментов в контекст диалога между итерациями, чтобы модель могла включать новую информацию в свои рассуждения.
- Избегать анти-паттернов: разбор естественноязыковых сигналов для завершения цикла, использование произвольного ограничения итераций как основного механизма останова или проверка текстового содержимого ассистента как индикатора завершения.
Агентный цикл от начала до конца
Рабочее определение агента у Anthropic — самое простое в отрасли: “LLMs autonomously using tools in a loop.” Расширенная LLM (модель + инструменты + поиск + память) — базовый строительный блок; каждый паттерн рабочего процесса (последовательность промптов, маршрутизация, параллелизация, оркестратор–воркеры, оценщик–оптимизатор) собирается из него.
┌──────────────────────────────────────────────────────────────┐
│ user prompt + tool definitions ─────────► messages array │
└──────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ POST /v1/messages (Claude reasons about the next action) │
└──────────────────────────────────────────────────────────────┘
│
▼
┌────── inspect response.stop_reason ──────┐
│ │
"tool_use" "end_turn"
│ │
▼ ▼
┌───────────────────────────┐ ┌─────────────────┐
│ 1. append assistant turn │ │ return final │
│ (incl. tool_use blocks)│ │ text to caller │
│ 2. execute each tool │ └─────────────────┘
│ 3. append a user turn │
│ with tool_result blocks│
│ 4. loop back to /messages │
└───────────────────────────┘Разбор одной итерации:
- Отправьте
messagesплюс схемуtoolsнаPOST /v1/messages. - Claude возвращает сообщение от
assistant. Егоcontent— список блоков: ноль или больше блоковtextи ноль или больше блоковtool_use. Верхнеуровневыйstop_reasonобобщает, почему генерация остановилась. - Если
stop_reason == "tool_use": добавьте ход ассистента дословно, выполните каждый запрошенный инструмент, добавьте один новый ход отuser, чьё содержимое — список блоковtool_result(по одному наtool_use_id), и снова вызовите API с обновлённой историей. - Если
stop_reason == "end_turn": модель решила, что задача завершена. Возвращаемся.
Результаты инструментов добавляются в историю диалога, а не отбрасываются после суммирования. Каждый новый запрос несёт всю историю целиком, поэтому Claude может выстраивать рассуждения через множество ходов. Модель — а не ваш код — решает, какой инструмент вызвать следующим, исходя из того, что она наблюдает. Это и есть различие между принятием решений моделью (Claude выбирает инструмент N+1 из текущего контекста) и предварительно сконфигурированными деревьями решений (ваш код статически вызывает tool_a() → tool_b() → tool_c()). Деревья решений — это рабочие процессы; агентные циклы — это агенты. Опубликованные рекомендации Anthropic предписывают предпочитать более простой рабочий процесс всегда, когда путь можно жёстко прописать.
Значения stop_reason, которые нужно знать
stop_reason присутствует в каждом успешном ответе Messages API. Это единственный сигнал, по которому следует разветвлять решение, продолжать ли цикл. Полный набор задокументированных значений приведён ниже.
| Значение | Смысл | Что должен делать ваш цикл |
|---|---|---|
end_turn | Claude естественно завершил ответ. | Выйти из цикла. Вернуть вызывающему коду блоки text из response.content. |
tool_use | Ответ содержит один или несколько блоков tool_use; Claude ожидает, что вы их выполните. | Добавить ход ассистента, выполнить каждый блок tool_use, добавить ход от user с соответствующими блоками tool_result (используйте тот же tool_use_id) и снова вызвать API. |
max_tokens | Вывод достиг параметра max_tokens. Ответ обрезан и может содержать неполный блок tool_use. | Обнаружьте обрезание в середине вызова инструмента, проверив, что у последнего блока содержимого type == "tool_use"; повторите запрос с большим max_tokens. Иначе запросите продолжение или покажите предупреждение об обрезании. |
stop_sequence | Вывод совпал с пользовательской строкой из stop_sequences. Совпавшая последовательность находится в response.stop_sequence. | Считать это успешным терминальным остановом для данного паттерна. Продолжить или завершить в зависимости от вашего протокола. |
pause_turn | Серверный цикл сэмплирования достиг своего ограничения итераций при работе с серверными инструментами (web search, web fetch, code execution и т. п.). Ответ может содержать блок server_tool_use без соответствующего server_tool_result. | Добавить ответ ассистента без изменений и снова вызвать API с теми же инструментами. Повторять до тех пор, пока не получите stop reason, отличный от pause_turn. |
refusal | Модель отказалась по соображениям безопасности (фильтр безопасности API в Sonnet 4.5+ / Opus 4.1+). | Не зацикливаться. Показать отказ вызывающему коду; при необходимости перефразировать, перенаправить на другую модель (например, Haiku 4.5) или эскалировать. |
model_context_window_exceeded | Генерация остановилась, потому что ответ достиг полного контекстного окна модели (а не max_tokens). По умолчанию в Sonnet 4.5+; более ранним моделям нужен бета-заголовок. | Обрабатывать аналогично max_tokens — ответ корректен, но ограничен. Продолжить, суммировать или сжать контекст. |
Ветвление по stop_reason — единственный корректный тест завершения. Не разбирайте текст вида “I’m done” или “Final answer:” — это канонический анти-паттерн, упомянутый ниже.
Эталонные реализации
Python — сырой цикл на Messages API
Минимальная исполняемая форма с использованием Python SDK anthropic (тот же паттерн цикла, который Anthropic показывает в своей документации).
from anthropic import Anthropic
client = Anthropic()
MODEL = "claude-opus-4-7"
tools = [{
"name": "get_weather",
"description": "Get current weather for a city.",
"input_schema": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"],
},
}]
def run_tool(name: str, tool_input: dict) -> str:
if name == "get_weather":
return f"Weather in {tool_input['location']}: 72F, clear"
raise ValueError(f"unknown tool: {name}")
def agent_loop(user_prompt: str) -> str:
messages = [{"role": "user", "content": user_prompt}]
while True:
resp = client.messages.create(
model=MODEL, max_tokens=4096, tools=tools, messages=messages,
)
if resp.stop_reason == "end_turn":
return "".join(b.text for b in resp.content if b.type == "text")
if resp.stop_reason == "pause_turn":
messages.append({"role": "assistant", "content": resp.content})
continue
if resp.stop_reason == "tool_use":
messages.append({"role": "assistant", "content": resp.content})
tool_results = [
{"type": "tool_result", "tool_use_id": b.id,
"content": run_tool(b.name, b.input)}
for b in resp.content if b.type == "tool_use"
]
messages.append({"role": "user", "content": tool_results})
continue
raise RuntimeError(f"unhandled stop_reason: {resp.stop_reason}")Замечания: ход ассистента добавляется дословно (блоки tool_use должны сохраниться в истории). Результаты инструментов возвращаются в одном сообщении от user, чей content — это список блоков tool_result, по одному на tool_use_id. pause_turn требует повторной отправки содержимого ассистента без изменений; не синтезируйте результат инструмента сами.
TypeScript — Claude Agent SDK
Для более высокоуровневого Claude Agent SDK от Anthropic (@anthropic-ai/claude-agent-sdk) цикл уже реализован за вас. Вы потребляете асинхронный поток типизированных сообщений и проверяете терминальный ResultMessage.
import { query } from "@anthropic-ai/claude-agent-sdk";
const stream = query({
prompt: "Find the failing tests in auth.ts and fix them.",
options: {
model: "claude-opus-4-7",
maxTurns: 20,
maxBudgetUsd: 1.0,
permissionMode: "acceptEdits",
allowedTools: ["Read", "Edit", "Bash", "Grep", "Glob"],
},
});
for await (const message of stream) {
if (message.type === "assistant") {
console.log(`turn: ${message.message.content.length} blocks`);
}
if (message.type === "result") {
if (message.subtype === "success") {
console.log("done:", message.result);
} else {
console.error("stopped early:", message.subtype);
}
}
}Внутри SDK запускает тот же цикл, управляемый stop_reason: Claude рассуждает, запрашивает инструменты, SDK их выполняет, результаты автоматически возвращаются обратно, и один полный ход Claude плюс выполнение инструментов — это то, что SDK называет ходом. Цикл заканчивается, когда Claude выдаёт сообщение ассистента без блоков tool_use. maxTurns и maxBudgetUsd — это предохранители, а не основной механизм останова; при срабатывании они выдают ResultMessage с подтипом error_max_turns или error_max_budget_usd.
Анти-паттерны, которых нужно избегать
- Разбор естественноязыковых сигналов для завершения цикла. Поиск “Final answer:” или “DONE” в
response.contentхрупок — модель может сформулировать завершение бесконечным числом способов и всё равно захотеть вызвать ещё один инструмент. Правильно: ветвиться только поresponse.stop_reason. - Использование ограничения итераций как основного механизма останова. Жёсткое
for _ in range(10):и выход по достижении предела означают, что вы прервёте работу в середине задачи на сложных запросах и потратите токены на простых. Правильно: пусть цикл завершаетstop_reason == "end_turn"; держите ограничения итераций иmax_budget_usdтолько как защитные предохранители. - Проверка текстового содержимого ассистента для решения о завершении. Один ход может содержать одновременно блоки
textиtool_use(Claude может комментировать, одновременно запрашивая инструмент). Трактовка “есть текст” как “готово” приводит к потере вызовов инструментов. Правильно: проверяйтеstop_reason; итерируйте по блокам содержимого по ихtype. - Отбрасывание хода ассистента при добавлении результатов инструментов. Отправка результатов инструментов без предварительного добавления хода ассистента с
tool_useприводит к некорректному массивуmessagesи ошибке API. Правильно: добавляйте ход ассистента дословно, затем один ход от пользователя с блокамиtool_result. - Добавление дополнительного текста после блоков
tool_result. Хвостовые блокиtextв том же ходе от пользователя приучают Claude ожидать пользовательский текст после каждого вызова инструмента, что приводит к пустым ответам сend_turn. Правильно: ход пользователя послеtool_useдолжен содержать только блокиtool_result. - Игнорирование
pause_turn. При работе с серверными инструментами сервер достигает собственного ограничения в 10 итераций и возвращаетpause_turnбезtool_result, который вы могли бы предоставить. Трактовка этого какend_turnобрывает агента. Правильно: добавьте ответ ассистента без изменений и вызовите API снова. - Игнорирование обрезания по
max_tokensвнутри блокаtool_use. Еслиstop_reason == "max_tokens"и последний блок —tool_use, JSON-вход неполон, и повтор с тем же лимитом снова провалится. Правильно: обнаружьте этот случай и повторите запрос с большимmax_tokens. - Жёстко прописанная последовательность инструментов. Вызовы
read_file → search → write_fileиз вашего собственного кода без участия модели в рассуждении — это рабочий процесс, а не агент. Это нормально, когда путь известен, — но не ждите от него восстановления на новых входах.
Точки фокусировки в стиле экзамена
- По заданному значению
stop_reasonопределить корректное действие цикла (продолжить с результатами инструментов, добавить и повторно отправить дляpause_turn, выйти наend_turn, повторить с увеличенным бюджетом наmax_tokensв середине инструмента, показать отказ). - Определить, какие изменения
messagesнужны между итерациями: добавить ход ассистента дословно (включая блокиtool_use), затем добавить ход пользователя с блокамиtool_result, привязанными поtool_use_id. - Отличать принятие решений моделью от предварительно сконфигурированных деревьев решений и выбирать правильный паттерн для описанной задачи (открытая задача — агент; чётко определённый фиксированный путь — рабочий процесс).
- Замечать анти-паттерны в примере кода: разбор текста для завершения, ограничение итераций как останов, отсутствующий ход ассистента, лишние блоки
textпослеtool_result, игнорированиеpause_turn. - Знать, что
max_turns/max_budget_usdв Claude Agent SDK — это предохранители; основной завершитель цикла по-прежнему “нет блоковtool_useв ответе ассистента”.
Ссылки
- Handling stop reasons — Claude API docs — авторитетный перечень всех значений
stop_reasonс примерами обработки на Python, TypeScript, Go, Java, C#, PHP и Ruby. - Messages API reference — Create a Message — схема запроса и ответа, включая типы блоков содержимого
text,tool_useиtool_result. - Tool use overview — определение инструментов и обмен
tool_use/tool_result. - Building effective agents — Anthropic Engineering (Dec 19, 2024) — канонический пост, определяющий различие рабочих процессов и агентов и строительный блок расширенной LLM. Обязательное чтение к экзамену.
- Effective context engineering for AI agents — Anthropic Engineering (Sep 29, 2025) — повторяет рабочее определение агента как “LLMs autonomously using tools in a loop”; разбирает компакцию и паттерны субагентов для долгих циклов.
- How the agent loop works — Claude Agent SDK docs — официальное описание жизненного цикла хода и сообщения в SDK,
max_turns,max_budget_usdи подтиповResultMessage. - Agent SDK reference — Python и TypeScript —
query()противClaudeSDKClient, типы сообщений и типизированный поток событий. - anthropics/claude-cookbooks — tool_use — исполняемые примеры цикла,
tool_choiceи программного вызова инструментов. - Anthropic Python SDK и TypeScript SDK — текущие формы клиентов, используемые в циклах выше.