
La salida estructurada de LLM es el mecanismo que garantiza que la respuesta de un modelo de lenguaje se ajuste a un esquema predefinido -- no solo JSON válido, sino JSON válido según el esquema con exactamente los campos, tipos y restricciones que especificaste. Todos los principales proveedores ahora lo soportan de forma nativa, y ha cambiado cómo se construyen las aplicaciones LLM en producción.
Resumen Rápido: Salidas Estructuradas de un Vistazo
Si tienes poco tiempo, aquí está el panorama en 2026:
| Aspecto | Detalles |
|---|---|
| Qué es | Respuestas de LLMs con esquema forzado -- estructura garantizada, no "mejor esfuerzo" |
| Quién lo soporta | OpenAI, Anthropic, Gemini, Cohere, xAI (Grok), más local vía Ollama/vLLM |
| Mecanismo clave | Decodificación restringida -- los tokens inválidos se enmascaran antes del muestreo |
| Modo JSON vs. Modo Estricto | Modo JSON = solo sintaxis válida. Modo Estricto = cumplimiento completo del esquema |
| Biblioteca Python | Pydantic (BaseModel + Field) para definición de esquema |
| Biblioteca TypeScript | Zod (z.object + .describe) para definición de esquema |
| Mejor enfoque inicial | OpenAI con Pydantic o Zod vía SDK nativo |
| Mejor biblioteca de producción | Instructor (Python) o SDK nativo (TypeScript) |
| Mayor trampa | Poner el campo de razonamiento DESPUÉS del campo de respuesta -- el modelo decide antes de pensar |
| Overhead de latencia | 50-200ms en la primera llamada (compilación del esquema), en caché después |
Ahora desglosemos cada parte.
¿Qué Son las Salidas Estructuradas de LLM?
La salida estructurada es la diferencia entre esperar que un LLM devuelva JSON válido y garantizarlo. Cuando activas la salida estructurada, el modelo físicamente no puede producir tokens que violen tu esquema. Defines un JSON Schema (o modelo Pydantic, o esquema Zod), lo pasas a la API, y recibes una respuesta que coincide con él en cada ocasión.
¿Por qué importa esto? Antes de las salidas estructuradas, los desarrolladores escribían frágiles analizadores de regex, envolvían cada llamada LLM en bloques try/catch JSON.parse, y aún así tenían que lidiar con respuestas "casi correctas" -- JSON válido al que le faltaba un campo o tenía el tipo incorrecto. Toda esa clase de errores ha desaparecido.
Hay tres niveles de imposición de estructura, y representan una evolución clara:
- Ingeniería de prompts -- "Por favor devuelve JSON con estos campos." Poco confiable. El modelo podría cumplir el 80-90% del tiempo.
- Modo JSON -- Garantiza JSON sintácticamente válido, pero no impone tu esquema. Podrías obtener
{"foo": "bar"}cuando esperabas{"name": string, "age": number}. - Modo Estricto / Decodificación restringida -- Garantiza 100% de cumplimiento del esquema. El modelo literalmente no puede generar tokens inválidos. Esto es lo que significa "salida estructurada" en 2026.
Desde principios de 2026, OpenAI, Anthropic y Google Gemini todos soportan salida estructurada nativa. El ecosistema ha convergido.
Veredicto: Si estás analizando respuestas LLM con regex o JSON.parse en producción, lo estás haciendo de la manera difícil. La salida estructurada nativa elimina toda esa clase de fallos.
Modo JSON vs. Modo Estricto: ¿Qué Cambió Realmente?
Esta distinción confunde a muchos desarrolladores porque los nombres suenan similares. No lo son.
| Característica | Modo JSON | Modo Estricto (Salidas Estructuradas) |
|---|---|---|
| Parámetro API | type: "json_object" | type: "json_schema" con strict: true |
| Garantiza JSON válido | Sí | Sí |
| Garantiza cumplimiento del esquema | No | Sí |
| Mecanismo | Sesgo de token post-hoc | Decodificación restringida (FSM) |
| Puede devolver campos inesperados | Sí | No |
| Puede omitir campos requeridos | Sí | No |
| Imposición de tipos | Ninguna | Completa (string, number, array, etc.) |
| Cuándo usar | No tienes un esquema de antemano | Todo en producción |
La cronología: OpenAI introdujo el Modo JSON a finales de 2023. Fue un paso adelante, pero los desarrolladores rápidamente se dieron cuenta de que "JSON válido" no era suficiente -- necesitaban JSON válido según el esquema. En agosto de 2024, OpenAI lanzó Salidas Estructuradas con Modo Estricto, que usa decodificación restringida para garantizar el cumplimiento del esquema. Para 2025-2026, todos los principales proveedores habían adoptado el mismo enfoque.
El Modo JSON todavía tiene un caso de uso estrecho: cuando genuinamente no conoces la forma de la respuesta de antemano y solo quieres algún JSON válido para exploración no estructurada. Pero eso es raro en producción.
Veredicto: Usa el Modo Estricto para todo en producción. El Modo JSON está efectivamente obsoleto para casos de uso vinculados a esquemas. Si tienes un esquema (y deberías tenerlo), usa type: "json_schema" con strict: true.
¿Cómo Funciona Realmente la Decodificación Restringida?
Aquí está el mecanismo que hace posible el 100% de cumplimiento del esquema -- no 99,9%, sino literalmente 100%.
Cuando envías un JSON Schema a un proveedor con el Modo Estricto activado, el esquema se compila en una máquina de estados finitos (FSM). Esta FSM representa cada camino válido a través de tu esquema. En cada paso de generación de tokens, el motor de inferencia verifica qué tokens mantendrían la salida en un camino válido y cuáles no. Los tokens inválidos tienen sus logits establecidos en infinito negativo antes del muestreo, lo que significa que tienen probabilidad cero de ser seleccionados.
<!-- IMAGE: constrained-decoding diagram showing FSM token masking during structured output generation -->Piénsalo como el autocompletado con esteroides. Si el modelo acaba de producir {"rating": y tu esquema dice que rating es un entero, los únicos tokens permitidos a continuación son tokens de dígitos. Comillas, letras, corchetes -- todo enmascarado. El modelo no puede generar "cinco" aunque "quiera" hacerlo.
Este es el mismo mecanismo central utilizado por XGrammar (el motor detrás de vLLM, SGLang y la mayoría de servidores de inferencia local) y Outlines (la biblioteca Python de código abierto para generación restringida). Los proveedores de API simplemente lo han integrado en su infraestructura de inferencia.
Hay un intercambio a conocer: la primera solicitud con un nuevo esquema incurre en un golpe de latencia de compilación (típicamente 50-200ms) mientras se construye la FSM. Las solicitudes posteriores con el mismo esquema usan una FSM en caché y añaden un overhead prácticamente nulo. También hay una consideración de calidad sutil -- restringir el vocabulario de tokens puede ocasionalmente reducir la calidad de salida para campos creativos o de forma libre, así que mantén tus esquemas enfocados en datos verdaderamente estructurados.
Veredicto: La decodificación restringida es lo que separa "generalmente funciona" de "siempre funciona." Es la ingeniería que hace que las salidas estructuradas estén listas para producción.
Implementación Multi-Proveedor: OpenAI, Anthropic y Gemini
Aquí hay algo que ninguna de las otras guías te muestra: la misma tarea de extracción implementada en los tres principales proveedores. Extraeremos una reseña de producto estructurada a partir de texto no estructurado.
El esquema Pydantic (compartido entre todos los proveedores):
from pydantic import BaseModel, Field
from typing import Literal
class ProductReview(BaseModel):
reasoning: str = Field(description="Think through the review before scoring")
rating: int = Field(description="Rating from 1-5", ge=1, le=5)
sentiment: Literal["positive", "negative", "neutral"]
pros: list[str] = Field(description="Key positive points")
cons: list[str] = Field(description="Key negative points")
summary: str = Field(description="One-sentence summary")Implementación OpenAI
from openai import OpenAI
client = OpenAI()
response = client.beta.chat.completions.parse(
model="gpt-4o-2024-08-06",
messages=[
{"role": "system", "content": "Extract a structured review from the text."},
{"role": "user", "content": review_text}
],
response_format=ProductReview, # Modelo Pydantic directamente
)
review = response.choices[0].message.parsed # Objeto ProductReview tipadoLa implementación de OpenAI es la más madura. El método parse() acepta un modelo Pydantic directamente y devuelve un objeto tipado. Una restricción: el Modo Estricto de OpenAI soporta un subconjunto de JSON Schema -- sin $ref, anyOf limitado, y todos los campos deben ser requeridos con additionalProperties: false.
Implementación Anthropic
from anthropic import Anthropic
client = Anthropic()
response = client.messages.create(
model="claude-sonnet-4-5-20250514",
max_tokens=1024,
messages=[
{"role": "user", "content": f"Extract a structured review:\n\n{review_text}"}
],
output_config={
"format": {
"type": "json_schema",
"json_schema": ProductReview.model_json_schema()
}
}
)
import json
review_data = json.loads(response.content[0].text)
review = ProductReview(**review_data)La salida estructurada nativa de Anthropic usa output_config.format con un JSON Schema. Alcanzó disponibilidad general a principios de 2026. Anthropic también soporta el patrón más antiguo de definir una herramienta "falsa" y extraer vía tool_use -- eso todavía funciona, pero la salida estructurada nativa es más limpia para extracción pura.
Implementación Gemini
from google import genai
client = genai.Client()
response = client.models.generate_content(
model="gemini-2.0-flash",
contents=f"Extract a structured review:\n\n{review_text}",
config={
"response_mime_type": "application/json",
"response_schema": ProductReview, # Modelo Pydantic directamente
}
)
import json
review = ProductReview(**json.loads(response.text))Gemini soporta modelos Pydantic directamente en el SDK de Python vía response_schema. Una característica única: Gemini respeta propertyOrdering en el esquema, por lo que puedes controlar el orden de salida de los campos (útil para el patrón razonamiento-primero).
Comparación de Proveedores
| Característica | OpenAI | Anthropic | Gemini |
|---|---|---|---|
| Parámetro API | response_format | output_config.format | response_schema |
| Entrada de esquema | Pydantic o JSON Schema | JSON Schema | Pydantic o JSON Schema |
| Modo estricto | strict: true | Implícito con json_schema | Implícito |
| Streaming | Sí (JSON parcial) | Sí | Sí |
| Manejo de rechazos | campo message.refusal | Respuesta de error | Respuesta de error |
| Alternativa tool-use | Sí | Sí (método original) | Sí |
| Caché de compilación de esquema | Sí (del lado del servidor) | Sí | Sí |
| Ordenamiento de propiedades | Sin soporte nativo | No | Sí (propertyOrdering) |
Veredicto: OpenAI tiene la DX más pulida con su método parse(). Anthropic ofrece los modelos subyacentes más capaces. El ordenamiento de propiedades de Gemini es únicamente útil. Los tres cumplen la tarea -- elige según tu relación existente con el proveedor.
Patrones Pydantic para Desarrolladores Python
Pydantic es el estándar de facto para definir esquemas de salida estructurada en Python. Aquí están los patrones que importan.
Esquema Básico con Descripciones
from pydantic import BaseModel, Field
from typing import Literal, Optional
class ExtractedEntity(BaseModel):
reasoning: str = Field(description="Think step by step about the entity")
name: str = Field(description="Full name of the entity")
entity_type: Literal["person", "company", "location"]
confidence: float = Field(description="Confidence score 0.0-1.0", ge=0.0, le=1.0)
context: Optional[str] = Field(description="Surrounding context, if relevant")Esas cadenas description no son solo para documentación -- se convierten en parte del JSON Schema enviado al modelo e influyen directamente en lo que el modelo genera. Piénsalas como ingeniería de prompts dentro del esquema.
Modelos Anidados
class Address(BaseModel):
street: str
city: str
country: str
postal_code: Optional[str] = None
class Company(BaseModel):
reasoning: str = Field(description="Analysis of the company details")
name: str
industry: Literal["tech", "finance", "healthcare", "retail", "other"]
headquarters: Address # Modelo anidado
key_products: list[str] = Field(description="Top 3 products or services")Mantén el anidamiento a un máximo de 2-3 niveles. Los esquemas profundamente anidados aumentan las tasas de error y ralentizan la compilación del esquema.
El Patrón Razonamiento-Primero
Este es el patrón de diseño de esquema de mayor impacto. Pon un campo reasoning antes de tus campos de respuesta:
# Malo -- el modelo se compromete con una respuesta antes de pensar
class ClassificationBad(BaseModel):
category: Literal["spam", "ham"]
confidence: float
# Bueno -- el modelo razona primero a través del problema
class ClassificationGood(BaseModel):
reasoning: str = Field(description="Analyze the text before classifying")
category: Literal["spam", "ham"]
confidence: float = Field(ge=0.0, le=1.0)Los LLMs generan tokens de izquierda a derecha. Si category viene primero, el modelo elige una categoría y luego la racionaliza. Si reasoning viene primero, el modelo trabaja el problema y luego se compromete con una categoría. Es chain-of-thought horneado en el esquema.
Exportación de JSON Schema
# Genera el JSON Schema para cualquier modelo Pydantic
schema = ProductReview.model_json_schema()
# Pasa esto a cualquier proveedor que acepte JSON Schema crudoVeredicto: Pydantic + campos descriptivos + orden razonamiento-primero es el trío Python de salidas estructuradas. Domina estos tres patrones y manejarás el 90% de los casos de uso.
Patrones Zod para Desarrolladores TypeScript
Zod es el equivalente TypeScript de Pydantic -- y es igual de central para los flujos de trabajo de salida estructurada.
Esquema Básico con Descripciones
import { z } from "zod";
const ProductReview = z.object({
reasoning: z.string().describe("Think through the review before scoring"),
rating: z.number().int().min(1).max(5),
sentiment: z.enum(["positive", "negative", "neutral"]),
pros: z.array(z.string()).describe("Key positive points"),
cons: z.array(z.string()).describe("Key negative points"),
summary: z.string().describe("One-sentence summary"),
});
// Inferir el tipo TypeScript automáticamente
type ProductReview = z.infer<typeof ProductReview>;Al igual que Field(description=...) de Pydantic, .describe() de Zod se convierte en parte del JSON Schema y guía la salida del modelo.
Integración con el SDK Node de OpenAI
import OpenAI from "openai";
import { zodResponseFormat } from "openai/helpers/zod";
const client = new OpenAI();
const response = await client.beta.chat.completions.parse({
model: "gpt-4o-2024-08-06",
messages: [
{ role: "system", content: "Extract a structured review." },
{ role: "user", content: reviewText },
],
response_format: zodResponseFormat(ProductReview, "product_review"),
});
const review = response.choices[0].message.parsed; // ¡Tipado!Integración con el SDK Vercel AI
import { generateObject } from "ai";
import { openai } from "@ai-sdk/openai";
const { object: review } = await generateObject({
model: openai("gpt-4o"),
schema: ProductReview,
prompt: `Extract a structured review:\n\n${reviewText}`,
});
// review está completamente tipado como ProductReviewEl SDK Vercel AI usa Zod de forma nativa con generateObject(), convirtiéndolo en la integración TypeScript más limpia. Funciona con OpenAI, Anthropic, Gemini y otros proveedores a través de una API unificada.
Conversión de JSON Schema
import { zodToJsonSchema } from "zod-to-json-schema";
const jsonSchema = zodToJsonSchema(ProductReview);
// Usar con cualquier proveedor que acepte JSON Schema crudoVeredicto: Zod + .describe() + el SDK Vercel AI es el stack TypeScript de salidas estructuradas. Si estás en el ecosistema Node/Next.js, este es el camino de menor resistencia.
Salida Estructurada vs. Llamada de Función: ¿Cuándo Usar Cada Una?
Esta es una de las fuentes de confusión más comunes. Ambas involucran esquemas, ambas devuelven datos estructurados -- pero resuelven problemas diferentes.
La salida estructurada dice: "Dame datos en esta forma exacta." Es para extracción, clasificación y formato. Estás extrayendo información estructurada de texto no estructurado.
La llamada de función (uso de herramientas) dice: "Aquí hay acciones que puedes tomar -- decide cuál ejecutar y proporciona los argumentos." Es para flujos de trabajo de agentes donde el modelo elige entre múltiples herramientas y activa acciones.
La confusión tiene sentido históricamente. La "salida estructurada" original de Anthropic era literalmente una llamada de función -- definías una herramienta falsa llamada extract_review y obtenías los argumentos. Eso todavía funciona, pero la salida estructurada nativa es más simple para extracción pura.
| Escenario | Mejor Enfoque | Por Qué |
|---|---|---|
| Extraer datos de texto | Salida estructurada | Directo, menor latencia, esquema único |
| Clasificar en categorías | Salida estructurada | Una respuesta, un esquema |
| Agente decidiendo qué herramienta llamar | Llamada de función | El modelo elige entre múltiples herramientas |
| Orquestación multi-paso | Llamada de función | Invocaciones de herramientas secuenciales |
| Extraer datos Y decidir la próxima acción | Ambas | Salida estructurada para extracción, llamada de función para orquestación |
La salida estructurada impulsa los pipelines de llamada de herramientas en los sistemas de agentes de IA. Consulta nuestra guía de agentes de IA para negocios para ver cómo encajan en los flujos de trabajo de producción.
Veredicto: Usa salida estructurada cuando sabes qué forma deben tener los datos. Usa llamada de función cuando el modelo necesita elegir una acción. En la práctica, la mayoría de las aplicaciones usan ambas -- salida estructurada para extracción de datos y llamada de función para orquestación de agentes.
Patrones de Producción: Errores, Reintentos y Streaming
Hacer que las salidas estructuradas funcionen en una demostración es fácil. Mantenerlas confiables en producción requiere manejar tres cosas: rechazos, fallos de validación y streaming.
Manejo de Rechazos
A veces un modelo rechaza generar tu salida solicitada -- típicamente porque los filtros de seguridad marcaron la entrada. Cuando esto sucede, las APIs de salida estructurada no devuelven tu esquema. Devuelven un rechazo.
response = client.beta.chat.completions.parse(
model="gpt-4o-2024-08-06",
messages=messages,
response_format=ProductReview,
)
# SIEMPRE verifica el rechazo antes de acceder al contenido analizado
if response.choices[0].message.refusal:
print(f"Model refused: {response.choices[0].message.refusal}")
else:
review = response.choices[0].message.parsedSi omites la verificación de rechazo e intentas acceder a .parsed en un rechazo, obtendrás None y un error posterior confuso. Verifica primero, siempre.
Patrones de Reintento con Retroalimentación de Validación
El cumplimiento del esquema está garantizado por la decodificación restringida, pero la corrección semántica no. El modelo podría devolver {"rating": 1, "sentiment": "positive"} -- esquema válido, contenido contradictorio. Ahí es donde entran la validación + los reintentos.
import instructor
client = instructor.from_openai(OpenAI())
# Instructor maneja los reintentos automáticamente
review = client.chat.completions.create(
model="gpt-4o",
response_model=ProductReview,
max_retries=3, # Reintentos con retroalimentación de error de validación
messages=[
{"role": "user", "content": review_text}
],
)Instructor retroalimenta el error de validación al modelo en el reintento, para que pueda autocorregirse. Para patrones de reintento manual sin Instructor:
from pydantic import ValidationError
for attempt in range(3):
try:
response = client.beta.chat.completions.parse(
model="gpt-4o-2024-08-06",
messages=messages,
response_format=ProductReview,
)
review = response.choices[0].message.parsed
# Ejecuta validación semántica adicional aquí
break
except ValidationError as e:
messages.append({"role": "assistant", "content": str(response)})
messages.append({"role": "user", "content": f"Validation error: {e}. Fix it."})Streaming de Salida Estructurada
Para respuestas estructuradas grandes -- arrays largos, muchos campos, objetos anidados complejos -- el streaming te permite renderizar resultados parciales progresivamente.
import instructor
client = instructor.from_openai(OpenAI())
# Streamear resultados parciales a medida que los campos se llenan
review_stream = client.chat.completions.create_partial(
model="gpt-4o",
response_model=ProductReview,
messages=[{"role": "user", "content": review_text}],
)
for partial_review in review_stream:
# Los campos se llenan uno por uno a medida que los tokens llegan en streaming
if partial_review.summary:
print(f"Summary so far: {partial_review.summary}")Una trampa: los fragmentos de streaming individuales no son schema-válidos por sí solos. El campo reasoning podría estar lleno mientras rating todavía sea None. Planifica tu UI en consecuencia -- muestra un estado de carga para los campos no llenos.
Veredicto: Las verificaciones de rechazo son no negociables. Los reintentos con retroalimentación de validación capturan errores semánticos. El streaming vale la pena para cualquier respuesta que tome más de un par de segundos.
Comparación de Bibliotecas de Salida Estructurada
Puedes usar salidas estructuradas a través de APIs nativas, pero las bibliotecas añaden validación, reintentos, streaming y soporte multi-proveedor. Aquí está el panorama.
Instructor es la opción más popular con 11K+ estrellas en GitHub y 3M+ descargas mensuales. Envuelve OpenAI, Anthropic, Gemini, Cohere, Ollama y más con una interfaz unificada basada en Pydantic. Características clave: reintentos automáticos con retroalimentación de validación, streaming vía create_partial(), y configuración simple (instructor.from_openai(client)). Si eres un equipo Python, empieza aquí.
BAML adopta un enfoque diferente: esquema-primero vía un DSL personalizado. Defines esquemas en archivos .baml y auto-generas clientes para Python, TypeScript, Ruby y más. Su algoritmo SAP (schema-aligned parsing) maneja elegantemente las salidas desordenadas del modelo. Mejor para equipos multi-lenguaje o cuando quieres contratos entre tu capa LLM y la capa de aplicación. Desventaja: paso de construcción adicional y nueva sintaxis que aprender.
LangChain ofrece .with_structured_output(schema) para salida estructurada independiente del proveedor. Conveniente si ya estás en el ecosistema LangChain. Desventaja: es una dependencia pesada, y la abstracción puede ocultar características específicas del proveedor que podrías necesitar.
APIs nativas -- llamadas directas con response_format / output_config -- no requieren dependencias más allá del SDK del proveedor. Obtienes control total y visibilidad total. Mejor para casos de uso simples o equipos que prefieren abstracción mínima.
| Biblioteca | Lenguajes | Proveedores | Reintentos Auto | Streaming | Estrellas GitHub | Curva de Aprendizaje |
|---|---|---|---|---|---|---|
| Instructor | Python, TS | 15+ | Sí | Sí | 11K+ | Baja |
| BAML | Python, TS, Ruby, Go | Todos (DSL-agnóstico) | Sí | Sí | 7K+ | Media |
| LangChain | Python, TS | 20+ | Parcial | Sí | 100K+ | Media-Alta |
| APIs nativas | Cualquiera | 1 por SDK | No | Sí | N/A | Baja |
Elegir la biblioteca de salida estructurada correcta es parte de una decisión más amplia sobre el stack de IA. Desglosamos el stack completo en nuestra guía Best AI Stack para SaaS.
Consulta nuestras Mejores Bibliotecas para Salidas Estructuradas de LLM [próximamente] para una comparación en profundidad de Instructor, BAML, Mirascope y más.
Veredicto: Empieza con Instructor para Python, APIs nativas para TypeScript. Muévete a BAML si necesitas contratos de esquema entre lenguajes. Evita LangChain solo para salidas estructuradas -- es excesivo.
Mejores Prácticas de Diseño de Esquemas (y Errores Comunes)
El diseño de tu esquema impacta directamente la calidad de la salida. Aquí están los patrones que importan y los errores que cuestan precisión.
Poner el Razonamiento Antes que las Respuestas
Cubrimos esto en la sección de Pydantic, pero vale la pena repetirlo porque es la decisión de diseño de mayor impacto:
# Antes: el modelo adivina la respuesta, luego la racionaliza
class Bad(BaseModel):
answer: str
reasoning: str
# Después: el modelo piensa primero, luego se compromete
class Good(BaseModel):
reasoning: str = Field(description="Think step by step")
answer: strLos LLMs generan de izquierda a derecha. El orden de los campos es el orden del prompt. Razonamiento primero significa que el modelo debe trabajar el problema antes de comprometerse con una respuesta.
La Tabla de Anti-Patrones
| Error | Problema | Solución |
|---|---|---|
| Campo de razonamiento después de la respuesta | El modelo decide antes de pensar | Mover el razonamiento antes de la respuesta |
| Profundamente anidado (4+ niveles) | Mayor tasa de error, compilación más lenta | Aplanar a 2-3 niveles |
| Sin descripciones de campos | El modelo adivina lo que quieres | Añadir .describe() / Field(description=...) |
| Manejo de nulos faltante | El modelo alucina un valor para llenar el campo | Usar Optional / .nullable() |
| Esquemas demasiado grandes (50+ campos) | Timeout de compilación, degradación de calidad | Dividir en múltiples llamadas |
| Opciones de enum vagas | El modelo elige la categoría incorrecta | Usar opciones específicas y no superpuestas |
Manejar los Nulos Explícitamente
Si un campo podría no tener datos en el texto fuente, hazlo opcional. Forzar un campo requerido cuando los datos no existen conduce a alucinaciones:
class PersonInfo(BaseModel):
name: str # Siempre presente
email: Optional[str] = Field(None, description="Email if mentioned, null otherwise")
phone: Optional[str] = Field(None, description="Phone if mentioned, null otherwise")Mantener los Esquemas Enfocados
Un esquema por tarea. No intentes extraer todo en un solo esquema masivo. Si necesitas 50+ campos, divide en múltiples llamadas de extracción. El Modo Estricto de OpenAI tiene límites prácticos en la complejidad del esquema, y aunque funcione, los esquemas muy grandes degradan la calidad de la salida.
Veredicto: Razonamiento-primero, campos descriptivos, nulos explícitos y esquemas enfocados. Haz bien estos cuatro y tu precisión en salidas estructuradas sube de manera medible.
Salida Estructurada con LLMs Locales
No necesitas un proveedor de API para salidas estructuradas. Los motores de inferencia local lo soportan a través de decodificación restringida basada en gramática -- el mismo mecanismo fundamental, ejecutándose en tu propio hardware.
Ollama
El camino más fácil para salidas estructuradas locales. Ollama acepta un JSON Schema vía el parámetro format:
import ollama
from pydantic import BaseModel
class Country(BaseModel):
name: str
capital: str
languages: list[str]
response = ollama.chat(
model="llama3.2",
messages=[{"role": "user", "content": "Tell me about Japan."}],
format=Country.model_json_schema(),
)
import json
country = Country(**json.loads(response.message.content))Ollama usa XGrammar internamente para la decodificación restringida. La misma garantía que los proveedores de API: 100% de cumplimiento del esquema.
vLLM y SGLang
Para inferencia local de calidad producción, vLLM y SGLang ambos soportan salidas estructuradas a través de los parámetros guided_json y guided_regex. XGrammar es el backend predeterminado, entregando un overhead prácticamente nulo en la generación de JSON -- hasta 3,5x más rápido que los motores de gramática alternativos.
Outlines
Outlines es la biblioteca Python de código abierto que pionó la generación restringida basada en gramática. Funciona con cualquier modelo de Hugging Face y soporta restricciones de JSON Schema, regex y gramática libre de contexto completa (CFG/EBNF). También está integrada en vLLM y SGLang como opción de backend de gramática.
La diferencia clave con los proveedores de API: la salida estructurada local no tiene limitaciones de subconjunto de esquema. Controlas completamente la gramática. Pero la calidad del modelo varía más -- un modelo local de 7B parámetros no igualará a GPT-4o o Claude en tareas de extracción complejas. El esquema siempre será válido; la calidad del contenido depende del modelo.
Veredicto: Ollama para desarrollo, vLLM/SGLang con XGrammar para producción. Las salidas estructuradas locales son suficientemente maduras para la mayoría de los casos de uso, con la advertencia de que los modelos más pequeños producen contenido de menor calidad dentro del esquema.
Preguntas Frecuentes
¿Qué es la salida estructurada en LLMs?
La salida estructurada es un mecanismo que garantiza que la respuesta de un LLM se ajuste a un JSON Schema predefinido. A diferencia del texto plano o incluso el Modo JSON, la salida estructurada usa decodificación restringida para asegurar que cada campo, tipo y restricción en tu esquema se cumple -- el 100% del tiempo, no "generalmente".
¿Cuál es la diferencia entre el Modo JSON y las Salidas Estructuradas?
El Modo JSON garantiza JSON sintácticamente válido pero no impone tu esquema -- podrías obtener cualquier objeto JSON válido. Las Salidas Estructuradas (Modo Estricto) garantizan cumplimiento completo del esquema a través de la decodificación restringida. Usa el Modo Estricto para producción; el Modo JSON solo es relevante cuando no tienes un esquema de antemano.
¿Qué proveedores LLM soportan salidas estructuradas de forma nativa?
OpenAI (desde agosto de 2024), Google Gemini (2024, expandido 2026), Anthropic (beta noviembre 2025, GA principios de 2026), Cohere y xAI (Grok) todos soportan salidas estructuradas nativas. En el lado local, Ollama, vLLM y SGLang las soportan a través de decodificación restringida basada en gramática.
¿Cómo garantiza la decodificación restringida el cumplimiento del esquema?
El JSON Schema se compila en una máquina de estados finitos (FSM). En cada paso de generación de tokens, solo se permiten tokens que mantengan la salida en un camino válido a través de la FSM -- los tokens inválidos tienen sus logits establecidos en infinito negativo. Esto significa que los tokens inválidos tienen probabilidad cero de ser generados, dándote una garantía matemática, no estadística.
¿Debo usar salida estructurada o llamada de función?
Usa salida estructurada para extracción y clasificación -- cuando quieres datos en una forma específica. Usa llamada de función para flujos de trabajo de agentes -- cuando el modelo necesita decidir qué acción tomar. Muchas aplicaciones de producción usan ambas: salida estructurada para extracción de datos y llamada de función para orquestación.
¿Puedo hacer streaming de salidas estructuradas?
Sí. OpenAI soporta streaming con el método parse(), e Instructor proporciona create_partial() para hacer streaming de modelos Pydantic que se llenan campo por campo. Ten en cuenta que los fragmentos de streaming individuales no son individualmente schema-válidos -- los campos se llenan incrementalmente.
¿Qué es la biblioteca Instructor?
Instructor es la biblioteca de salida estructurada más popular (11K+ estrellas en GitHub, 3M+ descargas mensuales). Envuelve los SDKs de proveedores con validación basada en Pydantic, reintentos automáticos con retroalimentación de validación y soporte de streaming. Funciona con OpenAI, Anthropic, Gemini, Cohere, Ollama y 10+ otros proveedores.
¿Funciona la salida estructurada con LLMs locales?
Sí. Ollama soporta salidas estructuradas vía el parámetro format con JSON Schema. vLLM y SGLang las soportan a través de los parámetros guided_json. Los tres usan XGrammar u Outlines para la decodificación restringida. La garantía de cumplimiento del esquema es la misma que los proveedores de API; la calidad del contenido depende del modelo.
¿Cuáles son los errores comunes en el diseño de esquemas?
Los principales errores: poner el campo de razonamiento después del campo de respuesta (el modelo decide antes de pensar), esquemas profundamente anidados (4+ niveles aumentan errores), descripciones de campos faltantes (el modelo adivina la intención), sin manejo de nulos para datos opcionales (fuerza alucinaciones), y esquemas demasiado grandes (50+ campos degradan la calidad).
¿La salida estructurada añade latencia?
Hay un overhead de compilación del esquema en la primera solicitud -- típicamente 50-200ms mientras se construye la FSM. Las solicitudes posteriores con el mismo esquema usan una FSM en caché y añaden una latencia prácticamente nula. Para la mayoría de las aplicaciones, esto es insignificante comparado con el tiempo total de inferencia del modelo.
¿Puedo usar salidas estructuradas con imágenes o entradas multimodales?
Sí. La salida estructurada se aplica al formato de respuesta, no a la entrada. Puedes enviar una imagen a GPT-4o o Gemini con un esquema de salida estructurada y obtener de vuelta un análisis schema-conforme de la imagen. Esto es poderoso para flujos de trabajo de extracción visual -- extrayendo datos estructurados de recibos, formularios o imágenes de productos.