Artículo técnico sobre cómo construir una pasarela de LLM agnóstica al proveedor: por qué definir el contrato de configuración antes que la abstracción, cómo un solo adaptador genérico cubre a la mayoría de los proveedores, por qué hace falta una reserva de tiempo para que un proveedor lento no agote los reintentos, por qué la lista de modelos permitidos debe aplicarse en el servidor, y cómo los modelos de razonamiento devuelven respuestas vacías pero exitosas si no se desactiva su salida de razonamiento.
Cómo Construimos una Pasarela de LLM Agnóstica al Proveedor
Si tu producto llama exactamente a un proveedor de modelos, la página de incidentes de ese proveedor es tu página de estado. Hacer intercambiable la ruta gestionada requirió menos código del esperado. La mayoría de las lecciones tuvieron que ver con fallos que parecen éxitos —un proveedor principal lento que deja sin margen a cada alternativa, un modelo de razonamiento que responde con un HTTP 200 vacío, una configuración en blanco que desactiva un proveedor en silencio— y con los controles en el servidor que impiden que la selección de modelo se convierta en un vector de gasto.
El Contrato Va Primero
El instinto es escribir una capa de abstracción. El primer paso más útil es definir la superficie de configuración, porque es lo que obliga a que la abstracción se mantenga honesta.
Escribe qué puede configurar un operador —qué proveedor va al frente, qué modelo sirve, qué alternativas lo respaldan— antes de escribir una sola línea de adaptador. Cada decisión de abstracción posterior se contrasta con una pregunta: ¿todavía se puede expresar en ese contrato?
Si construyes una secuencia así, hay una regla que importa más que las demás. Cada intento debe consumir de un único presupuesto de tiempo compartido, con una reserva retenida para que la última alternativa todavía tenga margen para responder en lugar de heredar las sobras de la ventana.
Esa regla es la que se descubre tarde. Sin una reserva, un proveedor principal lento consume toda la ventana de la solicitud y cada reserva falla por tiempo de espera, lo que en los registros se lee como «todos los proveedores están caídos».
Un Solo Adaptador Cubre a Casi Todos los Proveedores
Algunos proveedores necesitan un adaptador propio porque su formato de solicitud es realmente distinto. Casi todo lo demás habla un dialecto de solicitud común y muy extendido lo bastante parecido como para que un único adaptador genérico, parametrizado por URL base, nombre de modelo y clave de API, cubra al resto.
Aquí es donde aparece la tentación de sobreabstraer. Resiste la idea de añadir un registro de plugins. Un registro congelado de configuraciones de proveedor, tipado como una unión cerrada, da exhaustividad en tiempo de compilación y convierte un proveedor no soportado en un error de tipos en lugar de un error en ejecución.
Esa unión cerrada es también lo que hace seguro retirar un proveedor. Reemplazar uno se convierte en un error de compilación en cada punto que todavía lo menciona, en vez de una cadena muerta que se descubre en producción semanas después.
Rarezas de Razonamiento Que Fallan en Silencio
Los modelos de razonamiento gastan por defecto su presupuesto de salida en trazas internas. Cuando ese presupuesto se agota, la respuesta es técnicamente exitosa y está completamente vacía. Sin error, sin advertencia, HTTP 200 y nada en el campo de contenido. Incluso por debajo de ese límite, cada solicitud paga la latencia completa de razonamiento en una ventana pensada para un modelo rápido.
La solución es desactivar explícitamente la salida de razonamiento, y ahí viven las rarezas. Cada proveedor escribe ese interruptor a su manera: otra clave, otro nivel de anidación y, a veces, una clave distinta para cada generación de modelo. Una clave obsoleta no se rechaza; se ignora en silencio, así que un indicador desactualizado parece correcto en la revisión mientras cada solicitud sigue pagando el razonamiento.
Omite o escribe mal el interruptor y el proveedor parecerá sano mientras no devuelve nada, o responde demasiado despacio. Revisa el contenido, no el código de estado: una respuesta vacía con HTTP 200 es un fallo y debe tratarse como tal. Los interruptores pertenecen al adaptador, no a los puntos de llamada, y cada uno merece un comentario que explique que es obligatorio: el instinto natural al encontrarlos más adelante es borrarlos como configuración muerta.
Bloquea la Lista de Modelos en el Servidor
Dejar que los clientes elijan un modelo es una funcionalidad útil y un vector de gasto. Si la cadena del modelo se acepta tal cual, cualquier cliente puede dirigir cada solicitud hacia tu modelo más caro.
La solución es una única lista blanca compartida, consumida tanto por el selector del cliente como por un esquema en el servidor, de modo que un modelo no listado se rechaza en la frontera. Dos decisiones adyacentes importan igual:
Una selección explícita de modelo debería fijar también a su proveedor. Seleccionar un modelo pero enrutar hacia el proveedor que resulte ser el principal produce una clase confusa de error en la que la respuesta no corresponde al modelo solicitado.
Una selección explícita de modelo también debería omitir cualquier credencial BYOK almacenada. De lo contrario, quien elige un modelo gestionado puede ver esa solicitud enrutada en silencio hacia su propio endpoint personalizado, que no es ni lo que pidió ni lo que implica la facturación.
Lo Que Nos Enseñaron las Variables de Entorno Vacías
Una variable de entorno sin definir y una en blanco no son lo mismo, y la diferencia es fácil de pasar por alto. Según la plataforma y cómo se haya editado el valor, una variable «sin definir» puede llegar como cadena vacía en lugar de undefined. Un valor por defecto del esquema que solo se aplica a undefined aceptará "" sin objeciones y omitirá el valor por defecto, lo que significa que una variable en blanco deshabilita un proveedor en silencio en lugar de recurrir al valor previsto.
Normaliza las cadenas vacías a undefined en el preprocesador del esquema. Es un cambio de dos líneas que evita una categoría de configuración errónea que, de otro modo, permanece invisible hasta que llega el tráfico.
El Resultado
La ruta gestionada ya no depende de un único proveedor. Cuando el proveedor activo falla, la solicitud pasa a una alternativa en lugar de caer con él. Añadir o retirar un proveedor ahora es un cambio de configuración más las ediciones que exija el compilador, no una migración, y esa es la señal más clara de que la abstracción realmente se sostiene.