Salidas Estructuradas en LLMs: OpenAI vs Claude vs Gemini en 2026

Comparativa práctica de Structured Outputs entre OpenAI, Claude y Gemini en 2026: ejemplos con Pydantic, límites reales de cada proveedor y patrones multi-modelo para producción.

Salidas Estructuradas LLM 2026: Guía

Actualizado: 3 de agosto de 2026

Las salidas estructuradas en LLMs (structured outputs) son un mecanismo del proveedor que obliga al modelo a devolver JSON que cumple exactamente con un JSON Schema definido, usando decodificación restringida a nivel de token para garantizar validez sintáctica y de tipos. En 2026, OpenAI, Anthropic y Google Gemini exponen esta capacidad por rutas distintas: OpenAI vía response_format: {type: "json_schema", strict: true}, Anthropic vía tool_use con tool_choice forzado (y output_config.format para el modo nativo reciente), y Gemini vía responseSchema. He integrado los tres en producción y, honestamente, las diferencias importan más de lo que la mayoría de tutoriales admiten.

  • OpenAI garantiza ~99,9% de conformidad de esquema con strict: true; Claude Sonnet 4.6 vía tool use llega a ~99,8%; Gemini responseSchema alcanza ~98–99% con caveats en tipos union.
  • Las salidas estructuradas garantizan la forma, no la calidad: un modelo puede colapsar a un enum válido pero semánticamente incorrecto y pasar la validación.
  • Claude falla en frío con esquemas de ~8 KB o más ("compiled grammar is too large") mientras que OpenAI y Gemini los aceptan; diseña el schema pensando en el proveedor más restrictivo.
  • El streaming progresivo de campos funciona en OpenAI y Gemini; Claude entrega el bloque de tool_use completo al final, sin parseo campo por campo.
  • Overhead por request: OpenAI 80–120 tokens, Gemini 60–100, Anthropic 150–300 (el más caro a escala).
  • El paradigma correcto en 2026 es schema-first con Pydantic o Zod, evaluación por rúbrica antes de deploy, y routing por template al proveedor más barato que pase la rúbrica.

¿Qué son las salidas estructuradas en LLMs?

Las salidas estructuradas son la capacidad de un modelo de lenguaje para devolver datos que cumplen con un contrato tipado y verificable, en lugar de texto libre que hay que parsear con expresiones regulares o "esperar que el LLM se comporte". Técnicamente, el proveedor recibe un JSON Schema junto con el prompt y lo compila a una máquina de estados finitos (FSM) o una gramática libre de contexto (CFG). Durante la generación, cada vez que el modelo va a muestrear el siguiente token, la máquina enmascara con probabilidad cero cualquier token que rompería el esquema. ¿El resultado? El modelo literalmente no puede emitir JSON inválido.

Esto es un cambio arquitectónico, no cosmético. Hasta 2023, el patrón dominante era "responde en JSON, por favor" en el system prompt seguido de un try/except JSONDecodeError con reintentos. Ese patrón funciona el 80–95% del tiempo, lo cual suena razonable hasta que se calcula: a un millón de llamadas mensuales, un 5% de fallos son 50.000 excepciones que hay que reintentar, con la latencia y el coste que eso implica. Structured outputs elimina esa clase entera de errores del código de producción.

El caveat que quiero dejar claro desde el principio: la conformidad de esquema no implica corrección semántica. Si el schema define un campo sentiment: "positive" | "negative" | "neutral", el modelo puede colapsar a "neutral" por defecto en casos ambiguos solo para mantener la gramática válida. El JSON parsea, la aplicación no crashea, y en el dashboard todo se ve verde. Pero el clasificador está mintiendo. Por eso las evaluaciones sistemáticas de LLMs siguen siendo obligatorias, incluso con schemas garantizados.

JSON Mode vs Function Calling vs Structured Outputs: los tres niveles

Estos tres términos se usan como sinónimos en blogs y no lo son. Hay tres niveles distintos de garantía, con costes y trade-offs diferentes.

Nivel 1: JSON Mode

Configurable con response_format: {type: "json_object"} en OpenAI o el equivalente en otros proveedores. Garantiza solo que la salida sea JSON sintácticamente válido. No garantiza qué campos aparecen, sus tipos, ni si el enum es respetado. Sirve para casos donde el schema es muy dinámico o desconocido en tiempo de compilación. En 2026 lo considero legacy: si conoces la forma, usa el Nivel 3.

Nivel 2: Function Calling / Tool Use

El modelo elige entre una o más "funciones" que describes con un schema y devuelve los argumentos como JSON. Su propósito principal no es extraer datos sino decidir qué acción tomar: llamar a una API externa, consultar una base de datos, invocar otra herramienta. Es el mecanismo que sostiene los frameworks de agentes como LangGraph, CrewAI y AutoGen. La fiabilidad de conformidad ronda el 95–99% dependiendo del proveedor y del modelo.

Nivel 3: Structured Outputs nativos

Decodificación restringida a nivel de token con garantía de conformidad de esquema. OpenAI, Gemini y Anthropic (esta última desde finales de 2025) exponen esta modalidad. Es el nivel al que debería estar cualquier cosa que llegue a producción para extracción de datos, clasificación, o cualquier tarea donde la forma es conocida y estable.

Regla práctica que aplico: si el LLM tiene que decidir, es function calling; si el LLM tiene que producir un dato estructurado, es structured output. Los dos se pueden combinar. De hecho, una herramienta con strict: true es exactamente eso: function calling con garantía de schema en los argumentos.

Comparativa: OpenAI vs Claude vs Gemini en 2026

He rodado los tres proveedores contra los mismos schemas y los mismos datasets. Los números que aparecen abajo vienen tanto de mi propio banco de pruebas como de benchmarks públicos como el análisis de Future AGI y las notas de la documentación oficial de OpenAI sobre Structured Outputs.

Dimensión OpenAI (GPT-5.2) Anthropic (Claude Sonnet 4.6) Google (Gemini 2.5 Pro)
API primaria response_format: json_schema con strict: true tool_use forzado; output_config.format nativo responseSchema + responseMimeType: application/json
Conformidad de esquema ~99,9% ~99,8% (nativo); ~99,5% (tool use) ~98–99%
Schemas grandes (>8 KB) Sin problemas hasta ~64 KB Falla dura con "compiled grammar is too large" Acepta, degrada en unions anchas
Streaming campo a campo Nativo No (bloque único al final del stream) Nativo
Overhead de tokens por request 80–120 150–300 60–100
Refusals como error tipado Sí (message.refusal) Vía stop_reason Vía finishReason: SAFETY
additionalProperties: false requerido Sí, siempre No (más flexible) Sí en modo estricto
SDKs con parsing automático Pydantic (Python), Zod (TS) Pydantic manual, Zod manual Pydantic (via genai SDK)

Lectura corta: OpenAI es el más "compra-el-producto-y-funciona" para extracción; Claude gana en flexibilidad de schema y en la calidad del razonamiento antes de rellenar los campos; Gemini ofrece el mejor coste por token con soporte de streaming nativo. Ningún proveedor gana en todo, así que la pregunta correcta no es "cuál es mejor" sino "cuál es el más barato que pasa mi rúbrica para este template concreto".

OpenAI Structured Outputs con Pydantic

El patrón schema-first en Python con Pydantic + OpenAI es probablemente la experiencia de desarrollador más limpia que existe hoy para extracción estructurada. Defines el modelo una vez y el SDK se encarga de generar el JSON Schema, pasarlo con strict: true, parsear la respuesta y devolver una instancia tipada.

from openai import OpenAI
from pydantic import BaseModel, Field
from typing import Literal

client = OpenAI()

class InvoiceLine(BaseModel):
    description: str = Field(..., description="Concepto de la línea, tal cual figura")
    quantity: float = Field(..., ge=0)
    unit_price_eur: float = Field(..., ge=0)

class Invoice(BaseModel):
    vendor_name: str
    invoice_number: str
    issue_date: str = Field(..., pattern=r"^\d{4}-\d{2}-\d{2}$")
    currency: Literal["EUR", "USD", "GBP"]
    lines: list[InvoiceLine]
    total_eur: float = Field(..., ge=0)

response = client.chat.completions.parse(
    model="gpt-5.2",
    messages=[
        {"role": "system", "content": "Extrae la factura al schema. Si un campo no existe, devuelve null."},
        {"role": "user", "content": invoice_text},
    ],
    response_format=Invoice,
)

message = response.choices[0].message
if message.refusal:
    raise ValueError(f"Modelo rechazó: {message.refusal}")

invoice: Invoice = message.parsed
print(invoice.total_eur, len(invoice.lines))

Tres detalles que veo mal aplicados con frecuencia. Primero, parse() en lugar de create(): parse() es el método que integra Pydantic y devuelve el objeto ya validado. Segundo, siempre chequea message.refusal antes de acceder a parsed; un rechazo es un fallo tipado, no una excepción. Trátalo como un 403 en tu código. Tercero, parallel_tool_calls: false es obligatorio cuando usas structured outputs junto con tool calling, porque la combinación con paralelismo no está soportada y las llamadas fallan silenciosamente (me pilló esto en un pipeline de resúmenes hace unos meses, y perdí una tarde entera).

Sobre tamaño de schema: el límite práctico son ~30 campos totales, incluyendo los anidados. Cuando la extracción no cabe, divídela en dos calls. Por ejemplo, en el caso de la factura: una call para la cabecera y otra por página de líneas. Es más barato en latencia total que un schema monolítico, porque cada call se paraleliza y el modelo tiene menos contexto que mantener coherente.

Claude tool use y salida estructurada nativa

Anthropic tiene dos rutas para salida estructurada en 2026: la clásica tool_use con tool_choice forzado (funciona en toda la línea Claude 3+), y la nativa output_config.format: json_schema añadida en Sonnet 4.5 y superiores. La ruta tool use sigue siendo la que uso por defecto porque es la que más se parece a los patrones cross-provider y sobrevive a los saltos de versión.

import anthropic
import json

client = anthropic.Anthropic()

extract_tool = {
    "name": "extract_invoice",
    "description": "Extrae los campos de una factura al schema proporcionado.",
    "input_schema": {
        "type": "object",
        "properties": {
            "vendor_name": {"type": "string"},
            "invoice_number": {"type": "string"},
            "issue_date": {"type": "string", "pattern": r"^\d{4}-\d{2}-\d{2}$"},
            "currency": {"type": "string", "enum": ["EUR", "USD", "GBP"]},
            "total_eur": {"type": "number", "minimum": 0},
        },
        "required": ["vendor_name", "invoice_number", "issue_date", "currency", "total_eur"],
    },
}

message = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=[extract_tool],
    tool_choice={"type": "tool", "name": "extract_invoice"},
    messages=[{"role": "user", "content": invoice_text}],
)

tool_block = next(b for b in message.content if b.type == "tool_use")
invoice_data = tool_block.input  # ya es dict validado contra el schema

La documentación de referencia está en la guía oficial de tool use de Anthropic. Dos particularidades importantes. Primero, restricciones como minimum, maximum, minLength y pattern no se aplican durante la decodificación restringida. El SDK las elimina del schema, las mueve al campo description como texto plano, y las valida después. Si fallan, se puede reintentar. En la práctica, no cuentes con esas restricciones como garantía dura: cuando importan (formatos de fecha, códigos postales), valida en tu lado con Pydantic o Zod después.

Segundo, el famoso hard-fail con esquemas grandes. He visto esto en producción: schemas con anidamiento profundo y muchos campos string terminan superando los ~8 KB una vez convertidos a JSON Schema, y Claude devuelve "compiled grammar is too large" sin fallback. Para schemas de este tamaño la única solución es dividir la extracción o cambiar de proveedor para esa call específica. El bug está trackeado en el repositorio oficial del SDK de Anthropic y no tiene fix a la fecha.

Gemini responseSchema y sus limitaciones

Gemini fusionó JSON mode y structured outputs en una sola API: pasas responseMimeType: "application/json" junto con responseSchema y obtienes JSON garantizado contra el schema. El SDK oficial acepta Pydantic directamente.

from google import genai
from pydantic import BaseModel

class SentimentResult(BaseModel):
    label: str
    confidence: float
    key_phrases: list[str]

client = genai.Client()

response = client.models.generate_content(
    model="gemini-2.5-pro",
    contents="Analiza el sentimiento de este review: " + review_text,
    config={
        "response_mime_type": "application/json",
        "response_schema": SentimentResult,
    },
)

result: SentimentResult = response.parsed

Las limitaciones importantes que documenta la guía oficial de Gemini structured output: los tipos union con muchos miembros fallan silenciosamente (el modelo colapsa al primero), el anidamiento más allá de 3 niveles aumenta la tasa de error, y el subset soportado de JSON Schema es más estrecho que el de OpenAI. Un schema que funciona en GPT-5.2 puede no ser expresable en Gemini 2.5 Pro. Si estás construyendo un sistema multi-proveedor, diseña el schema para el proveedor más restrictivo, no para el más permisivo.

El punto fuerte de Gemini es el coste por token y el soporte nativo de streaming: para clasificación de alto volumen con schemas simples, es difícil de superar económicamente.

Evaluación antes de desplegar a producción

Esta es la sección que la mayoría de tutoriales saltan, y donde se hacen las diferencias reales. Un schema válido en un proveedor puede producir datos inútiles en el 20% de los casos, y el pipeline se lo traga sin protestar. He aprendido esto por la vía dura (literal: rompí un informe de compliance porque un enum silenciosamente colapsaba a "unknown").

La rúbrica mínima que aplico antes de mover un template a producción tiene tres capas:

  1. Conformidad de schema (automática): sobre un dataset de 500–2000 ejemplos, ¿qué porcentaje parsea sin errores? Objetivo: >99,5%.
  2. Corrección campo a campo (semi-automática con LLM-as-judge o ground truth): para cada campo del schema, ¿coincide el valor extraído con el esperado? Aquí es donde se detectan los enum-collapses y los strings truncados.
  3. Robustez adversarial (manual): inputs con datos faltantes, formatos raros, idiomas mezclados, ataques de prompt injection intentando modificar la extracción. Objetivo: sin regresión frente al baseline.

La ingeniería de contexto y las evaluaciones son dos caras de la misma moneda: el schema es parte del contexto que le das al modelo, y sin evals no sabes si el schema está bien diseñado. Mi regla personal: nunca ajusto un prompt o schema sin verlo primero contra la rúbrica.

Patrones multi-proveedor para producción

Ningún proveedor es 100% fiable. Para pipelines críticos he adoptado tres patrones que reducen la dependencia de un único vendor.

Fallback en cascada

Configuras un orden preferente (por coste, latencia o calidad) y, si un proveedor falla (refusal, timeout, schema too large), pasas al siguiente. Es el patrón más simple y el que resuelve la mayoría de incidentes. La clave es que el schema sea el mismo en todos los proveedores, lo cual obliga a diseñar para el subset común (el más restrictivo).

Router por template

Cada template de extracción se enruta al proveedor que pasó la rúbrica al menor coste. Una extracción simple de sentimiento va a Gemini Flash; una extracción de factura compleja con muchos campos condicionales va a GPT-5.2; una tarea creativa con estructura ligera va a Claude. Esto no es teoría: puede recortar entre el 30% y el 60% del coste mensual sin degradar calidad. Lo he visto en mi último proyecto B2B, donde el ahorro se comió el sueldo de un semestre.

Votación multi-modelo

Para casos de alto valor (compliance, extracción legal, transacciones financieras), corres el mismo prompt en 2 o 3 proveedores en paralelo y aplicas mayoría campo por campo. Es el patrón más caro pero el único que sube la fiabilidad por encima del techo individual de cada proveedor. Si los proveedores discrepan, ese registro se enruta a revisión humana. Este patrón se integra bien con los servidores MCP para exponer la extracción como una tool consumible por múltiples clientes.

Errores comunes y cómo evitarlos

Los cinco errores que veo con más frecuencia en code reviews de integraciones LLM:

  1. Confiar solo en la garantía de schema. Como ya insistí, schema válido ≠ dato correcto. Añade validación de reglas de negocio en Pydantic con @field_validator o Zod con .refine().
  2. Ignorar refusals. Cuando message.refusal viene poblado, tu message.parsed es None. Si accedes sin chequear, obtienes un AttributeError en producción a las 3 de la mañana (voz de la experiencia).
  3. Schemas dinámicos por request. Cada schema nuevo obliga al proveedor a compilar la gramática, lo cual paga latencia. Mantén un catálogo cerrado de schemas.
  4. Prompts que dicen "responde en JSON, por favor". Cuando ya tienes structured outputs, esa frase en el system prompt no aporta nada y a veces confunde al modelo. Elimínala.
  5. No versionar los schemas. Un cambio de schema es un cambio de contrato. Trátalos como migraciones de base de datos: número de versión, changelog, y capacidad de rollback.

Y un anti-patrón que merece mención aparte: intentar convertir a structured outputs una tarea que es fundamentalmente conversacional. Si el output natural es un párrafo con nuance, no lo metas a la fuerza en un schema con 15 campos, porque te va a devolver campos vacíos o rellenos con "N/A". Distingue entre tareas de extracción (schema-friendly) y tareas de generación (mejor con streaming de texto y post-procesado ligero).

Preguntas frecuentes

¿Cuál es la diferencia entre JSON mode y structured outputs?

JSON mode garantiza solo que la salida sea sintácticamente JSON válido, sin verificar la forma. Structured outputs, además, valida que la salida cumpla exactamente con un JSON Schema definido, usando decodificación restringida a nivel de token. En 2026, JSON mode se considera legacy para producción cuando la forma de la respuesta es conocida.

¿Se puede hacer streaming de salidas estructuradas con Claude?

Parcialmente. Claude entrega el bloque de tool_use completo al final del stream, no campo por campo. Si tu aplicación necesita mostrar campos progresivamente conforme se generan (por ejemplo, en una UI reactiva), OpenAI y Gemini son la opción adecuada porque soportan parsing incremental nativo.

¿Cuántos tokens extra consumen las salidas estructuradas?

El overhead por request varía por proveedor: OpenAI añade 80–120 tokens, Gemini 60–100, y Anthropic 150–300 (el más alto por la definición completa de la tool). A 500.000 requests mensuales, la diferencia entre proveedores puede suponer entre 60 y 450 dólares al mes según el modelo.

¿Es necesario usar Pydantic o Zod si el proveedor ya garantiza el schema?

Sí, siempre. El proveedor garantiza la forma sintáctica, pero no las reglas de negocio (rangos, relaciones entre campos, invariantes). Pydantic o Zod son tu segunda línea de defensa y también sirven para detectar si el modelo colapsó a valores por defecto semánticamente incorrectos aunque válidos según el schema.

¿Qué hago si mi schema falla con "compiled grammar is too large" en Claude?

Es un límite conocido de Anthropic para schemas de aproximadamente 8 KB o más una vez convertidos a JSON Schema. Las opciones son: dividir la extracción en múltiples calls con schemas más pequeños, simplificar el schema eliminando anidamiento profundo o campos poco críticos, o enrutar esa call específica a OpenAI o Gemini que aceptan schemas mucho más grandes.

Daichi Watanabe
Sobre el Autor Daichi Watanabe

LLM integration specialist with a strong opinion about function calling and an even stronger one about evaluations.