← Все статьи

Чек-лист подготовки LLM API к продакшену: таймауты, повторные запросы, circuit breaker и graceful degradation

Единый чек-лист для подготовки интеграций LLM API к продакшену: бюджеты таймаутов, retry-политики с jitter, паттерн circuit breaker, цепочки graceful degradation, обработка rate limit, надёжность стриминга и маршрутизация на основе health check.

· TheRouter

Подготовка LLM API к продакшену означает выстраивание пяти слоёв защиты, каждый из которых закрывает свой класс отказов. Таймауты предотвращают блокировку приложения зависшими соединениями. Retry с jitter восстанавливают работоспособность после временных сбоев без создания retry-штормов. Circuit breaker останавливают трафик к неисправному провайдеру до того, как каскадный отказ обрушит всю систему. Цепочки graceful degradation маршрутизируют на более дешёвые или кешированные альтернативы, когда основная модель недоступна. Health-check маршрутизация делает всё это проактивным. Этот чек-лист объединяет опыт, накопленный нами при эксплуатации OpenAI-совместимой маршрутизации через несколько провайдеров.

OpenAI-совместимость означает, что провайдер предоставляет endpoint chat-completions, чей контракт запроса и ответа достаточно близок к API OpenAI, чтобы немодифицированный вызов OpenAI SDK работал после замены трёх значений: API key, base URL, название модели. Минимальная поверхность на практике —POST /v1/chat/completions с messages, model и потоковым ответом в форме OpenAI.

Зачем нужен единый чек-лист

По отдельности каждая тема надёжности хорошо задокументирована. OpenAI публикует руководство по rate limit. Anthropic описывает коды ошибок и retry-поведение SDK. DeepSeek перечисляет коды ошибок с рекомендуемыми действиями. Мы отдельно разбирали таймауты, retry и идемпотентность и fallback-маршрутизацию.

Не хватает единого чек-листа, который связывает все эти элементы в целостный продакшен-деплоймент. На практике retry борются с circuit breaker, таймауты ломают логику fallback, а health check мониторят не те сигналы. Это руководство собирает фрагменты воедино.

Чек-лист: общий вид

Распечатайте эту таблицу, вставьте в deployment runbook и проверяйте каждый пункт перед выкаткой.

#КатегорияПроверкаПриоритет
1ТаймаутыConnect таймаут установлен (5-10 с)Критический
2ТаймаутыRead таймаут по сценарию (30-120 с non-streaming)Критический
3ТаймаутыTotal таймаут ограничивает end-to-end latencyКритический
4ТаймаутыPer-chunk deadline для streamingВысокий
5RetryПовторяются только 429 и 5xxКритический
6RetryExponential backoff + full jitterКритический
7RetryRetry-After учитываетсяКритический
8RetryМаксимум 3-5 попыток, retry budget <10% трафикаВысокий
9RetryRetry только на одном уровнеВысокий
10Circuit breakerПорог failure rate настроен (20-30% за 60 с)Высокий
11Circuit breakerВ open-состоянии мгновенный fail fastВысокий
12Circuit breakerHalf-open пробы тестируют восстановлениеСредний
13DegradationЦепочка деградации моделей определенаВысокий
14DegradationКеш/очередь при полном отказеСредний
15Rate limitRPM + TPM одновременно на уровне приложенияВысокий
16Rate limitПредварительная оценка токеновСредний
17StreamingОбработка partial response и reconnectСредний
18Мониторингp50/p95/p99 latency по провайдерамВысокий
19МониторингАлерты на error rate и смену состояний circuit breakerВысокий
20МониторингТрекинг cost per request и детекция аномалийСредний

Стратегия таймаутов

Таймауты — первая линия обороны. Без них единственный медленный или недоступный провайдер бессрочно блокирует соединения, потоки и внимание пользователя.

LLM API заметно медленнее обычных REST API. Один запрос генерации обычно занимает 5-60 секунд в зависимости от размера модели, длины ввода и вывода. Стандартные значения по умолчанию HTTP-клиента (часто 30 секунд total) вызывают ложные таймауты на легитимных запросах с длинной генерацией. Полное отсутствие таймаутов означает, что сбой провайдера тихо заблокирует ваше приложение.

Connect таймаут защищает фазу TCP/TLS handshake. Установите 5-10 секунд. Если провайдер не может принять соединение за это время, endpoint вероятно недоступен.

Read таймаут (для streaming — first-byte таймаут) защищает интервал между отправкой запроса и получением первого байта ответа. Для non-streaming 30-120 секунд разумно. Для streaming первый chunk должен прийти за 10-30 секунд; далее — per-chunk deadline 15-30 секунд.

Total таймаут ограничивает весь жизненный цикл запроса. Для non-streaming обычно 60-180 секунд. Если запрос не завершился в пределах общего бюджета, завершайте его независимо от частичного прогресса.

import httpx

# Продакшен-конфигурация таймаутов для LLM API
client = httpx.Client(
    timeout=httpx.Timeout(
        connect=5.0,       # TCP/TLS handshake
        read=60.0,         # Ожидание первого байта / следующего chunk
        write=10.0,        # Отправка тела запроса
        pool=10.0,         # Ожидание соединения из пула
    )
)

Рекомендации по провайдерам

ПровайдерConnectRead (non-streaming)Read (streaming, первый chunk)Примечания
OpenAI5-10 с60-120 с15-30 сМодели с длинным контекстом (GPT-5.5) могут потребовать больший read
Anthropic5-10 с60-120 с15-30 сExtended thinking длится дольше; сервер возвращает 504 при таймауте
DeepSeek5-10 с60-90 с10-20 сV4 Flash обычно быстрый; V4 Pro reasoning может потребовать больше времени
DashScope5-10 с60-90 с10-20 сOpenAI-совместимый endpoint; логика таймаутов та же

Retry-политика с exponential backoff и jitter

Retry восстанавливают работу после временных сбоев. Наивные retry приносят больше вреда, чем сам сбой.

Что повторять:

  • 429 (rate limit) с учётом Retry-After
  • 500 (internal server error) с backoff
  • 503 (server overloaded) с backoff
  • Connection reset, DNS таймаут, сбой TLS handshake

Что никогда не повторять:

  • 400 (bad request) — каждый раз упадёт так же
  • 401 (authentication error) — надо исправить API key
  • 402 (billing/balance) — надо пополнить баланс
  • 403 (permission error) — надо менять конфигурацию
  • 413 (request too large) — надо уменьшить запрос

Exponential backoff с full jitter предотвращает retry-штормы. Чистый exponential backoff без jitter синхронизирует все клиенты на повторную попытку в один момент, воссоздавая thundering herd на каждой итерации.

import random
import time

def retry_with_jitter(func, max_attempts=3, base_delay=1.0, max_delay=32.0):
    for attempt in range(max_attempts):
        try:
            return func()
        except RetryableError as e:
            if attempt == max_attempts - 1:
                raise

            # Учитываем Retry-After от провайдера
            retry_after = getattr(e, 'retry_after', None)
            if retry_after:
                delay = float(retry_after)
            else:
                # Full jitter: случайное значение от 0 до экспоненциального потолка
                exp_delay = min(max_delay, base_delay * (2 ** attempt))
                delay = random.uniform(0, exp_delay)

            time.sleep(delay)

Retry budget предотвращает ситуацию, когда один деградировавший endpoint поглощает всю ёмкость. Глобальное ограничение: retry не должны превышать 10% от общего трафика. При превышении бюджета — мгновенный fail fast и переключение на fallback вместо продолжения запросов к деградировавшему провайдеру.

Retry только на одном уровне предотвращает мультипликативный взрыв. Три retry на каждом уровне пятислойной цепочки сервисов = 3^5 = 243 бэкенд-вызова на один пользовательский запрос. Выберите один уровень — обычно внешний слой приложения или routing gateway.

При использовании routing-слоя типа TheRouter настраивайте retry на уровне маршрутизатора, а не в коде приложения. Маршрутизатор видит все endpoint провайдеров и принимает более разумные решения о том, когда повторить запрос, а когда переключиться на альтернативный маршрут.

Паттерн circuit breaker для LLM-провайдеров

Retry обрабатывают временные сбои. Circuit breaker обрабатывает системные сбои.

Circuit breaker отслеживает failure rate в скользящем окне и имеет три состояния.

Closed (нормальная работа): все запросы проходят к провайдеру. Breaker ведёт статистику успехов и отказов.

Open (сработал): failure rate превысил порог. Все запросы немедленно получают fast fail без обращения к провайдеру. Это даёт провайдеру время восстановиться.

Half-open (зондирование): после cooldown breaker пропускает несколько тестовых запросов. Если они успешны — breaker закрывается. Если нет — снова открывается.

Рекомендуемые пороги для LLM API

ПараметрРекомендуемое значениеОбоснование
Скользящее окно60 секундДостаточно для детекции, достаточно коротко для быстрой реакции
Порог failure rate20-30% запросовВыше, чем для типичных микросервисов — LLM API имеют базовый уровень ошибок
Последовательные отказы5-10Альтернативный триггер для endpoint с низким трафиком
Cooldown30-60 секундДостаточно для восстановления провайдера
Half-open пробы1-3 запросаМинимальный трафик для проверки
Сброс при успехе2-3 последовательных успеха в half-openПодтверждение стабильного восстановления

Дополнительные триггеры, специфичные для LLM

  • Деградация latency: срабатывание, когда p95 latency превышает базовый уровень в 3 раза
  • Аномалия стоимости: срабатывание, когда cost per request превышает установленный порог — ловит runaway agent loops
  • Частота разрыва streaming: срабатывание, когда более 30% streaming-ответов обрываются до финального chunk

Цепочки graceful degradation

Когда circuit breaker срабатывает, приложению нужен fallback-путь от основной модели через всё более дешёвые или простые альтернативы, вплоть до кешированных ответов.

Пример цепочки для coding assistant

УровеньМодельТриггерВлияние на пользователя
L0 (основная)DeepSeek V4 ProНормальная работаПолные возможности
L1 (fallback)DeepSeek V4 FlashL0 circuit open или latency >45 сЧуть менее точно, значительно быстрее
L2 (бюджетная)Qwen3.8 Flash через DashScopeL0 + L1 в openДругая модель, сопоставимый уровень
L3 (кеш)Кешированные ответы для частых запросовВсе live-провайдеры деградированыВозможно устаревшие, но доступные
L4 (деградация)Сообщение об ошибке с ETAВсе fallback исчерпаныЧестное уведомление о недоступности

Ключевые решения

Кросс-провайдерный fallback устойчивее, чем fallback внутри одного провайдера. Если OpenAI лежит, переключение на другую модель OpenAI может не помочь. Переключение на Anthropic или DeepSeek через маршрутизацию fallback в TheRouter обходит отказы на уровне провайдера.

Деградация модели часто лучше, чем ожидание таймаута. Ответ от меньшей модели за 2 секунды полезнее, чем 30 секунд ожидания таймаута от большой модели. Настраивайте пороги деградации по latency, а не только по ошибкам.

Кешированные ответы требуют политики свежести. Для FAQ-запросов 1 час кеша обычно приемлем. Для данных реального времени кеширование неуместно.

Честная деградация укрепляет доверие. Когда все fallback отказали, сообщите пользователю, что произошло и когда ожидается восстановление.

Обработка rate limit

Rate limit — не ошибка, а управление потоком. Продакшен-система должна поглощать rate limit, а не обрабатывать как сбой.

Двухосевой трекинг: LLM-провайдеры ограничивают по RPM (запросы в минуту) и TPM (токены в минуту) одновременно. Запросы с длинным контекстом легко превышают TPM, оставаясь в пределах RPM. Отслеживайте оба показателя на уровне приложения.

Предварительная оценка токенов предотвращает внезапное превышение TPM. Используйте tokenizer (tiktoken для OpenAI-совместимых API) для оценки количества токенов перед отправкой. Если расчётное количество превысит остаток TPM-бюджета — ставьте запрос в очередь, а не отправляйте.

Всегда указывайте max_tokens для ограничения длины вывода. Без этого модель, генерирующая необычно длинный ответ, тихо исчерпает ваш TPM-бюджет на одном запросе.

Поглощение пиков через очередь: вместо отбрасывания запросов сверх лимита ставьте их в очередь с ограниченным временем ожидания. Очередь на Redis или в памяти с максимальным ожиданием 5-10 секунд сглаживает пиковый трафик без потери запросов.

Надёжность streaming

Streaming (SSE) ответы создают проблемы надёжности, которых нет у batch-запросов.

Обработка partial response: streaming-ответ может оборваться. Приложение должно отслеживать полученный объём и наличие маркера завершения ([DONE] в OpenAI-совместимых API или message_stop у Anthropic). Partial response без стоп-маркера надо отметить как неполный.

Стратегия reconnect: не переподключайтесь слепо с тем же prompt. Провайдер мог частично обработать запрос и выставить счёт. Пометьте partial response как неполный и либо покажите его пользователю с предупреждением, либо начните новый запрос с нуля.

Per-chunk таймаут: установите deadline для каждого SSE chunk, а не только для первого байта. Провайдер, отправляющий первый chunk за 2 секунды, но потом зависающий на 60 секунд между chunk, фактически деградирован.

Health check и проактивная маршрутизация

Реактивная устойчивость (retry после сбоя, circuit breaker после ошибок) — базовый уровень. Проактивная устойчивость (маршрутизация от деградированного провайдера до того, как пользователи пострадают) — следующая ступень.

Синтетические health-пробы: отправляйте лёгкие тестовые запросы каждому провайдеру раз в 30-60 секунд. Используйте маленькую быструю модель и короткий prompt. Если latency пробы провайдера превышает базу в 2 раза или пробы начинают падать — снижайте вес маршрутизации до того, как пользовательский трафик пострадает.

Мониторинг status pages провайдеров: программно отслеживайте status.openai.com, status.claude.com, status.deepseek.com. При объявлении деградации или major outage — превентивно перенаправляйте трафик.

Мониторинг response headers: OpenAI возвращает x-ratelimit-remaining-requests и x-ratelimit-remaining-tokens в каждом ответе. Используйте их для предсказания момента достижения лимита и проактивного throttling.

При использовании TheRouter health-check маршрутизация встроена в конфигурацию model fallback. Маршрутизатор отслеживает здоровье провайдеров по всему проходящему трафику и обходит деградированные endpoint без кода health check на уровне приложения.

Мониторинг и определение SLO

Невозможно hardening то, что не измеряешь. Определите SLO для интеграции LLM API и алертите при нарушениях.

Ключевые метрики

МетрикаИзмерениеПример SLO
Успешность запросов1 - (error_count / total_count)>99.5% за 5-минутные окна
Latency p50Медианное end-to-end время<5 с для flash, <15 с для flagship
Latency p9595-й перцентиль<15 с для flash, <45 с для flagship
Latency p9999-й перцентиль<30 с для flash, <90 с для flagship
Время open circuit breakerОбщее кол-во секунд open в час<300 с/час
Cost per requestОбщие расходы / общие запросыНиже бюджетного порога
Retry rateretry_count / total_count<5% устойчиво, <10% пиковое
Streaming completion ratecomplete_streams / started_streams>99%

Условия алертов:

  • Успешность ниже SLO два окна подряд
  • Любой circuit breaker перешёл в open
  • Cost per request превысил 2x от 7-дневного скользящего среднего
  • Retry rate выше 10% более 5 минут

Как всё работает вместе

Защитные слои взаимодействуют. Порядок обработки запроса:

  1. Запрос поступает в приложение
  2. Предварительная проверка: оценка токенов, проверка бюджета rate limit. Если превышен — в очередь или отброс.
  3. Выбор маршрута: health-check маршрутизация выбирает лучший доступный провайдер/модель
  4. Применение таймаутов: connect + read + total обёртывают API-вызов
  5. Обработка сбоя: если запрос упал — классифицировать ошибку
  6. Решение о retry: повторяемые ошибки проходят exponential backoff с jitter в рамках retry budget
  7. Проверка circuit breaker: если circuit breaker провайдера в open — сразу в fallback
  8. Fallback-маршрутизация: если retry исчерпаны или circuit breaker открыт — маршрутизация к следующей модели в цепочке деградации
  9. Режим деградации: если все провайдеры лежат — кешированные ответы или честная ошибка
  10. Телеметрия: логирование каждой точки принятия решения для мониторинга и SLO

Строить всё это с нуля необязательно. LLM routing-слой обрабатывает шаги 3, 4, 6, 7, 8 на уровне инфраструктуры, позволяя коду приложения сосредоточиться на шагах 1, 2, 5, 9, 10.

FAQ

Какой таймаут ставить для extended thinking / reasoning моделей?

Модели типа DeepSeek R1 или Claude с extended thinking могут генерировать 60-180 секунд. Установите read таймаут минимум 120 с. Используйте streaming для visibility в прогресс. Total таймаут — 3x от ожидаемого времени генерации для данной модели и длины prompt.

Повторять ли tool calls и function calls?

Только если приёмник исполнения инструмента идемпотентен. Если tool call отправляет email, создаёт тикет или пишет в базу данных, retry может дублировать побочный эффект. Проверьте, выполнился ли предыдущий вызов, перед повтором. Используйте idempotency keys, если провайдер их поддерживает.

Как обрабатывать rate limit при нескольких API key?

Отслеживайте RPM и TPM per key, а не per application. Каждый ключ имеет свои лимиты. Rate limiter должен отслеживать каждый ключ независимо и маршрутизировать к ключам с оставшейся ёмкостью.

В чём разница между retry на уровне приложения и на уровне gateway?

Retry на уровне приложения происходят в вашем коде. Retry на уровне gateway — в routing-слое (например, TheRouter). Используйте одно или другое, но не оба. При двойном retry возникает мультипликативный retry-шторм. Gateway retry обычно предпочтительнее — gateway видит весь трафик и принимает более разумные решения.

Как часто настраивать пороги circuit breaker?

Пересматривайте ежемесячно. Характеристики надёжности провайдеров меняются. Подходящий порог при 99.5% uptime может быть слишком чувствительным при 99.9% или слишком мягким при деградации. Используйте данные мониторинга для тюнинга.

Когда использовать routing-слой, а когда писать retry и fallback самостоятельно?

Если вы вызываете одного провайдера с одной моделью — retry в коде приложения достаточно. Если несколько провайдеров, несколько моделей, circuit breaker и health-check маршрутизация — специализированный routing-слой убирает существенную сложность из кода приложения и централизует логику устойчивости.


Источники:

Помощь и контакты