English · Español
Break 00 — Desactivar la máscara JSON Schema en una tarea de salida estructurada¶
Pedimos a Mini-GPT que devuelva el past simple español de
worken JSON. Con la máscara JSON Schema activa, la salida es siempre parseable. Sin ella, alternamos entre prosa, markdown, frases incompletas, y JSON con comas finales. Contamos el porcentaje de parses fallidos sobre 50 muestras — esa cifra es la lección.
Este ejercicio /break apunta a la invariancia mecánica que aporta la generación estructurada. El bug es un booleano; el modo de fallo es una tasa de fallo de parseo medible.
Anclajes: theory/01-jsonmode-vs-grammar.md, theory/04-cfg-vs-regex-vs-jsonschema.md, .claude/commands/break.md.
Hipótesis¶
El aprendiz predice: "Cuando le pido a Mini-GPT 'dame el past simple español de work en JSON', con la máscara de esquema activa, cada salida parsea como JSON válido que coincide con el esquema. Sin la máscara, el modelo — entrenado en un corpus minúsculo de lenguaje natural, no en uno cargado de JSON — emite una mezcla de prosa, JSON parcial y JSON con comas finales. La tasa de fallo de parseo salta del 0% al ~40-70%."
El break¶
En src/minimask/conjugate_cli.py:
def conjugate(verb: str, tense: str, person: str) -> dict:
prompt = build_prompt(verb, tense, person)
- output = generate_with_mask(model, prompt, schema=CONJUGATE_SCHEMA)
+ output = generate_freeform(model, prompt) # /break: sin máscara de esquema
return json.loads(output) # lanzará excepción en la mayoría de salidas
Un swap: generate_with_mask → generate_freeform. La llamada json.loads se queda — y ahí es donde aflorarán los fallos.
Predecir, luego ejecutar¶
La plantilla de prompt:
Return ONLY JSON: {"verb": "work", "tense": "past simple", "person": "1st singular",
"english": "<form>", "spanish": "<form>"}
Verb: work
Tense: past simple
Person: 1st singular
JSON:
La respuesta de Mini-GPT con la máscara: siempre {"verb": "work", "tense": "past simple", "person": "1st singular", "english": "worked", "spanish": "trabajé"}. Parseable en cada muestra.
Sin la máscara, comportamientos esperados sobre 50 muestras (temperature 0,7):
- ~10 muestras: JSON válido, correcto (el modelo sí conoce algo de sintaxis JSON por exposición mínima en el corpus).
- ~15 muestras: JSON válido, pero falta un campo o sobran campos ("
spanish" mal escrito como "español"). - ~10 muestras: JSON con coma al final (
...,}) — inválido porjson.loadspero recuperable. - ~10 muestras: explicación en prosa / markdown en vez de JSON ("The past simple of 'work' is 'worked' (yo trabajé).").
- ~5 muestras: JSON truncado (
{"verb": "work").
Predicciones¶
- Tasa de fallo de parseo (
json.loadscrudo): ≈ 60-70%. - Tasa de fallo de validación de esquema (JSON válido pero claves equivocadas): además de los fallos de parseo, un ~10% adicional.
- Total de muestras útiles: ~10-20%.
- Con la máscara: fallos de parseo 0% por construcción; fallos de validación de esquema 0% por construcción.
Escribe tus predicciones en learners/borja/phase-30/notes/breaks.md antes de ejecutar.
Observa¶
Ejecuta la CLI de conjugación 50 veces con cada modo:
# Roto (sin máscara)
for i in $(seq 1 50); do just exp 30-conjugate --mode freeform --seed $i; done \
> experiments/30-freeform/results.jsonl
# Baseline (máscara activa)
for i in $(seq 1 50); do just exp 30-conjugate --mode masked --seed $i; done \
> experiments/30-masked/results.jsonl
Diagnósticos:
- Gráfico de barras:
parse_failures / 50para cada modo. Esperado: 30/50 freeform vs 0/50 masked. - Categoriza los 30 fallos freeform: coma final, prosa, campo faltante, truncamiento. Barra apilada.
- Lado a lado las primeras 10 salidas de cada modo. El contraste es la lección.
Síntoma que Borja verá¶
json.JSONDecodeErrorlanzado en la mayoría de muestras freeform.- Las salidas bonitas (cuando parsean) a menudo tienen drift en nombres de campo:
spanishse vuelveespañoloes. - Las salidas enmascaradas son aburridamente idénticas — justo el punto: estructura determinista, el contenido aún puede variar en los campos
english/spanish.
Causa oculta (una frase)¶
El decoder ya no está restringido por la máscara JSON Schema, así que la distribución natural del modelo sobre tokens de gramática inglesa (mayoritariamente prosa) compite con el objetivo de sintaxis JSON, y la prosa gana ~60% de las veces.
Cascada de pistas¶
- Imprime las primeras 5 salidas crudas de
generate_freeform. ¿Cuántas son JSON válido? - Compara con
generate_with_mask. ¿Qué es estructuralmente invariante entre ellas? - Relee
theory/04-cfg-vs-regex-vs-jsonschema.md§"La elección correcta para el tutor de gramática §A13". ¿Por qué la máscara de esquema es el componente que carga el peso aquí?
Diff de arreglo¶
def conjugate(verb: str, tense: str, person: str) -> dict:
prompt = build_prompt(verb, tense, person)
- output = generate_freeform(model, prompt)
+ output = generate_with_mask(model, prompt, schema=CONJUGATE_SCHEMA)
return json.loads(output)
Restaurar la ruta enmascarada. El arreglo también es un cambio de una línea.
Por qué esto enseña el concepto¶
El pitch entero de la generación estructurada: "no confías en que el modelo siga el formato — lo haces mecánicamente imposible desviarse." Este break hace que esa afirmación cargue peso en un modelo real. Con la máscara, el contrato JSON es un sistema de tipos — no puede ser violado. Sin la máscara, el contrato es una plegaria — el modelo normalmente lo respeta pero lo hace a una tasa catastrófica para parsers downstream que esperan entrada estructurada.
El impacto downstream: cualquier agente de tool-calling (Fase 31) que dependa de salida JSON del LLM es frágil sin una máscara de esquema. Los agentes de producción o bien usan constrained decoding por esquema, o se apoyan en bucles retry-y-valida a posteriori (que añaden latencia y coste). El tutor de gramática de la Fase 32 usa la ruta de máscara; este break muestra por qué.
Referencia¶
- Willard & Louf, Outlines (arXiv:2307.09702) — cuantifica la tasa de fallo JSON sobre modelos base comunes sin restricciones.
- OpenAI, documentación de Structured Outputs — la API de producción expone constrained decoding por esquema exactamente por esta razón.
Siguiente: restaura la ruta de máscara y ejecuta lab/02-end-to-end-conjugate.md para medir el sobrecoste de latencia del decoder enmascarado.