Привидение в истории сообщений: как tool_use без tool_result сломал бота

Охота за привидением в чате: как tool_use без tool_result сломал бот
Проект AI Agents — это система голосовых агентов с телеграм-интеграцией. Звучит просто, но под капотом там полноценная экосистема: асинхронная обработка сообщений, система памяти пользователей, рефлексия агента, напоминания. И вот однажды бот просто перестал запускаться.
Сначала казалось, что это типичная проблема с конфигурацией. Но логи рассказывали более странную историю. API Anthropic выбрасывал ошибку: “tool_use без соответствующего tool_result”. Как будто кто-то забыл закрыть скобку, но на уровне сессии.
Начал копать. Оказалось, что в handlers.py есть критический flow: когда агент вызывает инструмент через chat_with_tools(), а во время выполнения происходит исключение — session.messages остаётся в “повреждённом” состоянии. На сообщение прилетает tool_use блок, но соответствующего tool_result никогда не приходит. При следующем запросе эти повреждённые сообщения уходят обратно в API — и всё падает.
Это было в трёх местах одновременно: в обработчике нормальных команд (строка 3070), в системе напоминаний (2584) и где-то ещё. Классический паттерн копируй-вставь с одинаковым багом.
Решение оказалось простым, но необходимым: добавить автоматическую очистку session.messages в обработчик исключений. Когда что-то идёт не так во время вызова инструмента, просто очищаем последнее незавершённое сообщение. Вот и вся магия.
Пока чинил это, нашёл ещё несколько интересных проблем. Например, система рефлексии агента AgentReflector читала из таблицы episodic_memory, которая может просто не существовать в базе. Пришлось переписать логику проверки с правильной обработкой исключений SQLite.
И тут выяснилась ещё одна история: рефлексия использовала AsyncAnthropic напрямую вместо Claude CLI. Это означало, что каждый раз при рефлексии расходовались API credits. Пришлось мигрировать на использование CLI, как это было сделано в reminder_watchdog_system. Теперь агент может размышлять о своей работе совершенно бесплатно.
Отдельное приключение ждало команду /insights. Там была проблема с парсингом markdown в Telegram: символы вроде _, *, [, ] в тексте размышлений создавали невалидные сущности. Пришлось написать функцию для правильного экранирования спецсимволов перед отправкой в Telegram API.
В итоге: бот запустился, логирование стало нормальным, система памяти работает исправно. Главный урок — когда API жалуется на незавершённые блоки, смотри на обработку исключений. Там всегда что-то забыли почистить.
😄 Как отличить разработчика от отладчика? Разработчик пишет код, который работает, отладчик пишет код, который объясняет, почему первый не работает.
Метаданные
- Session ID:
- grouped_ai-agents_20260210_1710
- Branch:
- HEAD
- Dev Joke
- Что общего у Go и подростка? Оба непредсказуемы и требуют постоянного внимания
Часть потока:
Разработка: ai-agents