Back to blog
    Fine-Tuning para salida estructurada: Más allá del modo JSON hacia esquemas garantizados
    fine-tuningstructured-outputjsonschemasegment:developer

    Fine-Tuning para salida estructurada: Más allá del modo JSON hacia esquemas garantizados

    El modo JSON te da JSON válido. Fine-tuning te da cumplimiento de esquema garantizado — cada campo, cada tipo, cada vez. Así es cómo entrenar modelos que producen exactamente la estructura que tu app espera.

    EErtas Team·

    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 — sostenido con cinta adhesiva y loops de reintento.

    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 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 — aceptable para 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 findings debería ser [] porque no hay hallazgos
    • Valores mínimos/máximos: score: 0 y score: 100 — los modelos tienden a evitar 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 — 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)?
    • ¿Las longitudes de arrays son apropiadas para la entrada?

    Métricas de comparación

    Después del fine-tuning, compara contra tu línea base:

    MétricaGPT-4o con prompts8B ajustado8B ajustado + gramática
    JSON válido99.5%99.8%100%
    Cumplimiento de esquema93-96%99.2-99.7%100%
    Precisión semántica90-94%93-97%93-97%
    Tokens promedio/respuesta350280280
    Costo por 1K llamadas$2.50-$8.00$0 (local)$0 (local)

    El modelo ajustado tiene más cumplimiento de esquema, usa menos tokens (no rellena respuestas con verbosidad innecesaria), y no cuesta nada por llamada.

    Combinando fine-tuning con decodificación restringida

    Para aplicaciones donde incluso 99.5% no es suficiente — transacciones financieras, registros médicos, documentos legales — combina fine-tuning con decodificación restringida.

    La decodificación restringida (también llamada generación guiada por gramática) restringe los tokens de salida del modelo a solo aquellos que producen salida válida según una especificación de gramática. llama.cpp soporta esto vía gramáticas GBNF. Outlines y guidance proporcionan funcionalidad similar para inferencia basada en Python.

    Una gramática GBNF para tu esquema:

    root   ::= "{" ws "\"id\":" ws string "," ws "\"status\":" ws status "," ws "\"score\":" ws number "," ws "\"category\":" ws category "," ws "\"findings\":" ws findings "," ws "\"metadata\":" ws metadata ws "}"
    status ::= "\"approved\"" | "\"denied\"" | "\"pending\"" | "\"review\""
    category ::= "\"financial\"" | "\"operational\"" | "\"compliance\"" | "\"technical\""
    ...
    

    Con un modelo ajustado + gramática, obtienes:

    • 100% cumplimiento estructural (la gramática previene estructuras inválidas)
    • 99.5%+ precisión semántica (fine-tuning enseña valores correctos)
    • Cero costo por token (inferencia local)
    • Inferencia rápida (las restricciones de gramática realmente aceleran la generación ligeramente al reducir el espacio de búsqueda de tokens)

    La gramática maneja la estructura. El fine-tuning maneja el contenido. Juntos, producen salida en la que tu parser puede confiar incondicionalmente.

    Impacto en el rendimiento

    Los modelos ajustados generando salida estructurada son típicamente más rápidos que los modelos con prompts, por dos razones:

    1. Salidas más cortas: Los modelos con prompts frecuentemente incluyen texto explicativo, formato markdown, o meta-comentarios alrededor del JSON. Los modelos ajustados producen solo JSON crudo. Esto reduce tokens de salida en 20-40%.

    2. Generación más confiada: Cuando un modelo "conoce" el esquema (del fine-tuning), genera cada token con mayor confianza. Menos retrocesos en el proceso de muestreo. Time-to-first-token mediblemente menor y tasa de generación de tokens más rápida.

    Benchmark en un esquema de clasificación de tickets de soporte (8 campos, 2 enums, 1 objeto anidado):

    ConfiguraciónTokens promedio de salidaLatencia promedioValidez estructural
    GPT-4o + prompt420 tokens1.8s96.2%
    GPT-4o + API structured outputs310 tokens1.4s99.8%
    8B ajustado (Ollama)270 tokens0.4s99.5%
    8B ajustado + gramática265 tokens0.35s100%

    El modelo local ajustado es 4x más rápido y produce salida más corta y más confiable.

    Errores comunes en fine-tuning de esquemas

    Error 1: Datos de entrenamiento inconsistentes

    Si el 80% de tus ejemplos de entrenamiento usan "date": "2026-03-15" y el 20% usan "date": "March 15, 2026", el modelo aprende ambos formatos y cambia probabilísticamente entre ellos. Cada campo, cada formato, cada convención debe ser 100% consistente en todos los ejemplos de entrenamiento.

    Error 2: No entrenar con casos vacíos/null

    Si tu esquema permite campos opcionales o arrays vacíos, pero todos los ejemplos de entrenamiento tienen valores poblados, el modelo alucinará valores en lugar de producir null o []. Incluye 10-15% de ejemplos con cada campo opcional establecido en null y cada array vacío.

    Error 3: Entradas excesivamente homogéneas

    Si todas tus entradas de entrenamiento son aproximadamente de la misma longitud, complejidad y tema, el modelo se sobreajusta a esa distribución de entrada. Cuando ve una entrada significativamente diferente en producción, el cumplimiento de esquema cae. Varía tus entradas agresivamente.

    Error 4: Entrenar con JSON formateado bonito

    Si entrenas con JSON formateado con indentación, el modelo desperdicia tokens en espacios en blanco. Entrena con JSON minificado: "id":"RPT-004521","status":"approved",.... Esto reduce tokens de salida en 15-25% y mejora la velocidad de generación.

    Error 5: No validar los datos de entrenamiento

    Si incluso un ejemplo de entrenamiento tiene una violación de esquema, el modelo aprende que las violaciones son aceptables. Valida cada ejemplo de entrenamiento contra tu JSON Schema antes de incluirlo en el dataset. Automatiza esto. Sin excepciones.

    Primeros pasos

    1. Define tu esquema objetivo como un JSON Schema formal
    2. Recopila o genera 500-1,000 ejemplos de entrenamiento validados
    3. Formatea como pares de entrenamiento JSONL (system/user/assistant)
    4. Valida cada ejemplo programáticamente — descarta cualquiera con violaciones de esquema
    5. Ajusta en Ertas — Llama 3.1 8B o Qwen 2.5 7B son opciones fuertes para salida estructurada
    6. Evalúa con más de 100 ejemplos reservados, midiendo cumplimiento estructural y semántico
    7. Despliega vía Ollama, opcionalmente con gramática GBNF para estructura garantizada
    8. Monitorea salidas de producción y añade casos de fallo a tus datos de entrenamiento para la próxima iteración

    El objetivo es simple: el parser JSON de tu app nunca debería fallar por culpa del modelo. Fine-tuning te lleva ahí.


    Ship AI that runs on your users' devices.

    Ertas early bird pricing starts at $14.50/mo — locked in for life. Plans for builders and agencies.

    Lectura adicional

    Ship AI that runs on your users' devices.

    Early bird pricing starts at $14.50/mo — locked in for life. Plans for builders and agencies.

    Keep reading