В промышленных системах прямая зависимость от одного поставщика LLM (Single Point of Failure) гарантированно приводит к деградации сервиса. Внешние API подвержены регулярным инцидентам: исчерпанию лимитов частоты запросов (HTTP 429 Too Many Requests / TPM / RPM), перегрузкам кластеров инференса (HTTP 503 Service Unavailable), сетевым таймаутам и глобальным авариям в дата-центрах провайдеров.
Для обеспечения доступности уровня 99.95%+ в 2026 году архитектура строится на следующих принципах отказоустойчивости (High Availability):
Маршрутизатор отказоустойчивости перехватывает входящие запросы и управляет жизненным циклом вызова через конечный автомат Circuit Breaker.
[ Application Client ]
|
v
[ LiteLLM Proxy / Resilience Router ]
|
+------------------+------------------+
| |
[ Circuit: CLOSED ] [ Circuit: OPEN ]
(Primary OpenAI) (Fast Fail -> Fallback)
| |
(Fails: HTTP 429/503) |
| v
v [ Secondary: Azure OpenAI ]
[ Trip Circuit -> OPEN ] |
| (Fails / Timeout)
v v
[ Tertiary: Claude 3.5 / Local vLLM ] <--------+
|
v
[ Successful Response ]Состояния автомата Circuit Breaker:
Ниже приведена TypeScript-реализация устойчивого роутера с паттерном Circuit Breaker, экспоненциальной задержкой с полным джиттером и каскадным переключением провайдеров.
export type CircuitState = "CLOSED" | "OPEN" | "HALF_OPEN";
export interface CircuitBreakerOptions {
failureThreshold: number; // Количество ошибок подряд для размыкания
cooldownPeriodMs: number; // Время нахождения в состоянии OPEN
halfOpenSuccessThreshold: number; // Успешных попыток для восстановления
}
export class CircuitBreaker {
private state: CircuitState = "CLOSED";
private failureCount = 0;
private successCount = 0;
private nextAttemptTime = 0;
constructor(
public readonly name: string,
private readonly options: CircuitBreakerOptions
) {}
public canExecute(): boolean {
const now = Date.now();
if (this.state === "OPEN") {
if (now >= this.nextAttemptTime) {
this.state = "HALF_OPEN";
this.successCount = 0;
return true;
}
return false;
}
return true;
}
public recordSuccess(): void {
this.failureCount = 0;
if (this.state === "HALF_OPEN") {
this.successCount++;
if (this.successCount >= this.options.halfOpenSuccessThreshold) {
this.state = "CLOSED";
}
}
}
public recordFailure(): void {
this.failureCount++;
if (this.state === "HALF_OPEN" || this.failureCount >= this.options.failureThreshold) {
this.state = "OPEN";
this.nextAttemptTime = Date.now() + this.options.cooldownPeriodMs;
}
}
public getState(): CircuitState {
return this.state;
}
}Каскадный исполнитель запросов с Full Jitter Backoff:
import { CircuitBreaker } from "./circuit-breaker";
export interface ProviderEndpoint {
id: string;
name: string;
execute: (prompt: string) => Promise<string>;
}
export class ResilientLLMRouter {
private breakers = new Map<string, CircuitBreaker>();
constructor(private readonly providers: ProviderEndpoint[]) {
for (const provider of providers) {
this.breakers.set(
provider.id,
new CircuitBreaker(provider.name, {
failureThreshold: 3,
cooldownPeriodMs: 30_000, // 30 секунд отдыха
halfOpenSuccessThreshold: 2,
})
);
}
}
/**
* Вычисление задержки Exponential Backoff с Full Jitter
*/
private calculateJitterDelay(attempt: number, baseMs = 500, maxMs = 5000): number {
const exponential = Math.min(maxMs, baseMs * Math.pow(2, attempt));
return Math.floor(Math.random() * exponential);
}
private async sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* Выполнение запроса с перебором резервных провайдеров в порядке приоритета
*/
public async executeWithFallback(prompt: string): Promise<{ result: string; providerUsed: string }> {
const errors: string[] = [];
for (const provider of this.providers) {
const breaker = this.breakers.get(provider.id);
if (!breaker || !breaker.canExecute()) {
errors.push(`[${provider.name}]: Circuit Breaker is OPEN (Skipped)`);
continue;
}
// До 2 попыток с джиттером на одного провайдера (при кратковременных сетевых сбоях)
for (let attempt = 0; attempt < 2; attempt++) {
try {
const result = await provider.execute(prompt);
breaker.recordSuccess();
return { result, providerUsed: provider.name };
} catch (error) {
const isRateLimitOr5xx = error instanceof Error &&
(error.message.includes("429") || error.message.includes("503") || error.message.includes("502"));
if (attempt === 0 && isRateLimitOr5xx) {
const delay = this.calculateJitterDelay(attempt);
await this.sleep(delay);
continue;
}
breaker.recordFailure();
errors.push(`[${provider.name}] Error: ${error instanceof Error ? error.message : "Unknown"}`);
break; // Переход к следующему провайдеру в цепочке
}
}
}
throw new Error(`All LLM providers in fallback chain failed:\n${errors.join("\n")}`);
}
}Конфигурационный файл кластерного шлюза LiteLLM Proxy для балансировки нагрузки и автоматических фоллбэков:
model_list:
# Основной эндпоинт OpenAI Direct
- model_name: gpt-4o-cluster
litellm_params:
model: openai/gpt-4o
api_key: os.environ/OPENAI_PRIMARY_KEY
rpm: 1000
tpm: 80000
# Резервный эндпоинт в изолированном облаке Azure OpenAI
- model_name: gpt-4o-cluster
litellm_params:
model: azure/gpt-4o-deployment
api_base: https://corp-azure-east.openai.azure.com/
api_key: os.environ/AZURE_OPENAI_KEY
api_version: "2024-08-01-preview"
# Аварийный fallback на Claude 3.5 Sonnet
- model_name: gpt-4o-fallback
litellm_params:
model: anthropic/claude-3-5-sonnet-20241022
api_key: os.environ/ANTHROPIC_API_KEY
router_settings:
routing_strategy: least-busy # Балансировка по наименее загруженным соединениям
num_retries: 3
timeout: 30 # Таймаут ответа в секундах
allowed_fails: 2 # Количество ошибок до срабатывания cooldown
cooldown_time: 45 # Секунд исключения упавшего инстанса из пула
fallbacks:
- gpt-4o-cluster: ["gpt-4o-fallback"]Использование фиксированной задержки (sleep(1000 * attempt)). Когда у провайдера падает инференс, тысячи клиентов одновременно получают 503 и одновременно делают ретрай ровно через 1000 мс. Волнообразный всплеск трафика добивает восстанавливающийся шлюз (Self-Inflicted DDoS). Обязательно применение Full Jitter.
Переключение с OpenAI gpt-4o на Anthropic claude-3-5-sonnet без трансляции схемы вызова инструментов. У OpenAI аргументы передаются в tool_calls[0].function.arguments как JSON-строка, тогда как у Claude они передаются в content блоке с типом tool_use как распарсенный объект. LiteLLM решает эту задачу автоматически, но при ручной реализации требуется слой нормализации схемы.
Ожидание ответа от зависшего провайдера по 60–120 секунд. Пользователь закрывает вкладку уже на 5–10 секунде, но соединение висит, блокируя пул воркеров. Таймаут до первого токена (TTFT Timeout) должен быть агрессивным (обычно не более 4–6 секунд), после чего запрос должен немедленно эскалироваться на резервного провайдера.
В кластере из 20 подов Kubernetes каждый под держит счетчик ошибок в локальной памяти. Если провайдер упал, каждый из 20 подов по очереди сделает по 3 неудачные попытки (суммарно 60 ошибок), прежде чем все поды разомкнут цепь. Для крупных микросервисных систем состояние Circuit Breaker должно синхронизироваться в Redis.
Отправка запроса к первичному провайдеру (OpenAI) с контролем агрессивного таймаута
Перехват ошибки HTTP 429 / 503 или превышения TTFT таймаута (4 сек)
Инкремент счетчика сбоев и размыкание цепи при превышении failureThreshold
Нормализация системных промптов и схем вызова функций под резервного поставщика
Успешный инференс на вторичном провайдере (Azure / Anthropic) и возврат клиенту