Cómo monté un fallback entre LLMs gratis (Groq, OpenRouter, Pollinations) con degradación elegante: capas free encadenadas que no caen por cuota ni timeout.
El asistente de IA que vive en la terminal de este portfolio responde sobre mi experiencia y mis proyectos. No usa una API de pago. Y aun así, no se cae cuando un proveedor está saturado, sin cuota o simplemente lento. La pieza que lo hace posible no es un modelo mejor: es una cadena de fallback entre LLMs gratis, pensada para degradar con elegancia en lugar de devolver un error.
Este artículo cuenta cómo está montada de verdad —decisiones, números y los baches reales—, porque “resiliente” suena bien en un README pero se gana en los detalles.
El problema: los free tiers se caen, y se caen mal
Los planes gratuitos de los proveedores de LLM son generosos, pero tienen tres formas habituales de fallarte, casi siempre en el peor momento:
- Cuota agotada. Llegas al límite de peticiones por minuto o por día y empiezas a recibir
429. - Saturación del modelo. El endpoint responde
503o se queda colgado porque medio mundo usa el mismo modelo:free. - Latencia impredecible. El proveedor “funciona”, pero tarda 12 segundos. En una función serverless con límite de ejecución, eso es un fallo igual que un
500.
Si tu app depende de un único proveedor gratuito, cualquiera de los tres lo tumba. La respuesta ingenua —“pongo un try/catch y muestro un mensaje de error”— convierte cada hipo del proveedor en una mala experiencia para quien visita la web. La respuesta de ingeniería es otra: asumir que cada capa va a fallar y diseñar para que la siguiente recoja el testigo.
La idea: with_fallbacks sobre capas gratuitas
El patrón es el que muchos frameworks llaman with_fallbacks: defines una secuencia de proveedores ordenada por preferencia y, ante el primer fallo, pasas al siguiente sin que el usuario se entere. Lo importante no es el nombre, sino la propiedad que te da: el sistema solo cae cuando caen TODAS las capas a la vez, algo mucho menos probable que el fallo de una sola.
Mi cadena, de más rápida a último recurso:
- Groq — primario. Inferencia absurdamente rápida (Llama 3.3 70B). Cuando está disponible, la respuesta llega antes de que el usuario termine de leer la pregunta.
- OpenRouter — secundario. Agrega varios modelos
:free(gpt-oss-120b, Llama 3.3 70B, Qwen3). Aquí hay un matiz que cuento más abajo: el fallback es doble. - Pollinations — red de seguridad. Sin API key. Es el eslabón que garantiza que la cadena nunca esté vacía.
Hay una cuarta capa natural que encaja sin tocar la arquitectura: Gemini tiene un free tier sólido y habla el mismo dialecto, así que añadirlo es cuestión de una entrada más en la lista. El diseño está pensado para eso: las capas son intercambiables.
Por qué Pollinations va el último (y es clave)
Groq y OpenRouter necesitan API key. Si la key no está configurada, o el proveedor está caído, esa capa simplemente no existe en esa petición. Pollinations no pide key, así que siempre está ahí. Esto cambia una propiedad fundamental del sistema: la lista de proveedores activos nunca puede quedar vacía. Siempre hay al menos un candidato al que preguntar. Sin ese suelo, un día sin keys o con dos proveedores caídos te dejaría sin IA; con él, lo peor que pasa es que respondes un poco más despacio.
El detalle que lo hace simple: todos hablan OpenAI
La razón por la que esta cadena cabe en una sola función y no en tres integraciones distintas es que los tres proveedores exponen el formato de la API de OpenAI (/chat/completions, mismo shape de messages, misma forma de leer choices[0].message.content). Eso significa que una única función ask(provider, messages) sirve para los tres. Solo cambian la URL, la cabecera de autorización y poco más.
async function ask(provider, messages) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS);
try {
const res = await fetch(provider.url, {
method: "POST",
headers: authHeaders(provider),
body: JSON.stringify({ model: provider.model, messages, temperature: 0.6 }),
signal: controller.signal,
});
if (!res.ok) throw new Error(`${provider.name} HTTP ${res.status}`);
const content = (await res.json())?.choices?.[0]?.message?.content;
if (typeof content !== "string" || !content.trim()) {
throw new Error(`${provider.name} empty response`);
}
return content.trim();
} finally {
clearTimeout(timeout);
}
}
Esto es DRY aplicado en serio: la diversidad de proveedores se absorbe en la configuración (una lista de objetos), no en el código. Añadir Gemini es una entrada nueva en esa lista, no una rama nueva en la lógica.
El bucle de fallback es igual de aburrido, que es justo lo que quieres en una pieza crítica:
for (const provider of activeProviders()) {
try {
return ok(await ask(provider, [system, ...history]));
} catch {
// Proveedor caído o sin cuota → siguiente capa. Silencioso a propósito.
}
}
return ok(ERRORS[lang].unavailable); // todas cayeron: degradación final
El timeout: la decisión menos obvia y la más importante
Aquí está el bache que casi nadie anticipa. En Vercel (plan Hobby) una función serverless tiene un límite duro de ejecución de ~10 segundos. Mi primer instinto fue dar a cada proveedor un margen cómodo de 15 segundos. El resultado fue contraintuitivo: la cadena de fallback nunca llegaba a Pollinations. Con tres intentos secuenciales a 15s, la plataforma cortaba la función con un 504 antes de que el código pudiera pasar a la tercera capa. El fallback existía sobre el papel, pero la infraestructura lo mataba.
La solución fue presupuestar el tiempo al revés: si tengo ~10s y tres intentos, cada proveedor dispone de 3 segundos. Tres intentos × 3s caben en ~9s, con margen. Un AbortController por petición corta en seco al proveedor que no responda a tiempo y lo trata como un fallo más, pasando al siguiente. Un proveedor lento deja de ser un cuelgue: es solo otra capa que se salta.
La lección: en serverless, el timeout no es un parámetro de fiabilidad, es una restricción de presupuesto. Si la suma de tus reintentos no cabe en el límite de la plataforma, tu fallback es decorativo.
Fallback dentro del fallback
OpenRouter tiene su propia ruleta: un modelo :free puede estar saturado mientras otro responde sin problema. Por eso esa capa no apunta a un único modelo, sino a una lista de hasta tres (gpt-oss-120b, Llama 3.3 70B, Qwen3). OpenRouter intenta el primero y, si no puede, baja al siguiente, todo dentro de la misma llamada. Es un fallback anidado: capas de modelos dentro de una capa de proveedor.
Degradación elegante: el contexto se adapta a cada capa
No todos los modelos gratuitos aceptan el mismo tamaño de prompt. Los grandes (Groq, OpenRouter) tragan un contexto de sistema de ~18.000 caracteres sin pestañear; el modelo de Pollinations corta la entrada bastante antes, sobre ~7.000. Si le mando el contexto completo, falla por longitud y pierdo mi red de seguridad justo cuando más la necesito.
La solución es que cada proveedor declara su propio maxContext y el contexto del portfolio —generado dinámicamente desde mis proyectos y experiencia reales— se recorta a esa medida antes de enviarlo. Pollinations recibe una versión condensada; Groq, la completa. Degradar no es solo “usar otro proveedor”: es ajustar lo que le pides a lo que ese proveedor puede dar.
Esa misma filosofía de robustez está en el RAG que alimenta el asistente: si una content collection falla al leerse, el sistema degrada a un contexto reducido en vez de propagar un 500 no-JSON que rompería el cliente. Cada capa asume que la de abajo puede fallar.
Lo que esto tiene que ver con llevar IA a producción
Esta cadena es pequeña, pero condensa la forma en que abordo cualquier sistema de IA real: el modelo es la parte fácil; la fiabilidad es el producto. La misma mentalidad —asumir el fallo, presupuestar recursos, degradar con elegancia, no inventar lo que no tienes— es la que aplico en proyectos más serios, como la suite DevFlow AI o el matcher semántico de CV Finder AI, donde una respuesta tardía o errónea no es un detalle estético sino una decisión equivocada.
Construir un demo de LLM es cuestión de una tarde. Construir uno que siga en pie cuando tu proveedor favorito tiene un mal día, sin gastar un euro en APIs, es otra cosa. Y casi siempre es la que importa.
¿Tienes un sistema de IA que se cae con la cuota, gasta de más o se cuelga en producción? Es exactamente el tipo de problema que me gusta resolver. Hablemos y te cuento cómo lo enfocaría.
Artículos relacionados
GEO: que ChatGPT y Perplexity citen tu web
GEO (generative engine optimization): llms.txt, schema.org (Person, FAQPage, hasCredential), contenido extraíble y E-E-A-T para que la IA cite tu web.
Leer artículoClaude Code y MCP: agentes LLM en tu ciclo de desarrollo
Cómo integro agentes LLM con Claude Code y el Model Context Protocol (MCP) en el desarrollo real: contexto, herramientas y los límites que conviene poner.
Leer artículoRAG en español que sí funciona: guía práctica
RAG en español de verdad: arquitectura, chunking, embeddings con FastEmbed/ONNX, reranking, evaluación de recuperación y cómo frenar alucinaciones.
Leer artículo