← SCRAM AI Lab

Anthropic

Circuit breaker + retry exponencial para LLMs

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

Circuit breaker + retry exponencial para LLMs

Los LLMs fallan diferente

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.

¿Cuándo conviene activar el circuit breaker en lugar de un retry?

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.

Circuit breaker: el patrón que sí aplica

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:

  • Umbral: 5 fallas en 30 segundos → OPEN.
  • Cooldown: 60 segundos en OPEN antes de pasar a HALF_OPEN.
  • HALF_OPEN: deja pasar UNA llamada. Si pasa, vuelve a CLOSED. Si falla, otros 60s OPEN.
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.

Retry con backoff exponencial

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++;
    }
  }
}

Lo que NO debes reintentar

  • 400 (bad request): tu prompt es inválido. Reintentar es perder dinero.
  • 401/403: credencial mala. Falla rápido y alerta.
  • 422 (content policy): el modelo se negó. Retry no lo va a convencer.
  • Timeout en streaming a media respuesta: peligroso reintentar; vas a duplicar contenido. Mejor cortar gracefully.

Comparativa de estrategias de resiliencia

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 anti-patrón: retry indefinido en 429

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.

Impacto y consideraciones para infraestructura en LATAM

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.

Qué puede salir mal y cómo mitigarlo

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.

Métricas que importan

  • Estado del circuit breaker por provider, exportado a Prometheus: cuántas veces abrió, duración promedio en OPEN.
  • Distribución de retries: histograma de attempts. Si el p50 es >0, tu provider está degradado.
  • Latencia añadida por retry: el costo invisible. Un retry en backoff de 1.6s en un chatbot mata la fluidez.

Composición con fallback de 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.

resilience
llm
patrones
← Volver a SCRAM AI Lab