← SCRAM AI Lab
Implementa circuit breaker y backoff exponencial en llamadas a LLMs para blindar la disponibilidad y evitar sobrecostos por fallas y rate limits.
May 21, 2026
496 lecturas

Una API REST tradicional falla con 500, 502, 503 o timeout. Predecible. Los endpoints LLM fallan con: 429 (rate limit), 529 (Anthropic overloaded), 503 transitorio, timeouts a los 60s en respuestas largas, streams que se cortan a media generación, y respuestas vacías sin error. Tratarlos con el mismo patrón retry-3-veces-y-rendirse es ingenuo y te va a costar uptime.
Debes usar retries para fallas transitorias de red o picos aislados de concurrencia en peticiones individuales, mientras que el circuit breaker se activa cuando la tasa acumulada de fallos indica una degradación sostenida del proveedor. El retry protege llamadas individuales; el circuit breaker protege la estabilidad global de tu arquitectura evitando cascadas de errores y sobrecostos.
La idea es vieja pero crítica con LLMs: si tu provider ya está en mal momento, retries agresivos lo empeoran. El breaker observa fallas en ventana móvil; si pasan un umbral, abre el circuito y rechaza llamadas localmente por un tiempo antes de probar de nuevo con un "canario" en HALF_OPEN.
Configuración que funciona para chatbot en producción:
type State = "CLOSED" | "OPEN" | "HALF_OPEN";
class CircuitBreaker {
private state: State = "CLOSED";
private failures: number[] = [];
private openedAt = 0;
constructor(
private threshold = 5,
private windowMs = 30_000,
private cooldownMs = 60_000,
) {}
async run<T>(fn: () => Promise<T>): Promise<T> {
if (this.state === "OPEN") {
if (Date.now() - this.openedAt < this.cooldownMs) {
throw new Error("circuit_open");
}
this.state = "HALF_OPEN";
}
try {
const result = await fn();
if (this.state === "HALF_OPEN") this.reset();
return result;
} catch (err) {
this.recordFailure();
throw err;
}
}
private recordFailure() {
const now = Date.now();
this.failures = this.failures.filter(t => now - t < this.windowMs);
this.failures.push(now);
if (this.failures.length >= this.threshold || this.state === "HALF_OPEN") {
this.state = "OPEN";
this.openedAt = now;
}
}
private reset() { this.state = "CLOSED"; this.failures = []; }
}
Una instancia POR provider/modelo: el breaker de Opus es distinto del de Sonnet, porque pueden fallar independientemente.
Dentro del breaker, los retries tienen reglas distintas a las APIs REST:
async function withRetry<T>(fn: () => Promise<T>, opts = { maxRetries: 2 }): Promise<T> {
let attempt = 0;
while (true) {
try {
return await fn();
} catch (err) {
const code = (err as any).status;
const retryable = code === 429 || code === 529 || code === 503 || code === 408;
if (!retryable || attempt >= opts.maxRetries) throw err;
const retryAfter = (err as any).headers?.["retry-after"];
const delay = retryAfter
? Number(retryAfter) * 1000
: Math.min(200 * Math.pow(2, attempt) + Math.random() * 100, 3000);
await new Promise(r => setTimeout(r, delay));
attempt++;
}
}
}
Elegir el mecanismo adecuado depende del tipo de tráfico y del presupuesto de latencia de tu producto:
| Estrategia | Escenario recomendado | Impacto en latencia | Riesgo principal |
|---|---|---|---|
| Retry lineal simple | APIs internas con baja concurrencia | Bajo pero acumulativo | Efecto estampida (thundering herd) |
| Backoff exponencial con jitter | Picos transitorios de saturación (429/503) | Medio (incremento progresivo) | Agotamiento de timeouts en clientes móviles |
| Circuit Breaker aislado | Degradación global del proveedor | Cero durante estado OPEN (falla rápida) | Falsos positivos si el umbral es muy bajo |
| Breaker + Fallback a modelo alterno | Chatbots y flujos críticos de producción | Mínimo (salto directo a respaldo) | Variación en calidad o formato del output |
El error más caro que vas a cometer: poner retry infinito con backoff "porque eventualmente cede". Si pegaste tu cuota mensual de Anthropic, retries no la abren. Si estás rate-limited por minuto, sí, pero más de 3 retries es señal de que tu volumen rebasa tu plan y necesitas pedir aumento de cuota, no esperar más.
De acuerdo con los términos de servicio empresarial de OpenAI, el acuerdo de nivel de servicio (SLA) garantiza una disponibilidad del 99.5% mensual solo para contratos Enterprise. Esto implica que un sistema en producción puede enfrentar legalmente hasta 3.6 horas de indisponibilidad o degradación severa al mes. Sin un circuit breaker, esas 3.6 horas se traducirán en hilos de ejecución bloqueados y colapso de tus propios servidores.
Para empresas operando en México y el resto de América Latina, la resiliencia ante fallos de LLMs tiene implicaciones operativas críticas. La mayoría de los centros de inferencia de OpenAI y Anthropic se ubican en regiones del norte de Estados Unidos (como us-east-1 en Virginia o us-west-2 en Oregón). Esto introduce una latencia base de red que ronda entre 60 y 120 milisegundos por salto solo en transporte.
Cuando un servicio en Ciudad de México o Bogotá encadena tres retries con backoff frente a un endpoint degradado en Virginia, el tiempo de respuesta al usuario final salta fácilmente de 2 a más de 12 segundos, degradando la experiencia en conexiones móviles de velocidad variable. Implementar un circuit breaker local permite saltar de inmediato a proveedores alternativos o a modelos locales hosteados en instancias regionales sin pagar el peaje de la latencia acumulada internacional.
El fallo más común al implementar este esquema es el "thundering herd" en la transición de OPEN a HALF_OPEN. Si decenas de peticiones concurrentes detectan que el tiempo de cooldown expiró simultáneamente, todas intentarán validar el canal a la vez, inundando al proveedor y devolviendo el circuito a OPEN de inmediato. La solución consiste en aplicar una variable de jitter aleatorio al tiempo de cooldown de cada instancia de la aplicación.
Otro problema recurrente ocurre con las conexiones SSE (Server-Sent Events) interrumpidas. Cuando la respuesta se corta a la mitad, registrar el error en el circuit breaker es correcto, pero reintentar la llamada completa sin limpiar el estado del cliente genera duplicación de texto en pantalla. En flujos conversacionales, la interfaz debe truncar el stream y emitir un mensaje de contingencia antes de permitir un nuevo intento manual o redirigir el turno a otro modelo.
El breaker se combina con el router de tiers: si Opus está en OPEN, el wrapper cae a Sonnet automáticamente. La respuesta es marginalmente peor; el sistema sigue vivo. Sin esta combinación, un mal momento de Anthropic te tira el chatbot completo aunque OpenAI esté sano.
¿Tu chatbot tiene SLO de uptime medido contra el provider, o contra tu propio servicio? Si es contra el provider, la respuesta corta es: necesitas ambos patrones, no opcionalmente.
Artículos relacionados
Claude Opus 5.5 y Sonnet 5.5: precios, benchmarks y qué cambia en la API
Anthropic lanzó Opus 5.5 ($4/$20) y Sonnet 5.5 ($2/$10) en seis días. Sonnet supera a Opus en Terminal-Bench. Precios, cambios de API y cómo migrar.
Claude Fable 5.1 y Mythos 5.1: precio, benchmarks y qué cambia en tu enrutador de modelos
Anthropic lanzó Claude Fable 5.1 y Mythos 5.1 el 1 de septiembre de 2026: $10/$50 por millón de tokens, lectura de caché a $0.25 (75% menos), cinco niveles de esfuerzo y retención cero. Comparativa contra Fable 5 y Opus 5, y cómo decidir si migras.
Claude Opus 5: precio, contexto de 1M y por qué reordena tu router de LLMs
Claude Opus 5 introduce 1M de tokens y rendimiento de frontera a precio accesible. Analizamos costos, benchmarks y cómo reconfigurar tu router de LLMs.