LLM API Fallback Routing: Производственный план отказоустойчивости
Маршрутизация fallback для LLM API звучит просто, пока не случается первый реальный инцидент: поймать ошибку, переключить модель и попробовать снова. В продакшене это правило может превратить проблему одного провайдера в дублирующиеся вызовы инструментов, сломанный JSON, смешанный потоковый вывод, лавинообразный retry-трафик или ответ, который технически успешен, но больше не соответствует продуктового контракту.
Более безопасный дизайн рассматривает fallback как ограниченный конечный автомат, а не как список названий резервных моделей. Каждый запрос проходит через небольшой набор решений:
- Является ли сбой повторяемым?
- Безопасно ли повторить этот запрос?
- Должна ли следующая попытка использовать тот же целевой объект или другой?
- Сможет ли fallback сохранить требуемый контракт?
- Уже породил ли запрос выходные данные или побочные эффекты?
- Исчерпаны ли end-to-end задержка и бюджет попыток?
Этот план превращает эти вопросы в матрицу ошибок, политику маршрутизации, контроллер на TypeScript, тестовый план и чеклист развертывания для многопровайдерных LLM-приложений.
Четыре действия, лежащие в основе надежной маршрутизации fallback для LLM API
Не отправляйте каждую ошибку в один и тот же retry-цикл. Продакшн-роутеру нужны четыре разных действия.
| Действие | Используйте, когда | Типичные примеры |
|---|---|---|
| Повторить тот же целевой объект | Сбой выглядит временным, и текущий деплой может восстановиться в пределах срока запроса | Сброс соединения до заголовков, изолированный таймаут, короткое ожидание из-за ограничения по rate limit |
| Переключиться на эквивалентный целевой объект | Провайдер, регион, деплой или аккаунт недоступны, но тот же контракт модели доступен в другом месте | Региональный сбой, исчерпанная квота деплоя, повторяющиеся ответы 5xx |
| Перейти на другую модель | Проверенная альтернативная модель может сохранить минимальные возможности приложения и контракт вывода | Основная модель недоступна, а протестированная вторичная модель поддерживает те же инструменты и схему |
| Остановиться и показать ошибку | Повторение запроса не исправит проблему, может создать побочные эффекты или не позволит сохранить контракт | Неверная аутентификация, некорректный запрос, неподдерживаемый параметр, блокировка политикой, частичный stream |
Различие между failover и fallback имеет значение. Failover сохраняет логический контракт модели и меняет инфраструктуру. Fallback меняет модель или уровень возможностей. Failover обычно является вариантом с меньшим риском.
Если вам нужен более широкий дизайн request path вокруг алиасов, health scoring, биллинга и observability, начните с руководства по архитектуре AI API gateway. Эта статья сосредоточена на контроллере, который работает после выбора целевого объекта.
Создайте матрицу «ошибка → действие» до написания retry-кода
SDK провайдеров используют разные классы исключений и тела ответов, но роутер должен нормализовать их в небольшую внутреннюю таксономию.
| Нормализованный сбой | Повторить тот же целевой вызов? | Эквивалентный failover? | Кросс-модельный fallback? | Примечания |
|---|---|---|---|---|
| Сбой соединения до принятия запроса | Да, один раз | Да | Возможно | Соблюдайте один end-to-end дедлайн |
| Тайм-аут до заголовков ответа | Возможно | Да | Возможно | Повторяйте только запросы, которые безопасно воспроизвести |
Ограничение по скорости 429 |
После ограниченной задержки | Да | Возможно | Следуйте рекомендациям сервера, если они доступны; не создавайте шторм повторных попыток |
Ошибка 5xx у провайдера или перегрузка |
Максимум один раз | Да | Возможно | Открывайте circuit breaker после заданного порога отказов |
| Ошибка аутентификации или прав доступа | Нет | Нет | Нет | Исправьте учетные данные или политику; смена модели не поможет |
| Неверно сформированный запрос или неподдерживаемый параметр | Нет | Нет | Нет | Исправьте контракт клиента |
| Превышена длина контекста | Нет слепого повтора | Нет | Только при явной адаптации | Обрезка, суммаризация или маршрут с большим контекстом изменяет запрос |
| Отклонение по безопасности или политике | Нет слепого повтора | Нет | Обычно нет | Переключение провайдеров, чтобы обойти решение политики, — не стратегия надежности |
| Сбой валидации выходной схемы | Возможно с исправлением | Нет | Только если это было оценено | Держите исправление схемы отдельно от транспортных повторов |
| Поток прерывается до первого токена | Возможно | Да | Возможно | Пока еще нет пользовательского вывода |
| Поток прерывается после начала вывода | Нет автоматического переключения | Нет | Нет автоматического переключения | Не склеивайте два ответа модели вместе |
| Вызов инструмента, возможно, уже был выполнен | Нет слепого повтора | Нет | Нет слепого повтора | Требуйте idempotency keys или дедупликацию на уровне инструмента |
Официальная документация провайдеров подтверждает, почему нормализация необходима. Anthropic описывает отдельные ошибки rate-limit, API и overload и отмечает, что потоковый запрос может завершиться сбоем даже после первоначального успешного ответа. OpenAI также разделяет неверные запросы, rate limits и серверные сбои. Ваше приложение должно переводить сигналы, специфичные для провайдера, в стабильные внутренние решения, а не встраивать имена провайдеров повсюду в бизнес-логику.
Ограничьте один retry budget на весь запрос
Повторные попытки часто существуют сразу на нескольких уровнях: HTTP-клиент, SDK провайдера, шлюз, фоновая задача и сервис приложения. Если каждый уровень выполняет по три попытки, одно действие пользователя может превратиться в намного большее число обращений к upstream, чем предполагала команда.
Более безопасный шаблон:
- Выберите один уровень, который будет отвечать за повторные попытки LLM и fallback.
- Задайте один end-to-end дедлайн для пользовательского запроса или задачи.
- Задайте максимальное число upstream-попыток.
- Зарезервируйте часть дедлайна для fallback-цели.
- Используйте экспоненциальную задержку с jitter для временных сбоев.
- Останавливайтесь, когда оставшегося времени уже недостаточно для еще одной осмысленной попытки.
Рекомендации AWS по таймаутам, повторным попыткам, backoff и jitter описывают, как ретраи могут усиливать перегрузку, и рекомендуют ограниченное поведение вместо постоянного немедленного повторения. Тот же принцип применим к API моделей, где провайдер под нагрузкой меньше всего способен поглощать синхронизированный retry-трафик.
Практический интерактивный бюджет можно выразить как policy, а не как жестко заданные задержки:
type RetryBudget = {
deadlineMs: number;
maxAttempts: number;
maxSameTargetAttempts: number;
reserveForFallbackMs: number;
};
Конкретные значения зависят от продукта. Чат-интерфейс, агент для программирования, batch-оценщик и асинхронный видеопроцесс не должны использовать один и тот же бюджет.
Используйте circuit breaker, чтобы не направлять трафик в известные сбои
Circuit breaker предотвращает ситуацию, когда каждый новый запрос заново обнаруживает тот же самый инцидент.
Стандартные состояния:
- Closed: запросы идут нормально, пока router измеряет ошибки и задержку.
- Open: цель временно недоступна для использования, потому что ее недавнее поведение превысило порог.
- Half-open: небольшое число пробных запросов проверяет, восстановилась ли цель.
Документация Azure по паттерну circuit-breaker описывает этот цикл closed/open/half-open. Для LLM routing ключ breaker должен быть достаточно конкретным, чтобы изолировать отказавшую поверхность. Полезные измерения включают провайдера, модель, регион, развертывание, аккаунт и capability. Развертывание для текстового completion может быть здоровым, пока route для вызова инструментов или региональный endpoint терпит сбой.
Не открывайте circuit на каждую ошибку клиента. Неверная аутентификация, некорректные запросы, переполнение контекста и отказ по policy обычно говорят скорее о самом запросе, чем о состоянии провайдера. Breaker должен реагировать в первую очередь на временные инфраструктурные сигналы, такие как ошибки соединения, таймауты, перегрузка и ошибки сервера.
Сохраняйте capability contract между моделями
Fallback-модель не является безопасной только потому, что она принимает OpenAI-compatible запрос. Определите минимальный контракт для каждого alias маршрута.
route: support-agent-v3
requires:
modalities: [text]
streaming: true
tools: true
parallel_tool_calls: false
structured_output: json_schema
context_window_min: 64000
max_output_tokens_min: 4000
quality_gates:
task_success_rate_min: 0.94
schema_valid_rate_min: 0.995
policy:
same_model_failover_first: true
cross_model_fallback_allowed: true
Перед добавлением цели в набор fallback проверьте как минимум:
- Поддерживаемые параметры запроса
- Определение инструментов и поведение вызова инструментов
- Валидация структурированного вывода
- Форма событий стриминга
- Ограничения контекста и вывода
- Поведение безопасности, соответствующее приложению
- Поля учета токенов, используемые для контроля затрат
- Задержка и качество на репрезентативных запросах
Такой подход, основанный на контракте, особенно важен для рабочих процессов, которые охватывают несколько модальностей. Руководство по маршрутизации мультимодальных агентов содержит дополнительные проверки для маршрутов текста, изображений, аудио и видео.
Контроллер резервного перехода на TypeScript
Следующий пример намеренно не привязан к конкретному провайдеру. Он предполагает, что upstream-адаптеры нормализуют ошибки и ответы до того, как их увидит слой маршрутизации.
type FailureKind =
| "connect"
| "timeout"
| "rate_limit"
| "overloaded"
| "server_error"
| "invalid_request"
| "auth"
| "policy"
| "context_overflow"
| "partial_stream"
| "unknown";
type Target = {
id: string;
contractId: string;
healthy: boolean;
circuit: "closed" | "open" | "half_open";
};
type RequestState = {
attempt: number;
sameTargetAttempts: number;
deadlineAt: number;
outputStarted: boolean;
sideEffectsPossible: boolean;
};
function canReplay(state: RequestState): boolean {
return !state.outputStarted && !state.sideEffectsPossible;
}
function isTransient(kind: FailureKind): boolean {
return [
"connect",
"timeout",
"rate_limit",
"overloaded",
"server_error",
].includes(kind);
}
function chooseNextAction(
kind: FailureKind,
state: RequestState,
current: Target,
equivalent: Target | undefined,
fallback: Target | undefined,
) {
if (!canReplay(state) || kind === "partial_stream") return { type: "stop" };
if (!isTransient(kind)) return { type: "stop" };
if (state.attempt >= 3 || Date.now() >= state.deadlineAt) {
return { type: "stop" };
}
if (
state.sameTargetAttempts < 1 &&
current.circuit === "closed" &&
current.healthy
) {
return { type: "retry", target: current };
}
if (equivalent?.healthy && equivalent.circuit !== "open") {
return { type: "failover", target: equivalent };
}
if (
fallback?.healthy &&
fallback.circuit !== "open" &&
fallback.contractId === current.contractId
) {
return { type: "fallback", target: fallback };
}
return { type: "stop" };
}
Производственный код также нуждается в задержках с джиттером, распространении отмены, идентификаторах запросов, обновлениях circuit breaker, телеметрии и специфичном для адаптера разборе ошибок. Важное свойство заключается в том, что безопасность повторной отправки и совместимость контрактов проверяются до выбора следующей цели.
Рассматривайте резервный переход в потоковой передаче как отдельный протокол
Стриминг создает жесткую границу: как только контент попадает к клиенту, шлюз уже не может делать вид, что попытки не было.
Если вышестоящий сервис выходит из строя до того, как переслано первое событие, повторная попытка или резервный переход по-прежнему могут быть незаметными. После того как доставлен первый токен, delta инструмента, событие изображения или аудиофрагмент, автоматическое переключение модели рискует объединить два несовместимых ответа.
Используйте одну из следующих явных стратегий:
- Явно прервите поток с ошибкой. Верните стабильное событие ошибки с идентификатором запроса и позвольте клиенту предложить повторную попытку.
- Буферизуйте перед выдачей. Для коротких структурированных ответов валидируйте полный результат перед отправкой ниже по потоку. Это жертвует временем до первого токена.
- Реализуйте возобновление на уровне приложения. Начинайте новый ход с явным контекстом о том, что предыдущий ответ был прерван. Рассматривайте это как новую генерацию модели, а не как продолжение того же потока байтов.
Не склеивайте вывод двух моделей незаметно.
Отделяйте надежность вызова инструментов от надежности вызова модели
Запрос к LLM может быть повторно воспроизводимым, в то время как выбранный им инструмент — нет. Платеж, email, развертывание, запись в базу данных или создание тикета могут завершиться успешно, даже если соединение с моделью прервется до того, как приложение зафиксирует результат.
Защищайте инструменты с записью данных с помощью:
- Идемпотентного ключа, полученного из пользовательской операции, а не из попытки провайдера
- Надежной записи о выполнении инструмента
- Дедупликации на границе инструмента
- Четкого различия между
planned,started,succeededиunknown - Проверки человеком для неопределенных побочных эффектов с высоким воздействием
Если возможны побочные эффекты и их результат неизвестен, остановите автоматический fallback. Сначала согласуйте состояние инструмента.
Отслеживайте fallback как продуктовый результат
Низкий уровень ошибок у провайдера не доказывает, что fallback работает. Отслеживайте полный результат маршрута.
| Метрика | Что она показывает |
|---|---|
| Успешность по основному целевому пункту | Базовое состояние здоровья провайдера или развертывания |
| Коэффициент восстановления после повторной попытки | Полезны ли повторные попытки на том же целевом пункте |
| Коэффициент восстановления при эквивалентном failover | Ценность резервных развертываний или регионов |
| Коэффициент восстановления при кросс-модельном fallback | Ценность набора альтернативных моделей |
| Коэффициент отклонения контрактов | Как часто кандидаты на целевой пункт не проходят проверки допустимости |
| Валидность схемы после fallback | Остаются ли «успешные» ответы пригодными к использованию |
| Успешность задачи после fallback | Продолжают ли пользователи успешно завершать намеченную работу |
| Дополнительная задержка fallback | Стоимость надежности, которую платит пользователь |
| Разница в стоимости fallback | Влияние пути восстановления на биллинг |
| Длительность открытого circuit breaker и успешность проб | Насколько разумны пороги breaker и время восстановления |
Для каждой попытки записывайте причину маршрута: выбранный целевой пункт, нормализованную ошибку, задержку повторной попытки, состояние circuit, причину fallback, оставшееся время до дедлайна запроса и итоговый результат. Избегайте логирования чувствительных промптов или выводов, если политика данных продукта явно этого не разрешает.
Проверьте пути отказа до включения автоматического fallback
Запустите внедрение отказов в staging-среде, а затем включите policy поэтапно в production.
Тесты транспорта и провайдера
- Отключите соединение до получения заголовков ответа.
- Возвращайте повторяющиеся ограничения по скорости с рекомендациями по повторной попытке и без них.
- Смоделируйте перегрузку и ошибки сервера.
- Задержите основной маршрут до тех пор, пока срок ожидания запроса почти не исчерпается.
- Откройте целевой circuit и убедитесь, что трафик переходит на допустимый маршрут.
- Восстановите целевой маршрут и убедитесь, что проверки half-open не возвращают полный трафик слишком рано.
Contract tests
- Удалите обязательный tool из адаптера fallback.
- Возвращайте недопустимый structured output.
- Измените форму streaming event.
- Превысьте ограничения на context или output.
- Сравните качество fallback на фиксированном evaluation set.
Replay-safety tests
- Сбой до и после первого streamed event.
- Сбой после начала write-side tool.
- Повторите тот же idempotency key.
- Отмените запрос клиента, пока попытка fallback находится в ожидании.
Тест проходит только тогда, когда router выбирает ожидаемое действие и фиксирует причину.
Where Flatkey fits
Flatkey предоставляет один API key и OpenAI-compatible base URL для поддерживаемых моделей, с централизованным учетом использования и биллингом. Это создает стабильную границу интеграции для доступа к нескольким моделям и маршрутизации.
Команды приложений по-прежнему должны владеть route contract, описанным в этом playbook: какие ошибки можно повторять, какие targets эквивалентны, разрешен ли cross-model fallback, как дедуплицируются tools и какой порог качества должен соответствовать восстановленному ответу.
Для самого короткого пути интеграции используйте Flatkey integration starter. Если вы переносите существующий client, OpenAI-compatible API gateway checklist охватывает base URL, parameters, streaming и проверку формы error-shape.
Production rollout checklist
- Нормализуйте provider errors в стабильную внутреннюю taxonomy.
- Определите действия retry, equivalent failover, cross-model fallback и stop.
- Назначьте один component ответственным за retry budget.
- Обеспечьте один сквозной deadline и максимальное число попыток.
- Добавьте exponential backoff с jitter для transient failures.
- Связывайте circuit breaker с smallest useful failure domain.
- Определите versioned capability contract для каждого route alias.
- Заблокируйте automatic switching после начала partial output.
- Добавьте idempotency и reconciliation для write-side tools.
- Записывайте route reasons и итоговые task outcomes.
- Инжектируйте transport, overload, contract, streaming и side-effect failures.
- Выполните canary equivalent failover перед включением cross-model fallback.
- Добавьте kill switches для каждого target и fallback policy.
FAQ
What is fallback routing for LLM APIs?
Fallback routing for LLM APIs is a reliability policy that selects another eligible model or provider when the preferred route cannot complete a request. Safe fallback checks replay safety, capability compatibility, circuit health, latency budget, and output state before switching.
What is the difference between an LLM retry and fallback?
Повторная попытка повторяет запрос к той же цели. Отказоустойчивое переключение переводит запрос на эквивалентную инфраструктуру, сохраняя логический контракт модели. Кросс-модельный fallback меняет модель и поэтому требует более строгой проверки совместимости и качества.
Should an LLM API retry every 429 or 5xx error?
Нет. Повторные попытки должны ограничиваться сквозным дедлайном, лимитом попыток, политикой backoff, состоянием circuit и проверкой replay-safety. Эквивалентное переключение при отказе может быть лучше, чем многократные обращения к неработоспособной цели.
Can an LLM router switch models during a stream?
Не прозрачно после того, как вывод уже достиг клиента. Безопасное поведение по умолчанию — явно завершить stream с ошибкой или начать новый turn на уровне приложения. Склеивание частичных ответов от разных моделей может нарушить контракт ответа.
When should cross-model fallback be disabled?
Отключайте его, когда альтернативная модель не может сохранить требуемые tools, структурированный вывод, ограничения контекста, поведение безопасности, пороги качества или гарантии отсутствия побочных эффектов. Также отключайте автоматический replay после частичного вывода или неуверенного выполнения tool.
How many fallback attempts should an LLM request make?
Универсального числа нет. Используйте наименьшее ограниченное количество попыток, которое соответствует бюджету задержки продукта и данным тестов. Router должен остановиться, когда оставшегося дедлайна недостаточно для еще одной полезной попытки.
Надежный fallback не означает «попробовать все». Это означает сделать следующее действие явным, совместимым, безопасным для replay, наблюдаемым и легким для остановки.



