Mejora de lanzamientos de Datalab: un modelo de visión de pesos abiertos 9B que extrae JSON estructurado de archivos PDF mediante esquemas

Datalab ha lanzado lift, un modelo de visión de pesos abiertos 9B para extracción estructurada. Le pasa un esquema JSON y devuelve un objeto JSON que coincide. El modelo lee archivos PDF e imágenes directamente y luego los decodifica según su esquema.

Este es el primer modelo de Datalab construido exclusivamente para extracción. El equipo ya distribuye herramientas de OCR de código abierto: chandra, marcador y surya. lift extiende ese trabajo a la extracción de campos basada en esquemas.

lift obtiene una precisión de campo del 90,2 % en la prueba comparativa de 225 documentos de Datalab. El equipo de investigación lo considera el modelo pequeño autohospedable más potente que probaron. Se ejecuta a una media de 9,5 segundos por documento.

¿Qué es el elevador Datalab?

lift es un modelo de visión de parámetros 9B para extracción estructurada. Acepta el esquema JSON estándar como entrada. Devuelve JSON válido de esa forma como salida.

El modelo maneja documentos de varias páginas en una sola pasada. Puede leer valores que abarcan varias páginas. Los documentos completos entran a la vez, no página por página.

Se incluyen dos modos de inferencia con el paquete. La inferencia local pasa por HuggingFace. La inferencia remota se ejecuta a través de un servidor vLLM, que Datalab recomienda para producción.

El código es Apache 2.0. Las pesas utilizan una licencia OpenRAIL-M modificada.

lift ingresa a un campo pequeño pero creciente de modelos de extracción abierta. Algunos están diseñados específicamente, como la familia NuExtract. Otros son modelos generales de visión y lenguaje presionados para extracción, como Qwen3.5-9B. Combina una base visión-lenguaje con una decodificación restringida por esquemas y una abstención entrenada. En el punto de referencia de Datalab, lidera ese grupo abierto en precisión de campo.

Decodificación restringida por esquemas: el mecanismo central

La principal opción de diseño es la decodificación restringida por esquemas. lift decodifica su salida directamente contra su esquema. El resultado siempre es un JSON válido con la forma correcta.

Esto es lo que sucede bajo el capó. lift primero convierte su esquema JSON en un modelo Pydantic. Luego lo normaliza en un esquema JSON estricto. El esquema se pasa al servidor vLLM como una restricción de formato de respuesta.

Durante la generación, el servidor compila el esquema en una gramática. En cada paso, el modelo asigna una probabilidad a cada posible siguiente token. La gramática define qué tokens son continuaciones válidas. Los tokens que romperían el esquema están enmascarados. El modelo sólo puede tomar muestras de lo que queda.

Es por eso que la salida siempre es un JSON válido con la forma correcta. La estructura se aplica token por token, no se verifica después.

Esta garantía tiene un límite estricto. La decodificación restringida gobierna la estructura y los tipos, no el significado. Un campo escrito como número contendrá un número. Si tiene el número correcto es una cuestión aparte. El modelo puede emitir un valor válido que simplemente sea incorrecto. La validez no es la corrección.

lift también amplía todos los campos para permitir nulos. Cada hoja escalar en el esquema compilado acepta su tipo o nulo. Entonces el modelo puede abstenerse en cualquier campo sin romper la estructura. La abstención es a la vez una conducta entrenada y una propiedad de la restricción.

Escribe un esquema JSON estándar. Los tipos admitidos incluyen cadenas, números, enteros, booleanos, matrices de ellos, matrices de objetos y objetos anidados. Una descripción de campo guía el modelo cuando un nombre es ambiguo.

Aquí es también donde vive un modo de falla silencioso. Algunas construcciones no se pueden compilar: enum, anyOf/oneOf, $ref y adicionalProperties. Cuando lift no puede compilar su esquema, no se detiene. Registra una advertencia y genera sin la restricción. La garantía estructural ya no existe, sin ningún error grave. Es posible que la salida no coincida en absoluto con su esquema.

La regla práctica es simple. Mantenga los esquemas dentro del subconjunto admitido. Valide el JSON devuelto con su esquema en sentido descendente. No asuma un resultado válido solo porque se devolvió la llamada.

Aquí hay un esquema de factura simple:

{ "type": "object", "properties": { "invoice_number": {"type": "string", "description": "Identificador de factura"}, "total": {"type": "number", "description": "Importe total adeudado"}, "line_items": { "type": "array", "items": { "type": "object", "properties": { "description": {"type": "string"}, "amount": {"tipo": "número"} } } } }, "requerido": ["número_factura", "total"] }

Abstención por defecto

La extracción real es difícil por una razón que no es obvia. Más allá de leer los campos que existen, el verdadero desafío no es inventar campos que están ausentes.

Un modelo que alucina con un documento de identidad fiscal es peor que uno que no devuelve nada. El error es silencioso y difícil de detectar en el futuro. lift está capacitado para dejar nulos los campos que realmente faltan.

Marque un campo como obligatorio sólo cuando deba aparecer. Los campos ausentes de un documento se devuelven nulos. Esto le proporciona un extractor que puede informar que un valor no está presente.

Punto de referencia

Datalab evaluó el aumento en un punto de referencia de extracción de 225 documentos. Los documentos tenían entre 6 y 64 páginas cada uno, con aproximadamente 11.000 campos puntuados. Se plantaron casos contradictorios a lo largo del set.

Esos casos incluyen valores entre páginas y listas exhaustivas. También incluyen campos que deben dejarse nulos y distractores cercanos. También se probó la agregación de múltiples fuentes.

Cada modelo recibió las mismas imágenes de página renderizadas. Cada uno extrajo todos los documentos en una sola pasada. La puntuación era una coincidencia exacta determinista con la verdad fundamental, con tolerancia numérica y cadenas normalizadas.

ModeloTamañoPrecisión de campoPrecisión de todo el documentoLatencia media*CaracterísticasAPI de Datalab: 95,9 % 44,4 % 30,8 s Citas + Verificación Gemini Flash 3.5: 91,3 % 40,0 % 28,1 slift9B 90,2 % 20,9 % 9,5 s Contenido de Azure Comprensión: 83,4%22,2%73,7sCitasNuExtract34B81,5%8,4%8,3sQwen3.5-9B9B76,32%24,0%16,8s

* Por documento, 8 solicitudes simultáneas. Los modelos locales (lift, Qwen3.5-9B, NuExtract3) se entregaron con vLLM en una sola GPU. Gemini, Datalab y Azure se ejecutaron mediante API. La latencia varía según el hardware y la carga; tratarlo como relativo.

Aquí importan dos detalles. La precisión del campo es la fracción de campos individuales extraídos correctamente. La precisión de todo el documento es la fracción de documentos en los que todos los campos son correctos.

En cuanto a precisión de campo, lift lidera los modelos autohospedables. Está por delante de NuExtract3 y la base Qwen3.5-9B. También es el más rápido de los modelos precisos de la tabla.

Con una media de 9,5 s, la elevación es aproximadamente 3 veces más rápida que la de Gemini Flash 3.5. Se mantiene dentro de aproximadamente un punto de la precisión de campo de ese modelo. La precisión de todo el documento es una métrica más difícil: cada campo debe ser correcto. Aquí las puntuaciones de elevación son del 20,9%, solo por delante de NuExtract3. Las API alojadas lideran, con un 44,4% y un 40,0%.

Una nota sobre la lectura de estos números. Este es el punto de referencia propio de Datalab, así que trátelo como un resultado del proveedor. Su diseño adversario premia a los modelos sintonizados para abstenerse, lo cual es ascensor. La precisión del documento completo es baja para todos los modelos, alcanzando un máximo del 44,4%. Esto refleja lo difícil que es la extracción en una sola pasada en documentos largos. Los números también son una instantánea; Los modelos cambian.

Ésta es la realidad de la extracción de un solo modelo y una sola pasada en documentos impresos. Le indica dónde encaja el ascensor. Es excelente para la extracción a nivel de campo que alimenta una revisión humana o análisis agregados. Todavía no es un paso adelante para la automatización sin intervención y en la que todos los campos deben ser perfectos. Para esa última milla, la API alojada de Datalab agrega verificación por campo, citas y puntuaciones de confianza con el mismo enfoque.

Un flujo de trabajo profesional: del esquema a los datos revisados

Tres casos de uso muestran la forma del trabajo. Procesamiento de facturas: defina número_factura, total y elementos de línea, y un ID_impuesto faltante devuelve nulo. Revisión de contrato: un acuerdo de dos páginas conlleva un valor en todas las páginas, que la extracción en un solo paso une. Canalizaciones de documentos: una cola de cuentas por pagar confía en que las fechas de vencimiento ausentes devuelvan valores nulos, evitando errores silenciosos.

Este es uno de ellos como un flujo de trabajo de un extremo a otro. El objetivo es un conjunto de datos limpio y revisado, no un resultado del modelo sin procesar.

1. Defina el esquema. Agregue una descripción a cualquier campo cuyo nombre no sea obvio. Marque solo los campos verdaderamente obligatorios según sea necesario.

2. Ejecute la extracción. Pase el esquema y el archivo para levantar. Utilice un dict, una ruta de archivo o un nombre de esquema guardado.

3. Bifurcación según el resultado. Una llamada fallida o una extracción nula pasa a revisión. Un valor requerido faltante también se revisa, ya que nulo es una abstención, no un error.

4. Valida antes de confiar. Verifique el JSON devuelto con su esquema. Esto detecta el respaldo silencioso cuando no se puede compilar un esquema.

from lift import extraer esquema = { "tipo": "objeto", "properties": { "número_factura": {"tipo": "cadena", "descripción": "Identificador de factura"}, "total": {"tipo": "número", "descripción": "Importe total adeudado"}, "fecha_de vencimiento": {"tipo": "cadena", "descripción": "Fecha de vencimiento del pago, ISO 8601"}, "line_items": { "tipo": "array", "items": { "type": "object", "properties": { "description": {"type": "string"}, "amount": {"type": "number"} } } } }, "required": ["invoice_number", "total"] } result = extract("invoice.pdf", esquema) si result.error o result.extraction es Ninguno: queue_for_review("invoice.pdf", Reason="extraction_failed") else: data = result.extraction # Un campo obligatorio aún puede ser nulo. Eso es abstención, no un colapso. si data.get("total") es Ninguno: queue_for_review("invoice.pdf", Reason="missing_total") más: guardar(datos)

Algunos consejos de diseño de esquemas que dan resultados en la práctica:

Escriba una descripción para campos ambiguos; es su principal palanca de precisión. Mantenga los esquemas dentro del subconjunto admitido y valide la salida en sentido descendente. Prefiere esquemas planos y poco profundos; el anidamiento profundo es más difícil de extraer de manera confiable. Marque los campos requeridos con moderación, para que los espacios en blanco genuinos puedan resultar nulos. Utilice –page-range (CLI) o page_range (Python) para limitar los archivos PDF largos. Reutilice un InferenceManager en todas las llamadas para amortizar la carga del modelo.

Autohospedaje frente a alojamiento: cuál utilizar

levanta barcos como pesos abiertos y Datalab ejecuta una API alojada con el mismo enfoque. La elección es cuestión de limitaciones, no de prestigio.

ElegirCuándoSe aplican reglas locales o de residencia de datos; necesita control de costos en grandes volúmenes; quieres control de latencia en tus propias GPU; las ejecuciones deben funcionar sin conexión. API de Datalab alojada Necesita verificación por campo, citas y puntuaciones de confianza; desea la máxima precisión; preferiría no gestionar la infraestructura; el volumen es bajo o está a ráfagas.

Una advertencia para el autohospedaje. El uso comercial necesita una licencia según los términos modificados de OpenRAIL-M. Es gratuito para investigación, uso personal y nuevas empresas con menos de 5 millones de dólares en financiación o ingresos, y no para su uso en competencia con la API de Datalab.

Empezando

La ruta más rápida es la CLI. lift-pdf requiere Python 3.12 o posterior.

pip install lift-pdf # Servir el modelo con vLLM (recomendado) lift_vllm # Extraer contra un esquema lift_extract input.pdf ./output –schema esquema.json

Cada archivo produce dos resultados. .json contiene la extracción que coincide con su esquema. _metadata.json contiene el recuento de páginas, el recuento de tokens y la información de errores para la depuración.

La API de Python es igualmente pequeña:

from lift import extract # esquema: un dict, una ruta, una cadena JSON en línea o un nombre de biblioteca result = extract("document.pdf", "schema.json") si result.extraction no es Ninguno: datos = result.extraction # dict que coincide con el esquema

Verifique result.extraction para ver el dictado que coincida con su esquema. Una extracción nula indica una falla que puedes inspeccionar. El backend de HuggingFace utiliza el método hf y necesita pip install lift-pdf[hf].

Schema Studio se envía como una aplicación Streamlit. Le permite crear, guardar y probar esquemas con sus propios documentos. Instálelo con pip install lift-pdf[app], luego ejecute lift_app.

Para producción, lift_vllm lanza un contenedor Docker con un tamaño de lote adaptado a su GPU. Las GPU compatibles son: h100, a100-80, a100/a100-40, l40s, a10, l4, 4090, 3090, t4.

Explicador interactivo

${f.desc}

${etiqueta}`; fila.appendChild(cb); fila.appendChild(meta); camposEl.appendChild(fila); }); } función jval(v, sangría){ const pad = ' '.repeat(sangría); const pad2 = ' '.repeat(sangría+1); if(v===null) return ` nulo` ; if(tipode v==='número') return ` ${v} `; if(tipode v==='booleano') return ` ${v} `; if(typeof v==='cadena') return ` "${v}" `; if(Array.isArray(v)){ si(v.length===0) devolver '[]'; elementos const=v.map(it=>pad2+jval(it,indent+1)).join(',n'); devolver `[n${elementos}n${pad}]`; } // objeto const claves=Objeto.claves(v); const body=keys.map(k=>`${pad2} "${k}" : ${jval(v[k],indent+1)}`).join(',n'); devolver `{n${cuerpo}n${pad}}`; } función extraer(){ const sel = asegurarSel(actual); campos constantes = DOCS[actual].fields.filter(f=>sel.has(f.nombre)); if(fields.length===0){ jsonEl.innerHTML = ` // No hay campos seleccionados, nada que extraer `; statsEl.innerHTML=""; noteEl.innerHTML=""; emitirTamaño(); devolver; } // Construir objeto que respete la forma del esquema; ausente -> nulo (abstención) const obj={}; let encontrado = 0, nulos = 0, intervalos = 0; campos.forEach(f=>{ if(f.absent){ obj[f.name]=null; nulls++; } else { obj[f.name]=f.val; found++; if(f.span) spans++; } }); líneas const = Object.keys(obj).map(k=>{ const f = campos.find(x=>x.name===k); const badge = f.absent ? ` null · abstenido ` : ` encontrado `; return ` "${k}" : ${jval(obj[k],1)}${badge}`; }); jsonEl.innerHTML = `{n${lines.join(',n')}n}`; statsEl.innerHTML = ` ${found} encontró ${nulls} null ${fields.length} campos `; let n = `La salida es JSON válido de exactamente la forma que seleccionó, es decir, decodificación restringida por esquema. `; if(nulls>0) n += `Los campos no presentes en el documento resultaron nulos en lugar de una suposición (abstención). `; if(spans>0) n += `el valor_total se resuelve a partir de valores que abarcan dos páginas, se leen en una sola pasada.`; notaEl.innerHTML = n; emitirTamaño(); } función render(){ buildTabs(); docEl.innerHTML = DOCS[actual].render; construirCampos(); jsonEl.innerHTML = ` // Presione Extraer para ejecutar el esquema seleccionado `; statsEl.innerHTML=""; noteEl.innerHTML=""; emitirTamaño(); } document.getElementById('ejecutar').onclick = extraer; document.getElementById('all').onclick = ()=>{ const sel=ensureSel(current); const todo = DOCS[actual].campos; const allOn = all.every(f=>sel.has(f.nombre)); all.forEach(f=> allOn? sel.delete(f.nombre): sel.add(f.nombre)); construirCampos(); emitirTamaño(); }; /* cambio de tamaño automático: publicar componente offsetHeight + 40 al padre */ function emitSize(){ requestAnimationFrame(()=>{ const h = (document.querySelector('.wrap').offsetHeight || document.body.offsetHeight) + 40; parent.postMessage({type:'lift-demo-height', height:h}, '*'); }); } window.addEventListener('cargar', ()=>{ render(); emitSize(); }); window.addEventListener('resize', emitSize);