Что происходит под капотом ИИ-агента: разбор диалога построчно
Агент не «думает» — он вызывает функции, читает их ответы и решает, что делать дальше. Разбираем один диалог с инструментами построчно: где модель ошибается, где тратит токены и почему цикл иногда не останавливается.

Когда вы просите ИИ-агента «проверить погоду в Москве и написать письмо коллеге», под этой одной командой скрывается не один запрос к модели, а серия из нескольких обращений: модель отвечает, вызывает функцию, получает результат, снова отвечает. Пользователь видит финальный текст. Разработчик, который платит за токены и ловит баги, видит цикл — и именно в этом цикле прячутся лишние расходы, зацикливания и галлюцинации инструментов, которых на самом деле нет.
Разберём один типовой диалог с агентом, у которого есть два инструмента: get_weather(city) и send_email(to, text). Пройдём по шагам так, как их видит бэкенд — не «магия», а последовательность сообщений с ролями и полезной нагрузкой.
Из чего состоит один «ход» агента
Агент — это цикл1 вокруг языковой модели. На каждой итерации происходит одно и то же:
- Вы отправляете модели весь список сообщений плюс описание доступных инструментов.
- Модель возвращает либо текстовый ответ пользователю, либо запрос на вызов инструмента (tool call) с аргументами.
- Если это вызов инструмента — ваш код выполняет функцию и добавляет её результат в список сообщений с ролью
tool. - Список сообщений снова целиком отправляется модели. Переходим к шагу 1.
Цикл останавливается, когда модель вернула обычный текст без вызова инструмента — или когда сработал ваш ограничитель по числу шагов. Ключевое, что часто упускают: на каждой итерации в модель уходит вся история заново. Модель не помнит предыдущий шаг — она каждый раз читает весь диалог с нуля.
Разбор диалога построчно
Пользователь пишет: «Узнай погоду в Москве и, если холоднее +5, напиши Ане, чтобы взяла куртку». Смотрим, что происходит в списке сообщений.
Ход 1: модель решает вызвать инструмент
Мы отправляем модели два сообщения — системный промпт и запрос пользователя — плюс схемы обоих инструментов. Модель не может сама узнать погоду, поэтому вместо текста возвращает структуру примерно такого вида:
{
"role": "assistant",
"content": null,
"tool_calls": [{
"id": "call_01",
"function": {
"name": "get_weather",
"arguments": "{\"city\": \"Moscow\"}"
}
}]
}Обратите внимание на две детали. Во-первых, content пустой — модель ничего не говорит пользователю, она просит вызвать функцию. Во-вторых, arguments — это строка с JSON внутри, а не готовый объект. Её нужно распарсить, и здесь модель регулярно ошибается: пишет "Москва" вместо "Moscow", если ваш API ждёт латиницу, или добавляет лишнее поле. Валидация аргументов до вызова — не роскошь, а защита от падения.
Ход 2: ваш код выполняет функцию
Модель тут не участвует. Ваш бэкенд вызывает реальный get_weather("Moscow"), получает, скажем, {"temp": 2, "desc": "облачно"} и добавляет в историю сообщение:
{
"role": "tool",
"tool_call_id": "call_01",
"content": "{\"temp\": 2, \"desc\": \"облачно\"}"
}Поле tool_call_id связывает ответ с конкретным запросом. Если инструментов вызвано несколько за раз, именно по id модель понимает, какой результат к какому вызову относится. Потерять id — значит получить перепутанные ответы.
Ход 3: модель читает результат и принимает решение
Теперь мы снова отправляем весь список — четыре сообщения — модели. Она видит: температура +2, это холоднее +5, условие выполнено. И возвращает второй вызов инструмента, уже send_email. Именно на этом шаге агент проявляет «логику»: условие «если холоднее +5» проверяет не ваш код, а модель, прочитав число из ответа инструмента.
Ход 4: отправка письма и финал
Ваш код выполняет send_email, кладёт результат в историю, отправляет всё модели в последний раз. Модель видит, что письмо ушло, и возвращает обычный текст: «Готово, в Москве +2, написал Ане про куртку». Вызовов инструментов больше нет — цикл останавливается.
Итого одна пользовательская команда — это четыре обращения к модели и два реальных вызова функций. И с каждым обращением вы отправляете растущую историю целиком. К четвёртому ходу вы платите за токены первого сообщения уже в четвёртый раз.
Где это ломается и сколько стоит
Три типовые проблемы, которые видно только на уровне разбора построчно:
- Рост контекста. История отправляется заново на каждом шаге. Диалог из 10 вызовов инструментов с длинными результатами (например, выдача поиска) легко раздувает контекстное окно2 и счёт за токены. Обрезка старых tool-результатов или их сжатие — обязательная гигиена.
- Зацикливание. Модель вызывает инструмент, получает результат, который её не устраивает, и вызывает тот же инструмент снова с почти теми же аргументами. Без ограничителя по числу шагов цикл может крутиться, пока не упрётся в лимиты API.
- Галлюцинация инструмента. Модель может «вызвать» функцию, которой вы ей не давали, или придумать поле в аргументах. Если ваш код это не проверяет и слепо исполняет, вы получаете либо ошибку, либо неверное действие.
Чем разные подходы к вызову инструментов отличаются
| Подход | Как модель зовёт инструмент | Ограничение |
|---|---|---|
| Нативный tool calling (OpenAI, Anthropic, Google) | Структурированное поле в ответе API | Работает только у моделей с этой возможностью |
| Function calling через JSON в тексте | Модель пишет JSON в обычном ответе, вы парсите | Ломается при невалидном JSON, нужна строгая обработка |
| ReAct-промптинг (Reasoning + Acting) | Модель чередует «мысль» и «действие» текстом по шаблону | Дороже по токенам, менее надёжный парсинг |
Нативный tool calling сегодня — путь по умолчанию, если ваша модель его поддерживает: провайдер сам возвращает разобранную структуру, а не текст, из которого надо выковыривать JSON регулярками. ReAct остаётся вариантом для моделей без нативной поддержки инструментов.
Кому это пригодится и кому нет
Пригодится:
- Разработчику, который собирает агента и получает счёт в разы выше ожидаемого — почти всегда причина в том, что история отправляется заново на каждом шаге.
- Тому, кто отлаживает «агент завис» или «агент сделал не то»: без логирования каждого сообщения с ролью вы не увидите, на каком ходу модель приняла неверное решение.
- Тем, кто выбирает между фреймворком (LangChain, LlamaIndex) и своим циклом: понимая, что внутри всего четыре шага, вы честно оцените, нужна ли вам абстракция сверху.
Не пригодится: если вы используете готового ассистента в вебе или приложении без своего кода. Там цикл и управление историей уже реализованы за вас, и лезть в них незачем.
1 Агент — здесь: программа, которая в цикле вызывает языковую модель, даёт ей выполнять инструменты (функции) и передаёт результаты обратно модели, пока задача не решена. Не путать с «умом» модели: логику ветвления в диалоге выше выполняет именно модель, но исполняет функции и хранит историю ваш код.
2 Контекстное окно — максимальный объём текста (в токенах), который модель обрабатывает за один запрос: и история, и системный промпт, и ответ. Когда история диалога с инструментами перерастает это окно, самые старые сообщения приходится обрезать.
Prompt-инженер: Идеальные запросы для Midjourney, ChatGPT и других моделей.
Спросить за 15 ₽Источники: OpenAI — Function calling (документация), Anthropic — Tool use with Claude (документация)
Частые вопросы
Почему счёт за токены растёт быстрее, чем число сообщений?
Модель сама выполняет функции?
Что делать, если агент зацикливается на одном инструменте?
Можно ли доверять аргументам, которые модель передаёт в функцию?
Нужен ли фреймворк вроде LangChain, чтобы сделать агента?
Как узнать, на каком шаге агент принял неверное решение?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.