Salida estructurada de LLM: JSON Schema obligatorio y fiabilidad en tool calling

En producción suena la alarma: fallo en tool calling del Agent, cinco reintentos seguidos, todos por errores de formato de parámetros. Reviso los logs y el campo city debería ser "北京", pero el LLM devolvió {"name": "北京", "id": null}. El analizador se cae y todo el pipeline de datos se detiene.
Fue un agujero grande del año pasado.
Desde entonces empecé a estudiar de forma sistemática la salida estructurada de LLM: desde Structured Outputs de OpenAI hasta Tool Use de Anthropic, desde los reintentos automáticos de Instructor hasta la decodificación restringida de Outlines. Al principio pensé que bastaba con «escribir mejor el prompt», pero no — no es un problema de prompts, es un problema de arquitectura de fiabilidad.
Este artículo comparte esa «arquitectura de fiabilidad en tres capas»: capa de validación de parámetros, capa de reintentos con feedback y capa de decodificación restringida. Al final comparo horizontalmente las soluciones de OpenAI, Claude y Gemini para ayudarte a elegir. También incluyo plantillas de código listas para producción.
1. Por qué la salida estructurada es la base de un Agent
Empiezo con el «format drift» que viví. No es un caso aislado: es la pesadilla de quien desarrolla Agents.
Tres formas de format drift
Primera: campos faltantes. Pides un objeto de usuario con name, age y email, y recibes {"name": "张三"} — faltan los otros dos. No falla siempre, falla a veces. En producción, «a veces» es «inevitable».
Segunda: errores de tipo. La documentación dice claro: user_id es entero. El LLM devuelve "user_id": "12345", una cadena. La validación Pydantic en Python falla y se rompe toda la cadena de llamadas.
Tercera: contenido extra. La más sigilosa. Le pides JSON y te añade al inicio «Here is the response:» y al final «I hope this helps!». El analizador JSON no sabe qué hacer.
Los datos oficiales de OpenAI lo dejan claro: JSON Mode (solo garantiza JSON válido) falla entre un 5% y un 10%; Structured Outputs (obliga a seguir el Schema) tiene una tasa de fallo inferior al 0.1%. Dos órdenes de magnitud de diferencia.
Por qué importa tanto
Quizá pienses: «¿No es solo un fallo de parsing? Añade unos reintentos».
El problema es que reintentar no es gratis.
Coste de llamadas API. Una llamada a GPT-4 puede costar unos céntimos; cinco reintentos son varias veces más. Si tu Agent procesa 100.000 peticiones al día con una media de 2 reintentos por petición, haz tú la cuenta.
Latencia acumulada. Una llamada de 2 segundos, tres reintentos: el usuario espera más de 6 segundos. En diálogo en tiempo real, es inaceptable.
Experiencia de usuario rota. El usuario pregunta por el tiempo, tu Agent se queda colgado 10 segundos y acaba con «error del sistema». La próxima vez no vuelve.
La salida estructurada no es un «extra bonito»: es la base para que un Agent funcione de forma estable. A continuación explico cómo resolverlo — no con «mejor prompt», sino con una arquitectura fiable.
2. Arquitectura de fiabilidad en tres capas
Esta arquitectura la resumí después de muchos tropiezos. No es bala de plata, pero baja la probabilidad de errores de formato del 5-10% a casi cero.
L1: capa de validación de parámetros — primera línea de defensa
Aquí la idea es simple: define la estructura de datos esperada con Pydantic, fuerza la conversión de tipos y filtra con listas blancas.
from pydantic import BaseModel, Field, field_validator
from typing import Optional, List
from datetime import datetime
class ToolCallParams(BaseModel):
"""Modelo de parámetros para tool calling"""
city: str = Field(..., min_length=1, max_length=50, description="Nombre de la ciudad")
date: Optional[datetime] = Field(None, description="Fecha de consulta")
units: str = Field("metric", pattern="^(metric|imperial)$")
@field_validator("city")
@classmethod
def validate_city(cls, v: str) -> str:
# Validación por lista blanca
allowed_cities = {"北京", "上海", "广州", "深圳", "杭州"}
if v not in allowed_cities:
raise ValueError(f"Ciudad no soportada: {v}, soportadas: {allowed_cities}")
return v
Pydantic hace tres cosas: conversión forzada de tipos (cadena "123" a entero 123), detección de campos faltantes y validación personalizada. Es la capa más básica y la más importante.
L2: capa de reintentos — autocorrección con feedback
Cuando los datos del LLM no pasan la validación, no basta con reintentar a ciegas: hay que devolver el error para que se corrija. La biblioteca Instructor encapsula muy bien esta lógica.
import instructor
from openai import OpenAI
from pydantic import ValidationError
client = instructor.patch(OpenAI())
def get_weather_with_retry(user_query: str, max_retries: int = 3):
"""Mecanismo de reintento con feedback de errores"""
messages = [{"role": "user", "content": user_query}]
for attempt in range(max_retries):
try:
response = client.chat.completions.create(
model="gpt-4o",
response_model=ToolCallParams, # Modelo Pydantic
messages=messages,
temperature=0.1 # Temperatura baja para salida estructurada
)
return response # Validación automática superada
except ValidationError as e:
# Devolver el error al LLM para que corrija
error_msg = f"Validación de parámetros fallida: {str(e)}\nCorrige y devuelve JSON con el formato correcto."
messages.append({"role": "assistant", "content": "Generando parámetros..."})
messages.append({"role": "user", "content": error_msg})
if attempt == max_retries - 1:
raise Exception(f"Sigue fallando tras {max_retries} reintentos: {e}")
# Ejemplo de uso
result = get_weather_with_retry("帮我查一下北京明天的天气")
La idea central: el LLM no adivina a ciegas; sabe qué falló y por qué. Con feedback, puede corregir. En mis pruebas, con este mecanismo la tasa de éxito en reintentos subió del 60% a más del 95%.
L3: capa de decodificación restringida — evitar errores desde el origen
Las dos capas anteriores son «remedio después del hecho»; L3 es «prevención antes».
La decodificación restringida funciona así: al generar cada token, una máquina de estados finitos (FSM) limita las opciones y obliga a producir solo secuencias que cumplen el Schema. Es como un «freno» al LLM: no puede salirse del formato.
Hay dos opciones principales:
Outlines (open source, ideal para modelos locales):
from outlines import models, generate
import json
# Cargar modelo local
model = models.transformers("Qwen/Qwen2.5-7B-Instruct")
# Definir Schema
schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"}
},
"required": ["name", "age"]
}
# Crear generador restringido
generator = generate.json(model, schema)
result = generator("提取用户信息: 张三今年28岁")
# 100% conforme al Schema, sin reintentos
guided_json de vLLM (para desplegar modelos grandes):
from vllm import LLM, SamplingParams
llm = LLM(model="Qwen/Qwen2.5-72B-Instruct")
sampling_params = SamplingParams(
temperature=0.0,
guided_decoding_backend="outlines",
guided_json={ # Pasar JSON Schema directamente
"type": "object",
"properties": {
"tool_name": {"type": "string"},
"arguments": {"type": "object"}
}
}
)
L3 tiene coste de compilación extra: la FSM se construye a partir del Schema. Si el Schema cambia a menudo, recompilar añade latencia. En la mayoría de Agents el Schema es estable y el coste es aceptable.
Cómo elegir entre las tres capas
| Escenario | Solución recomendada |
|---|---|
| Llamadas a OpenAI API | L1 + L2 (Pydantic + Instructor) |
| Llamadas a Claude API | L1 + L2 (Claude no soporta Strict Mode) |
| Modelos locales desplegados | L1 + L3 (Outlines/vLLM guided_json) |
| Fiabilidad extrema | L1 + L2 + L3 completas |
3. Comparativa de proveedores: OpenAI, Claude y Gemini
Aquí entran las diferencias de implementación. Sin comparativa horizontal es fácil tropezar: «salida estructurada» no significa lo mismo en cada proveedor.
OpenAI: Strict Mode, cumplimiento obligatorio
OpenAI lanzó Structured Outputs en agosto de 2024; hoy es la opción más fiable entre APIs comerciales.
El mecanismo clave es strict: true. Activado, la salida del LLM queda restringida al JSON Schema definido con garantía del 100% de cumplimiento. Por debajo usa decodificación restringida basada en gramáticas, similar a Outlines.
from openai import OpenAI
client = OpenAI()
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "提取用户信息"}],
response_format={
"type": "json_schema",
"json_schema": {
"name": "user_info",
"strict": True, # Parámetro clave
"schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"}
},
"required": ["name", "age"]
}
}
}
)
# Salida 100% conforme al Schema
OpenAI reporta tasa de fallo inferior al 0.1% en Strict Mode. En mi uso no he visto errores de formato — con limitaciones: no soporta Schemas recursivos; estructuras anidadas complejas requieren adaptaciones.
Anthropic Claude: Tool Use, sin garantía de cumplimiento
Claude usa otra vía: Tool Use (tool calling).
Defines una herramienta y Claude la invoca con parámetros. Pero hay trampa: el parámetro strict existe pero la documentación oficial dice que se ignora. Claude no garantiza que los parámetros cumplan el Schema.
Texto oficial de Anthropic (actualizado abril 2026):
“The
strictparameter is currently ignored for tool definitions. Claude will make a best effort to provide valid arguments, but does not guarantee schema compliance.”
Traducción: lo intentará, pero no lo garantiza. Con Claude en tool calling, añade siempre L1 (validación) y L2 (reintentos).
import anthropic
client = anthropic.Anthropic()
# Definición de herramienta en Claude
tools = [{
"name": "get_weather",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string"}
},
"required": ["city"]
}
}]
response = client.messages.create(
model="claude-3.5-sonnet",
max_tokens=1024,
tools=tools,
messages=[{"role": "user", "content": "北京天气"}]
)
# Importante: validar manualmente los parámetros de tool_use
for block in response.content:
if block.type == "tool_use":
# Validación Pydantic aquí
validated_params = ToolCallParams.model_validate(block.input)
Google Gemini: Controlled Generation
Gemini usa Controlled Generation con el parámetro response_schema para fijar la estructura de salida.
import google.generativeai as genai
model = genai.GenerativeModel('gemini-1.5-pro')
response = model.generate_content(
"提取用户信息",
generation_config={
"response_mime_type": "application/json",
"response_schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"}
},
"required": ["name", "age"]
}
}
)
La fiabilidad de Gemini está entre OpenAI y Claude: hay restricción, pero no con la fuerza de «cumplimiento obligatorio» de OpenAI. En pruebas reales la tasa de fallo ronda el 1-2%: mejor que JSON Mode, pero lejos de Strict Mode.
Modelos open source: dependen de Outlines/vLLM
Modelos open source (Qwen, Llama, Mistral) no traen salida estructurada nativa; hace falta tooling externo. Las opciones habituales son Outlines y guided_json de vLLM.
Detalle interesante: un modelo open source con Outlines puede ser más fiable que algunas APIs comerciales, porque la FSM es restricción dura, no «lo intentamos pero no prometemos».
Tabla rápida de elección
| Necesidad | Solución recomendada | Motivo |
|---|---|---|
| Solo API, máxima estabilidad | OpenAI + Structured Outputs | ~0.1% de fallos, la más fiable |
| Razonamiento complejo + tools | Claude + validación L1/L2 | Fuerte en razonamiento, hay que validar |
| Modelo privado desplegado | Qwen/Llama + Outlines | Coste controlado, alta fiabilidad |
| Formato crítico (finanzas, salud) | OpenAI Strict u Outlines | Casi cero fallos |
| Prototipo rápido | Instructor + cualquier API | Bien encapsulado, reintentos automáticos |
4. Plantillas de código para producción
Aquí van plantillas que he validado en producción. Puedes usarlas directamente.
Plantilla 1: OpenAI Structured Outputs completo
"""
Ejemplo completo de OpenAI Structured Outputs
Para: tool calling, extracción de datos, generación de informes, etc.
"""
from openai import OpenAI
from pydantic import BaseModel, Field
from typing import List, Optional
import json
# 1. Definir modelo Pydantic
class SearchQuery(BaseModel):
"""Parámetros de búsqueda"""
keywords: List[str] = Field(
...,
min_length=1,
max_length=5,
description="Lista de palabras clave"
)
filters: Optional[dict] = Field(
default=None,
description="Filtros opcionales"
)
limit: int = Field(
default=10,
ge=1,
le=100,
description="Número de resultados"
)
# 2. Pydantic a JSON Schema
def model_to_schema(model: type[BaseModel]) -> dict:
"""Convierte modelo Pydantic a JSON Schema"""
schema = model.model_json_schema()
# Limpiar metadatos de Pydantic
schema.pop("title", None)
for prop in schema.get("properties", {}).values():
prop.pop("title", None)
return schema
# 3. Llamada de salida estructurada
client = OpenAI()
def extract_search_params(user_input: str) -> SearchQuery:
"""Extrae parámetros de búsqueda del input del usuario"""
schema = model_to_schema(SearchQuery)
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{
"role": "system",
"content": "你是一个搜索助手,帮助用户提取搜索参数。"
},
{"role": "user", "content": user_input}
],
response_format={
"type": "json_schema",
"json_schema": {
"name": "search_query",
"strict": True,
"schema": schema
}
},
temperature=0.1 # Temperatura baja para salida estructurada
)
# 4. Analizar y validar de nuevo
raw_content = response.choices[0].message.content
data = json.loads(raw_content)
return SearchQuery.model_validate(data)
# Ejemplo de uso
if __name__ == "__main__":
query = extract_search_params(
"我想找一些关于 Python 异步编程的文章,只要最近一个月的,最多 20 条"
)
print(query)
# SearchQuery(keywords=['Python', '异步编程'], filters={'date_range': 'last_month'}, limit=20)
Plantilla 2: reintentos automáticos con Instructor
"""
Ejemplo de reintentos automáticos con Instructor
Para: Claude API, OpenAI JSON Mode (no Strict), escenarios que toleran fallos
"""
import instructor
from openai import OpenAI
from pydantic import BaseModel, Field, ValidationError
class AgentAction(BaseModel):
"""Decisión de acción del Agent"""
action_type: str = Field(
...,
pattern="^(search|execute|respond|clarify)$"
)
parameters: dict = Field(default_factory=dict)
reasoning: str = Field(..., min_length=10)
# patch del cliente OpenAI
client = instructor.patch(OpenAI())
def get_agent_decision(
context: str,
user_request: str,
max_retries: int = 3
) -> AgentAction:
"""
Obtiene la decisión del Agent con reintentos automáticos
Args:
context: Contexto actual de la conversación
user_request: Petición del usuario
max_retries: Máximo de reintentos
Returns:
AgentAction: Decisión validada
"""
messages = [
{"role": "system", "content": "你是一个智能助手,分析用户需求并决定下一步行动。"},
{"role": "user", "content": f"上下文: {context}\n\n用户请求: {user_request}"}
]
try:
response = client.chat.completions.create(
model="gpt-4o",
response_model=AgentAction, # Validación automática de Instructor
messages=messages,
max_retries=max_retries, # Reintentos integrados
temperature=0.1
)
return response
except ValidationError as e:
# Instructor ya reintentó max_retries veces
raise Exception(f"Error de formato irreparable, revisa la definición del modelo: {e}")
# Ejemplo de uso
decision = get_agent_decision(
context="用户正在查询天气信息",
user_request="帮我查北京明天的天气,要是晴天就推荐户外活动"
)
print(f"Tipo de acción: {decision.action_type}")
print(f"Parámetros: {decision.parameters}")
print(f"Razonamiento: {decision.reasoning}")
Plantilla 3: salida estructurada con Outlines en local
"""
Ejemplo de salida estructurada con Outlines en modelos locales
Para: despliegue privado, coste sensible, requisitos de privacidad
"""
from outlines import models, generate
from pydantic import BaseModel
from typing import List
import json
# Definir estructura de datos
class ProductInfo(BaseModel):
"""Información de producto"""
name: str
price: float
category: str
tags: List[str]
# Cargar modelo (primera carga tarda unos segundos)
model = models.transformers("Qwen/Qwen2.5-7B-Instruct")
# Crear generador estructurado
# Nota: el schema se compila a FSM en la primera llamada (~1-2 s)
schema_str = json.dumps(ProductInfo.model_json_schema())
generator = generate.json(model, schema_str)
def extract_product_info(description: str) -> ProductInfo:
"""
Extrae información estructurada de la descripción del producto
Args:
description: Texto de descripción
Returns:
ProductInfo: Información estructurada
"""
prompt = f"从以下商品描述中提取关键信息,以 JSON 格式返回:\n{description}"
# Resultado 100% conforme al Schema
result = generator(prompt)
# Convertir a modelo Pydantic (segunda validación)
return ProductInfo.model_validate(result)
# Ejemplo de uso
description = """
这款蓝牙耳机采用最新的降噪技术,价格 299 元,
属于数码配件类,适合运动、通勤等场景使用。
"""
product = extract_product_info(description)
print(product)
# ProductInfo(name='蓝牙耳机', price=299.0, category='数码配件', tags=['运动', '通勤'])
Plantilla 4: flujo completo de tool calling
"""
Flujo completo de validación de parámetros en tool calling
Incluye: definición Schema → llamada LLM → validación → reintentos → ejecución
"""
from openai import OpenAI
from pydantic import BaseModel, Field, field_validator, ValidationError
from typing import Callable, Dict, Any
import json
# 1. Modelo de parámetros de herramienta
class WeatherQueryParams(BaseModel):
"""Parámetros de consulta meteorológica"""
city: str = Field(..., min_length=1, max_length=50)
date_offset: int = Field(default=0, ge=-7, le=7, description="Desplazamiento de fecha, 0=hoy")
@field_validator("city")
@classmethod
def validate_city(cls, v: str) -> str:
allowed = {"北京", "上海", "广州", "深圳", "杭州", "成都", "武汉"}
if v not in allowed:
raise ValueError(f"Ciudad no soportada, opciones: {allowed}")
return v
# 2. Gestor de tool calling
class ToolCallManager:
"""Gestiona el flujo completo de tool calling"""
def __init__(self):
self.client = OpenAI()
self.tools: Dict[str, Callable] = {}
def register_tool(self, name: str, func: Callable, param_model: type[BaseModel]):
"""Registra una herramienta"""
self.tools[name] = {
"function": func,
"param_model": param_model
}
def execute_with_retry(
self,
tool_name: str,
user_request: str,
max_retries: int = 3
) -> Any:
"""Ejecuta tool calling con reintentos"""
tool_config = self.tools[tool_name]
param_model = tool_config["param_model"]
schema = param_model.model_json_schema()
messages = [
{"role": "system", "content": f"提取工具 '{tool_name}' 的调用参数"},
{"role": "user", "content": user_request}
]
for attempt in range(max_retries):
try:
# Llamar al LLM para obtener parámetros
response = self.client.chat.completions.create(
model="gpt-4o",
messages=messages,
response_format={
"type": "json_schema",
"json_schema": {
"name": tool_name,
"strict": True,
"schema": schema
}
},
temperature=0.1
)
# Validar parámetros
params = param_model.model_validate_json(
response.choices[0].message.content
)
# Ejecutar herramienta
return tool_config["function"](params)
except ValidationError as e:
# Devolver error para corrección
messages.append({
"role": "user",
"content": f"Validación fallida: {e}\nCorrige el formato de parámetros."
})
continue
raise Exception(f"Tool calling fallido tras {max_retries} reintentos")
# 3. Ejemplo de uso
def get_weather(params: WeatherQueryParams) -> str:
"""Simula consulta meteorológica"""
# Aquí iría la lógica real de la API
return f"{params.city} 未来 {params.date_offset} 天天气晴朗"
manager = ToolCallManager()
manager.register_tool("get_weather", get_weather, WeatherQueryParams)
result = manager.execute_with_retry(
"get_weather",
"帮我查一下北京明天的天气"
)
print(result) # 北京未来 1 天天气晴朗
Estas plantillas cubren los escenarios más habituales. Combínalas y adáptalas según tu caso.
5. Buenas prácticas en producción
El código no lo es todo: en producción hay muchos detalles. Comparto trampas que pisé y cómo las resolví.
Temperature: no la subas demasiado
En salida estructurada, Temperature entre 0.0 y 0.2. OpenAI lo recomienda y en pruebas es lo más estable.
¿Problema de temperatura alta? El LLM «dispersa» más y la salida es más aleatoria. La aleatoriedad es enemiga del formato estructurado: buscas determinismo, no creatividad. Con Temperature 0.7 tuve ~15% de errores de formato; con 0.1 casi desaparecieron.
Reintentos: no todo error merece reintento
Antes de reintentar, clasifica el error:
| Tipo de error | ¿Reintentar? | Motivo |
|---|---|---|
| Formato de parámetros (campo faltante, tipo incorrecto) | Sí + feedback de error | El LLM puede autocorregirse |
| Error de servicio API (429, 500) | Sí + backoff | Problema temporal del servidor |
| Validación de negocio (ciudad fuera de lista blanca) | No, devolver error | Requiere confirmación del usuario |
| Fallo de ejecución de herramienta (resultado vacío) | No, usar fallback | Problema de la herramienta |
He visto reintentos infinitos para todo: una ciudad fuera de lista blanca, el LLM adivina 10 veces mal y acaba en timeout. Clasificar errores es clave.
Comparativa de overhead de rendimiento
| Solución | Latencia extra | Coste extra | Fiabilidad |
|---|---|---|---|
| Restricción por prompt (sin parámetros especiales) | +0 ms | +0% | 5-10% fallos |
| JSON Mode (solo OpenAI) | +50 ms | +0% | 2-5% fallos |
| Structured Outputs (Strict) | +100 ms | +0% | <0.1% fallos |
| Reintentos Instructor | +200-500 ms/reintento | coste × reintentos | ~0% fallos |
| Outlines FSM | +1-2 s (primera compilación) | +0% | 100% cumplimiento |
Elige según prioridad: máxima estabilidad → Structured Outputs u Outlines; prototipo rápido → Instructor; presupuesto ajustado → JSON Mode + validación manual.
Métricas de monitorización: tres imprescindibles
Tras el despliegue, monitoriza:
- Tasa de fallos de formato: porcentaje de peticiones que no pasan validación. Por encima del 1%, investiga.
- Reintentos medios: lo normal es 0.5-1.5. Por encima de 2, revisa modelo o Schema.
- Latencia media: la salida estructurada añade 50-200 ms respecto a salida libre; mantén un techo aceptable.
Uso Prometheus + Grafana y reviso el informe semanal. Una vez los reintentos medios saltaron de 0.8 a 2.5: el Schema había cambiado pero no el código — la monitorización lo detectó a tiempo.
Conclusión
En resumen, una idea: en 2026 la salida estructurada ya no es un problema difícil — si usas el método correcto.
La arquitectura en tres capas (validación + reintentos + decodificación restringida) cubre de «que funcione» a «que funcione de forma estable». Al elegir proveedor: OpenAI Strict Mode es lo más sólido; Claude exige validación propia; modelos open source con Outlines pueden ser muy fiables.
Las plantillas están en el capítulo 4, listas para adaptar. Si empiezas con Agents, Instructor es buen punto de partida — reintentos y feedback integrados; cuando domines el flujo, valora Outlines para cumplimiento al 100%.
Si tienes dudas, comenta o escríbeme. Es contenido denso; espero que te ahorre algunos tropiezos.
Flujo completo para implementar OpenAI Structured Outputs
Pasos completos desde la definición del modelo Pydantic hasta la llamada de salida estructurada
⏱️ Estimated time: 15 min
- 1
Step 1: Definir el modelo de datos con Pydantic
Crea clases de modelo Pydantic y usa Field para definir restricciones:
• Usa `Field(..., min_length=1, max_length=50)` para rangos de longitud de cadenas
• Usa `Field(default=10, ge=1, le=100)` para rangos numéricos
• Usa `@field_validator` para lógica de validación personalizada (p. ej. filtrado por lista blanca)
• Usa `Optional[T]` para campos opcionales - 2
Step 2: Convertir el modelo Pydantic a JSON Schema
Usa el método `model.model_json_schema()`:
```python
schema = SearchQuery.model_json_schema()
schema.pop("title", None) # limpiar metadatos de Pydantic
```
Asegúrate de que el Schema cumpla los requisitos de OpenAI Structured Outputs. - 3
Step 3: Llamar a la API de OpenAI con Strict Mode activado
Configura el parámetro `response_format` en la petición:
• `type: json_schema` — especifica el tipo de salida estructurada
• `strict: True` — activa el modo de cumplimiento obligatorio
• `json_schema.name` — nombre del Schema (personalizable)
• `json_schema.schema` — el JSON Schema convertido en el paso anterior - 4
Step 4: Analizar la respuesta y validar de nuevo
Aunque Strict Mode garantiza el 100% de cumplimiento, conviene una segunda validación:
• Usa `json.loads()` para analizar la cadena de respuesta
• Usa `model.model_validate(data)` para la validación Pydantic
• Captura la excepción `ValidationError` y gestiona casos límite - 5
Step 5: Configurar el parámetro Temperature
Usa temperatura baja en escenarios de salida estructurada:
```python
temperature=0.1 # recomendado 0.0-0.2
```
Evita temperaturas altas que aumenten la aleatoriedad y afecten la estabilidad del formato.
FAQ
¿Qué hacer si el LLM devuelve JSON con formato incorrecto?
• Capa L1 de validación de parámetros: define modelos de datos con Pydantic, conversión automática de tipos y validación de campos
• Capa L2 de reintentos: usa la biblioteca Instructor para reintentar automáticamente y devolver el error al LLM para autocorrección
• Capa L3 de decodificación restringida: usa Outlines o guided_json de vLLM para garantizar el cumplimiento desde el origen
¿En qué se diferencian la salida estructurada de OpenAI y Claude?
¿Cómo elegir la solución de salida estructurada adecuada?
• **Llamadas a OpenAI API**: Structured Outputs + Strict Mode (más estable)
• **Llamadas a Claude API**: validación Pydantic + reintentos con Instructor (requiere validación propia)
• **Despliegue de modelos locales**: Outlines o vLLM guided_json (coste controlado, alta fiabilidad)
• **Prototipado rápido**: biblioteca Instructor (bien encapsulada, lista para usar)
• **Escenarios exigentes (finanzas/salud)**: OpenAI Strict o Outlines (casi cero fallos)
¿Cómo configurar el parámetro Temperature?
¿Cuánto overhead de rendimiento añade la salida estructurada?
• **Restricción por prompt**: +0 ms de latencia, 5-10% de tasa de fallo
• **JSON Mode**: +50 ms de latencia, 2-5% de tasa de fallo
• **Structured Outputs**: +100 ms de latencia, <0.1% de tasa de fallo
• **Reintentos con Instructor**: +200-500 ms por reintento, tasa de fallo cercana a 0%
• **Outlines FSM**: +1-2 s en la primera compilación, 100% de cumplimiento
Elige según la fiabilidad requerida y el presupuesto.
¿Qué errores conviene reintentar y cuáles no?
**Reintentar**:
• Errores de formato de parámetros (campos faltantes, tipos incorrectos) — el LLM puede autocorregirse
• Errores de servicio API (429, 500) — problemas temporales del servidor
**No reintentar**:
• Fallos de validación de negocio (ciudad fuera de lista blanca) — requiere confirmación del usuario
• Fallo en ejecución de herramienta (resultado vacío) — problema de la herramienta, usar fallback
Reintentos infinitos provocan timeout; distinguir tipos de error permite un manejo eficiente.
16 min de lectura · Publicado el: 6 may 2026 · Actualizado el: 21 ago 2026
Guía de ingeniería de AI Agents
Si llegaste desde búsqueda, lo más rápido es ir al artículo anterior o siguiente de esta misma serie.
Anterior
LangGraph vs AutoGen: comparación del seguimiento de estado — checkpoint, recuperación por timeout y decisión de framework
Comparación en profundidad del seguimiento de estado LangGraph vs AutoGen: 12 dimensiones (mecanismo Checkpoint, recuperación por timeout, soporte distribuido), casos reales, árbol de decisión y código ejecutable para elegir el framework adecuado.
Parte 11 de 16
Siguiente
Guía práctica de benchmarks de evaluación de Agent: pruebas de rendimiento desde AgentBench hasta DeepEval
Explicación detallada de benchmarks y frameworks de evaluación de Agent, comparación de cinco referencias como AgentBench, WebArena y τ-Bench, métodos de evaluación a nivel de componente con DeepEval y ejemplos de código completos.
Parte 13 de 16



Comentarios
Inicia sesión con GitHub para dejar un comentario