LLM API не упал, а прод всё равно встал: 6 ошибок в таймаутах и ретраях
Большинство разработчиков оборачивают вызов LLM в три ретрая и считают задачу решённой.
В продакшене такая схема превращает один медленный запрос в минуту ожидания, умножает расходы и иногда повторно запускает действия агента.
Разберу шесть ошибок, из-за которых механизм отказоустойчивости сам становится причиной сбоя.
1. Один таймаут на весь запрос
Вызов LLM состоит из нескольких этапов:
— подключение к провайдеру; — ожидание первого токена; — генерация ответа; — вызов инструментов; — обработка результата.
Если поставить единый таймаут в 60 секунд, система может потратить почти всё время на основной провайдер. Для fallback-модели уже не останется запаса.
Я использую общий временной бюджет запроса. Например, 15 секунд:
— до 8 секунд основной модели; — до 5 секунд резервной; — 2 секунды на сеть и обработку.
Fallback должен укладываться в общий дедлайн, а не запускать новый полный цикл.
2. Ретраи выполняются для любых ошибок
Повторять запрос имеет смысл при:
— 429 Too Many Requests; — временных сетевых ошибках; — ответах 502, 503 и 504; — разрыве соединения до получения ответа.
Но ретрай не исправит:
— неверный API-ключ; — ошибочный формат запроса; — превышение контекстного окна; — запрещённый контент; — отсутствующую модель.
Повторение таких запросов только увеличивает задержку и расходы.
3. Повторный вызов агента создаёт повторное действие
Для обычного чат-бота дублированный ответ неприятен, но не критичен.
У агента последствия серьёзнее. Повторный запрос может:
— дважды создать задачу; — повторно отправить письмо; — продублировать запись в CRM; — провести одну операцию несколько раз.
Каждому действию нужен idempotency key. Перед выполнением инструмента система должна проверить, не обрабатывалась ли эта операция ранее.
Ретраить генерацию текста и ретраить действие во внешней системе — не одно и то же.
4. Все запросы повторяются одновременно
Допустим, провайдер начал отвечать медленнее. Сотни запросов получают ошибку и через секунду одновременно запускаются повторно.
Возникает retry storm: нагрузка растёт именно в момент, когда сервис уже испытывает проблемы.
Поэтому между попытками нужен случайный интервал — jitter. Например, от 200 до 800 мс.
Также стоит ограничить общее количество параллельных повторов через очередь или semaphore.
5. Fallback существует только на схеме
Часто в конфигурации указаны две модели, но резервная получает промпт и параметры основного провайдера.
В результате она возвращает другой формат, не поддерживает нужный инструмент или не помещает запрос в своё контекстное окно.
Fallback необходимо тестировать как отдельный рабочий сценарий:
— совместим ли формат tools; — совпадает ли структура JSON; — поддерживается ли нужное контекстное окно; — проходит ли валидация ответа; — устраивает ли качество результата.
Наличие второй модели ещё не означает отказоустойчивость.
6. В логах нет истории попыток
Сообщение «LLM request failed» почти бесполезно.
Для каждого запроса я сохраняю:
— correlation ID; — провайдера и модель; — длительность попытки; — код ошибки; — число ретраев; — факт переключения на fallback; — количество входных и выходных токенов; — итоговый статус.
Тогда можно быстро понять: проблема в модели, сети, лимитах или собственной логике.
Надёжная схема — это не «попробовать ещё три раза»
Для большинства проектов достаточно:
1. Общего дедлайна на весь запрос. 2. Одной дополнительной попытки только для временных ошибок. 3. Backoff с jitter. 4. Idempotency key для действий агента. 5. Проверенного fallback-провайдера. 6. Полной трассировки всех попыток.
Иначе ретраи не спасают систему, а просто делают сбой более долгим и дорогим.
Как у вас устроена работа с ошибками LLM API: простой retry или отдельный gateway с бюджетами и fallback?