← К техническим практикам

AIAgent

Цикл Tool Calls в Kimi K3: как остановить в 2026

Около 3 мин чтения

Цикл Tool Calls в Kimi K3: как остановить в 2026

В официальной памятке Kimi для повторного вызова инструмента выделены три обязательных признака: одинаковые function.name, одинаковые function.arguments и отсутствие нового полезного результата. Это означает, что сначала нужно доказать корректность цепочки сообщений, а уже затем останавливать модель как зациклившуюся. (официальная памятка Kimi по устранению неполадок)

Симптом: Kimi K3 снова вызывает одну функцию, потоковые параметры собираются неправильно, а фоновая задача не завершается.
Самое быстрое решение: сохраните полный след вызова, проверьте assistant и tool_call_id, затем включите программный предохранитель по повтору, времени, раундам и побочным эффектам.

Эта статья предназначена разработчикам инструментальных Agent-приложений, инженерам автоматизированных платформ и командам, где AI Agent может отправлять письма, создавать заказы, менять данные или запускать другие необратимые операции. Если вы используете Kimi K3 API только для обычного текста без инструментов, большая часть проверки вам не понадобится.

Последняя проверка — 3 августа 2026 года. Данные сверены с официальными материалами Kimi по устранению неполадок, формату Tool Calls, API и отладке в Playground.

До исправления сохраните доказательства и остановите риск

Не начинайте с изменения системного промпта. При повторном Tool Calls сначала нужно понять, что именно повторяется: отображение в интерфейсе, повтор SDK, повтор сетевого запроса или фактический новый вызов модели.

Сохраните для одного задания:

  • обезличенный массив messages до каждого запроса;
  • ответ assistant с полем tool_calls;
  • finish_reason;
  • tool_call.id, имя функции и исходную строку function.arguments;
  • результат фактического выполнения инструмента;
  • идентификатор запроса, время начала и окончания;
  • сведения о повторной отправке после сетевой ошибки;
  • расход API, если он доступен в ответе или журнале клиента.

Секреты, токены, адреса клиентов, бизнес-идентификаторы и реальные параметры замените на <API_KEY>, <ORDER_ID>, <USER_ID> и другие заполнители. Не удаляйте структуру полей: для диагностики важны не значения сами по себе, а порядок и соответствие объектов.

Если функция изменяет данные, немедленно переведите её в режим чтения, песочницу или «сухой прогон». Это особенно важно для отправки сообщений, оплаты, создания заявок и массового обновления записей. Повторный вызов безопасного поиска и повторная запись в платёжный шлюз — это разные классы риска, даже если модель использует один и тот же механизм Tool Calls.

Что вы видите в журнале Наиболее вероятное объяснение Первое действие
Один вызов в API-журнале, но два отображения Ошибка интерфейса или обработчика событий Сверить request_id и фактический ответ
Один request_id, несколько попыток выполнения Повтор внутри раннера или SDK Проверить автоматические ретраи и состояние цикла
Новый request_id, тот же name и JSON аргументов Повторный запрос модели Сравнить результат инструмента и добавить детектор прогресса
Ошибка tool_call_id not found Нарушен порядок сообщений или изменён ID Вернуть исходный assistant и точный ID
Разные фрагменты потока дают разный JSON Ошибка сборки потоковых данных Сохранить сырые фрагменты и отключить выполнение до полной сборки

Документация Kimi отдельно указывает, что некоторые ошибки SDK повторяются автоматически. В стандартной конфигурации могут повторяться сетевые ошибки, тайм-ауты, конфликты, ограничения частоты и серверные ошибки. Поэтому один пользовательский запрос не всегда равен одному обращению к API.

Первый этап: восстановите правильную цепочку сообщений

Базовый цикл Kimi K3 API должен выглядеть так:

system
user
assistant: tool_calls
tool: результат для каждого tool_call_id
assistant: следующий ответ или новый tool_calls

Критическая ошибка — выполнить функцию, но не добавить в messages исходный ответ assistant, который содержал tool_calls. Вторая ошибка — отправить role=tool с новым или сокращённым идентификатором. Официальный пример Kimi рекомендует добавлять ответ модели в контекст как есть, а затем добавлять результат каждого инструмента с соответствующим tool_call_id. (официальный пример Tool Calls)

Минимальная проверка без потоковой передачи должна исключить клиентскую обвязку:

response = client.chat.completions.create(
    model="kimi-k3",
    messages=messages,
    tools=tools,
    stream=False,
)

choice = response.choices[0]

if choice.finish_reason == "tool_calls":
    assistant_message = choice.message
    messages.append(assistant_message)

    for call in assistant_message.tool_calls:
        args = json.loads(call.function.arguments)
        result = execute_read_only(call.function.name, args)

        messages.append({
            "role": "tool",
            "tool_call_id": call.id,
            "name": call.function.name,
            "content": json.dumps(result),
        })

В рабочем коде добавьте проверки до выполнения:

assert call.id
assert call.function.name in allowed_tools
assert isinstance(call.function.arguments, str)

После выполнения проверьте, что число отправленных role=tool сообщений совпадает с числом вызовов, а каждый ID присутствует ровно там, где ожидается. Если модель вернула несколько инструментов в одном ответе, нельзя обработать только первый и сразу отправить новый запрос: остальные вызовы должны получить ответы либо быть явно отклонены вашим раннером.

Именно поэтому неправильный tool_call_id чаще приводит к ошибке формата, а не к интеллектуальному циклу. Но плохо обработанная ошибка может создать внешний цикл: приложение повторяет старый запрос, повторно запускает раннер и снова отправляет неполное состояние.

Для отдельного теста используйте официальный материал Kimi по Tool Calls, а интерактивный результат сравните с отладкой Tool Calls в Playground. Playground показывает идентификаторы вызовов, параметры, результаты и статистику токенов, поэтому подходит для отделения ошибки модели от ошибки вашего клиента.

Второй этап: соберите потоковые аргументы без догадок

В режиме stream=True имя функции и параметры могут приходить фрагментами. Нельзя считать каждый фрагмент самостоятельным вызовом и нельзя запускать инструмент, как только появилась первая часть JSON.

Для каждого индекса вызова храните отдельное состояние:

calls[index].id
calls[index].function.name
calls[index].function.arguments
calls[index].finished

Алгоритм должен быть таким:

  1. Получить потоковое событие.
  2. Найти индекс конкретного вызова.
  3. Добавить фрагмент имени к имени только в предусмотренном поле.
  4. Добавить фрагмент аргументов к строке только того же индекса.
  5. Сохранить исходный фрагмент в журнал.
  6. Дождаться признака завершения ответа или полной сборки.
  7. Проверить JSON стандартным парсером.
  8. Проверить схему и разрешённые поля.
  9. Только после этого запустить функцию.

Не исправляйте молча лишние кавычки, пропущенные запятые или обрезанный объект. «Умный» восстановитель JSON может превратить повреждённый поток в юридически значимое действие с неправильным параметром. Если парсинг не проходит, состояние должно перейти в ошибку или ручную проверку, а не в повторную отправку того же вызова без объяснения причины.

Поле Что фиксировать Что считать ошибкой
Индекс вызова Позицию в потоке Фрагмент попал в другой вызов
id Полное значение исходного ID ID перезаписан или создан клиентом
function.name Итоговое имя функции Имя собирается из аргументов
function.arguments Полную исходную строку Парсинг до завершения строки
Результат Версию и статус операции Результат не связан с ID вызова

Важная граница: finish_reason=tool_calls означает, что модель ожидает обработки инструментов, а finish_reason=stop — что текущий ответ завершён. Значение length указывает на ограничение длины генерации, поэтому его нельзя трактовать как доказательство успешного завершения Agent-задачи. Официальное API-описание рекомендует сначала проверять finish_reason, если ответ неполный или обрезан.

Третий этап: отличите повтор от нового шага

После исправления сообщений и потоковой сборки внедрите детектор повторов в раннер, а не только в промпт.

Сравнивайте как минимум:

ключ повтора =
имя инструмента
+ нормализованные аргументы
+ наблюдаемый прогресс результата

Нормализация должна быть детерминированной. Например, сортируйте ключи JSON, убирайте незначимые пробелы и приводите эквивалентные представления к одному виду. Не удаляйте поля, которые влияют на бизнес-операцию: дату, валюту, адрес, идентификатор клиента или режим записи.

Результат инструмента должен отвечать на вопрос: изменилось ли что-либо после предыдущей попытки? Для поиска это может быть новый набор записей. Для обновления — изменённый статус. Для отправки сообщения — подтверждение поставщика с идемпотентным ключом. Строка «операция выполнена» без проверяемого состояния не является достаточным признаком прогресса.

Официальная рекомендация Kimi допускает напоминание в системном сообщении после повторяющихся одинаковых вызовов. В документации приведены ориентиры для напоминаний после нескольких последовательных повторов, но там же подчёркивается, что это пример логики, а не обязательное поле API и не универсальный норматив для каждого Agent.

Используйте условную схему:

  • Если имя, аргументы и результат изменились — продолжайте цикл и запишите причину продолжения.
  • Если имя и аргументы совпали, но результат дал новый прогресс — разрешите ограниченное продолжение.
  • Если имя, аргументы и результат не изменились — остановите цикл и верните статус no_progress.
  • Если инструмент имеет побочный эффект — остановите его до проверки идемпотентности, даже если модель просит повторить.
  • Если причина повтора неясна — сохраните состояние и передайте задачу оператору.

Не подменяйте остановку вымышленным успехом. Пользователь должен получить сообщение вроде: «Задача остановлена: инструмент не предоставил нового результата; требуется проверка». Это лучше, чем ложное подтверждение оплаты, заказа или изменения данных.

Первый день: добавьте идемпотентность и жёсткие ограничения

К концу первого рабочего дня у каждого опасного инструмента должна появиться отдельная политика выполнения.

Для записи используйте идемпотентный ключ, производный от устойчивого идентификатора задания и логического действия:

<JOB_ID>:<ACTION>:<RESOURCE_ID>

Ключ не должен зависеть от случайного tool_call.id, если повторный запрос может создать новый ID. На стороне инструмента храните статус операции: new, started, completed, failed или эквивалентные состояния. При повторе с тем же ключом возвращайте прежний результат либо безопасный статус, но не запускайте действие заново.

Для операций с деньгами, письмами и заказами добавьте подтверждение:

  1. Модель формирует намерение.
  2. Раннер проверяет схему, права и идемпотентный ключ.
  3. Система показывает или фиксирует план действия.
  4. Отдельный шлюз разрешает запись.
  5. Результат связывается с ключом операции.

Ограничения должны быть независимыми:

  • максимум раундов Tool Calls;
  • максимальное время задания;
  • максимальный накопленный расход API;
  • максимум опасных действий;
  • максимум повторов одного ключа;
  • остановка при отсутствии прогресса.

Не выбирайте одно «магическое» число для всех задач. Для чтения каталога, анализа файлов и изменения платежного состояния нужны разные политики. Внутренний порог должен быть подтверждён тестом: что произойдёт при сетевом повторе, при пустом результате, при частичном успехе и при перезапуске процесса.

Добавьте контроль фоновых задач. После срабатывания защитного механизма проверьте, что в очереди не осталось ожидающего запроса, рабочий процесс не продолжает старый цикл, а повторная доставка сообщения не запускает действие повторно. Для восстановления сохраните последний безопасный контрольный пункт: входные сообщения, подтверждённые результаты инструментов и состояние идемпотентных операций.

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

Первая неделя: проведите производственную приёмку

В течение первой недели прогоните четыре группы образцов:

  1. обычный вызов, который завершается;
  2. тот же инструмент с изменёнными параметрами;
  3. одинаковый инструмент и параметры без нового прогресса;
  4. сетевой повтор после тайм-аута или серверной ошибки.

Для каждого образца ожидаемый результат должен быть формальным. В первом случае Agent завершает задачу. Во втором продолжает работу с новым ключом состояния. В третьем срабатывает no_progress и запрос останавливается. В четвёртом повтор не создаёт вторую побочную операцию.

Проверьте не только ответ пользователю, но и инфраструктуру:

  • не осталось ли фоновых HTTP-запросов после аварийной остановки;
  • не отправился ли один и тот же побочный эффект повторно;
  • совпадают ли request_id, ID выполнения инструмента и запись операции;
  • виден ли расход API по каждой попытке;
  • можно ли восстановить задачу с последнего контрольного пункта;
  • не попали ли секреты и реальные бизнес-данные в логи.

Для общей проверки формата используйте официальный обзор Kimi API, а для разделения ошибок запроса, авторизации, лимитов и серверных сбоев — справочник кодов ошибок Kimi API. API совместим с распространённым форматом Chat Completions, но это не освобождает ваш раннер от проверки порядка сообщений и состояния инструментов.

Если вы запускаете длительные регрессионные образцы, важна не только модель, но и среда, где процесс остаётся доступным, а журналы не исчезают после закрытия ноутбука. Для временной проверки можно рассмотреть аренду Mac mini, а для команды, которой нужен постоянно доступный удалённый узел в США, — аренду Mac mini на востоке США. Это не заменяет защиту в коде, но упрощает длительное наблюдение, повторный запуск тестов и сохранение артефактов.

Матрица решения перед включением записи

Используйте эти условия как контрольную точку перед переводом Agent в рабочий режим:

  • Если исходный assistant сохраняется без изменений, все tool_call_id совпадают, а потоковые аргументы проходят проверку JSON — переходите к детектору повторов.
  • Если хотя бы один ID потерян или переписан — оставайтесь в минимальном тесте без потоковой передачи и исправьте транспорт сообщений.
  • Если детектор видит совпадение имени и аргументов, но результат меняется — разрешайте продолжение только для безопасного чтения.
  • Если результат не меняется — возвращайте no_progress, сохраняйте контрольный пункт и не подделывайте успешный ответ.
  • Если инструмент пишет данные, отправляет сообщение или влияет на оплату — обязательны идемпотентный ключ, проверка состояния и ручной маршрут восстановления.
  • Если задача превышает ваш лимит времени, раундов, расхода или опасных действий — останавливайте её независимо от того, продолжает ли модель генерировать новые вызовы.

Частые вопросы

Почему Kimi K3 снова и снова вызывает одну и ту же функцию?

Проверьте три слоя. Сначала убедитесь, что assistant с tool_calls не потерян, затем сравните tool_call_id и фактический результат. После этого нормализуйте аргументы и определите, появился ли новый прогресс. Только совпадение имени функции, аргументов и бесполезного результата даёт основание считать повтор настоящим циклом.

Может ли неправильный tool_call_id вызвать бесконечный цикл?

Сам по себе неправильный ID обычно приводит к ошибке сопоставления сообщений. Цикл появляется из-за раннера, который скрывает эту ошибку, повторно отправляет старое состояние или выполняет запрос после частичного сбоя. Сохраняйте исходный ID, ID из role=tool, request_id и порядок сообщений. Если они различаются, сначала исправляйте протокол, а не промпт.

Как правильно собирать параметры потокового Tool Calls?

Разделите состояние по индексу вызова. Имя и строку function.arguments накапливайте отдельно, не смешивайте фрагменты разных функций и не запускайте инструмент до завершения JSON. После сборки выполните синтаксическую и схематическую проверку. Если данные обрезаны, переведите задание в ошибку или ручную проверку, сохранив все исходные фрагменты.

Как остановить Agent с побочным эффектом?

Перед выполнением проверяйте идемпотентный ключ и состояние операции. Для письма, платежа, заказа или записи в базу используйте отдельный шлюз подтверждения. При повторе без прогресса возвращайте технический статус остановки, а не «успешно». После аварийного завершения проверьте очереди и фоновые рабочие процессы, чтобы скрытый повтор не продолжил действие.

Сколько раундов Tool Calls допустимо оставлять в AI Agent?

Количество раундов выбирается по риску, а не по универсальному совету. Для безопасного чтения допустим более длинный цикл, для записи и оплаты — существенно более строгий. Задайте независимые ограничения по раундам, времени, расходу и побочным действиям, затем подтвердите их четырьмя регрессионными группами: норма, новые параметры, отсутствие прогресса и сетевой повтор.

Если текущая среда разработки не сохраняет потоковые логи, перезапускает рабочие процессы без контрольного пункта или теряет процесс при закрытии сессии, она добавляет к проблеме Tool Calls ещё три риска: неполную диагностику, скрытые повторы и невозможность доказать, была ли операция выполнена. Для короткого локального теста этого может быть достаточно, но для недельной регрессии и автономных Agent-задач такой подход плохо подходит.

В этом случае аренда удалённого Mac у Kvmkit даёт более удобный постоянный узел для онлайн-запуска, журналирования и повторной проверки — при условии, что лимиты и идемпотентность уже реализованы в самом приложении. Начните с одной безопасной функции в песочнице, добейтесь корректного no_progress, а затем переносите длительные тесты в постоянно доступную среду.

Частые вопросы

Почему Kimi K3 снова и снова вызывает одну и ту же функцию?

Сначала проверьте, что ответ assistant с полем tool_calls добавлен в messages без изменений, а результат каждого вызова возвращён как role=tool с тем же tool_call_id. Если цепочка корректна, повтор считается реальным только при совпадении имени функции, нормализованных аргументов и отсутствии нового полезного прогресса в результате инструмента.

Может ли неправильный tool_call_id вызвать бесконечный цикл?

Неверный идентификатор обычно вызывает ошибку согласованности запроса, а не настоящий цикл: модель не может сопоставить результат с исходным вызовом. Однако если клиент скрывает ошибку, повторяет старое состояние или повторно отправляет assistant без корректного tool-сообщения, внешний раннер может выглядеть как зациклившийся. Логируйте оба идентификатора и исходный порядок сообщений.

Как правильно собирать параметры потокового Tool Calls?

Собирайте фрагменты по индексу вызова, отдельно накапливайте имя функции и строку function.arguments. Не запускайте инструмент после первого неполного фрагмента: дождитесь завершения вызова, проверьте, что строка является валидным JSON, и только затем разбирайте её. Исходные фрагменты сохраняйте рядом с итоговой сборкой для расследования.

Как остановить Agent, если он повторяет инструмент с побочным эффектом?

Вынесите побочный эффект за безопасный шлюз. Перед отправкой письма, оплатой, созданием заказа или записью в базу проверьте идемпотентный ключ, состояние операции и подтверждение. При совпадении вызова без нового прогресса остановите цикл, верните оператору понятный статус и сохраните точку восстановления вместо фиктивного успешного результата.

Сколько раундов Tool Calls допустимо оставлять в AI Agent?

Универсального числа нет: предел зависит от стоимости операции, задержки и риска побочного эффекта. Отдельно задайте максимум раундов, времени, накопленного расхода и опасных действий. Официальные напоминания о повторе нельзя превращать в общий норматив: используйте их как сигнал, а рабочие пороги определяйте тестами и бизнес-риском.

CI/CD на M4 Mac mini — без лишних хлопот

Xcode, Fastlane, CocoaPods, and SPM are first-class on macOS. Mac mini M4 unified memory keeps signing and archiving smooth; ~4W standby power suits 24/7 build nodes.

View Kvmkit plans

Нужна техническая поддержка или консультация?

При проблемах с Mac-инстансами или CI/CD сначала загляните в центр помощи.