«Агент сделал не то» — с этой формулировкой приходят чаще всего, и почти всегда следом идёт попытка починить финальный промпт. Промпт переписывается, поведение меняется случайным образом, через день всё повторяется.
Причина в том, что финальный ответ — это последнее звено. Ошибка родилась раньше, обычно на первом наблюдении, которое оказалось не тем, чем его считал автор. Дальше модель рассуждала логично на неверных данных — и именно поэтому итоговый текст выглядит связным и убедительным.
Трассировка читается как улика: не «что он ответил», а «где он впервые увидел неправду».
#Быстрая проверка
Три вопроса, отсекающие большинство случаев. Задайте их до того, как открывать лог целиком.
- Вызовы вообще были? Если в логе нет ни одного вызова инструмента, а вы ждали действия — проблема не в цепочке, а в том, что модель не вызвала инструмент. Это другой класс, разобранный в материале про пределы агентов на маленькой локальной модели.
- Первый вызов вернул то, что вы ожидали? Откройте результат первого вызова и прочитайте его глазами. В большинстве разобранных случаев ответ здесь «нет», и дальше можно не смотреть.
- Сколько было шагов? Если цепочка упёрлась в предел — вы отлаживаете не ошибку, а исчерпание бюджета.
#Что должно быть в трассировке
Лог, по которому нельзя ответить на шесть вопросов из следующего раздела, бесполезен. Минимальный состав записи на каждый вызов:
- порядковый номер шага в ходе;
- имя инструмента;
- аргументы — целиком, а не «переданы параметры»;
- статус: успех или ошибка;
- результат или хотя бы его начало;
- длительность;
- идентификатор, связывающий вызов с конкретным сообщением и разговором.
Последний пункт кажется бюрократией ровно до первого разбора, когда в логе три параллельных разговора. В Persona каждое исполнение инструмента фиксируется отдельной записью с именем, порядковым номером, ссылкой на сессию и сообщение и статусом завершения, а в поток интерфейса уходят структурированные кадры о начале и конце вызова — так что видно не только «что-то происходило», но и что именно.
Про то, почему логи стоит писать структурированно, а не строкой, — в материале про структурированные логи.
#Пример трассировки с точкой сбоя
Задача: «посчитай, сколько я потратил на подписки в прошлом месяце, данные в папке finance».
шаг 1 ВЫЗОВ list_dir({"path": "finance"})
СТАТУС ok, 40 мс
РЕЗУЛЬТАТ FILE subs-2026-07.csv 2100b
FILE subs-2026-08.csv 1980b
FILE archive.zip 44100b
шаг 2 ВЫЗОВ read_file({"path": "subs-2026-08.csv"})
СТАТУС ok, 12 мс
РЕЗУЛЬТАТ name,amount,date
Хостинг,890,2026-08-03
Музыка,299,2026-08-07
… 14 строк …
◀── ТОЧКА СБОЯ
здесь август, а просили июль
шаг 3 ВЫЗОВ read_file({"path": "subs-2026-08.csv"})
СТАТУС ok, 11 мс
РЕЗУЛЬТАТ (то же самое)
◀── повтор, дедупликации нет
шаг 4 ОТВЕТ «За прошлый месяц вы потратили 4188 ₽
на 16 подписок»
Что видно из лога и не видно из ответа.
Первое. Ошибка на шаге 2, а не на шаге 4. Модель прочитала августовский файл вместо июльского. Число в ответе получено правильным суммированием неправильных данных — и выглядит совершенно достоверно.
Второе. Причина ошибки — не «модель глупая». Ей никто не сказал, какой сейчас месяц. «Прошлый месяц» невычислим без текущей даты, а в контексте её не было. Чинить надо не промпт, а подстановку даты.
Третье. Шаг 3 — повтор шага 2. Он не изменил результат, но стоил вызова модели и места в контексте. Это отдельный дефект: нет дедупликации одинаковых вызовов.
Четвёртое. Все статусы — ok. Отсутствие ошибок в логе не означает отсутствия ошибок в работе. Самые дорогие сбои проходят со статусом «успех».
#Шесть вопросов к логу
Чек-лист, который проходится по порядку. Первый же ответ «нет» указывает, где чинить.
1. Был ли вызван инструмент вообще?
нет → проблема в описании инструмента или в промпте
2. Первый вызов получил ожидаемые аргументы?
нет → модели не хватило данных на входе (дата, путь, имя)
3. Первый результат — то, что вы бы получили сами?
нет → проблема в инструменте или в данных, не в модели
4. Есть ли повторяющиеся вызовы с одинаковыми аргументами?
да → нет дедупликации; смотрите, почему модель не поняла результат
5. Все ли статусы «успех» при неверном итоге?
да → инструмент возвращает правдоподобную неправду
6. Цепочка закончилась сама или упёрлась в предел?
предел → это не ошибка рассуждения, это бюджет
Вопрос 3 — центральный. Формулируется он именно так: «то, что вы бы получили сами». Не «выглядит ли результат правдоподобно» — правдоподобно выглядит почти всё.
#Типовые находки
Инструмент вернул ошибку в человекочитаемом виде, и модель приняла её за данные. Строка «[error] файл не найден» — это текст, и модель может пересказать её как содержимое. Признак — в ответе появляются формулировки из ваших сообщений об ошибках.
Аргументы пришли с лишним префиксом. Модель добавила к пути имя рабочей папки, потому что увидела его в выводе списка каталога. Лечится тем, что список выводит пути ровно в том виде, в котором их надо передавать обратно, и говорит об этом прямо.
Результат обрезан на середине. Инструмент вернул больше, чем помещается в предел, и модель увидела половину таблицы. В логе это видно по длине результата, упирающейся в круглое число.
Вызов есть в логе, но эффекта нет. Значит, вызов был отклонён политикой прав. Отказ обязан попадать в лог как отдельная запись, иначе он неотличим от того, что инструмент не вызывался.
Между шагами прошло много времени. Инструмент ходил в сеть и упирался в таймаут. Длительность в записи существует именно для этого.
#Что делать с находкой
Порядок исправления, от дешёвого к дорогому:
- Подставить недостающие данные. Текущая дата, рабочий каталог, часовой пояс — всё, что модель не может знать, должно приходить в контекст, а не угадываться.
- Переписать описание инструмента. Пример значения параметра, явное указание, что вернётся при ошибке, непохожее имя. Правила формулировок — в руководстве по написанию своего MCP-сервера.
- Сузить инструмент. Заменить универсальный на конкретный: не «прочитать файл», а «прочитать выписку за месяц».
- Добавить дедупликацию и пределы. Одинаковые вызовы, предел результата, предел числа шагов.
- И только потом трогать промпт. Он последний в списке, потому что его правки хуже всего воспроизводятся.
Механика самого цикла, который вы читаете в логе, — в разборе паттерна ReAct.
#Если ничего не помогло
Три вещи, которые стоит проверить, когда лог выглядит нормально, а результат нет.
Лог неполный. Проверьте, попадают ли в него отклонённые вызовы, обрезанные результаты и вызовы, упавшие с исключением. Дыра в логе выглядит как отсутствие проблемы.
Вы читаете лог не того хода. При параллельных разговорах записи перемешиваются. Если в записи нет идентификатора разговора и сообщения, добавьте его — это дешевле, чем гадать.
Проблема не в агенте. Часть жалоб на агента — это задачи, где агент не нужен: последовательность известна заранее и должна быть кодом. Признак — вы каждый раз ожидаете от него одной и той же цепочки действий.
И отдельный случай: если модель ходит по кругу, это не отладка трассировки, а самостоятельный класс с собственными механизмами и предохранителями — разбор в материале про зацикливание агента.
#Частые вопросы
#С чего начинать разбор, если агент выдал неверный ответ?
С первого вызова инструмента, а не с финального текста. Прочитайте его результат глазами и сравните с тем, что получили бы сами. В большинстве случаев расхождение обнаруживается здесь, и дальше по логу идти не нужно.
#Что обязательно писать в лог вызова?
Имя инструмента, аргументы целиком, статус, начало результата, длительность и идентификаторы разговора и сообщения. Аргументы «целиком» — принципиально: запись «вызван read_file» не позволяет понять, какой именно файл был прочитан, а это и есть половина всех ошибок.
#Почему все статусы «успех», а результат неверный?
Потому что инструмент отработал корректно и вернул не те данные: не тот файл, устаревшую запись, пустой список. Статус описывает исполнение инструмента, а не соответствие результата задаче. Именно поэтому пятый вопрос чек-листа существует отдельно.
#Нужно ли логировать содержимое результатов?
Хотя бы начало — без него нельзя ответить на третий вопрос чек-листа. При этом результаты могут содержать личные данные, поэтому разумно ограничивать объём и хранить такие записи не дольше, чем нужно для разбора.
#Как отличить ошибку модели от ошибки инструмента?
По третьему вопросу: если бы вы выполнили тот же вызов с теми же аргументами руками и получили тот же результат — инструмент отработал правильно, и дело в том, какие аргументы выбрала модель. Если результат отличается от ожидаемого — дело в инструменте или в данных.
#Сколько прогонов нужно, чтобы найти закономерность?
Для устойчивого сбоя хватает десяти. Если место поломки стабильно — это свойство конфигурации, и его надо чинить структурно. Если разброс большой — обычно дело в описаниях инструментов, дающих модели неоднозначный выбор.
#Вывод
Трассировка читается от начала, а не от конца: ошибка почти всегда рождается на первом наблюдении, которое оказалось не тем, чем его считал автор, а связный финальный ответ — это следствие, а не источник.
Пройдите шесть вопросов по порядку и чините в порядке от подстановки недостающих данных к описаниям инструментов, оставив правку промпта на последнее место. И если в логе нет аргументов вызовов — начните с этого: без них разбор превращается в угадывание.