Un mismo esquema JSON puede devolver una respuesta limpia y tipada con un modelo de OpenRouter, pero producir claves distintas, una cadena vacía o un error 400 con el siguiente, aunque el cuerpo de la petición sea idéntico. El usuario de Reddit u/MicBeckie probó modelos Qwen mediante las salidas estructuradas de OpenRouter y resumió el resultado así: «9 de cada 10 veces siempre obtenía errores». En cambio, los modelos de OpenAI del mismo montaje respetaban el esquema.
No estamos ante un fallo puntual que puedas solucionar abriendo un ticket. OpenRouter determina la compatibilidad con salidas estructuradas por endpoint, no por modelo. Además, «compatible» puede significar tres cosas: desde aplicar el esquema de forma estricta y nativa hasta limitarse a sugerírselo al modelo. En esta guía repasamos cómo funciona el enrutamiento, las seis formas en que estas peticiones fallan en la práctica y las medidas necesarias para llevarlas a producción. La mecánica de cumplimiento se basa en la documentación oficial de salidas estructuradas; las pruebas de los fallos proceden de los hilos de desarrolladores enlazados en el texto.
Qué significa realmente que OpenRouter admita salidas estructuradas
OpenRouter acepta el parámetro response_format con type: "json_schema", un name para el esquema, la marca strict y el propio esquema JSON Schema. Una petición mínima tendría este aspecto:
{
"model": "openai/gpt-4o",
"messages": [{ "role": "user", "content": "Extract the shipping info" }],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "shipping_info",
"strict": true,
"schema": {
"type": "object",
"properties": {
"tracking_number": { "type": "string", "description": "Carrier tracking ID" },
"carrier": { "type": "string" },
"eta_days": { "type": "number", "description": "Days until delivery" }
},
"required": ["tracking_number", "carrier", "eta_days"],
"additionalProperties": false
}
}
}
}
Hay dos detalles de la documentación oficial que determinan si la función llegará a funcionar:
- La compatibilidad es por endpoint, no por modelo. Un modelo disponible a través de cinco proveedores puede ofrecer salidas estructuradas solo en dos. La sección Providers de la página del modelo muestra el parámetro
structured_outputspara cada proveedor, y la documentación avisa de que «la compatibilidad del endpoint también puede cambiar con el tiempo». - La cobertura se amplió desde una base muy limitada. OpenRouter anunció las salidas estructuradas el 12 de diciembre de 2024 con compatibilidad exclusiva para OpenAI 4o y los modelos de Fireworks. El resto llegó después, proveedor a proveedor, así que cualquier lista de modelos escrita hoy puede quedarse obsoleta rápidamente.
La documentación también recomienda añadir descripciones a todas las propiedades y configurar additionalProperties: false. En los niveles de cumplimiento más bajos, el esquema funciona además como parte de las instrucciones para el modelo.
Los tres niveles de cumplimiento que se esconden tras una misma opción
strict: true no significa exactamente lo mismo en todos los destinos. La guía oficial divide el comportamiento de los proveedores en tres niveles:
| Nivel | Qué hace el proveedor con tu esquema | ¿Puedes fiarte de la respuesta? |
|---|---|---|
| Modo estricto nativo | Aplica el esquema exactamente durante la decodificación | Sí: la respuesta coincide con el esquema por construcción |
| Formato traducido | Convierte el esquema a un formato de salida estructurada propio del proveedor | En general sí: queda limitado a las funciones que admita ese formato |
| Sugerencia reforzada | Inyecta el esquema como orientación para el modelo | No: en un buen día respeta la forma; en uno malo inventa claves |
OpenRouter no indica en tiempo de petición qué nivel utiliza cada endpoint; la documentación remite a la información de cada proveedor. Los modos estrictos nativos también limitan las funciones de JSON Schema aceptadas. Por eso, algunas palabras clave poco habituales pueden hacer fallar un endpoint estricto y pasar sin problemas en otro que solo las trate como sugerencias.
La página de enrutamiento por proveedor documenta un caso especial con Claude: cuando usas response_format.type: "json_schema", OpenRouter añade automáticamente la cabecera beta structured-outputs-2025-11-13 de Anthropic, que activa argumentos de herramientas estrictos y validados contra el esquema. En cambio, si envías definiciones de herramientas con strict: true dentro de tools, debes incluir esa cabecera beta explícitamente. Si no lo haces, OpenRouter elimina strict y enruta la petición sin esa opción. El fallo es silencioso: las llamadas a herramientas dejan de validarse contra el esquema y no aparece ningún error.
Seis formas en que puede fallar el mismo esquema
Dos tipos de error aparecen inmediatamente y están documentados en la guía oficial. Los otros cuatro se repiten en hilos de la comunidad y son los que más tiempo hacen perder.
Fallo inmediato 1: el endpoint no admite salidas estructuradas. La petición devuelve un error que indica que la capacidad no está disponible. Es molesto, pero al menos resulta claro. Fallo inmediato 2: el esquema JSON no es válido. La API rechaza la petición porque el esquema no se puede analizar o incumple las reglas del endpoint.
Fallo silencioso 1: el esquema se ignora. La respuesta es JSON válido, pero corresponde a otro esquema. Esto contaba u/DaniyarQQQ en el hilo sobre esquemas ignorados de r/LocalLLaMA:
Devuelve un JSON que no se parece en absoluto a mi esquema.
u/MicBeckie describía así el problema para diagnosticarlo en ese mismo hilo:
O veo respuestas correctas cuando el JSON coincide exactamente con lo que se pide, o recibo un error sin poder consultar el JSON.
Fallo de envoltorio 2: un 400 sobre tool_choice que nunca enviaste. En el caso de LangChainJS enlazado, withStructuredOutput() implementaba la «salida estructurada» forzando tool_choice hacia una función generada. En modelos que anuncian llamadas a herramientas, pero no admiten una elección de herramienta forzada, la petición termina con invalid_request_error. En el caso de DeepSeek v4, el error mencionaba directamente al modelo: deepseek-reasoner does not support this tool_choice. u/shansoft se encontró exactamente con este problema mediante LangChainJS (hilo), donde la suposición de u/eyueldk —«si dice que admite llamadas a herramientas, debería admitir salidas estructuradas»— resultó ser falsa. Admitir tool calls y admitir esquemas estrictos son capacidades distintas.
Fallo silencioso 3: ni error ni contenido. Un informe sobre gpt-oss-120b describe una petición con esquema estricto que devuelve un 400 al usar la ruta directa del proveedor, pero responde mediante OpenRouter con un 200 y un message.content vacío. Otro hilo de r/openrouter muestra un modelo «compatible» que solo devuelve [1] o [1.1]. Si el SDK analiza sin quejarse una cadena vacía, el fallo se desplaza tres capas hacia abajo.
Fallo silencioso 4: el endpoint se queda colgado. u/Beneficial-Loss-1031 hablaba de endpoints que sí anunciaban salidas estructuradas para DeepSeek v4 (hilo):
deepinfra/fp4yakashml/fp8tienen la opción de salida estructurada, pero esperé 3 minutos en cada uno a que la API respondiera y no obtuve nada.
| # | Tipo de fallo | Qué observas | Causa habitual |
|---|---|---|---|
| 1 | Endpoint no compatible | Error: las salidas estructuradas no son compatibles | La petición se envió a un proveedor sin esa capacidad |
| 2 | Esquema no válido | Error de la API al enviar la petición | El esquema incumple las reglas del endpoint |
| 3 | Esquema ignorado | JSON válido, claves incorrectas | Cumplimiento en el nivel de sugerencia |
| 4 | 400 por tool_choice | invalid_request_error | El SDK simula el esquema mediante una llamada forzada a una herramienta |
| 5 | Contenido vacío | 200, message.content vacío | El proveedor gestiona mal el modo estricto |
| 6 | Bloqueo | No hay respuesta durante minutos | No confirmado en el informe: una espera de 3 minutos en endpoints fp4/fp8 |
Blinda la petición antes de culpar al modelo
El ajuste con mayor impacto es require_parameters: true dentro del objeto provider. De forma predeterminada vale false, y los parámetros desconocidos se reenvían a proveedores que pueden ignorarlos silenciosamente. Incluso con false, response_format y las salidas estructuradas funcionan como una preferencia flexible entre endpoints: se priorizan, pero no están garantizados. Al establecerlo en true, el enrutamiento se limita a endpoints compatibles con todos los parámetros enviados, tal como explica la documentación de enrutamiento por proveedor:
{
"model": "deepseek/deepseek-chat",
"messages": [{ "role": "user", "content": "Extract the shipping info" }],
"response_format": { "type": "json_schema", "json_schema": { "name": "shipping_info", "strict": true, "schema": { "...": "..." } } },
"provider": {
"require_parameters": true,
"order": ["fireworks"],
"allow_fallbacks": false
}
}
Cada restricción reduce el grupo de proveedores elegibles, y allow_fallbacks: false cambia disponibilidad por determinismo. La misma documentación de enrutamiento describe la estrategia predeterminada como un reparto basado en el tiempo de actividad y en el inverso del cuadrado del precio durante los 30 segundos anteriores. Optimiza por coste y salud del servicio, no por compatibilidad con esquemas. Fijar order a un único proveedor y desactivar los fallback hace que el enrutamiento sea reproducible: la petición ya no saltará a otro proveedor durante una interrupción. Aun así, tendrás que comprobar el nivel de cumplimiento de ese endpoint.
Hay dos hábitos de auditoría que cubren lo que el enrutamiento no puede:
- Comprueba qué proveedor atendió la petición. Los metadatos de generación de OpenRouter muestran el proveedor utilizado en cada generación, junto con el modelo, la latencia y el recuento de tokens. Si la calidad de la respuesta cambia, esos datos permiten saber si cambió el comportamiento del modelo o el proveedor elegido por el router.
- Valida siempre en el cliente. Ninguno de los tres niveles sustituye a un análisis con Pydantic o Zod en tu aplicación. La lección recurrente de los hilos de pruebas de r/LLMDevs es que «JSON válido», «válido según el esquema» y «semánticamente correcto» son tres niveles distintos; y solo los dos primeros forman parte, aunque sea parcialmente, del trabajo de la API.
El streaming funciona, pero el análisis corre de tu cuenta
Las salidas estructuradas son compatibles con stream: true. La documentación describe el contrato así: el modelo transmite fragmentos de JSON válidos y, cuando termina el stream, la respuesta completa coincide con el esquema. Ese cumplimiento hereda el nivel del endpoint: uno basado en sugerencias todavía puede ensamblar una respuesta que no cumpla el esquema. Por eso debes validar el objeto final. Además, la documentación no incluye un analizador incremental; en interfaces sensibles a la latencia, ese es el verdadero problema de ingeniería. En el hilo sobre buenas prácticas de streaming de r/LLMDevs, u/am174744 lo resumía así:
Al final acabé escribiendo una función que completa el JSON por su cuenta. — u/am174744
«…en realidad es una máquina de estados». — u/ImNotLegitLol, al matizar la idea de reparar y después analizar
En la práctica tienes varias opciones: analizar el JSON parcial con un parser tolerante al streaming, mostrar únicamente los campos ya completos o renunciar al renderizado incremental y enseñar un indicador de carga hasta ensamblar el objeto final.
Qué puede arreglar Response Healing —y qué no—
El plugin Response Healing de OpenRouter está pensado para peticiones no retransmitidas con json_schema y corrige problemas de formato: JSON truncado, fences de Markdown sobrantes y otros casos parecidos. Sus límites son más importantes de lo que repara:
- El streaming queda fuera. La documentación limita el plugin a peticiones sin streaming.
- Las violaciones del esquema también quedan fuera. Healing consigue que el JSON se pueda analizar, pero no convierte en conforme una respuesta que haya ignorado tu esquema. El tercer tipo de fallo anterior permanece intacto.
Cómo elegir modelos que respeten de verdad los esquemas
Las listas de modelos caducan; los criterios de evaluación no. Tres filtros cubren buena parte de los fallos anteriores:
- Cumplimiento estricto nativo. Prioriza modelos cuyo proveedor aplique el esquema durante la decodificación, en lugar de proveedores que lo traduzcan o lo usen como sugerencia. La tabla Providers de la página del modelo muestra qué endpoints anuncian
structured_outputs; el nivel del proveedor determina la calidad del cumplimiento. - Un único proveedor auditable. Contrasta la atribución del proveedor con un endpoint conocido por funcionar bien y hazlo en varias llamadas. Si el router reparte las peticiones entre proveedores de distintos niveles, tu tasa de fallos se convierte en una lotería de enrutamiento. Fija el proveedor o elige un modelo disponible con un solo proveedor.
- Una prueba de humo propia. Las señales de la comunidad envejecen rápido en ambos sentidos: tanto los errores de Qwen mencionados arriba como la falta de compatibilidad de DeepSeek v4 pueden cambiar cuando los proveedores actualicen sus endpoints. La única cifra de fiabilidad que importa es la que obtienes con tu propio esquema.
Preguntas frecuentes sobre las salidas estructuradas de OpenRouter
¿Cuál es la diferencia entre json_object y json_schema?
json_object solo solicita JSON sintácticamente válido; json_schema proporciona un esquema que la respuesta debe cumplir. json_object garantiza la sintaxis JSON, no que se respeten los campos de tu esquema. Si el código posterior necesita nombres de campo concretos, debes validarlo por tu cuenta.
¿Qué modelos de OpenRouter admiten salidas estructuradas?
No hay una lista estática en la que confiar: la compatibilidad depende del endpoint, cambia con el tiempo y comenzó únicamente con OpenAI 4o y los modelos de Fireworks en diciembre de 2024. Consulta la sección Providers de la página del modelo y revisa la marca structured_outputs de cada endpoint.
¿Por qué el modelo ignora mi esquema?
Hay tres causas habituales: la petición llegó a un endpoint que solo ofrece sugerencias o que no es compatible —solución: require_parameters: true y fijar el proveedor—; el esquema utiliza palabras clave que el modo estricto del endpoint rechaza; o un envoltorio del SDK está simulando la salida estructurada mediante llamadas a herramientas en un modelo que no admite una elección de herramienta forzada.
¿Puedo usar Pydantic o LangChain con las salidas estructuradas de OpenRouter?
Sí. La documentación oficial describe un formato de petición compatible con la API de OpenRouter al estilo de chat completions, por lo que los esquemas generados con Pydantic y el SDK de OpenAI funcionan directamente. withStructuredOutput() de LangChain también funciona, pero comprueba que envía response_format en lugar de simularlo mediante tool_choice, que es lo que provocó errores 400 con DeepSeek v4.
¿Funcionan las salidas estructuradas con streaming?
Sí. El stream emite fragmentos de JSON válidos, pero que el objeto final cumpla el esquema depende del nivel de cumplimiento del endpoint. Valida el objeto ensamblado por tu cuenta. El análisis incremental de los fragmentos es responsabilidad de tu aplicación, y Response Healing no se aplica a los streams.
¿OpenRouter valida las respuestas contra mi esquema?
No como garantía para todos los endpoints: el cumplimiento depende del nivel del proveedor, y Response Healing solo corrige JSON mal formado, no incumplimientos del esquema. La validación en el cliente sigue siendo obligatoria.
La prueba de humo de 10 llamadas
Antes de poner en producción cualquier modelo con salidas estructuradas, haz esta prueba:
- Fija un esquema representativo: complejidad media,
additionalProperties: falsey descripciones en todas las propiedades. - Envía 10 peticiones idénticas con
strict: trueyrequire_parameters: true, con los fallback activados. Esta fase prueba deliberadamente el comportamiento de fallback, así que déjalos encendidos. - Evalúa cada respuesta en tres niveles: ¿el JSON se puede analizar? ¿Es válido según el esquema? ¿Tiene sentido semánticamente?
- Registra qué proveedor atendió cada respuesta mediante los metadatos de generación. Una tasa de 10/10 repartida entre cuatro proveedores distintos es una lotería de enrutamiento, no una garantía.
- Decide: publicar tal cual, fijar
provider.orderal endpoint que aprobó la prueba y repetir las 10 llamadas con el proveedor fijado, o cambiar de modelo y añadir una capa de validación y reintento en el cliente.
El umbral de aprobación lo decides tú, pero cualquier resultado inferior a 9/10 con un esquema fijo significa que los reintentos y la validación no son opcionales. Son parte del producto.
Para seguir leyendo: cómo el router automático de OpenRouter elige proveedores, cómo reducir costes con la caché de prompts de OpenRouter y cómo solucionar los límites de tasa 429 de OpenRouter.