Когда LLM падает, сервер не должен падать с ним
Если коротко
- Один сценарий, в котором ретраи не лечат, а добивают.
- Три состояния предохранителя: closed → open → half-open. Сорок строк кода.
- Ретраи и breaker — это слои, не альтернативы. Без одного из них продакшен — лотерея.
~4s loop, ease-out: three state pills labeled "closed", "open", "half-open" transition in sequence. Closed = soft emerald glow, open = soft rose pulse, half-open = soft amber pulse. A small spark icon moves across to indicate "probe call". Transparent background, clean line work, 800x300, ~24fps.
Представьте официанта. Кухня сгорела. Он продолжает принимать заказы. Со столиков уже разносится дым, а он бегает между ними, обещая, что «скоро принесут».
Без circuit breaker ваш LLM-клиент во время чужого даунтайма ведёт себя ровно так.
Сценарий: провайдер лежит 15 минут
API отвечает 503. Без предохранителя на каждый пользовательский запрос вы делаете три ретрая с экспоненциальной задержкой — 12-30 секунд. Соединения копятся, воркеры заняты, дашборд висит.
Пользователь нажимает «обновить». Получает копию проблемы. Через минуту вы успешно положили свой сервер раньше, чем восстановится чужой.
Circuit breaker говорит коротко: «мы знаем, что не работает. Перестаём бить туда сами.»
Три состояния
Closed. Норма. Запросы уходят в LLM. Считаем подряд идущие ошибки. Один успешный ответ обнуляет счётчик.
Open. После N подряд идущих ошибок (5 — нормальный старт) переключаемся. Любой новый запрос мгновенно возвращает ошибку, не уходя в LLM. Длится фиксированный cooldown — 30 секунд.
Half-open. Через 30 секунд пропускаем один пробный запрос. Успешен — возвращаемся в closed. Нет — снова open, ещё на 30 секунд.
Три состояния, один счётчик, один таймер. Сорок строк Python.
Где живёт предохранитель
На клиенте LLM, не на эндпойнте. Один экземпляр на процесс, общий для всех вызовов. Тогда состояние агрегирует все ошибки, а не одну конкретную сессию.
Несколько провайдеров — у каждого свой breaker. Падение одного не должно гасить вызовы к другому. Иначе вы изобрели единую точку отказа из попытки построить отказоустойчивость.
Ретраи против предохранителя — это не «или»
Это слои.
Ретраи (tenacity, экспоненциальная задержка) защищают от одного сбоя — сети, мгновенного 503. Они работают внутри одного вызова.
Breaker защищает от длительной деградации — когда вся внешняя система легла. Он работает между вызовами.
Оба нужны. Конфликта нет. Без обоих в продакшене вы тренируете удачу, а не надёжность.
- Поставьте tenacity на HTTP-вызов к LLM (3 попытки, экспоненциальный backoff, retry на TransportError + 5xx).
- Оберните клиент в маленький
CircuitBreaker— это правда сорок строк. - В демо ARA на странице «Система» виден живой индикатор состояния.