Перейти к содержанию

agent-loop — как агент работает под капотом

Главный секрет: в агенте нет никакой магии. Это обычные запросы к LLM-серверу — как из чата — только «особенные»: в запросе перечислены инструменты, а модель в ответе может попросить один из них вызвать. Дальше — цикл.

Вспомни: модель — это угадайка текста, она физически ничего не может запустить. Всё, что она умеет — написать текст. Тогда как агент «читает файлы» и «запускает команды»?

Фокус такой:

  1. Агент шлёт серверу не просто твой вопрос, а вопрос + список своих инструментов: «у меня есть "прочитать файл", "запустить команду" — если надо, скажи, что вызвать».
  2. Модель отвечает не человеческим текстом, а запиской: «вызови "прочитать файл" с именем app.log». Это всё ещё просто текст! Модель ничего не запускала.
  3. Агент читает записку, сам выполняет действие на твоём компьютере и дописывает результат в разговор.
  4. И отправляет весь разговор заново: вопрос + записка + результат. Модель смотрит и либо просит следующий инструмент, либо отвечает по-человечески — тогда цикл кончился.
sequenceDiagram
    participant U as 👤 Ты
    participant A as 🤖 Агент
    participant L as ☁️ LLM-сервер
    U->>A: «что за ошибки в логах?»
    A->>L: запрос №1: вопрос + список инструментов
    L-->>A: 📝 «вызови read_file("app.log")»
    Note over A: агент САМ читает файл<br/>(модель не умеет)
    A->>L: запрос №2: всё то же + содержимое файла
    L-->>A: «ошибки две: кончилось место и ...»
    A-->>U: готовый ответ

Из этого следуют неожиданные вещи:

  • Сервер не помнит агента между запросами. Каждый запрос — самодостаточный, с полной историей внутри. «Диалог» — иллюзия, которую поддерживает агент.
  • Чем дольше агент работает, тем дороже каждый шаг — история-то растёт, а токены платные.
  • Модель можно «обмануть» — она верит результатам инструментов. Поэтому агенту нужны ограничители (см. agents.md).

Агентный цикл — это последовательность обычных stateless HTTP-запросов к LLM-серверу. «Особенность» ровно одна: поле tools в запросе и ответ с tool call вместо (или вместе с) текстом.

Запрос №1 — сообщение + декларация тулов

json { "model": "claude-...", "messages": [ { "role": "user", "content": "что за ошибки в логах?" } ], "tools": [ { "name": "read_file", "description": "Читает файл с диска", "input_schema": { "type": "object", "properties": { "path": { "type": "string" } }, "required": ["path"] } } ] }

Ответ — модель «просит» вызвать тул

json { "stop_reason": "tool_use", "content": [ { "type": "tool_use", "id": "toolu_01", "name": "read_file", "input": { "path": "app.log" } } ] }

Это просто JSON, сгенерированный моделью как текст. Сервер ничего не выполнял — он даже не знает, существует ли app.log.

Запрос №2 — та же история + результат тула

Агент выполнил чтение файла у себя и шлёт продолжение — весь диалог целиком, добавив ответ модели и результат:

json { "messages": [ { "role": "user", "content": "что за ошибки в логах?" }, { "role": "assistant", "content": [ { "type": "tool_use", "id": "toolu_01", "...": "..." } ] }, { "role": "user", "content": [ { "type": "tool_result", "tool_use_id": "toolu_01", "content": "ERROR: disk full\nERROR: timeout ..." } ] } ], "tools": [ "...тот же список..." ] }

Цикл крутится, пока stop_reason не станет end_turn — тогда в content обычный текст, и агент показывает его пользователю.

sequenceDiagram
    participant A as Агент (слой 3)
    participant L as LLM-сервер (слой 2)
    loop пока stop_reason == "tool_use"
        A->>L: POST /v1/messages { messages[], tools[] }
        L-->>A: { tool_use: name + input }
        Note over A: выполняет тул локально,<br/>аппендит tool_result в messages[]
    end
    L-->>A: { stop_reason: "end_turn", content: текст }

(Формат выше — Anthropic Messages API; у OpenAI-совместимых серверов то же самое называется tool_calls / role: "tool" — идея идентична.)

Следствия, которые важно понимать

  • Stateless. Сервер не хранит состояние агента: каждый запрос несёт всю историю. Память, «личность», проект — всё это агент собирает в messages сам.
  • Контекст и стоимость растут с каждым шагом. Длинная сессия = дорогие запросы; агенты сжимают/суммируют историю (context management).
  • Тулы декларативны. Модель видит только name + description + JSON Schema. Чем лучше описание — тем точнее выбор тула. MCP (см. mcp.md) — стандартный способ подсунуть агенту этот список со стороны.
  • Валидировать и ограничивать — обязанность агента. Модель может сгенерировать вызов несуществующего тула или опасные аргументы; агент проверяет схему, спрашивает подтверждение, режет лимиты.

Связанные страницы

  • agents.md — что такое агент и какие бывают
  • llm-server.md — API, в которое всё это шлётся
  • llm-model.md — почему каждый раз нужен весь контекст
  • mcp.md — стандартный способ раздать агенту тулы