Ir al contenido principal
Retry y circuit breaker en agentes IA de producción

Retry y circuit breaker en agentes IA de producción

AI Integration
7 min readPor Daily Miranda Pardo

Tu agente está en producción. A las 2:14 de la mañana, la API del LLM devuelve un 529 overloaded. No hay nadie mirando. El agente intenta la llamada, recibe el error, y el flujo completo se corta. El cliente que esperaba una respuesta en dos segundos no recibe nada. El proceso que dependía del resultado del agente queda bloqueado.

¿Qué hace tu agente cuando la infraestructura falla? Si la respuesta es "se rompe", tienes un problema de diseño, no de código.

Por qué los agentes de producción necesitan estrategia de fallos

En local y en staging, las APIs responden siempre. En producción, no. Las APIs de LLM fallan por varias razones que ocurren con más frecuencia de la que la documentación admite:

  • Rate limits (429 Too Many Requests): demasiadas peticiones en poco tiempo
  • Sobrecarga del proveedor (529 Overloaded): ocurre en Anthropic, OpenAI y todos los demás
  • Timeouts de red (ETIMEDOUT): la conexión se establece pero la respuesta no llega
  • Errores de servidor transitorios (500, 503): el proveedor tiene un problema momentáneo

La mayoría de estos errores son transitorios: si vuelves a intentarlo 2 segundos después, funciona. El problema es que la mayoría de los agentes están implementados sin ninguna lógica de reintento. Un solo fallo derriba el flujo completo.

Hay dos patrones que resuelven esto en producción: retry con backoff exponencial y circuit breaker. No son alternativos: se usan juntos.

Retry con backoff exponencial y jitter

La idea básica del retry es simple: si la llamada falla, espera un poco y vuelve a intentarlo. El error está en la implementación naive que reintenta inmediatamente en un bucle.

Si tu agente falla y reintenta en el mismo instante, y hay 200 agentes haciendo lo mismo porque el proveedor está saturado, acabas de multiplicar el problema por 200. Esto se llama thundering herd y convierte un problema temporal en una caída sostenida.

La solución: backoff exponencial con jitter. Cada intento espera más que el anterior, y se añade un componente aleatorio (jitter) para que los reintentos de múltiples instancias no se sincronicen.

interface RetryOptions {
  maxAttempts: number;
  baseDelayMs: number;
  maxDelayMs: number;
  retryableStatuses: number[];
}

async function withRetry<T>(
  fn: () => Promise<T>,
  options: RetryOptions = {
    maxAttempts: 4,
    baseDelayMs: 1000,
    maxDelayMs: 16000,
    retryableStatuses: [429, 500, 503, 529],
  }
): Promise<T> {
  let lastError: Error;

  for (let attempt = 1; attempt <= options.maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error as Error;
      const status = (error as any)?.status;

      // No reintentar errores permanentes (400, 401, 404...)
      if (status && !options.retryableStatuses.includes(status)) {
        throw error;
      }

      if (attempt === options.maxAttempts) break;

      // Backoff exponencial: 1s, 2s, 4s, 8s...
      const exponentialDelay = options.baseDelayMs * Math.pow(2, attempt - 1);
      // Jitter: ±30% del delay calculado
      const jitter = exponentialDelay * 0.3 * (Math.random() * 2 - 1);
      const delay = Math.min(
        Math.round(exponentialDelay + jitter),
        options.maxDelayMs
      );

      console.warn(`[retry] intento ${attempt} fallido (${status}), esperando ${delay}ms`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }

  throw lastError!;
}

// Uso en el agente
const response = await withRetry(() =>
  client.messages.create({
    model: 'claude-opus-4-8',
    max_tokens: 2048,
    messages,
  })
);

Un detalle importante: no todos los errores deben reintentarse. Un 400 Bad Request o un 401 Unauthorized son errores permanentes. Reintentar un 400 con los mismos parámetros nunca va a funcionar; lo único que consigues es consumir créditos y añadir latencia. Solo reintentas los errores transitorios.

Circuit breaker: no hagas llamadas que sabes que van a fallar

El retry resuelve fallos puntuales. El circuit breaker resuelve cuando el proveedor está caído de verdad.

Si la API lleva 3 minutos fallando, no tiene sentido que cada petición llegue hasta el proveedor, espere el timeout, y vuelva tras el backoff. Ese tiempo de latencia se acumula para el usuario, y la carga innecesaria sobre la infraestructura empeora la situación.

El circuit breaker monitoriza los fallos y, cuando supera un umbral, deja de enviar peticiones durante un periodo de tiempo. En lugar de hacer la llamada, devuelve un error inmediato. Tras el periodo de espera, deja pasar una sola petición de prueba. Si tiene éxito, vuelve al estado normal. Si falla, extiende el bloqueo.

type CircuitState = 'CLOSED' | 'OPEN' | 'HALF_OPEN';

class CircuitBreaker {
  private state: CircuitState = 'CLOSED';
  private failureCount = 0;
  private lastFailureTime = 0;

  constructor(
    private readonly failureThreshold = 5,
    private readonly recoveryTimeoutMs = 30_000
  ) {}

  async execute<T>(fn: () => Promise<T>): Promise<T> {
    if (this.state === 'OPEN') {
      const elapsed = Date.now() - this.lastFailureTime;
      if (elapsed < this.recoveryTimeoutMs) {
        throw new Error('Circuit breaker OPEN: servicio no disponible');
      }
      this.state = 'HALF_OPEN';
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  private onSuccess() {
    this.failureCount = 0;
    this.state = 'CLOSED';
  }

  private onFailure() {
    this.failureCount++;
    this.lastFailureTime = Date.now();
    if (this.failureCount >= this.failureThreshold) {
      this.state = 'OPEN';
      console.error(`[circuit-breaker] OPEN tras ${this.failureCount} fallos`);
    }
  }
}

// Uso combinado: circuit breaker + retry
const breaker = new CircuitBreaker(5, 30_000);

const response = await breaker.execute(() =>
  withRetry(() => client.messages.create({ model: 'claude-opus-4-8', max_tokens: 2048, messages }))
);

El circuit breaker se instancia una sola vez por proveedor (no por llamada) y se comparte entre todas las instancias del agente. Si está en estado OPEN, todas las peticiones al mismo proveedor fallan rápido sin llegar a la red.

Dead letter queue: los fallos que no se pueden recuperar

Cuando un agente procesa una tarea asíncrona —generar un informe, enviar una notificación, actualizar un registro— y falla incluso después de todos los reintentos, necesitas saber qué pasó y poder procesarlo después.

La solución es una dead letter queue (DLQ): antes de descartar la tarea, se guarda en una tabla de base de datos con el payload original, el número de intentos y el último error. Un proceso de monitorización revisa la DLQ y puede reintentar manualmente, alertar al equipo o ejecutar una lógica de compensación.

Esto conecta directamente con el patrón de agentes stateful que publicamos antes: el estado del agente debe incluir el historial de intentos fallidos para que no se pierda ninguna tarea crítica.

Degradación elegante: cuando no puedes esperar al LLM

Para flujos síncronos donde el usuario espera una respuesta, no siempre puedes permitirte los 15-20 segundos de backoff. En esos casos, la estrategia es degradación elegante: si el LLM no responde en X ms, devuelve una respuesta predeterminada o transfiere al agente humano.

Esto es parte del diseño del servicio de integración IA para empresas: cada flujo tiene definido su comportamiento ante fallo. No hay agente que se rompa silenciosamente sin que algo lo gestione.

Cuándo construir esto tú mismo vs cuándo no

Estos patrones no son opcionales si tienes agentes en producción con SLA. Son la diferencia entre un agente que funciona en staging y uno que funciona a las 3 de la mañana cuando nadie lo mira.

La implementación básica que ves aquí lleva menos de un día. El problema es que la mayoría de equipos construyen el agente primero y añaden la resiliencia "cuando haya tiempo", que en la práctica significa nunca, hasta que hay un incidente.

Si estás construyendo agentes IA para tu empresa y quieres asegurarte de que resisten fallos reales de infraestructura antes de salir a producción, cuéntame dónde estás:

Hablamos sobre cómo construir agentes que no se rompen en producción →

Compartir artículo

LinkedInXWhatsApp

¿Procesos repetitivos en tu empresa?

Descarga gratis el Mapa de Automatización IA — los 5 procesos que más tiempo roban y cómo resolverlos.

Sin spam. Solo el PDF. Puedes darte de baja cuando quieras.

Escrito por Daily Miranda Pardo

Ayudo a empresas a automatizar procesos, crear agentes IA y conectar sistemas inteligentes.