Skip to main content
Leer un documento es fácil. Sacar los mismos campos de cada documento, en una forma en la que tu código pueda confiar, es el trabajo de verdad. Hay dos caminos desde un archivo hasta un registro. Puedes extraer el texto y pasárselo a un modelo, o puedes mostrar la página a un modelo que sabe ver. El camino que necesitas depende de cómo se creó el archivo, y un PDF no te dice de qué tipo es con solo mirarlo. Este tutorial construye ambos y deja que la API decida entre ellos:
Por el camino haremos lo siguiente:
  1. Sacar el texto de un PDF con /augment/text-parser
  2. Describir el registro que queremos como un esquema JSON
  3. Extraerlo, con el esquema aplicado en lugar de sugerido
  4. Manejar el archivo que no contiene texto en absoluto
  5. Comparar lo que producen los dos caminos a partir de la misma página

Configuración

Necesitas Python 3.9 o más reciente, el paquete requests y una clave de API de Venice. Consulta Generar una clave de API si no tienes una.
Usaremos un artículo público como documento de ejemplo, para que puedas seguirlo con el mismo archivo:
Crea extract.py:
Fíjate en que AUTH y JSON_HEADERS están separados. El parser acepta una subida multipart, y establecer Content-Type tú mismo en una petición multipart impide que requests añada el boundary, lo que falla de una forma que es molesta de diagnosticar.

1. Sacar el texto

/augment/text-parser acepta un PDF, DOCX, XLSX o archivo de texto plano de hasta 25 MB y devuelve el texto con un recuento de tokens. Los documentos se procesan en memoria y el contenido no se conserva.
El recuento de tokens es la parte útil de esa respuesta. Te dice lo que te costará el documento en la próxima petición antes de hacerla, lo cual importa porque un PDF largo puede fácilmente crecer más de lo que pensabas gastar.

2. Describir el registro que quieres

Pedir JSON a un modelo te devuelve JSON con más o menos la forma que pediste. Pasar un esquema te devuelve JSON que coincide, porque el esquema restringe la generación en lugar de aconsejarla.
Vale la pena poner additionalProperties: False en todos los niveles. Sin esto, un modelo que encuentre algo interesante puede añadir una clave que nunca planeaste, y el código que lea el resultado no la esperará.

3. Extraer

Una única llamada, con response_format llevando el esquema y strict activado:
Cada extracción de este tutorial pasa por un pequeño lector, porque las dos formas en las que esta llamada falla llegan ambas como HTTP 200:
Fíjate en el último autor. El artículo no da ninguna afiliación para Illia Polosukhin, y el esquema dice que affiliation es obligatorio, así que el modelo devolvió una cadena vacía en vez de omitirlo. Eso es el esquema haciendo exactamente lo que le dijiste.
Una cadena vacía y un valor ausente son hechos distintos, y required los mezcla. Si necesitas distinguir “el documento no lo dice” de “el documento no dice nada aquí”, tipifica el campo como {"type": ["string", "null"]} y pide null en el system prompt. El modo estricto acepta la unión y obtienes null en lugar de "".

Apagar el razonamiento

disable_thinking es la línea de esa petición que da para discutir, así que aquí va el argumento. El modelo de texto por defecto razona antes de responder, y el razonamiento se saca del mismo presupuesto de completación que el JSON. Ejecuta la misma extracción cuatro veces y observa lo que gasta el modelo: Subir el presupuesto no arregla el primer problema, solo eleva el techo que se le permite alcanzar al modelo. La ejecución que gastó 4003 tokens volvió con finish_reason de length y una cadena vacía. Apagar el razonamiento hizo que esta extracción fuera cinco veces más barata y, más útil todavía, que fuera igual cada vez. El esquema ya está haciendo el trabajo que haría el razonamiento, que es decidir qué forma tiene la respuesta.
Cuando el presupuesto sí se acaba, el modelo normalmente ya ha escrito algo de JSON, así que obtienes un objeto truncado en lugar de un error. json.loads falla entonces sobre una cadena sin cerrar en algún punto del medio, lo que parece un bug de parseo y no lo es. read_record comprueba primero finish_reason para que el mensaje diga lo que realmente pasó.

4. Cuando no hay texto que obtener

Un PDF producido por un escáner contiene imágenes de páginas, no texto. Nada en el nombre del archivo lo dice, y nada en el tamaño del archivo lo delata tampoco. No hace falta que lo detectes tú, porque el parser lo hace:
Eso llega como HTTP 400, y es una señal de enrutamiento más que un fallo. La vía del texto no está disponible para este archivo, así que toma la otra: renderiza la página y deja que un modelo la mire.
Ahora los dos caminos se pueden conectar, con el propio error del parser eligiendo entre ellos:
El fallback lee una página. Eso vale para un formulario, una factura o una portada, y está mal para cualquier cosa más larga, porque el resto del documento deja de existir en silencio. Renderiza cada página y envíalas como varias imágenes cuando la respuesta pueda no estar en la primera página.

5. En qué discrepan los dos caminos

Ejecuta ambos contra la misma primera página y los registros vuelven casi idénticos. El “casi” es la parte interesante: El camino del texto preservó la Ł. El camino de visión devolvió una L ASCII, porque lee formas de letras en vez de códigos de carácter, y un diacrítico es un pequeño detalle visual que sobrevive mal. Si estás emparejando nombres extraídos contra una base de datos, esa diferencia decide si se encuentra la fila. El octavo autor importa más. La página no indica afiliación para Illia Polosukhin, y el camino del texto lo reporta fielmente como una cadena vacía cada vez. El camino de visión, en algunas ejecuciones, ha rellenado el campo con un vecino plausible de la misma página. Leer píxeles deja más espacio para inferir que leer caracteres, y un campo obligatorio es una invitación a rellenarlo. Cuando no puedes comprobar la salida a mano, esa es una razón para preferir el texto parseado allí donde el documento lo ofrezca. El coste está más cerca de lo que parece. Con el razonamiento apagado en ambos lados, los dos caminos ejecutaron un prompt de tamaño parecido en esta página: La imagen tenía 923.732 caracteres de base64, y nada de eso es lo que pagas. Las imágenes se tokenizan por tamaño, no por la longitud de su codificación, así que un PNG grande no cuesta lo que parece. Prefiere el texto parseado cuando el documento tenga texto. Preserva los caracteres exactos, no cuesta nada extra ir más allá de la primera página, y no le importa cómo estaba maquetada la página. Recurre a la visión cuando el parser diga que no hay nada que leer, o cuando el significado esté en la maquetación, como en un gráfico, un sello o una firma.

Extraer otra cosa

Nada de lo anterior es específico de artículos. Cambia el esquema y el system prompt, y el pipeline extrae facturas:
Los campos description están haciendo trabajo de verdad. Una fecha solo es inequívoca una vez que has dicho qué formato quieres, y 03/04/2026 significa dos días diferentes según quién lo escribiera.

Próximos pasos

  • Valida el resultado contra el esquema con pydantic o jsonschema, para que un registro mal formado falle en la frontera y no tres funciones después.
  • Almacena el texto extraído con Embeddings para buscar en varios documentos en lugar de volver a extraerlos.
  • Adjunta documentos directamente a una completación de chat con File Inputs cuando quieras respuestas en vez de registros.
  • Dale el extractor a un agente como herramienta, usando Construir un agente que usa herramientas con llamada a funciones.

Procesamiento de documentos

Referencia del endpoint text-parser.

Respuestas estructuradas

Cómo json_schema restringe una completación.

Visión

Enviar imágenes a un modelo de chat.

File Inputs

Adjunta un documento sin parsearlo tú mismo.