Цикл агента — что происходит внутри
Когда я первый раз услышал «AI-агент», я представил какую-то нейросеть отдельного типа — может быть с памятью, может быть с самосознанием. На деле всё проще: агент это while-цикл, в котором на каждой итерации крутится один и тот же chat-запрос, но с подросшим контекстом. Понимание этого цикла — ключ ко всему. Когда ты видишь как именно агент думает и принимает решения, исчезает вся магия и становится ясно где он сильный, где слабый и где надо подстелить соломки.
Шаг 1 — отправляем запрос с инструментами
Цикл начинается с первого вызова модели. В запрос ты кладёшь: системный промпт (роль агента и правила), сообщения пользователя, и список доступных инструментов с их описаниями. Каждый инструмент — это JSON-схема: имя, описание что он делает, какие параметры принимает. Это критически важно: модель НЕ видит код инструментов, она видит только их описания. От качества описания зависит будет ли модель использовать инструмент правильно. Плохое описание = модель путается в параметрах или вообще игнорирует инструмент.
Шаг 2 — модель решает: ответить или вызвать инструмент
Claude получил твой запрос и думает. У ответа есть поле stop_reason. Если stop_reason === "end_turn" — модель решила что готова дать финальный ответ, цикл заканчивается. Если stop_reason === "tool_use" — модель хочет вызвать один или несколько инструментов. В content ответа лежит блок tool_use с именем инструмента и параметрами. Это решение модель принимает сама — на основе системного промпта, контекста и описаний инструментов. Твой код не выбирает за неё.
Шаг 3 — выполняем инструмент и возвращаем результат
Это работа твоего кода. Берёшь имя инструмента из tool_use, ищешь у себя соответствующую функцию, вызываешь её с параметрами, получаешь результат. Дальше формируешь сообщение типа "user" с блоком tool_result — указываешь tool_use_id из ответа модели, и кладёшь результат (текст, JSON, что угодно). Важно: если инструмент упал — не выкидывай ошибку наружу, а верни её как tool_result со строкой описания ошибки. Модель умеет адаптироваться: «инструмент вернул ошибку, попробую другой подход» — это нормальное поведение агента.
Шаг 4 — итерируемся пока не end_turn
После tool_result делаешь новый chat-запрос со всем накопленным контекстом — старые сообщения, ответ модели с tool_use, и твой tool_result. Модель видит результат своего инструмента и снова решает: ответить пользователю или вызвать ещё что-то. И так пока не end_turn. На практике для типовых задач хватает 2-5 шагов. Если шаг 20 — что-то идёт не так. Поэтому второе обязательное стоп-условие — счётчик итераций. Третье — лимит токенов. Без этих трёх (end_turn, max_steps, max_tokens) агент превращается в денежную дыру.
Что хранится в контексте между шагами
Контекст растёт с каждой итерацией. На шаге N модель видит: системный промпт + ВСЕ сообщения пользователя + ВСЕ свои предыдущие ответы (включая tool_use блоки) + ВСЕ результаты инструментов. У Claude Sonnet окно 200K токенов, у Opus тоже. Для большинства задач этого вагон. Но если агент работает над долгой задачей с большими данными (читает 30 файлов и анализирует их) — окно может закончиться. Тогда либо включаешь Claude с 1M-контекстом, либо делаешь summarization — на каком-то этапе сжимаешь старую переписку в краткое резюме.
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic();
const tools = [
{
name: 'get_weather',
description: 'Получить текущую погоду по городу',
input_schema: {
type: 'object',
properties: {
city: { type: 'string', description: 'Название города' },
},
required: ['city'],
},
},
];
async function runTool(name: string, input: any): Promise<string> {
if (name === 'get_weather') {
// тут реальный API-вызов, для примера просто хардкод
return JSON.stringify({ city: input.city, temp: -3, sky: 'облачно' });
}
return 'Неизвестный инструмент';
}
async function runAgent(userTask: string) {
const messages: any[] = [{ role: 'user', content: userTask }];
const MAX_STEPS = 10;
for (let step = 0; step < MAX_STEPS; step++) {
const response = await client.messages.create({
model: 'claude-sonnet-4-6',
max_tokens: 2048,
tools,
messages,
});
// Финальный ответ — выходим
if (response.stop_reason === 'end_turn') {
const text = response.content.find((c: any) => c.type === 'text');
return text?.text ?? '';
}
// Модель хочет вызвать инструменты
if (response.stop_reason === 'tool_use') {
messages.push({ role: 'assistant', content: response.content });
const toolResults = [];
for (const block of response.content) {
if (block.type === 'tool_use') {
const result = await runTool(block.name, block.input);
toolResults.push({
type: 'tool_result',
tool_use_id: block.id,
content: result,
});
}
}
messages.push({ role: 'user', content: toolResults });
} else {
// Что-то непонятное — выходим
throw new Error(`Unexpected stop_reason: ${response.stop_reason}`);
}
}
throw new Error('Agent exceeded max steps');
}
const answer = await runAgent('Какая погода в Москве?');
console.log(answer);Минимальный рабочий цикл агента на TypeScript + Anthropic SDK