Reintenta la misma ruta de una API LLM solo cuando el fallo sea probablemente transitorio, la operación pueda repetirse sin duplicados y todavía quede presupuesto de tiempo, intentos, tokens y dinero. Cambia a un modelo de respaldo únicamente si esa ruta ya ha superado el mismo contrato de entrada, salida, herramientas, seguridad, datos, latencia, coste y calidad. Si no, encola, devuelve un modo degradado controlado o falla de forma cerrada.
La decisión no empieza por maxRetries
Empieza por estas cinco puertas:
| Puerta | Pregunta | Si no se cumple |
|---|---|---|
| Propietario | ¿Es un fallo temporal de red/proveedor? | Corregir request, auth, policy o presupuesto |
| Compromiso | ¿Se puede repetir sin salida visible ni efecto duplicado? | Conciliar el estado o detener |
| Presupuesto | ¿Quedan intentos, tiempo, tokens, coste y tolerancia? | Encolar o degradar |
| Equivalencia | ¿El respaldo aprobó los fixtures del workflow? | No cambiar de forma silenciosa |
| Salud | ¿Una prueba limitada aporta información y queda registrada? | Abrir el circuito |
Un fallback no es “otro reintento”. Puede cambiar el proveedor, context window, JSON Schema, tools, filtros de seguridad, residencia de datos, precio, latencia y calidad.
Dos respuestas 429 pueden exigir acciones opuestas
La guía de errores de OpenAI distingue un 429 por velocidad de un 429 por cuota o gasto agotado. El primero necesita pacing; el segundo necesita que el propietario del presupuesto cambie el límite. El backoff no crea créditos.
Normaliza el error antes de decidir:
- 5xx o sobrecarga que el detalle del proveedor clasifica como transitorio: backoff limitado con jitter; el código HTTP por sí solo no basta;
- 429 de velocidad con ventana corta: respetar
retry-aftery bajar concurrencia; - cuota, saldo o spend limit: detener los reintentos síncronos;
- 400, schema o context incorrecto: modificar la petición;
- 401/403: reparar credenciales, permisos o ruta;
- safety/policy: no buscar un modelo menos restrictivo;
- stream parcial o tool con estado incierto: conciliar antes de repetir.
Cuenta también los intentos internos. La referencia actual de errores de Anthropic indica que sus SDK oficiales reintentan por defecto dos veces los fallos transitorios de conexión, rate limit y 5xx, respetando retry-after. La guía de Gemini también documenta reintentos automáticos. Un maxRetries: 3 en tu aplicación no representa necesariamente tres llamadas.
Usa un presupuesto de recuperación para todo el workflow
textintentos totales = petición inicial + SDK + gateway + aplicación + fallback + reentrega de cola
Añade límites de tiempo total, coste/tokens adicionales y degradación de calidad aceptable. Un chat interactivo y un informe nocturno no deberían usar la misma política.
AWS explica en timeouts, retries y backoff con jitter que los reintentos en varias capas multiplican la carga y pueden retrasar la recuperación de un servicio saturado. Debe existir un único punto que decida; las demás capas informan de sus intentos reales.
yamlworkflow: resumen-interactivo max_total_attempts: 3 max_elapsed_ms: 8000 max_extra_cost_usd: 0.02 safe_replay_until: first_visible_token approved_fallback: resumen-backup queue_allowed: false fail_closed_on: [auth, policy, unknown_commit]
Son valores ilustrativos. Derívalos del SLO y valídalos con fallos inyectados.
El límite decisivo es lo ya comprometido
Un timeout solo demuestra que el cliente no recibió el resultado esperado. No demuestra que el proveedor no aceptó la petición ni que una herramienta no ejecutó la acción.
- Antes de la aceptación: el replay suele ser más sencillo.
- Después de aceptar, sin respuesta: el resultado puede ser desconocido.
- Después del primer token visible: otro modelo no puede continuar de forma transparente.
- Después de una escritura, webhook, correo o tool: puede existir un efecto lateral.
Para acciones, usa un operation ID o idempotency key controlado por la aplicación. Conserva requested → started → committed → acknowledged. Si el estado es incierto, consúltalo y concílialo. Llamar a un segundo modelo no es una comprobación.
En streaming, permite cambio transparente solo antes del primer evento visible. Después, muestra un estado interrumpido, deja que el usuario reinicie o usa un protocolo real de reanudación. No unas dos respuestas distintas.
Aprobar un fallback para un workflow concreto
Prueba la ruta alternativa con los mismos fixtures:
- Entrada: contexto, imágenes/archivos, system instructions e idioma.
- Salida: JSON Schema, campos obligatorios, refusal y truncation.
- Tools: nombres, argumentos, llamadas paralelas, resultado e idempotencia.
- Seguridad: bloqueos, tareas de alto impacto y escalado humano.
- Datos: región, retención, tenant y clases permitidas.
- Operación: p95/p99, streaming, request IDs y visibilidad de estado.
- Economía: unidades de precio, cache, quota owner y gasto máximo.
- Calidad: un conjunto de evals y un umbral propio del workflow.
Un modelo pequeño puede ser válido para clasificación y no para explicar una política al cliente. Si ningún modelo generativo pasa, una respuesta cacheada, una regla determinista o un estado “inténtalo más tarde” puede ser mejor.
Centraliza la decisión
tstype Action = "retry" | "fallback" | "queue" | "degrade" | "fail_closed"; function decide(p: { owner: "transient" | "rate" | "quota" | "request" | "auth" | "policy" | "unknown"; commitState: "none" | "committed" | "unknown"; attemptsLeft: number; elapsedMsLeft: number; costLeft: number; retryAfterMs: number; retryExpectedMs: number; retryExpectedCost: number; fallbackExpectedMs: number; fallbackExpectedCost: number; primaryRoute: "healthy" | "degraded" | "open"; fallbackRoute: "healthy" | "unhealthy"; fallbackApproved: boolean; queueAllowed: boolean; degradeApproved: boolean; }): Action { if (["request", "auth", "policy"].includes(p.owner)) return "fail_closed"; if (p.commitState !== "none") return "fail_closed"; if (p.owner === "unknown") return p.queueAllowed ? "queue" : "fail_closed"; const canRetry = p.attemptsLeft > 0 && p.elapsedMsLeft >= p.retryAfterMs + p.retryExpectedMs && p.costLeft >= p.retryExpectedCost; const canFallback = p.attemptsLeft > 0 && p.elapsedMsLeft >= p.fallbackExpectedMs && p.costLeft >= p.fallbackExpectedCost; if (canRetry && ["transient", "rate"].includes(p.owner) && p.primaryRoute !== "open") { return "retry"; } if ( canFallback && ["transient", "rate", "quota"].includes(p.owner) && p.fallbackApproved && p.fallbackRoute === "healthy" ) return "fallback"; if (p.queueAllowed) return "queue"; return p.degradeApproved ? "degrade" : "fail_closed"; }
retryAfterMs solo limita el reintento de la ruta principal. El fallback usa su propia latencia y coste previstos, y fallbackApproved incluye una cuota independiente; por tanto, una espera larga del primario no bloquea una alternativa sana que sí cabe en el SLO. Un estado committed o unknown exige conciliación o un protocolo verificado de reanudación/idempotencia fuera del despachador automático. Antes de cada nueva llamada, persiste una fila con workflow ID, índice, rutas, provider request ID, clase de error, espera, tokens, coste, commit y resultado. Un 500 también puede deberse a la petición —la guía actual de Gemini menciona un contexto de entrada demasiado largo—, así que el código nunca basta para marcarlo como transitorio.
Demuestra cada rama en staging
Inyecta un 429 de velocidad, un 429 de cuota, un 503 antes del stream, una desconexión después de tokens visibles, un timeout tras el commit de una tool, una salida fallback sin campo requerido y un circuito abierto. Añade el contraejemplo donde el retry-after de la ruta principal supera el SLO restante, pero una alternativa sana y aprobada cabe en su propio presupuesto de latencia y coste: la acción esperada es fallback, no cola.
Cada fixture debe producir una sola acción esperada. Separa las métricas primary success, retry recovery, fallback recovery, degraded y fail closed. Si solo miras el éxito final, el fallback puede ocultar que la ruta principal está fallando.
Cuando ya tienes un 429 concreto, empieza por el owner correcto: límites de OpenAI API, rate limit de Claude API o límites de Gemini API.
Si necesitas probar varios modelos ya aprobados detrás de un endpoint compatible, consulta en staging la documentación actual de LaoZhang AI. Un endpoint unificado simplifica adaptadores, pero no demuestra equivalencia de schema, tools, datos, coste o calidad.
La aceptación no es “acabó con 200”. Es poder explicar cada acción, contar todos los intentos en un único presupuesto, demostrar la equivalencia del respaldo y conservar los fallos aunque la última ruta tenga éxito.



