Молчаливый API: когда успех — это просто пустота

Когда API молчит: охота на призрак в системе обработки команд
Это была обычная воскресенье в проекте ai-agents. Пользователь Coriollon отправил простую команду через Telegram: “Создавай”. Три слова. Невинные на вид. Но система ответила молчанием — и началась охота на баг, которая заняла почти семь минут и три попытки переподключения.
Что мы видим в логах
Сначала всё выглядит нормально. Запрос приходит в 12:23:58. Система маршрутизирует его на Claude API с моделью Sonnet. Промпт имеет 5344 символа — немалый объём контекста. Первый запрос уходит в API и… здесь начинается интересное.
API отвечает за 26 секунд. Кажется, успешно: is_error: False, num_turns: 2, даже token usage выглядит логичным. Но вот result: '' — пустой результат. Система ловит эту аномалию и логирует cli_empty_response.
Мой первый инстинкт: “Может, сетевой глюк?” Система делает то же самое — ждёт 5 секунд и повторяет запрос. Вторая попытка в 12:24:31. История повторяется: успех по метрикам, но снова пустой ответ.
Третий раз — не удача
К третьей попытке я уже понял, что это не случайный сетевой перебой. Система работает корректно, API возвращает success: true, токены учитываются (даже видны попадания в кэш: cache_read_input_tokens: 47520). Но результат, ради которого всё затевалось, так и не приходит.
Вот здесь кроется классическая ловушка в работе с LLM API: успешный HTTP-ответ не гарантирует наличие полезной нагрузки. API может успешно обработать запрос, но вернуть пустое поле result — это может означать, что модель вернула только служебные данные (вроде использованных токенов) без фактического содержимого.
Финальная попытка заканчивается в 12:25:26. Три запроса, три молчания, общее время ожидания — почти семь минут. Система логирует финальную ошибку: message_handler_error: CLI returned empty response.
Чему это учит
Когда вы работаете с внешними API, особенно с такими мощными, как Claude, недостаточно проверять только HTTP-статус. Нужно валидировать содержимое ответа. В данном случае система сделала ровно это — поймала пустой результат и попыталась восстановиться через retry-логику с экспоненциальной задержкой (5, 10 секунд).
Но вот что интересно: кэшированные токены (видны в каждом логе) говорят, что контекст был успешно закэширован. Это означает, что на второй и третий запрос система платила дешевле — 0.047 и 0.037 USD вместо 0.081 на первый запрос. Автоматическое кэширование контекста в Claude API — это фишка, которая спасает в ситуациях вроде этой.
Корень проблемы остался в логах как загадка: был ли это timeout на стороне модели, недопонимание в структуре запроса или что-то ещё — сказать сложно. Но система сработала как надо: зафиксировала проблему, задокументировала все попытки, сохранила данные сессии для постмортема.
Lesson learned: в системах обработки команд от пользователей нужна не только retry-логика, но и мониторинг пустых ответов. И да, Telegram-боты любят такие фокусы.
😄 API успешно вернул ошибку об ошибке успеха — вот это я называю отличной синхронизацией!
Метаданные
- Session ID:
- grouped_ai-agents_20260209_1226
- Branch:
- HEAD
- Dev Joke
- Copilot: автодополнение, которое знает, что ты хочешь написать. Иногда даже правильно.
Часть потока:
Разработка: ai-agents