Gateway de modelos IA: cómo diseñar una arquitectura empresarial sin lock-in de proveedor
"La arquitectura es la suma de las decisiones que cuesta caro revertir."
En 2024, OpenAI subió el precio de GPT-4 Turbo un 50% respecto a la versión anterior en algunos tramos de uso, y a lo largo de 2025 cambió tres veces la estructura de cabeceras de function calling. Anthropic renombró la familia Claude 3 a Claude 3.5, luego a Claude 4, y por el camino movió el parámetro system de string a lista de bloques tipados. Google migró de PaLM API a Gemini API con ruptura de contrato. Mistral pasó de una API tipo OpenAI a añadir su propio esquema de tool use en JSON. En dieciocho meses, cualquier equipo que hubiera escrito código directo contra un SDK oficial ha tocado su capa de integración entre cuatro y seis veces.
La lectura habitual en las salas de arquitectura es: "esto pasa porque el mercado es joven". La lectura correcta es otra. El mercado seguirá cambiando así los próximos cinco años como mínimo, porque el coste marginal de un token cae un 40% anual desde 2023 según el índice de LLM Price Check, y cada caída obliga al proveedor a rediseñar tramos, límites y features para no canibalizar su gama alta. Si tu arquitectura absorbe ese ruido en cada release, no tienes un problema de proveedor. Tienes un problema de diseño.
El lock-in no vive en la llamada a la API. Vive en las decisiones de prompts, parsing, tokens y caché que atas a las particularidades de un vendor. Y esas decisiones se cambian con una capa gateway que las hace neutras. Eso es todo.
Gateway, router y adaptador: tres piezas, tres responsabilidades
La primera confusión que veo en cada revisión de arquitectura es tratar estas tres palabras como sinónimos. No lo son. Cada una resuelve un problema distinto y colapsarlas en un solo módulo es la razón por la que muchos "gateways caseros" acaban siendo un if provider == "openai" dentro de un helper.
Adaptador. Traduce el contrato interno de tu aplicación al contrato específico de un proveedor. Un adaptador de Anthropic sabe que system va fuera del array messages. Un adaptador de OpenAI sabe que tools es una lista de objetos con type: "function". Un adaptador es código tonto, sin lógica de negocio, que recibe un mensaje neutro y devuelve una respuesta neutra. Un adaptador por proveedor. Sin excepciones.
Router. Decide a qué adaptador enviar cada petición. La decisión se toma con reglas: tipo de tarea, presupuesto, latencia objetivo, disponibilidad. El router no sabe nada de cómo Anthropic serializa tool_use. Sabe que la tarea "clasificar intención" va a un modelo barato y la tarea "redactar propuesta legal" va a un modelo grande. La lógica del router es la política de tu empresa, no la de un vendor.
Gateway. Es la capa de red que envuelve router y adaptadores, y añade lo que hace que esto sea explotable en producción: autenticación centralizada, rate limiting por equipo, caché semántica, observabilidad, auditoría, retries y fallback. Un gateway se despliega como servicio independiente y expone una API HTTP interna. Tus aplicaciones no hablan con OpenAI. Hablan con el gateway.
Traducción: adaptador es traductor, router es árbitro, gateway es aduana. Confundir las tres capas es el error que hace que "cambiar de proveedor" tarde tres semanas en lugar de tres horas.
Un equipo mediano con quince desarrolladores y cuatro productos que consumen LLM se ahorra una release completa por trimestre solo con separar bien estas tres piezas. No es teoría. Es lo que mide cualquier equipo que haya migrado de "SDK oficial en cada servicio" a "cliente HTTP contra gateway interno".
El contrato de mensaje neutro: la abstracción que hace todo lo demás posible
Un gateway sin contrato neutro es un proxy. Y un proxy no te libera del lock-in, solo mueve el problema una capa. La pieza que de verdad importa es el esquema interno de mensaje que tu aplicación produce antes de llegar al gateway, y que el gateway consume sin depender de ningún vendor.
Un contrato mínimo viable tiene siete campos:
role:system,user,assistant,tool. Cuatro valores. Cero variaciones por proveedor.content: lista de bloques tipados (text,image,tool_use,tool_result). No un string plano. Los strings planos son la primera trampa.tools: lista de definiciones de herramientas en JSON Schema estricto. Sintype: "function"embebido, sinparameterscon extensiones propietarias.params: temperatura, max_tokens, stop sequences, response_format. Un vocabulario común, no el del último SDK que hayas leído.metadata: task_id, tenant_id, user_id, cost_budget. Lo que el router necesita para decidir.cache_key: hash determinista del contenido semántico de la petición. Si no está, la caché no puede vivir en el gateway.schema_version: entero que sube cuando cambias el contrato. Sin esto no puedes migrar sin romper clientes antiguos.
Un ejemplo real. Una Fintech de Nóminas con seis servicios internos consume LLM para clasificar tickets, extraer datos de PDFs de nóminas, generar respuestas a incidencias y auditar cambios en fichas de empleado. Antes del gateway, cada servicio importaba el SDK de OpenAI y construía el objeto messages a mano. Cuando decidieron probar Claude para la extracción de PDFs (mejor rendimiento con documentos largos), les tocó reescribir el helper de cada servicio. Tres sprints, dos regresiones en producción, un incidente de facturación por no calcular bien el nuevo esquema de tokens.
Después del gateway, cada servicio manda un objeto con esos siete campos a /v1/complete. El gateway lo traduce al adaptador que toque. Cambiar de proveedor para el flujo de PDFs es una línea en la tabla de reglas del router: task_type = "pdf_extract" -> provider = "anthropic". Cero cambios en el servicio consumidor. Cero regresiones en los demás flujos.
Regla: si tu contrato interno se parece al esquema de un proveedor concreto, no tienes un contrato interno, tienes ese proveedor disfrazado. La prueba es explicar el esquema a alguien sin decirle qué vendor usas. Si adivina el vendor, has fallado.
El detalle de los bloques tipados
El error más silencioso al diseñar el contrato es aceptar content como string. Funciona para chat simple. Se rompe en cuanto entra visión, tool use, thinking blocks, citations o cualquier feature multimodal. Todos los proveedores serios ya soportan al menos tres de esas cinco.
Un bloque tipado tiene forma { "type": "...", ...campos }. Un bloque de texto es { "type": "text", "text": "..." }. Un bloque de imagen es { "type": "image", "source": { "media_type": "...", "data": "..." } }. Un bloque de tool call es { "type": "tool_use", "id": "...", "name": "...", "input": {...} }. El adaptador se encarga de aplanar o expandir según lo que espere el vendor.
Los proveedores que hoy aceptan string plano en content cuando solo hay texto lo hacen por compatibilidad hacia atrás. Ninguno recomienda ese formato para código nuevo. Diseñar tu contrato con lista de bloques desde el día uno es baratísimo. Rediseñarlo cuando ya tienes veinte flujos en producción es una migración de meses.
Enrutamiento por coste, latencia y disponibilidad: la política, no el capricho
Un router serio decide con tres variables medibles y una tabla de reglas versionada. Nada de "vamos a probar cuál va mejor". Nada de "el CTO prefiere Anthropic". Las decisiones se toman con datos y se auditan.
Coste por token. Cada proveedor publica su tarifa por millón de tokens de entrada y salida. En septiembre de 2025 el rango va de 0,15 dólares por millón de tokens de entrada en modelos pequeños (Haiku 3.5, GPT-4o mini, Gemini Flash) a 15 dólares por millón en modelos grandes (Opus 4, GPT-4 Turbo, Gemini Pro 1.5). Cien veces de diferencia entre el más barato y el más caro. Si tu router manda cada petición al modelo grande "por si acaso", tu factura mensual multiplica por veinte lo que tendría que ser.
Latencia observada. No la que promete el proveedor en su marketing. La que mides tú, en tu región, con tu volumen. Un modelo cuya API responde en 1200 ms de mediana es inservible para un chatbot conversacional que necesita el primer token en menos de 400 ms. La medición se hace con percentil 95 sobre los últimos siete días, no con la media, porque la mediana esconde las colas largas que rompen la experiencia.
Disponibilidad. Tasa de éxito 2xx sobre total de peticiones. Un proveedor con 99,5% de disponibilidad en producción tiene ~3,6 horas de caída al mes. Si eso te tumba un flujo crítico, necesitas fallback automático a otro vendor. No hay excepciones.
La tabla de reglas del router, en el caso mínimo, tiene esta forma:
| Task type | Primary provider | Fallback | Max latency P95 | Max cost per call |
|---|---|---|---|---|
| chat_customer | openai/gpt-4o-mini | anthropic/haiku-3.5 | 800 ms | 0,002 EUR |
| pdf_extract | anthropic/sonnet-4 | openai/gpt-4o | 12000 ms | 0,05 EUR |
| classify_intent | mistral/small | openai/gpt-4o-mini | 400 ms | 0,001 EUR |
| generate_report | anthropic/opus-4 | openai/gpt-4-turbo | 30000 ms | 0,50 EUR |
| translate_es_en | google/gemini-flash | openai/gpt-4o-mini | 600 ms | 0,001 EUR |
Esa tabla vive en configuración, no en código. Se cambia con un pull request de dos líneas, se despliega en minutos y se audita en git. Añadir un nuevo modelo local (por ejemplo, un Llama 3.3 servido en tu propia infraestructura) es añadir una fila. Nada más.
El presupuesto duro por tenant
En una plataforma con varios clientes o equipos internos, el router debe leer un presupuesto mensual por tenant desde la metadata de la petición. Si el tenant lleva gastado el 90% de su cuota, el router degrada silenciosamente al modelo más barato del mismo grupo funcional. Si supera el 100%, devuelve 402 con un mensaje claro. Esto no es opcional en 2026. Sin control de presupuesto duro, una fuga de bucle (un servicio mal escrito que llama al LLM en un while True) te vacía la tarjeta antes de que la alerta de facturación llegue al correo del CFO.
Tokens, caché y observabilidad: la contabilidad que no puede vivir en el cliente
El segundo lock-in silencioso está en la contabilidad. Cada proveedor cuenta tokens con un tokenizer distinto: tiktoken para OpenAI, el tokenizer propio de Anthropic (accesible vía la API de token counting), SentencePiece para Gemini, otro SentencePiece diferente para Mistral. Si tu aplicación intenta estimar tokens en el cliente antes de llamar, estás importando el tokenizer del vendor y quedándote atado.
Solución: el conteo de tokens vive en el gateway. Siempre. El cliente envía el mensaje neutro y el gateway devuelve, junto con la respuesta, un bloque usage normalizado: input_tokens, output_tokens, cached_tokens, cost_eur. El cliente no calcula nada. El gateway registra cada llamada en una tabla de auditoría con tenant_id, task_id, provider, model, usage, latency_ms, cache_hit.
Caché semántica, no textual
La caché ingenua guarda hash(prompt) -> response. Funciona para peticiones idénticas byte a byte. En un producto real, dos usuarios preguntan lo mismo con espacios y comas distintas, y la caché falla. La caché semántica hashea sobre la representación canonicalizada del mensaje: tras normalizar espacios, minúsculas, orden estable de campos JSON. Y en flujos con RAG, hashea también la lista ordenada de IDs de documentos recuperados.
Con caché semántica activada en una plataforma que atiende 200.000 peticiones diarias, el hit rate típico está entre el 15% y el 35% según el tipo de flujo. En el tramo alto, el ahorro directo de factura es de miles de euros al mes. El hit rate sube al 60% en flujos con prompts largos y variables cortas (típico de asistentes documentales), porque la respuesta cacheada no depende del vendor, solo del contenido.
Nota crítica: la caché tiene que vivir en el gateway, no en el cliente. Si vive en el cliente, cada aplicación mantiene su propia caché, la deduplicación entre productos se pierde, y cambiar de proveedor invalida cachés que llevan semanas calentadas.
Observabilidad agnóstica
La observabilidad de LLM no es la observabilidad de un microservicio HTTP. Necesitas cinco métricas específicas:
- Cost per task type. Suma de
cost_eurpor hora, agrupado portask_id. Detecta fugas. - Latency P50/P95/P99 per provider. Sirve para ajustar la tabla de reglas del router.
- Cache hit rate per task type. Si un flujo cae por debajo del 10%, revisa la clave de caché.
- Token efficiency ratio.
output_tokens / input_tokens. Un ratio bajo (0,1) indica prompts hinchados con contexto innecesario. Un ratio alto (5+) indica generación libre sin límite claro. - Error rate per provider. Separado por código: 429 (rate limit), 500 (vendor), 400 (contrato), 401 (auth). Cada uno se ataca distinto.
Las cinco se exportan en OpenTelemetry desde el gateway. Cualquier stack de observabilidad (Grafana, Datadog, Honeycomb, la solución open source de turno) las consume igual. Nada en tu aplicación sabe que existen. Cambiar de proveedor cambia los datos, no el instrumento.
Migración de un flujo en producción: paso a paso sin apagar la luz
Explicar cómo migrar un flujo entre dos proveedores en producción es la mejor prueba de que un diseño de gateway está bien. Si la migración es un evento nocturno con congelación de despliegues, el diseño está mal. Si es una operación de horario laboral con rollback en un click, está bien.
Escenario: una Consultoría Administrativo con un asistente interno que responde preguntas de empleados sobre normativa laboral, procedimientos internos y estado de nóminas. El flujo actual usa OpenAI GPT-4o para todas las respuestas. Volumen: 8.000 peticiones diarias, coste mensual ~ 900 euros. El equipo quiere probar Anthropic Sonnet 4 porque una evaluación offline muestra mejor precisión en respuestas con citas de convenios colectivos.
Paso 1: shadowing. El router se configura para enviar el 100% del tráfico a OpenAI (respuesta que ve el usuario) y en paralelo, en modo asíncrono, replicar la petición contra Anthropic. La respuesta de Anthropic no se sirve al usuario. Se guarda en una tabla shadow_responses con la respuesta de OpenAI al lado. Duración: dos semanas.
Paso 2: evaluación. Un job diario compara ambas respuestas con un evaluador (puede ser otro LLM con prompt estricto, o un humano en una muestra aleatoria de 200 casos). Se mide: corrección factual, adherencia al tono corporativo, latencia, coste. Si Anthropic gana en tres de cuatro métricas, se aprueba el cambio.
Paso 3: canary. El router pasa del 100% OpenAI al 90% OpenAI / 10% Anthropic. Los usuarios del canary son aleatorios, identificados por hash del user_id. Duración: una semana. Si las métricas de calidad se mantienen y no hay incidencias, se sube al 50/50.
Paso 4: rollout completo. 100% Anthropic. OpenAI queda como fallback en la tabla de reglas. Si Anthropic falla o supera el presupuesto, el router deriva a OpenAI automáticamente.
Paso 5: limpieza. Después de un mes estable, se retira el shadowing y se archivan las tablas de comparación. Los prompts se revisan para aprovechar features específicas de Anthropic (por ejemplo, prompt caching en bloques de sistema largos, que baja el coste otro 30%).
Duración total: cinco semanas. Cero downtime. Cero cambios en el código de la aplicación consumidora. Todos los cambios viven en la tabla de reglas del router, en configuración versionada. Rollback en cualquier momento con un git revert y un despliegue.
Esta secuencia no es un ideal teórico. Es la que usa cualquier equipo de plataforma serio con un gateway bien montado. La única condición es haberse tomado en serio los tres puntos anteriores: contrato neutro, política de enrutamiento por reglas, contabilidad centralizada.
Los tres errores típicos que reintroducen lock-in por la puerta de atrás
Un gateway bien diseñado se puede sabotear en dos semanas si el equipo no vigila estos tres patrones. Los veo en cada revisión.
Error 1: prompts con features propietarias del vendor
Síntoma: el prompt de sistema incluye instrucciones específicas de un proveedor. Frases como "usa tu bloque de thinking antes de responder", o "responde en JSON estructurado usando tu response_format", o "invoca la herramienta con el formato XML de anthropic". Cuando el router intenta enrutar ese prompt a otro vendor, el resultado se degrada porque el otro vendor no entiende la instrucción.
Solución: los prompts se escriben en lenguaje agnóstico. "Piensa antes de responder" en lugar de "usa thinking". "Devuelve la respuesta como objeto JSON con estos campos" en lugar de "usa response_format json_object". Las features específicas del vendor se activan desde el adaptador, no desde el prompt. Si el vendor soporta thinking nativo, el adaptador lo activa a partir de un flag en params.enable_reasoning = true. Si no lo soporta, el prompt genérico funciona igual.
Error 2: parsing frágil de la respuesta
Síntoma: el código consumidor hace response.choices[0].message.content o response.content[0].text directamente. Cuando cambias de vendor, ese acceso se rompe. La solución "sencilla" es meter un try/except en el cliente. Y ya has vuelto al principio.
Solución: el gateway devuelve siempre una respuesta con el mismo esquema: { "message": { "role": "assistant", "content": [bloques tipados] }, "usage": {...}, "provider": "...", "model": "..." }. El cliente accede a response.message.content sin más. El parsing de la respuesta específica del vendor vive en el adaptador de salida, no en el cliente.
Para respuestas estructuradas (JSON estricto), el gateway valida contra el JSON Schema que el cliente envió en params.response_schema. Si el vendor no soporta salida estructurada nativa, el adaptador la fuerza con un post-parser. El cliente recibe siempre JSON válido. Cero try/except por vendor.
Error 3: caché con clave que incluye datos del vendor
Síntoma: la clave de caché se construye con hash(provider + model + prompt). Suena bien. Es un desastre. Cuando el router cambia el vendor para un mismo task, la caché no encuentra el resultado y hace una llamada nueva. Peor: dos peticiones idénticas con distinto vendor pagan dos veces la misma respuesta.
Solución: la clave de caché se construye sobre el contenido semántico canonicalizado y el task_id. Nada más. El vendor no entra en la clave. Si la respuesta cacheada existe, se sirve al cliente sin llamar a ningún LLM. Si no existe, el router elige vendor, se llama, y se guarda la respuesta con esa misma clave neutra. La próxima vez cualquier vendor puede servirla desde caché.
Excepción legítima: cuando las respuestas del vendor A y del vendor B son sistemáticamente distintas en tono, formato o precisión, y esa diferencia es visible al usuario. En ese caso, la clave de caché sí incluye el vendor, pero se acepta explícitamente el coste. Se documenta en la tabla de reglas y se revisa cada trimestre.
Errores frecuentes
Error 1: Gateway como proxy tonto. Síntoma: el "gateway" es un nginx con rewrite de URLs que apunta a api.openai.com. Solución: si tu gateway no traduce contratos, no observa, no cachea y no enruta, no es un gateway, es un proxy. Reescribe con una capa de aplicación en el lenguaje de tu stack (Python con FastAPI, Node con Fastify, Go con chi) que implemente los cuatro puntos.
Error 2: Un solo modelo por defecto para todo. Síntoma: la tabla de reglas tiene una única entrada, "cualquier task -> gpt-4o". Solución: mide el coste por task_type durante dos semanas. Identifica los tres tasks que se llevan el 80% del gasto. Degrada esos tres al modelo más barato que pase tu evaluación de calidad. Ahorro típico: 60% de la factura sin tocar experiencia.
Error 3: Fallback sin criterio. Síntoma: cuando el proveedor primario falla, el gateway reintenta contra el mismo. Tres veces. Con backoff. La cascada termina devolviendo 500 al usuario tras diez segundos. Solución: el fallback se dispara al primer 5xx o al primer timeout. Va a un vendor distinto, con un modelo equivalente en calidad. La política se define en la tabla, no en el código del cliente.
Error 4: Sin versionado del contrato de mensaje. Síntoma: se añade un campo nuevo al esquema neutro y los clientes antiguos empiezan a fallar con "unknown field". Solución: cada mensaje incluye schema_version. El gateway acepta N y N-1 durante al menos un trimestre. La deprecación de N-2 se anuncia con dos semanas de antelación en el changelog interno.
Error 5: Auditoría sin PII scrubbing. Síntoma: la tabla de auditoría guarda el prompt completo con datos de clientes: emails, DNIs, historiales médicos. Cuando el DPO pide un informe, aparece un problema legal. Solución: el gateway aplica scrubbing de PII antes de escribir en auditoría. Los prompts se hashean o se enmascaran. La respuesta cruda no se persiste más de 30 días.
Preguntas frecuentes
¿Cómo se evita el lock-in con un proveedor de IA como OpenAI o Anthropic?
Con una capa gateway propia que define un contrato de mensaje neutro (roles, bloques tipados, JSON Schema para tools) y adaptadores por proveedor que traducen ese contrato al SDK específico. Las aplicaciones consumidoras solo hablan con el gateway. El vendor se cambia editando la tabla de reglas del router, sin tocar código de negocio. Los tres puntos críticos son: prompts sin instrucciones propietarias, parsing de respuesta unificado, caché con clave semántica que no incluye el vendor.
¿Qué es un gateway de modelos y para qué sirve en una empresa?
Un gateway de modelos es un servicio interno que centraliza todas las llamadas a proveedores de LLM. Sirve para cuatro cosas concretas: negociar el contrato entre tu aplicación y cualquier vendor (adaptadores), decidir qué modelo usar según coste, latencia y disponibilidad (router), reducir factura con caché semántica compartida, y auditar cada llamada con métricas homogéneas. Sin gateway, cada equipo integra el SDK del vendor a mano y cada cambio de precio o de API obliga a una migración transversal.
¿Cómo se cambia de proveedor de LLM sin rehacer el sistema?
Con la operación de migración en cinco pasos: shadowing (dos semanas replicando peticiones contra el nuevo vendor sin servirlas al usuario), evaluación offline con métricas de calidad, canary del 10% al 50%, rollout completo, y retirada del vendor anterior a fallback. Si el gateway está bien diseñado, ningún servicio consumidor cambia una línea de código. Los cambios viven en la tabla de reglas del router, versionada en git.
¿Qué patrón de arquitectura permite mezclar OpenAI, Anthropic y modelos locales en el mismo producto?
El patrón gateway + router + adaptadores. El gateway expone una API interna única. El router decide por task_type y política de coste/latencia. Cada adaptador (uno por vendor, más uno por modelo local desplegado con vLLM, Ollama o TGI) traduce el contrato neutro al formato del backend. Un modelo Llama local convive en la misma tabla de reglas que un GPT-4o remoto. La aplicación consumidora no distingue entre uno y otro.
¿Cuánto ahorra en factura un gateway bien montado?
Depende del punto de partida. En equipos que consumen entre 5.000 y 100.000 dólares al mes en LLM y aún no separan tareas por modelo, el ahorro típico al introducir gateway con router por task_type está entre el 40% y el 65% del gasto. La caché semántica añade otro 15%-30% según el tipo de flujo. Sin cambios visibles en la experiencia de usuario.
¿Compensa montar un gateway propio o usar uno de terceros (OpenRouter, LiteLLM, Portkey)?
Depende del tamaño y del sector. Un equipo con menos de 5.000 dólares mensuales en LLM y sin requisitos regulatorios estrictos puede usar un gateway de terceros y ganar tiempo. Un equipo con datos regulados (sanidad, banca, sector público), con volumen alto, o con lógica de enrutamiento específica del negocio, debería montar el suyo. La razón no es la funcionalidad, es la superficie de datos: el gateway ve todos tus prompts en claro. Ese punto no se externaliza a la ligera.
Cierre
El lock-in con un proveedor de LLM es un problema de arquitectura, no de vendor. El vendor cambia condiciones cada trimestre. Es su trabajo. Es lo que hace cualquier proveedor de infraestructura en un mercado con caída de precios del 40% anual. Tu trabajo como arquitecto es que ese cambio no llegue a tu código. Llegar a ese punto no requiere una plataforma nueva. Requiere separar bien tres capas, escribir un contrato neutro que sobreviva a cinco cambios de SDK, y meter la contabilidad donde tiene que estar.
No es complicado. Es disciplina.
Si necesitas revisar la arquitectura actual de tu plataforma con IA y decidir qué mover al gateway primero, qué modelos consolidar y qué políticas de enrutamiento te ahorran factura sin degradar experiencia, esto es exactamente lo que se hace en una sesión de orquestación de modelos IA. Sales con un mapa de tus flujos actuales, la tabla de reglas propuesta y las tres migraciones prioritarias por impacto en coste y en riesgo. Si quieres empezar por ahí, reserva una cita.
Lecturas relacionadas
- Escala semántica: cómo diseñar una taxonomía de contenido que sobreviva a tres reorganizaciones (serie: escala-semantica, orden 1)
- Caché semántica para LLM: cuándo funciona, cuándo se convierte en un pozo (próximo en serie, orden 3)
Fuentes
- LLM Price Check (histórico de precios por proveedor, 2023-2026): llmpricecheck.com
- OpenAI API Changelog: platform.openai.com/docs/changelog
- Anthropic API Migration Guides: docs.anthropic.com
- Google Gemini API Reference: ai.google.dev
- Mistral AI Documentation: docs.mistral.ai
