Schema drift: por qué la extracción estructurada falla en silencio, y cómo hacer que falle a gritos
Desglose del fallo que no da error: el modelo devuelve algo casi con la forma correcta, el esquema lo acepta, y tres pasos después tienes una respuesta equivocada que nadie marcó.
La extracción estructurada casi nunca falla como uno se prepara. Esperas un JSON malformado y un error de parseo. Lo que llega es un JSON bien formado con un campo renombrado, un número que viene como texto, o una clave que ayer no estaba — y todo lo de aguas abajo aceptándolo sin rechistar.
1. Qué es el schema drift, en concreto
No es un suceso, es la distancia que se abre entre la forma que supusiste y la que llega. Y se abre por los dos lados:
- Desde el modelo. Una versión nueva redacta lo mismo de otra forma, o empieza a envolver el resultado en un objeto más.
- Desde el prompt. Alguien añade una frase y la salida gana un campo que nadie pidió.
- Desde el origen. La API de la que extraes renombra una clave o cambia un tipo.
- Desde el consumidor. Tu propio código empieza a necesitar un campo que al extractor nunca se le pidió.
2. Qué pinta tiene en un flujo visual
En un lienzo, el contrato entre dos pasos es un mapeo de campos hecho a mano el día que se montó. Cuando hay drift, ese mapeo no da error: se resuelve a vacío. Se repiten tres formas:
| Qué derivó | Qué enseña la ejecución | Qué le llega al cliente |
|---|---|---|
| Se renombró un campo | Verde. El mapeo devuelve vacío. | Un mensaje con un hueco donde iba la cifra, o un cero. |
| Un número llegó como texto | Verde. Concatenación en vez de suma. | Un total que son dos números pegados. |
| Apareció un campo de más | Verde. Nadie lo lee. | Nada — hasta que alguien lo necesita y llevaba meses ahí sin usarse. |
| El modelo envolvió el resultado | Verde. El mapeo apunta un nivel por encima. | Salida vacía, reportada como ejecución correcta. |
3. Declara la forma, y rechaza las sorpresas
El primer movimiento no cuesta casi nada: declara la forma y di que cualquier otra cosa es un error. La estrictez es opcional, así que hay que pedirla.
{
"type": "object",
"additionalProperties": false,
"required": ["sku", "cantidad", "precio_unitario"],
"properties": {
"sku": { "type": "string", "minLength": 1 },
"cantidad": { "type": "integer", "minimum": 1 },
"precio_unitario": { "type": "number", "exclusiveMinimum": 0 }
}
}additionalProperties: false es la línea que convierte un drift silencioso en un rechazo que se ve. required es la que convierte un campo que falta en un error en vez de en un indefinido.Fíjate en lo que hacen los tipos: integer y no number para una cantidad; un mínimo que descarta el cero y los negativos; un precio que no puede ser cero. Un tipo que acepta cualquier cosa no atrapa nada — casi todo el valor de un esquema está en las restricciones, no en los nombres de los campos.
4. Puertas de validación, y qué pasa cuando una falla
Un esquema te dice que la carga está mal. No te dice qué hacer con ella, y esa decisión es donde los sistemas se diferencian:
- Rechaza en la frontera. La validación corre antes de que nada actúe sobre el dato. Una carga mala no llega al código que iba a usarla.
- Repara una vez, con el error en la mano. Devuélvele al modelo el mensaje de validación —qué campo, qué se esperaba— y vuelve a pedir. Esto sí merece la pena porque es un intento DISTINTO, no el mismo repetido.
- Escala, no des vueltas. Si el segundo intento falla igual, para. Un tercer intento idéntico no es control de errores: es la misma apuesta, pagada otra vez.
- Cae en algo determinista. Responde con plantilla, o pásalo a una persona. Una respuesta que no puedes respaldar es peor que ninguna.
5. Pruebas de contrato: la parte que se salta todo el mundo
Un esquema te protege en ejecución. Lo que te protege al construir es un juego de cargas reales —las que has visto de verdad— comprobadas contra el esquema actual en cada commit.
fixtures/
01-normal.json el caso feliz
02-campo-renombrado.json lo que llegó el día que se rompió
03-numero-como-texto.json ídem
04-envuelto-de-mas.json ídem
05-campo-extra.json ídemLa disciplina es simple y es el método entero: cuando algo deriva en producción, el arreglo no está terminado hasta que la carga que lo rompió es una prueba. Si no, vas a arreglar el mismo drift dos veces.
6. Qué medir
No vamos a soltarte aquí nuestras cifras, porque un número de resiliencia sin el método detrás es decoración. Lo útil es saber cuáles cuatro mirar, y qué te dice cada una cuando se mueve:
| Qué medir | Qué te dice cuando sube |
|---|---|
| Extracciones rechazadas en la puerta | O el origen derivó, o tu esquema es más estricto que la realidad. Las dos cosas conviene saberlas, y no son la misma. |
| Reparadas en el segundo intento | Tu bucle de reparación se está ganando el coste. Si es casi cero, el reintento es teatro. |
| Escaladas después de reparar | El drift es estructural, no puntual. Toca cambiar el esquema. |
| Campos declarados frente a los que el origen manda | Cobertura. El hueco es por donde va a venir el próximo fallo silencioso. |
7. Lista de endurecimiento
- Cada extracción tiene su esquema declarado, con
additionalProperties: false. - Restricciones, no solo tipos: rangos, longitudes, enumeraciones donde el conjunto se conoce.
- La validación corre antes de que nada actúe sobre el dato, no después.
- Un intento de reparación, alimentado con el error de validación. Nunca un reintento a ciegas.
- Un respaldo determinista para cuando la reparación falla: plantilla o persona, pero decidido de antemano.
- Cargas doradas en el control de versiones, incluida cada una que te ha roto algo.
- Tasas de rechazo y de reparación en un panel que alguien mire de verdad.
De dónde sale lo del esquema
El comportamiento por defecto que se cita arriba sale de la documentación de JSON Schema.
- object — Por defecto, «providing additional properties is valid»: un esquema acepta campos que no declaró. Poner
additionalPropertiesenfalsees lo que hace que deje de aceptarlos — o sea que la validación estricta hay que PEDIRLA, no viene puesta.
Consultado el 2026-09-29.
Preguntas frecuentes
¿Qué es el schema drift en extracción con LLM?
La distancia entre la forma que tu código espera y la que llega de verdad. Puede venir de una versión del modelo, de una edición del prompt o de la API de origen — y como JSON Schema acepta por defecto los campos no declarados, normalmente valida limpio.
¿Hay que reintentar cuando falla la validación?
Una vez, y solo si le das al modelo algo nuevo: el propio error de validación. Reenviar el mismo prompt no es un reintento, es el mismo intento repetido al mismo precio.
¿Basta con la validación estricta?
Convierte un fallo silencioso en uno visible, que es casi todo el valor. Lo que no hace es decidir qué pasa después: para eso hace falta un paso de reparación, una vía de escalado y un respaldo determinista.
¿Dónde encajan las pruebas de contrato?
Atrapan el drift al construir en vez de a las tres de la mañana. Guarda una carga por cada vez que algo te ha roto la extracción y córrelas contra el esquema actual en cada commit.