Guía10 min

Fallback de modelos en agentes: otro modelo, no el mismo 429

Resumen

Un fallback cambia de modelo o de proveedor cuando el primario no responde. Distinto de reintentos (misma llamada) y del circuit breaker (cortar la dependencia). Vercel AI Gateway models[] + Cloudflare Universal endpoint y cf-aig-step. Cero fallback en 401/403 ni en spend limit. El modelo que contestó va al log, no al prompt.

VercelCloudflareOpenAI
Cadena de tres modelos donde el primario falla y el segundo responde al agente

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 no “se cae el modelo”. Se cae una llamada: 503, 529, timeout al primer byte, o un 429. Si reintentas el mismo modelo, eso es reintentos. Si cortas la dependencia, eso es circuit breaker. El fallback cambia de modelo o proveedor cuando el primario no sirve esta petición.

Vercel AI Gateway (docs, 2026-07-28) lo dice en una línea: configure model failover to specify backups that are tried in order if the primary model fails or is unavailable. Cloudflare AI Gateway (docs, 2026-04-20) hace lo mismo en el Universal endpoint: un array de proveedores y el header cf-aig-step te dice quién contestó.

Contrato: cadena corta (2–3 modelos). Fallback solo en fallo transitorio o timeout. Cero fallback en 401/403 ni en spend/quota. El modelo efectivo se loguea; no se reescribe el system prompt. El backup tiene que poder hacer las tools de esa llamada.

La regla de oro: misma petición, otro cerebro

CapaQué cambiaQué no cambia
RetryEspera + jitter. Mismo modelo, mismo requestEl proveedor
FallbackModelo o proveedor siguiente de la listaEl turno, las tools, el tenant
BreakerDeja de llamar esa dependenciaEl resto del agente
Kill switchApaga el sistemaNada: no hay turno

El retry asume que el mismo endpoint va a despertar. El fallback asume que ese modelo no va a servir ahora. Mezclarlos sin tope es una factura: Cloudflare documenta retries con máximo 5 intentos y delay máximo 5 s (request handling, 2026-06-15). 3 modelos × 5 retries no es resiliencia: es un timeout del webhook.

Qué códigos sí caen al backup (y cuáles no)

OpenAI (error codes, 2026-09-06) y Claude (API errors, 2026-09-06) no son un interruptor único. Lee el código, no el meme de “cualquier 4xx”.

Señal¿Fallback?Por qué
500 OpenAI / 500 Claude api_errorSí, tras 1 retry cortoFallo del servidor. El backup puede estar sano
503 OpenAI server_is_overloaded / 529 Claude overloaded_errorEl modelo pedido está saturado. Otro modelo (u otro proveedor) es el punto
Timeout al primer byte (Cloudflare cf-aig-request-timeout)El primario no arrancó. No esperes el stream eterno
429 rate limit con Retry-AfterPrimero espera; fallback si el techo es del modeloPace + header. Si el techo es de org, el backup del mismo vendor también 429
429 OpenAI credit_balance_exhausted / spend / usage limitNoRetrying billing, spend, or quota errors won't restore API access
400 Claude por spend limit de org/workspaceNoNo es un request mal formado: es techo. Sube el límite o degrada el producto
401 / 403NoAuth rota. Rotar la clave, no el modelo
400 schema / 413 request too largeNoEl backup no arregla un JSON inválido ni un PDF de 40 MB

Regla práctica: fallback = el primario no está disponible. No es “el primario contestó mal y quiero otro tono”. Eso es un eval, no un failover.

Cómo se declara la cadena (sin magia)

Vercel: model + providerOptions.gateway.models

El primario va en model. Los backups van en models[], en orden. Si un modelo tiene varios proveedores, order elige Azure antes que OpenAI para ese modelo; si todos fallan, pasa al siguiente de models.

const result = streamText({
  model: "anthropic/claude-fable-5",
  prompt,
  providerOptions: {
    gateway: {
      models: ["anthropic/claude-opus-5", "google/gemini-3.1-pro-preview"],
    },
  },
});

La respuesta sale del primero que gana. El metadata modelAttempts lista cada intento con canonicalSlug (creator/model-name) y modelId del proveedor (provider:model). Loguea success, statusCode y responseTimeMs. No vuelques el array al LLM.

Cloudflare: array en el Universal endpoint + cf-aig-step

El body es un array. El primero es el primario; cada objeto extra es un fallback. Cloudflare dispara el siguiente ante error o timeout. El header cf-aig-step es el contrato:

  • cf-aig-step:0 — ganó el primario
  • cf-aig-step:1 — ganó el segundo
  • cf-aig-step:2 — ganó el tercero

Sin ese header (o sin modelAttempts en Vercel) no sabes si el backup está cargando el 40 % de los turnos. Eso no es “alta disponibilidad”: es un modelo caro que ya no corre.

Cadena de fallback primario a backup

El backup no es un alias barato

Tres trampas que rompen agentes aunque el HTTP sea 200:

  1. Tools distintas. Si el primario tiene create_invoice y el backup no hace function calling, el turno “tuvo éxito” y el efecto no ocurrió. El backup acepta las mismas tools, o degrada a solo lectura.
  2. Contexto y límite. Un fallback a un modelo con ventana más chica recorta el historial a ciegas. Recorta tú, con presupuesto, o no lo pongas en la cadena.
  3. Costo y cuota. El backup “barato” que se dispara en cada 429 de rate limit puede agotar el techo del segundo proveedor. Eso vive en presupuestos por tenant, no en un catch anónimo.

Cadena típica de un bot de soporte, no un ranking de blogs:

  1. Primario: el modelo con el que evaluaste (tools + tono).
  2. Backup mismo vendor: variante más pequeña o menos saturada.
  3. Backup otro vendor: solo si el contrato de tools cabe. Si no cabe, degrada a mensaje humano / cola, no a un modelo que inventa la tool.

Timeout del primario: AbortSignal en tools HTTP y el timeout del gateway. Cloudflare mide el timeout al primer byte: si el SSE abre y se queda mudo, corta el stream; no agregues el cuarto modelo.

Qué loguear (y qué no meter al prompt)

Mínimo por turno, fuera del contexto del modelo:

  • primary_model, effective_model, step (cf-aig-step o índice de modelAttempts)
  • statusCode del fallo que disparó el salto
  • latencia por intento
  • tenant_id (para cruzar con cuotas)

Prohibido: pegar el token, el body completo, o un dump de modelAttempts en el system prompt. El modelo no necesita saber que es el plan B. El operador sí: si step>=1 sube de golpe, el primario está enfermo o mal tipado, no “el usuario pregunta difícil”.

Si el breaker del primario está Open, no dispares fallback en loop contra el mismo vendor: el backup 2 no arregla una outage de región. Ahí el fallback útil es otro proveedor o la degradación del producto.

Paso que ganó el backup y queda en el log

Checklist

  1. Lista de 2 o 3 modelos, ordenada, en config — no en el prompt.
  2. Fallback solo ante 5xx, 503/529, timeout de primer byte, o 429 de modelo tras respetar Retry-After.
  3. Cero fallback en 401/403, spend/quota, 400 de schema, 413.
  4. El backup implementa las mismas tools o degrada el producto de forma explícita.
  5. Log de effective_model + step por turno; alerta si step>=1 supera tu umbral (empieza en 10 %).
  6. Tope: 1 retry corto por modelo, luego salto. No 5×3.
  7. El humano mueve el kill switch; el fallback no apaga el bot.

FAQ

¿Fallback es lo mismo que routing barato→caro? No. Routing por costo elige modelo antes de fallar. Fallback elige después de un fallo. No los mezcles en el mismo array sin saber cuál regla ganó.

¿Puedo poner el mismo modelo en dos proveedores? Sí, y Vercel lo cubre con order (Azure luego OpenAI para el mismo slug). Eso es failover de proveedor, no de cerebro. Sigue siendo un fallback: cambia el host, no el contrato de tools.

¿El SDK de Claude ya reintenta solo? Sí: transitorios (conexión, rate limit, 5xx) con backoff, dos veces por defecto, honrando retry-after. Eso no es tu cadena de modelos. Si dejas el SDK en 2 retries y 3 modelos en el gateway, multiplica. Baja retries del SDK a 0 o 1 cuando el gateway ya hace failover.

¿Dónde vive esto? En el cliente HTTP (gateway o wrapper), no como texto en el hub de operación. El curso arranca el loop en /curso/instalar-agente; esta guía evita que un 503 mate el turno.

El fallback bueno es aburrido: lista corta, un header de quién ganó, backup que todavía llama tus tools. El malo es un catch que prueba “cualquier modelo” hasta el silencio.