Проблема: единственный провайдер = единая точка отказа
Ретрай отвечает на «моргнуло — повторим». Fallback — на другой вопрос: «а что если не починится, сколько ни повторяй?». Провайдеры LLM — внешние сервисы, и они падают по-настоящему: были громкие многочасовые простои OpenAI, Anthropic, облаков. Если весь агент завязан на одного — это single point of failure: его падение = падение твоего продукта, и ты ничего не можешь сделать, кроме как ждать.
Fallback снимает эту зависимость: когда основной путь мёртв, есть запасной. Разница с ретраем принципиальна:
| Retry (прошлый урок) | Fallback (этот урок) | |
|---|---|---|
| Против чего | Мгновенный transient-сбой | Длительный отказ / исчерпанный ретрай |
| Действие | Повторить то же самое | Переключиться на другое |
| Тот же провайдер? | Да | Нет — запасной провайдер/модель |
| Результат | Тот же ответ, чуть позже | Ответ от альтернативы (возможно, хуже) |
Они не конкурируют, а образуют лестницу: сначала ретрай (несколько попыток с backoff) — вдруг быстро восстановится; исчерпали → fallback на запасной вариант; и он мёртв → graceful degradation (частичный ответ — финальный урок раздела). Каждый уровень включается, когда предыдущий сдался. Fallback стоит между ретраем и полной деградацией.
Виды fallback
«Запасной вариант» — понятие широкое. У агента есть несколько осей, по которым можно откатиться:
- Другой провайдер, та же модель-класс. Лёг OpenAI → идём в Azure OpenAI или к аналогичной модели Anthropic. Самый прямой fallback: качество почти то же.
- Другая модель того же провайдера. Недоступна флагманская → берём модель попроще/подешевле у того же вендора. Работает, если проблема в конкретной модели, а не во всём провайдере.
- Более дешёвая/быстрая модель. Осознанная деградация качества ради доступности: лучше ответ похуже, чем никакого.
- Кэш / заготовленный ответ. Если запрос уже был — отдать закэшированный; для типовых — шаблонный ответ.
- Локальная/self-hosted модель как последний рубеж независимости от внешних API.
Ключевой нюанс: fallback на другую модель того же провайдера не спасёт, если лёг весь провайдер (а падает обычно именно так — целиком). Для реальной устойчивости запасной вариант должен быть у независимого вендора/инфраструктуры (другое облако, другая компания). Иначе это иллюзия резервирования: обе «руки» держатся за одну упавшую опору.
Fallback chain
На практике fallback оформляют как цепочку: упорядоченный список вариантов от лучшего к запасному. Запрос идёт по цепочке: не сработал первый (после своих ретраев) → второй → третий. Первый успешный ответ возвращается.
import structlog
log = structlog.get_logger()
# цепочка от лучшего к запасному; ② — ДРУГОЙ вендор (не тот же провайдер!)
PROVIDERS = [
("openai:gpt-4o", call_openai), # ① основной
("anthropic:claude", call_anthropic), # ② независимый вендор
("local:llama", call_local), # ③ последний рубеж, хуже но жив
]
def generate_with_fallback(prompt: str) -> str:
last_err = None
for name, call in PROVIDERS:
try:
result = call(prompt) # внутри у call — свой retry+backoff
if name != PROVIDERS[0][0]:
log.warning("fallback_used", provider=name) # НЕ основной → сигнал!
return result
except Exception as e:
log.error("provider_failed", provider=name, error=str(e))
last_err = e
continue # переходим к следующему в цепочке
raise RuntimeError("all providers failed") from last_err # → graceful degradation
# фреймворки дают это из коробки: LangChain .with_fallbacks([...]),
# прокси-роутеры (LiteLLM и т.п.) маршрутизируют между провайдерами
Подводный камень: модели не взаимозаменяемы
Соблазнительно думать «просто подставим другую модель». Но модели разных провайдеров/семейств не эквивалентны, и наивная подмена ломается на деталях:
- Разный формат API. Структура запроса/ответа, имена полей, способ передачи tool-вызовов отличаются между OpenAI и Anthropic. Нужен слой абстракции или прокси, приводящий к единому виду.
- Разное поведение на том же промпте. Промпт, вылизанный под одну модель, на другой может дать иной формат/качество. Иногда fallback-модели нужен свой промпт.
- Разные возможности. Не у всех одинаковый tool calling, structured output, размер контекста. Fallback на слабую модель может не уметь того, на чём построен агент.
- Разный контекст-лимит. Запрос, влезавший в 200k токенов, не влезет в модель со 8k — fallback упадёт по другой причине.
Классическая ловушка: fallback настроен, но никогда не проверялся — и в момент реальной аварии выясняется, что запасная модель не понимает формат tool-вызовов, и агент падает в fallback тоже. Fallback-путь — такой же код, он должен быть покрыт интеграционными тестами (прошлый раздел): регулярно прогоняй запросы через запасные провайдеры, проверяй совместимость промптов и форматов. Непротестированный fallback — это не резерв, а ложное чувство безопасности.
Цена fallback: качество и стоимость
Fallback — это компромисс, а не бесплатная страховка. Спускаясь по цепочке, ты обычно жертвуешь:
- Качеством. Запасная (более дешёвая/локальная) модель отвечает хуже. Осознанное решение: «хуже, но есть» лучше, чем «идеально, но недоступно» — но пользователь получает деградированный ответ.
- Стоимостью/латентностью. Иногда наоборот: запасной вариант дороже или медленнее. Плюс двойной расход, если считать неудачные попытки основного.
- Консистентностью. Ответы от разных моделей отличаются по стилю/формату — пользователь может заметить «скачок».
Опаснейшая ошибка — тихий fallback: система молча работает на запасном варианте, никто не знает, и все думают, что всё хорошо, — пока не приходит счёт или жалобы на качество. Fallback-rate (доля запросов, ушедших не на основной провайдер) — обязательная метрика на дашборде (раздел observability). Рост fallback-rate = основной провайдер деградирует прямо сейчас → алерт. Логируй, кто именно ответил на каждый запрос. Fallback без наблюдаемости маскирует проблему вместо того, чтобы её решать.
Типичные ошибки
Запасная модель у того же вендора не спасёт при падении всего провайдера (а падает обычно целиком). Настоящий резерв — у независимого вендора/инфраструктуры.
Настроен, но никогда не проверялся → в аварию выясняется, что запасная модель не умеет tool calling/формат. Покрывай fallback интеграционными тестами и прогоняй регулярно.
Система молча деградирует, никто не знает — до счёта/жалоб. Мониторь fallback-rate, логируй, кто ответил, ставь алерт на рост.
Промпт/контекст/возможности под одну модель не переносятся автоматом на другую. Учитывай формат API, свои промпты, лимиты контекста для fallback-модели.
Шпаргалка
FALLBACK = план Б при ДЛИТЕЛЬНОМ отказе (когда retry исчерпан, провайдер лёг надолго)
единственный провайдер = SINGLE POINT OF FAILURE (падают по-настоящему, надолго)
retry: повторить ТО ЖЕ · fallback: переключиться на ДРУГОЕ
лестница: retry → fallback → graceful degradation
ВИДЫ: другой ВЕНДОР (та же модель-класс) · др. модель того же вендора ·
дешевле/быстрее (осознанная деградация) · кэш/шаблон · локальная модель
✓ настоящий резерв — у НЕЗАВИСИМОГО вендора (иначе лягут вместе)
FALLBACK CHAIN: упорядоченный список лучший→запасной, первый живой отвечает
каждый уровень: свой retry → не помог → следующий
качество/стоимость по цепочке обычно ДЕГРАДИРУЮТ («хуже, но есть» > «идеально, но нет»)
инструменты: LangChain .with_fallbacks([...]) · прокси-роутеры (LiteLLM и т.п.)
⚠️ МОДЕЛИ НЕ ВЗАИМОЗАМЕНЯЕМЫ:
разный формат API · разное поведение на промпте (нужен свой промпт) ·
разные возможности (tool calling/structured output) · разный контекст-лимит
→ fallback-путь ТЕСТИРОВАТЬ (интеграционные тесты), а не надеяться
ЦЕНА: качество↓ · стоимость/латентность (иногда↑) · консистентность (разный стиль)
📊 МОНИТОРЬ FALLBACK-RATE (доля не-основного) → алерт на рост = провайдер деградирует
логируй КТО ответил · тихий fallback = маскировка проблемы
ГРАБЛИ: тот же провайдер · непротестированный путь · тихо без метрики · игнор совместимости
Практическое задание
Задание: построй fallback для агента
- Для агента из модулей 03–05 определи single point of failure: какой один провайдер/сервис уронит весь агент, если ляжет надолго?
- Спроектируй fallback chain из 2–3 уровней. Убедись, что хотя бы один уровень — у независимого вендора. Обоснуй порядок.
- Проверь совместимость: одинаков ли формат tool-вызовов/structured output у основной и запасной модели? Что придётся адаптировать (промпт, парсинг)?
- Реализуй цепочку (вручную или через
.with_fallbacks). Залогируй, кто ответил на каждый запрос. - Смоделируй падение основного провайдера (заставь его кидать ошибку) и проверь, что агент дошёл до запасного и вернул ответ. Работает ли fallback-путь на самом деле?
- Придумай метрику fallback-rate и алерт: при какой доле не-основных ответов ты хочешь получить сигнал? Почему тихий fallback опасен?
- Со звёздочкой: добавь в цепочку «дешёвую» модель как предпоследний уровень и оцени, насколько просела бы quality-метрика (из раздела оценки), если бы весь трафик пошёл через неё.
Что дальше
Ретрай и fallback реагируют на отказ постфактум. Но часть отказов можно предотвратить — и самый частый из них у LLM это 429 «слишком много запросов». Управлять своим темпом запросов, чтобы не упираться в лимиты провайдера, — тема следующего урока: rate limiting и управление квотами.