Устойчивость AI-агента к сбоям LLM-провайдеров: fallback-цепочки и Circuit Breaker в CodeLab
Что делать агенту, когда LLM-провайдер отвечает 429, падает по таймауту или перегружен: классификация ошибок, последовательный fallback и Circuit Breaker на примере CodeLab.
Контекст
AI-агент зависит от внешнего LLM API так же, как любой сервис зависит от своей базы данных. Только это API живёт в чужой инфраструктуре: провайдер может упереться в лимит запросов, ответить по таймауту, вернуть внутреннюю ошибку или сообщить, что модель перегружена.
Для чат-бота сбой — это одно неудачное сообщение. Для агента, который выполняет план из многих шагов, — потерянная работа. В CodeLab агент в одном цикле может сделать до 10 итераций вызовов инструментов, создавая и редактируя файлы проекта. Если провайдер упал на седьмом шаге, пользователь не должен начинать всё сначала.
Поэтому в CodeLab встроена fallback-система: при ошибке основного провайдера запрос автоматически уходит резервному.
Шаг 1: не все ошибки одинаковые
Первое решение — разделить ошибки на те, которые имеет смысл повторить у другого провайдера, и те, которые не имеет.
Retryable — переключаемся на следующего провайдера:
| Тип | Что случилось | Пример |
|---|---|---|
rate_limit |
Превышен лимит запросов | HTTP 429 |
timeout |
Таймаут запроса | Connection timeout |
internal_error |
Внутренняя ошибка провайдера | HTTP 500 |
service_unavailable |
Сервис недоступен | HTTP 503 |
model_unavailable |
Модель временно недоступна | Model overloaded |
Non-retryable — сразу возвращаем ошибку:
| Тип | Что случилось |
|---|---|
auth_error |
Ошибка аутентификации |
invalid_request |
Некорректный запрос |
Логика простая: временная перегрузка у одного провайдера никак не говорит о состоянии другого, поэтому переключение имеет смысл. А неверный ключ или некорректный запрос — проблема конфигурации, которую нужно показать пользователю, а не прятать за перебором провайдеров.
Шаг 2: последовательный fallback
Базовая стратегия — sequential: провайдеры перебираются в заданном порядке, пока один не обработает запрос успешно.
[llm.fallback]
enabled = true
strategy = "sequential"
order = ["openai", "openrouter", "ollama"]
max_attempts = 3
retry_on = ["rate_limit", "timeout", "internal_error"]
- Запрос уходит первому провайдеру.
- При retryable-ошибке — следующему по списку.
- При успехе ответ возвращается агенту.
- Если не справился никто — агент получает явную ошибку
AllProvidersFailed.
Параметр retry_on позволяет сузить набор ошибок, на которые реагирует fallback. Если переключение «не срабатывает», первое, что стоит проверить, — есть ли этот тип ошибки в списке.
Шаг 3: Circuit Breaker
Одного fallback недостаточно. Представьте, что основной провайдер лёг надолго. Тогда каждый запрос сначала ждёт таймаут у него и только потом уходит резервному — агент становится медленным, хотя рабочий провайдер есть.
Circuit Breaker решает это: он отслеживает ошибки каждого провайдера и временно исключает сбойного из цепочки.
| Состояние | Что значит |
|---|---|
| Closed | Провайдер работает нормально |
| Open | Слишком много ошибок — провайдер исключён из цепочки |
| HalfOpen | После паузы провайдер получает тестовый вызов |
circuit_breaker = CircuitBreaker(
failure_threshold=5, # ошибок до открытия цепи
reset_timeout=60, # секунд до попытки восстановления
half_open_max_calls=1, # тестовых вызовов в HalfOpen
)
После failure_threshold ошибок провайдер перестаёт получать запросы. Через reset_timeout секунд он переходит в HalfOpen и получает пробный вызов: успех возвращает его в строй, ошибка — снова исключает.
Шаг 4: наблюдаемость
Автоматическое переключение не должно быть незаметным — иначе вы узнаете о проблемах с основным провайдером только из счёта. В CodeLab есть шина событий провайдеров, и на событие FallbackTriggered можно подписаться:
from codelab.server.llm.events import event_bus, FallbackTriggered
async def on_fallback(event: FallbackTriggered) -> None:
print(f"Fallback: {event.from_provider} → {event.to_provider}")
event_bus.subscribe(FallbackTriggered, on_fallback)
Рецепты цепочек
- Надёжность:
openai → openrouter → ollama. Облачный провайдер основной, агрегатор — резерв, локальная модель через Ollama — последний рубеж, который работает даже без интернета. - Экономия:
ollama → openrouter → openai. Сначала бесплатная локальная модель, облако — только когда она не справилась. - Разработка: провайдер
mockс выключенным fallback — предсказуемые ответы без трат на API.
Что дальше
Сейчас в CodeLab реализована последовательная стратегия. Архитектура оставляет точки расширения для стратегий выбора провайдера по стоимости, по задержке и «умного» выбора на основе ML — их можно подключить, не меняя остальной код агента.
Выводы
- Классифицируйте ошибки. Повторять имеет смысл только временные сбои; ошибки конфигурации нужно показывать сразу.
- Fallback без Circuit Breaker медленный. Не давайте лежащему провайдеру съедать таймаут на каждом запросе.
- Держите локальную модель в конце цепочки. Это единственный провайдер, который не зависит от чужой инфраструктуры.
- Делайте переключения видимыми. Событие о fallback должно попадать в логи и мониторинг.
Документация по настройке fallback и LLM-провайдеров — на сайте CodeLab.