Saltar al contenido
← Volver a Novedades

Ingeniería12 min de lectura

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ónQué le llega al cliente
Se renombró un campoVerde. El mapeo devuelve vacío.Un mensaje con un hueco donde iba la cifra, o un cero.
Un número llegó como textoVerde. Concatenación en vez de suma.Un total que son dos números pegados.
Apareció un campo de másVerde. Nadie lo lee.Nada — hasta que alguien lo necesita y llevaba meses ahí sin usarse.
El modelo envolvió el resultadoVerde. El mapeo apunta un nivel por encima.Salida vacía, reportada como ejecución correcta.
Ninguno lanza una excepción. Ese es el problema entero: tus alertas vigilan fallos, y esto no lo es.

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:

  1. 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.
  2. 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.
  3. 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.
  4. 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         ídem
Todas menos la primera son un incidente real. Eso es lo que hace que valga la pena correrlas: son la lista de las formas en que esto se ha roto de verdad en tu casa.

La 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é medirQué te dice cuando sube
Extracciones rechazadas en la puertaO 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 intentoTu bucle de reparación se está ganando el coste. Si es casi cero, el reintento es teatro.
Escaladas después de repararEl drift es estructural, no puntual. Toca cambiar el esquema.
Campos declarados frente a los que el origen mandaCobertura. El hueco es por donde va a venir el próximo fallo silencioso.
Míralas como tasas en el tiempo, no como un número suelto. Que los rechazos se dupliquen de un día para otro dice más que cualquier valor absoluto.

7. Lista de endurecimiento

  1. Cada extracción tiene su esquema declarado, con additionalProperties: false.
  2. Restricciones, no solo tipos: rangos, longitudes, enumeraciones donde el conjunto se conoce.
  3. La validación corre antes de que nada actúe sobre el dato, no después.
  4. Un intento de reparación, alimentado con el error de validación. Nunca un reintento a ciegas.
  5. Un respaldo determinista para cuando la reparación falla: plantilla o persona, pero decidido de antemano.
  6. Cargas doradas en el control de versiones, incluida cada una que te ha roto algo.
  7. 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 additionalProperties en false es 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.

Seguir leyendo