EvolCode
треки/ИИ-агенты·02 / 07
10 мин чтения

Цикл агента — что происходит внутри

Когда я первый раз услышал «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 — на каком-то этапе сжимаешь старую переписку в краткое резюме.

примерtypescript
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

главное
  • 01Агент = while-цикл вокруг chat API. Ничего магического.
  • 02Стоп-условие №1: stop_reason === "end_turn". Стоп-условие №2: лимит шагов. Без второго агент может зациклиться.
  • 03Качество инструментов = качество их ОПИСАНИЙ. Модель видит описания, не код.
  • 04Ошибки инструментов возвращай как tool_result, а не выбрасывай — модель умеет восстанавливаться.
  • 05Контекст растёт каждый шаг. Для долгих задач думай про summarization или Claude с 1M-контекстом.
проверь себя
01Что является обязательным стоп-условием в цикле агента, помимо end_turn?
02Что модель «видит» когда решает — вызывать ли инструмент?
03Что лучше сделать если инструмент упал с ошибкой?
← к треку