Fine-Tuning para salida estructurada: JSON Schema
El modo JSON te da JSON válido. El fine-tuning te da cumplimiento de esquema garantizado en cada campo, cada tipo, cada vez. Así se entrena un modelo.
Tu app espera un objeto JSON con exactamente 8 campos, tipos específicos para cada campo, enums para dos de ellos, y un array anidado de objetos con su propio esquema. Le pides a GPT-4 que lo produzca. La mayor parte del tiempo, obtienes lo que pediste. A veces obtienes 7 campos. Ocasionalmente obtienes un string donde esperabas un entero. De vez en cuando, el modelo inventa un valor de enum que no existe.
Al 95% de cumplimiento de esquema, 1 de cada 20 llamadas a la API produce salida que tu parser no puede manejar. Si tu app hace 10,000 llamadas de salida estructurada por día, eso son 500 fallos. Cada día. Tu código de manejo de errores se vuelve más complejo que tu lógica de negocio real. Añades reintentos, prompts de fallback, correctores de post-procesamiento. El sistema funciona, pero es frágil. La cinta adhesiva y los loops de reintento lo mantienen en pie.
Fine-tuning cambia la ecuación. Un modelo entrenado con 500-1,000 ejemplos de tu esquema exacto no se desvía, no alucina campos, no inventa valores de enum. El cumplimiento de esquema pasa de 95% con prompting a 99.5%+ con fine-tuning. A 10,000 llamadas por día, esa es la diferencia entre 500 fallos y 50.
El espectro de salida estructurada
Hay una jerarquía de enfoques de salida estructurada, del menos confiable al más:
Nivel 1: Basado en prompt ("Por favor produce JSON")
Pídele al modelo que produzca JSON en tu prompt. Incluye un ejemplo. Espera lo mejor.
- Tasa de cumplimiento: 80-90%
- Modos de fallo: JSON inválido (comillas faltantes, comas finales), campos faltantes, tipos incorrectos, campos extra, envuelto en markdown
- Cuándo usar: Solo para prototipado
Nivel 2: Modo JSON
El modo JSON de OpenAI, o configuraciones equivalentes en otras APIs. Fuerza al modelo a producir JSON sintácticamente válido.
- Tasa de cumplimiento: 95-98% (JSON válido, pero el cumplimiento de esquema varía)
- Modos de fallo: JSON válido que no coincide con tu esquema: campos requeridos faltantes, nombres de campo incorrectos, tipos no coincidentes, campos extra
- Cuándo usar: Cuando necesitas JSON válido pero puedes tolerar desviación de esquema
Nivel 3: Function Calling / API de Structured Outputs
Los structured outputs de OpenAI con un JSON schema, o endpoints de function-calling. La API impone el esquema a nivel de decodificación.
- Tasa de cumplimiento: 99%+ para estructura de esquema
- Modos de fallo: Estructura correcta pero valores incorrectos: valores de enum alucinados, contenido semánticamente incorrecto, strings vacíos donde se espera contenido
- Cuándo usar: Cuando necesitas cumplimiento de esquema desde APIs en la nube y puedes aceptar el costo por token
Nivel 4: Modelo ajustado
Un modelo entrenado con cientos de ejemplos de tu esquema exacto. Conoce los nombres de campos, tipos, valores válidos y expectativas semánticas.
- Tasa de cumplimiento: 99.5-99.9%
- Modos de fallo: Casos extremos raros en entradas muy fuera de la distribución de entrenamiento
- Cuándo usar: Sistemas de producción con alto volumen donde la confiabilidad y el costo importan
Nivel 5: Modelo ajustado + decodificación restringida
Modelo ajustado con decodificación restringida (gramática de llama.cpp, Outlines, o guidance) que hace imposibles los tokens inválidos.
- Tasa de cumplimiento: 100% cumplimiento estructural
- Modos de fallo: JSON estructuralmente perfecto con valores semánticamente incorrectos (raro con fine-tuning)
- Cuándo usar: Cuando necesitas cero fallos estructurales y estás corriendo inferencia local
Por qué el prompting tiene un techo
El problema fundamental con la salida estructurada basada en prompts: el modelo está generando tokens secuencialmente, y nada le impide generar un token que viole tu esquema.
Cuando le pides a GPT-4 que produzca "status": "approved" | "denied" | "pending", el modelo puede generar "status": "approved" en una llamada y "status": "Approved" en la siguiente. O "status": "approve". Cada uno es un string JSON válido. El modelo no tiene ninguna restricción que diga "solo estos tres valores exactos son aceptables."
Prompts más largos con esquemas más detallados ayudan, pero tienen rendimientos decrecientes:
- Descripción de esquema de 1 línea: ~85% cumplimiento
- Esquema detallado con ejemplos: ~92% cumplimiento
- Esquema + ejemplos few-shot + restricciones explícitas: ~95-97% cumplimiento
No puedes llegar al 99.5% con prompts. La generación de tokens del modelo es probabilística. Sin importar cuán bueno sea tu prompt, la probabilidad de generar el token incorrecto nunca es exactamente cero.
Fine-tuning cambia la distribución de probabilidad del modelo directamente. Después de entrenar con 500 ejemplos donde status es siempre exactamente "approved", "denied", o "pending", el modelo asigna probabilidad cercana a cero a cualquier otro valor para ese campo. El cumplimiento viene de los pesos, no del prompt.
Construyendo datasets de entrenamiento que cumplen con el esquema
La calidad de tus datos de entrenamiento determina la calidad de tu cumplimiento de esquema. Así es cómo construir un dataset que produce salida estructurada confiable.
Paso 1: Define tu esquema formalmente
Comienza con una especificación JSON Schema:
{
"type": "object",
"required": ["id", "status", "score", "category", "findings", "metadata"],
"properties": {
"id": {"type": "string", "pattern": "^RPT-[0-9]{6}quot;},
"status": {"type": "string", "enum": ["approved", "denied", "pending", "review"]},
"score": {"type": "number", "minimum": 0, "maximum": 100},
"category": {"type": "string", "enum": ["financial", "operational", "compliance", "technical"]},
"findings": {
"type": "array",
"items": {
"type": "object",
"required": ["description", "severity", "recommendation"],
"properties": {
"description": {"type": "string"},
"severity": {"type": "string", "enum": ["low", "medium", "high", "critical"]},
"recommendation": {"type": "string"}
}
}
},
"metadata": {
"type": "object",
"required": ["analyst", "date", "version"],
"properties": {
"analyst": {"type": "string"},
"date": {"type": "string", "format": "date"},
"version": {"type": "string"}
}
}
}
}Paso 2: Genera ejemplos diversos y válidos
Necesitas 500-1,000 ejemplos de entrenamiento. Cada uno debe ser una instancia perfectamente válida de tu esquema. Hay tres enfoques para generarlos:
Desde datos de producción: Si tienes datos existentes que siguen este esquema (de una base de datos, de salidas previas de API), conviértelos en ejemplos de entrenamiento. Esta es la mejor fuente porque refleja distribuciones de valores del mundo real.
Desde GPT-4 con validación: Usa GPT-4 para generar ejemplos diversos, luego valida cada uno contra tu JSON Schema. Descarta cualquiera que no valide. Al 95% de cumplimiento de GPT-4, descartarás aproximadamente 1 de cada 20, lo cual resulta aceptable para la generación de datasets. Pasa los ejemplos validados por tu parser real para verificar doblemente.
Generación programática: Escribe un script que genere instancias aleatorias válidas de tu esquema. Esto garantiza validez estructural pero puede carecer de coherencia semántica. Úsalo para complementar ejemplos reales, no como tu única fuente.
Paso 3: Varía las entradas, no solo las salidas
Tus datos de entrenamiento necesitan entradas diversas pareadas con salidas que cumplen el esquema. Si cada entrada es "Analiza este informe financiero," el modelo aprende el esquema pero no puede generalizar a nuevas frases de entrada.
Incluye variaciones:
- Diferentes longitudes y complejidad de entrada (una oración a tres párrafos)
- Diferentes formatos de entrada (solicitudes formales, preguntas casuales, instrucciones estructuradas)
- Diferentes niveles de contexto (información completa vs. información parcial)
- Casos extremos (entradas ambiguas, entradas con información faltante, entradas que deberían producir arrays vacíos)
Paso 4: Incluye casos extremos explícitamente
Los casos donde los modelos con prompts fallan más son los casos extremos. Asegúrate de que tus datos de entrenamiento cubran:
- Arrays vacíos: Cuando
findingsdebería ser[]porque no hay hallazgos - Valores mínimos/máximos:
score: 0yscore: 100, porque los modelos tienden a evitar los extremos - Valores de string largos: Campos con valores de más de 200 caracteres (los modelos a veces truncan)
- Caracteres especiales: Comillas, saltos de línea, unicode en campos de string
- Manejo de null: Si tu esquema permite campos nullables, incluye ejemplos con valores null
Paso 5: Formatea como pares de entrenamiento
Estructura cada ejemplo como una conversación system/user/assistant:
{
"messages": [
{
"role": "system",
"content": "You are a report analysis system. Analyze the provided report and output a structured assessment."
},
{
"role": "user",
"content": "Report: Q3 revenue declined 12% due to supply chain disruptions. Two compliance violations were identified in the procurement process. The technical infrastructure handled the load increase from the holiday promotion without incidents."
},
{
"role": "assistant",
"content": "{\"id\": \"RPT-004521\", \"status\": \"review\", \"score\": 62, \"category\": \"operational\", \"findings\": [{\"description\": \"Revenue decline of 12% attributed to supply chain disruptions\", \"severity\": \"high\", \"recommendation\": \"Diversify supplier base and establish backup procurement channels\"}, {\"description\": \"Two compliance violations in procurement process\", \"severity\": \"medium\", \"recommendation\": \"Audit procurement workflows and retrain staff on compliance requirements\"}], \"metadata\": {\"analyst\": \"system\", \"date\": \"2026-03-15\", \"version\": \"1.0\"}}"
}
]
}La respuesta del asistente debe ser JSON crudo: sin formato markdown, sin texto explicativo, sin bloques de código. Entrena al modelo para producir JSON y nada más.
Midiendo el cumplimiento de esquema
No te fíes de "se ve bien." Mide el cumplimiento programáticamente:
Cumplimiento estructural
Pasa cada salida por un validador de JSON Schema. Cuenta el porcentaje que pasa. Esto captura campos faltantes, tipos incorrectos, valores de enum inválidos y violaciones de estructura de esquema.
Cumplimiento semántico
La validez estructural no significa que el contenido es correcto. Un modelo puede producir "status": "approved", "score": 0, algo estructuralmente válido pero semánticamente sospechoso (¿aprobado con una puntuación de 0?). Construye verificaciones semánticas:
- ¿Los valores de enum correlacionan correctamente con otros campos?
- ¿Los valores numéricos están en rangos realistas?
- ¿Los valores de string contienen contenido relevante (no lorem ipsum ni strings vacíos)?