Reintentos, timeouts y rate limits en agentes de IA
Resumen
Cómo reintentar llamadas a OpenAI, Gemini y Claude sin duplicar acciones ni quemar cuota: qué códigos sí (429, 500, 503, 529), cuáles no (400, 401, 403), Retry-After, backoff con jitter, RPM/TPM y el patrón de cola para un agente en producción.

Qué resuelve
Esta pieza se queda en la decisión práctica: qué instalar, qué riesgo agrega y cómo aplicarlo sin romper operación.
Un agente en producción no falla porque el modelo “se equivoque de tono”. Falla porque la API responde 429, 503 o se queda colgada 40 segundos y tu webhook ya contestó 200. Si reintentas mal, duplicas un cargo, un mensaje o un ticket. Si no reintentas, el usuario ve silencio. Esta guía fija qué códigos se reintentan, con qué espera y dónde vive la cola, con lo que publican OpenAI, Gemini y Claude hoy (3 de septiembre de 2026).
El runtime (Vercel, Workers, VPS) pone el techo de tiempo; eso está en dónde desplegar el agente. Aquí el foco es la política de reintento frente al proveedor del modelo.
La regla de oro: no todos los errores son transitorios
| Código | Qué significa (docs) | ¿Reintentar? |
|---|---|---|
| 400 | Request inválido (schema, parámetros). Claude también usa 400 al tocar un spend limit de org/workspace | No, salvo que hayas cambiado el request |
| 401 / 403 | Auth o permisos | No. Rota la clave, no el loop |
| 404 | Recurso inexistente | No |
| 408 / timeout de cliente | Tu lado cortó | Sí, con tope y jitter |
| 429 | Rate limit, slow_down, o (según proveedor) cuota/crédito agotado | A veces. Lee error.code y Retry-After |
| 500 | Error interno del proveedor | Sí, backoff corto |
| 503 | Modelo sobrecargado (OpenAI: server_is_overloaded) | Sí, respeta Retry-After |
| 529 | Claude overloaded_error (tráfico alto global) | Sí, backoff. No es tu RPM |
Si no distingues un 429 de cuota agotada de un 429 de RPM, vas a reintentar hasta vaciar el bolsillo o hasta que el proveedor te corte. OpenAI documenta 429 distintos: credit_balance_exhausted (no hay créditos prepaid) frente a “rate limit reached” y slow_down. El primero se resuelve recargando, no esperando 2 segundos.

OpenAI: Retry-After, jitter y el SDK
OpenAI mide RPM, RPD y TPM. Pasarte cualquiera de los tres dispara 429. Los límites son por organización, no por usuario: el compañero que corre evals a las 9 AM comparte tu techo.
Qué hacer, según sus propias docs:
- Si llega
Retry-After, úsalo. Los SDK oficiales ya lo honran en los reintentos elegibles. - Si no llega header, exponential backoff con jitter y un tope de reintentos (3–5 es suficiente para un webhook).
- 500: espera breve y reintenta; si persiste, mira status.openai.com.
- 503 (
server_is_overloaded): otra vezRetry-Aftersi existe.
No copies un sleep(2 ** n) sin jitter: mil agentes haciendo lo mismo a los 2, 4, 8 segundos recrean la sobrecarga. El jitter (espera aleatoria dentro de un rango) es la diferencia entre un retry útil y un DDoS a ti mismo.
Gemini: RPM, TPM, RPD por proyecto
Gemini documenta tres ejes: requests per minute, tokens per minute (input) y requests per day. El ejemplo oficial es brutalmente simple: RPM 20 y haces 21 en un minuto → error, aunque te sobre TPM. Los límites van por proyecto, no por API key. El RPD se resetea a medianoche hora del Pacífico.
El error es 429 RESOURCE_EXHAUSTED. La receta oficial:
- Esperar y reintentar un rato.
- Bajar el costo de cada request (menos contexto, menos output).
- Si lo pegas en uso normal, pedir un aumento de límite.
No inventes números de RPM en el código: leelos del proyecto (AI Studio / Cloud) y trátalos como config. Un hardcode de “60 RPM” queda obsoleto en el siguiente tier.
Claude: 429, 529 y el request_id
Claude separa spend limits (techo mensual de dinero) y rate limits (techo de requests en una ventana). Un 429 puede ser rate limit o haber tocado el spend cap / el límite del workspace de Claude Code. Un 400 también puede ser spend limit a nivel org. Si reintentas un spend cap, solo alargas el dolor: el header retry-after sigue fallando hasta que haya cuota otra vez.
Códigos que sí son transitorios:
- 500
api_error: backoff exponencial; si no cede, manda elrequest_ida soporte. Claude lo incluye en el JSON de error. - 529
overloaded_error: tráfico alto de todos los usuarios, no tu RPM. El SDK oficial reintenta algunos de estos solo.
Subir de golpe el tráfico puede dar 429 por acceleration limits aunque tu tier “alcance”. La doc pide ramp-up gradual. Un cron que dispara 200 agentes a las 00:00 es el anti-patrón.
El patrón que sí funciona en un agente
El webhook del canal (Telegram, WhatsApp, Slack) suele exigir 200 rápido. No reintentes la inferencia dentro de ese request: si el modelo tarda o 429, te comes el timeout de la plataforma y además reintentas a ciegas.
Patrón mínimo:
- Validar firma. Encolar el trabajo. Responder 200.
- Un worker toma el job con un idempotency key (id del mensaje / event id).
- Llamar al modelo con timeout de cliente (30–60 s según el modelo y el max_tokens).
- Si el error es transitorio: el job vuelve a la cola con delay (
Retry-Aftero backoff+jitter). Tope de 3–5 intentos. - Si el error es permanente (400/401/403, cuota agotada): marcar el job como dead letter, alertar, no loop.
- Si la tool del agente tiene efecto (cobrar, enviar mail, crear ticket): la tool también es idempotente. Reintentar el modelo no debe reenviar el mail.

Eso encaja con la arquitectura de webhooks, colas y memoria. El reintento vive en la cola, no en un for dentro del handler.
Timeouts: corta tú, no el usuario
Un timeout de cliente más largo que el del host (función serverless, reverse proxy) es inútil: el host mata primero. Alinea:
- Timeout del HTTP client < timeout de la función/proceso.
max_tokensy tools acotados para que una corrida quebe en ese presupuesto.- Si el agente necesita más tiempo, no alargues el request: parte el trabajo (plan → pasos) o muévelo a un runtime largo (Docker en VPS).
Un timeout debe ser reintentable solo si la operación es idempotente. Si ya ejecutaste charge_card y el POST se colgó antes de leer la respuesta, reintentar cobra dos veces. Guarda el id de la tool call.
Checklist
- Clasificas 400/401/403 como no-retry; 500/503/529 como retry; 429 según
error.code+Retry-After. - Hay tope de intentos (3–5) y jitter. Nada de
while true. - El webhook no reintenta inferencia: encola y sale.
- Idempotency key por mensaje/evento. Tools con efecto también.
- Logs con status,
error.code,request_id(Claude) y número de intento, para observabilidad. - Alertas distintas: “cuota agotada” ≠ “RPM”.
- Límites leídos del proyecto (RPM/TPM/RPD), no hardcodeados.
- Un eval de 10 fallos simulados (429, 500, 400) antes de producción.
FAQ
¿El SDK ya reintenta, para qué una cola? El SDK reintenta la misma llamada HTTP. No protege el webhook, no hace idempotencia de tools y no te saca de un spend cap. Úsalo como primera red; la cola es la segunda.
¿Cuántos reintentos? Tres es el default razonable. Cinco si el job es barato y el 503 es frecuente. Más de eso es un loop de costo.
¿Puedo bajar de modelo al 429? Sí como fallback explícito (más barato / más cuota), no como retry silencioso del mismo request. Documenta el fallback: el usuario y tus evals deben saber que cambió el modelo.
¿Retry en function calling? Reintenta la llamada al modelo. No reejecutes la tool a menos que sea idempotente o que tu código confirme que no corrió.
Empieza por loguear status + error.code una semana. La política de retry se escribe con esos números, no con un tutorial genérico de backoff.
Lecturas relacionadas
Sigue explorando Agentes en Producción y otras piezas para builders.

Streaming de respuestas en agentes de IA: SSE sin romper tools

Human-in-the-loop en agentes de IA: cuándo pausar y cómo reanudar

Sin docker.sock en el agente: el bot no es root del host
