Что такое circuit breaker и как он применяется к LLM API вызовам?

Краткий тезис

Circuit breaker (автоматический выключатель) — это паттерн отказоустойчивости, который предотвращает каскадные отказы в распределённых системах. Применительно к LLM API он защищает приложение от перегрузок, превышения лимитов и дорогостоящих повторных вызовов при сбоях. Когда количество ошибок превышает порог (например, 50% за 10 секунд), цепь «размыкается», и все последующие вызовы мгновенно перенаправляются в fallback (кэш, другой провайдер, сообщение об ошибке). Через заданный таймаут цепь переходит в half-open состояние и пропускает пробный запрос для проверки восстановления сервиса.


1. Термин: Circuit breaker (автоматический выключатель)

Circuit breaker — это паттерн, заимствованный из электротехники. В программных системах он реализует три состояния:

СостояниеОписаниеПоведение при вызове
Closed (замкнут)Нормальная работаВызовы проходят к LLM API, мониторится частота ошибок
Open (разомкнут)Сервис считается недоступнымВсе вызовы мгновенно отклоняются или идут в fallback, без реального запроса к API
Half-open (полуоткрыт)Проверка восстановленияПропускается ограниченное число пробных запросов; если успешны — цепь закрывается, иначе снова открывается

Ключевые параметры:

  • Порог ошибок (error threshold) — доля или абсолютное количество ошибок за окно времени (например, 5 ошибок за 10 секунд или 50% ошибок).
  • Таймаут восстановления (timeout|reset timeout) — время, через которое цепь переходит из open в half-open (например, 30 секунд).
  • Размер окна (sliding window) — период, за который считаются ошибки (фиксированное или скользящее окно).

2. Зачем circuit breaker для LLM API вызовов?

LLM API (OpenAI, Anthropic, локальные модели) имеют специфические проблемы:

  • Rate limits — превышение лимита запросов в минуту/день приводит к HTTP 429.
  • Перегрузка провайдера — временные сбои (5xx), высокая задержка.
  • Дороговизна — повторные вызовы при ошибках тратят квоту и деньги.
  • Каскадные отказы — если один компонент (например, эмбеддер) падает, все зависимые сервисы тоже начинают фейлить, усугубляя ситуацию.
  • Нестабильность сети — таймауты, DNS-ошибки.

Circuit breaker позволяет:

  • Быстро отключать проблемный API, не дожидаясь таймаута каждого запроса.
  • Давать время сервису восстановиться (backpressure).
  • Прозрачно переключаться на резервные варианты (fallback).

3. Как работает circuit breaker: пошаговый сценарий

  1. Closed: Все вызовы идут к LLM API. Счётчик ошибок (например, HTTP 429, 5xx, таймауты) обновляется в скользящем окне.
  2. Порог превышен: Доля ошибок > 50% за последние 10 секунд → цепь размыкается (open).
  3. Open: Любой вызов к API немедленно возвращает fallback-ответ (кэш, заглушка, другой провайдер) без реального HTTP-запроса. Таймер восстановления запущен.
  4. Half-open: Через 30 секунд цепь переходит в half-open. Пропускается один пробный запрос (или несколько, в зависимости от конфигурации).
    • Если запрос успешен → цепь закрывается (closed), счётчики сбрасываются.
    • Если запрос снова ошибка → цепь снова открывается (open), таймер сбрасывается.
  5. Цикл повторяется.

Формула порога (пример):

error_rate = (число ошибок за окно) / (общее число запросов за окно)
if error_rate > threshold → open

4. Реализация на Python с tenacity

Библиотека tenacity предоставляет декораторы для повторных попыток, но circuit breaker в ней нет напрямую. Однако можно реализовать свой breaker поверх tenacity или использовать готовые библиотеки (например, pybreaker, circuitbreaker). Ниже пример с circuitbreaker:

from circuitbreaker import circuit
import time
import openai  # гипотетический клиент

@circuit(
    failure_threshold=5,          # 5 ошибок подряд
    recovery_timeout=30,          # 30 секунд до half-open
    expected_exception=Exception  # любые исключения считаются ошибкой
)
def call_llm_api(prompt: str) -> str:
    response = openai.Completion.create(
        engine="gpt-4",
        prompt=prompt,
        max_tokens=100
    )
    return response.choices[0].text

# Использование с fallback
def get_llm_response(prompt: str) -> str:
    try:
        return call_llm_api(prompt)
    except Exception:
        # fallback: кэш или другой провайдер
        return get_from_cache(prompt) or "Сервис временно недоступен"

Важно: circuitbreaker по умолчанию считает ошибки последовательно (failure_threshold подряд). Для скользящего окна по времени нужно использовать кастомный брейкер или библиотеку resilience4j (Java) через адаптер.


5. Интеграция с RAG/Agentic системами

В архитектуре Agentic RAG circuit breaker применяется на нескольких уровнях:

  • Retrieval API (векторная БД, эмбеддер) — если эмбеддер падает, retrieval не работает, breaker переключает на fallback (например, BM25).
  • LLM API (генерация ответа) — breaker переключает на кэшированный ответ, другую модель (локальную) или сообщение об ошибке.
  • Внешние инструменты (калькулятор, поиск в интернете) — breaker изолирует сбойный инструмент, не блокируя весь агент.

Пример конфигурации для агента:

# Словарь брейкеров для разных сервисов
breakers = {
    "openai": CircuitBreaker(failure_threshold=3, recovery_timeout=60),
    "pinecone": CircuitBreaker(failure_threshold=5, recovery_timeout=30),
    "serpapi": CircuitBreaker(failure_threshold=2, recovery_timeout=120)
}

def agent_workflow(query):
    # Попытка retrieval
    if not breakers["pinecone"].call(pinecone_query, fallback=bm25_search):
        return "Поиск временно недоступен"
    # Генерация
    response = breakers["openai"].call(llm_generate, fallback=cached_answer)
    return response

6. Альтернативы и дополнения

ПаттернОписаниеОтличие от circuit breaker
Retry (повтор)Повторяет запрос при ошибке (обычно с exponential backoff)Не защищает от каскадных отказов; может усугубить нагрузку
Rate limiting (ограничение частоты)Ограничивает количество запросов в единицу времениПредотвращает превышение лимитов, но не реагирует на ошибки сервера
Bulkhead (перегородка)Изолирует ресурсы (пулы потоков) для разных сервисовПредотвращает «заражение» одного сервиса другим, но не отключает проблемный
Fallback (запасной вариант)Возвращает альтернативный ответ при сбоеЧасто используется вместе с breaker как действие при open
Timeout (таймаут)Ограничивает время ожидания ответаНеобходим, но не решает проблему повторных таймаутов

Circuit breaker часто комбинируют с retry (retry внутри closed состояния) и fallback (действие при open).


7. Мониторинг и метрики

Для production-системы нужно отслеживать:

  • Текущее состояние каждого breaker (closed/open/half-open) — метка в Prometheus.
  • Количество переходов в open — alert при частых размыканиях.
  • Доля fallback-ответов — показатель качества обслуживания.
  • Время восстановления — среднее время между open и half-open.

Пример метрик (Python + prometheus_client):

from prometheus_client import Counter, Gauge

breaker_state = Gauge('llm_circuit_breaker_state', '0=closed,1=half-open,2=open', ['service'])
breaker_trips = Counter('llm_circuit_breaker_trips_total', 'Number of times breaker opened', ['service'])

8. Продвинутые техники

  • Адаптивный порог — порог ошибок динамически подстраивается под историческую частоту ошибок (например, на основе скользящей медианы).
  • Машинное обучение — предсказание отказов по временным рядам (задержка, ошибки) и упреждающее размыкание.
  • Каскадные breaker'ы — если один breaker открыт, автоматически открываются зависимые (например, при отказе эмбеддера открывается breaker генерации).
  • Полуавтоматическое восстановление — half-open может требовать ручного подтверждения для критичных сервисов.

9. Практические рекомендации для production

  1. Начинайте с простого — используйте готовую библиотеку (pybreaker, circuitbreaker, resilience4j).
  2. Настройте пороги под конкретный API — для OpenAI (частые 429) порог может быть выше, для локальной модели — ниже.
  3. Всегда добавляйте fallback — кэш, другой провайдер, сообщение пользователю.
  4. Мониторьте и алертите — breaker не должен быть «чёрным ящиком».
  5. Тестируйте с хаос-инжинирингом — искусственно вызывайте ошибки, чтобы проверить поведение.
  6. Учитывайте идемпотентность — если запрос неидемпотентен, breaker может привести к дублированию (например, оплата). Для LLM это редко актуально.

Пет-проект для закрепления

Задача: Реализовать простого Telegram-бота, который отвечает на вопросы через OpenAI API, но защищён circuit breaker'ом.

Инструменты: Python, python-telegram-bot, openai, circuitbreaker, redis (для кэша).

Шаги:

  1. Создайте бота, который принимает сообщения и вызывает openai.ChatCompletion.create.
  2. Оберните вызов в @circuit(failure_threshold=3, recovery_timeout=60).
  3. Реализуйте fallback: при открытом breaker возвращайте последний кэшированный ответ из Redis или заглушку «Сервис временно недоступен».
  4. Добавьте логирование состояний breaker в файл.
  5. Напишите скрипт, который имитирует ошибки (например, подменяет API-ключ на невалидный) и проверяет, что бот переключается на fallback.

Ожидаемый результат: Бот корректно обрабатывает сбои API, не зависает, пользователь видит осмысленный ответ даже при отказе.


Связь с другими вопросами

ВопросТема
401Retry strategies для LLM API
402Fallback-механизмы в RAG
403Rate limiting и его применение
405Graceful degradation в агентных системах
406Мониторинг и алертинг LLM-вызовов
407Кэширование ответов LLM

Навигация