Las APIs de LLM son servicios remotos con características poco habituales: las peticiones tardan de segundos a minutos, la capacidad se comparte entre miles de clientes, los límites de frecuencia se miden en tokens además de en peticiones y las caídas de los proveedores populares salen en las noticias. Una funcionalidad que funciona perfectamente en una demo puede fallar a diario en producción. La buena noticia: los patrones de fiabilidad son bien conocidos en sistemas distribuidos. Esta guía los adapta a las integraciones con LLM.
Conoce tus modos de fallo
| Fallo | Señal típica | ¿Reintentar? |
|---|---|---|
| Límite de frecuencia superado | HTTP 429, a menudo con cabecera retry-after |
Sí, tras el retraso indicado |
| Proveedor sobrecargado | HTTP 529 / 503 o error "overloaded" específico del proveedor | Sí, con backoff |
| Error del servidor | HTTP 500, 502, 504 | Sí, con backoff e intentos limitados |
| Timeout / conexión reiniciada | Timeout en el cliente, stream roto | Sí, si la operación se puede repetir con seguridad |
| Petición no válida | HTTP 400 (parámetros erróneos, contexto demasiado largo) | No: corrige la petición |
| Autenticación / permisos | HTTP 401, 403 | No: alerta |
| Rechazo de contenido o bloqueo por política | Respuesta correcta con rechazo o motivo de parada | No: gestiónalo en la lógica de la aplicación |
| Salida truncada | Motivo de fin max_tokens / length |
Quizá: continúa o aumenta el límite |
| Salida estructurada no válida | Error de análisis o de validación | Sí, una vez, con el mensaje de error |
La documentación de los proveedores enumera los códigos y cabeceras exactos (errores de Anthropic, límites de Anthropic, límites de OpenAI). Clasifica los errores de forma explícita en tu cliente; tratarlo todo como "reintenta tres veces" acaba machacando una petición rota o rindiéndose ante un fallo pasajero.
Timeouts: fíjalos de forma deliberada
Los timeouts por defecto de los clientes HTTP rara vez son adecuados para los LLM:
- Timeout de conexión: corto (5–10 s). No poder conectar no es un problema del LLM.
- Tiempo hasta el primer token: en streaming, un plazo aparte (por ejemplo, 20–30 s) detecta pronto las peticiones atascadas.
- Timeout de inactividad entre fragmentos: un stream que deja de enviar datos durante 30–60 s probablemente esté muerto.
- Plazo total: derivado del caso de uso; una respuesta de chat puede tener 60–90 s y un informe en segundo plano, varios minutos.
Propaga los plazos: si tu endpoint HTTP tiene un presupuesto de 30 segundos, la llamada al modelo no puede tener 120. Y cancela siempre la petición al proveedor cuando el cliente se desconecta; si no, pagas tokens que nadie lee; consulta cómo crear un chat de IA en Next.js.
Reintentos con backoff exponencial y jitter
Reintenta los errores pasajeros con retrasos que crecen exponencialmente y con aleatoriedad, para que miles de clientes no reintenten al unísono. El enfoque de "full jitter" —un retraso aleatorio entre cero y el tope exponencial— funciona bien bajo contención (AWS Architecture Blog).
import random, time
RETRYABLE = {429, 500, 502, 503, 504, 529}
def call_with_retries(fn, max_attempts=4, base=1.0, cap=30.0):
for attempt in range(1, max_attempts + 1):
try:
return fn()
except ApiError as e:
if e.status not in RETRYABLE or attempt == max_attempts:
raise
retry_after = e.headers.get("retry-after")
delay = float(retry_after) if retry_after else random.uniform(0, min(cap, base * 2 ** attempt))
time.sleep(delay)
Reglas que evitan que los reintentos empeoren las cosas:
- Respeta
retry-aftercuando el proveedor lo envíe. - Limita los intentos (3–5) y el tiempo total de reintentos dentro del plazo de la petición.
- Reintenta en una sola capa. Si reintenta el SDK, tu código que lo envuelve y la cola que lo rodea, un fallo se convierte en decenas de llamadas. La mayoría de los SDK oficiales reintentan automáticamente; configúralos en lugar de apilar tus propios reintentos.
- Usa un presupuesto de reintentos, por ejemplo que los reintentos no superen el 10% de las peticiones en un minuto, para que una caída del proveedor no multiplique tu tráfico. El libro de SRE de Google lo trata a fondo.
Límites de frecuencia: gestiónalos de forma proactiva
Los proveedores limitan las peticiones por minuto, los tokens de entrada por minuto y los tokens de salida por minuto, por modelo y por nivel de organización. Toparse con ellos es normal a escala. Mejor que reaccionar a los 429:
- Rate limiting en el cliente con un token bucket por modelo, dimensionado un poco por debajo de tus límites.
- Clases de prioridad: primero las peticiones interactivas; los lotes y el trabajo en segundo plano ocupan la capacidad restante.
- Colas para el trabajo en segundo plano con límites de concurrencia; consulta la sección de más abajo.
- APIs por lotes para todo lo que pueda esperar horas: mucho más baratas y con límites separados; consulta optimización de costes de LLM.
- Prompt caching reduce la presión de tokens en prefijos repetidos; algunos proveedores cuentan los tokens cacheados de forma distinta frente a los límites.
- Vigila el margen disponible: las cabeceras de respuesta suelen indicar las peticiones y tokens restantes; expórtalas como métricas.
Fallbacks: modelos y proveedores
Cuando el modelo principal no está disponible o está limitado, un fallback mantiene viva la funcionalidad:
- Mismo proveedor, otro modelo: a menudo un modelo más pequeño con capacidad separada.
- Mismo modelo a través de otra plataforma: muchos modelos están disponibles en varias nubes (por ejemplo, la API del propio proveedor y los grandes marketplaces de la nube), lo que da diversidad de proveedor con un comportamiento idéntico.
- Modelo de otro proveedor: la máxima independencia, pero con otro comportamiento; hay que probar los prompts y el análisis de la salida con cada fallback.
const chain = [
{ provider: "primary", model: process.env.CHAT_MODEL! },
{ provider: "cloud-b", model: process.env.CHAT_MODEL_CLOUD_B! },
{ provider: "fallback", model: process.env.FALLBACK_MODEL! },
]
export async function generate(req: Req) {
let lastError: unknown
for (const target of chain) {
if (breaker.isOpen(target.provider)) continue
try {
const res = await callModel(target, req, { timeoutMs: 45_000 })
breaker.success(target.provider)
return { ...res, servedBy: target }
} catch (e) {
lastError = e
if (!isRetryableAcrossProviders(e)) throw e // e.g. 400 invalid request
breaker.failure(target.provider)
}
}
throw lastError
}
Registra qué destino sirvió cada petición (servedBy) e inclúyelo en las trazas. Ejecuta tu batería de evals contra cada modelo de fallback para saber qué calidad reciben los usuarios durante un incidente; consulta evals de LLM. Los gateways, como proxies tipo LiteLLM o gateways de IA de la nube, pueden centralizar esta lógica para todos los servicios.
Circuit breakers
Un circuit breaker deja de enviar peticiones a una dependencia que está fallando, falla rápido en su lugar y comprueba periódicamente si se ha recuperado. Con proveedores de LLM, abre el circuito cuando las tasas de error o las latencias superen un umbral en una ventana corta y envía el tráfico al fallback de inmediato, en lugar de esperar a que cada petición agote su timeout. Resilience4j para servicios JVM, Polly para .NET y librerías como opossum para Node implementan el patrón.
Idempotencia para agentes y efectos secundarios
Los reintentos son peligrosos cuando la operación tiene efectos secundarios. Un paso de agente que crea una factura y luego agota el timeout puede reintentarse y crear una segunda factura. Protege los efectos secundarios:
- Claves de idempotencia en las operaciones de escritura, derivadas del ID de ejecución y del número de paso. APIs como la de Stripe muestran el patrón.
- Comprueba antes de actuar: las herramientas que crean registros deberían buscar primero un registro existente de la misma ejecución.
- Guarda puntos de control del estado del agente tras cada paso completado para que una ejecución reanudada no repita trabajo terminado.
- Separa la decisión de la ejecución: el modelo propone una acción; el código determinista la ejecuta exactamente una vez.
Esto importa aún más en sistemas multiagente y de larga duración.
Colas para trabajo en segundo plano y masivo
Para el trabajo con LLM no interactivo —procesamiento de documentos, enriquecimiento, resúmenes nocturnos— pon las peticiones en una cola:
- Los workers toman trabajos con un límite de concurrencia que respeta los límites de frecuencia.
- Los trabajos fallidos se reintentan con backoff y van a una cola de mensajes fallidos tras N intentos.
- Los trabajos son idempotentes y llevan todas las entradas necesarias para un reintento.
- El rendimiento se adapta solo: cuando el proveedor va lento, la cola crece en lugar de fallar las peticiones.
Esta arquitectura también hace el coste predecible y combina bien con las APIs por lotes. Mis pipelines de extracción la usan; consulta procesamiento de documentos con LLM.
Degradación elegante
Decide de antemano qué ven los usuarios cuando la IA no está disponible:
- Asistente de chat: un mensaje claro con un enlace al soporte humano o a la búsqueda, no un spinner eterno.
- Búsqueda con IA: recurre a la búsqueda por palabras clave.
- Clasificación y routing: recurre a reglas o a una cola por defecto para triaje humano.
- Generación de contenido: deja que los usuarios sigan sin ella y pon la generación en cola para más tarde.
Los feature flags permiten apagar rápidamente las funcionalidades de IA durante un incidente. Las mejores funcionalidades de IA son aditivas: el producto sigue funcionando sin ellas.
Observabilidad para la fiabilidad
Mide lo que necesitas para gestionar todo lo anterior:
- Tasa de errores por tipo (429, 5xx, timeouts) y por proveedor y modelo.
- Número de reintentos y tasa de fallback; tiempo dedicado a reintentos.
- Percentiles de latencia, incluido el tiempo hasta el primer token.
- Margen disponible frente a los límites de frecuencia.
- Cambios de estado de los circuit breakers.
Alerta ante la activación de fallbacks y tasas de error sostenidas. La configuración de trazas está en observabilidad de LLM.
Lista de comprobación de fiabilidad
- Los errores están clasificados; solo se reintentan los pasajeros
- Timeouts: conexión, primer token, inactividad y plazo total
- Backoff exponencial con jitter, respetando
retry-after, con reintentos en una sola capa - Rate limiting en el cliente y prioridad entre trabajo interactivo y en segundo plano
- Al menos un modelo o proveedor de fallback probado para las funcionalidades críticas
- Circuit breaker por proveedor
- Claves de idempotencia para cada efecto secundario desencadenado por la IA
- Cola con gestión de mensajes fallidos para los trabajos en segundo plano
- Experiencia degradada definida y feature flag para desactivar las funcionalidades de IA
- Paneles y alertas de errores, fallbacks y latencia
Preguntas frecuentes
¿Deberíamos tener siempre un segundo proveedor? Para funcionalidades críticas de cara al cliente, sí, o al menos el mismo modelo a través de una segunda plataforma. Para herramientas internas, puede bastar con un modo degradado claro.
¿Los SDK oficiales gestionan los reintentos? La mayoría reintenta unas cuantas veces ante errores de conexión, 429 y 5xx por defecto. Configura sus límites y no añadas otra capa de reintentos encima.
¿Cómo probamos la fiabilidad? Inyecta fallos en staging: simula 429, streams lentos y caídas, y confirma que los fallbacks, los breakers y la experiencia degradada funcionan.
¿Es más difícil hacer fiable el streaming? Sí. Un stream puede fallar a mitad; decide si muestras la respuesta parcial, la reanudas o la regeneras, y nunca reintentes en silencio una respuesta a medio enviar.
Fuentes
- AWS Architecture Blog. Exponential Backoff And Jitter.
- Google SRE Book. Addressing Cascading Failures y Handling Overload.
- Martin Fowler. CircuitBreaker.
- Anthropic. API errors y rate limits.
- OpenAI. Rate limits guide.
- Stripe. Idempotent requests.