Skip to main content
Leggere un documento è facile. Estrarre gli stessi campi da ogni documento, in una forma su cui il tuo codice possa fare affidamento, è il vero lavoro. Ci sono due strade per andare da un file a un record. Puoi estrarre il testo e passarlo a un modello, oppure puoi mostrare la pagina a un modello che sappia vedere. Quale ti serve dipende da come è stato prodotto il file, e un PDF non ti dice di che tipo sia guardandolo. Questo tutorial costruisce entrambe le strade e lascia che sia l’API a decidere tra le due:
Lungo il percorso faremo:
  1. Estrarre il testo da un PDF con /augment/text-parser
  2. Descrivere il record che vogliamo come schema JSON
  3. Estrarlo, con lo schema imposto piuttosto che richiesto
  4. Gestire il file che non ha alcun testo al suo interno
  5. Confrontare ciò che le due strade producono a partire dalla stessa pagina

Configurazione

Ti servono Python 3.9 o successivo, il pacchetto requests e una chiave API Venice. Consulta Generare una chiave API se non ne hai una.
Useremo un paper pubblico come documento di esempio, così puoi seguire con lo stesso file:
Crea extract.py:
Nota che AUTH e JSON_HEADERS sono separati. Il parser accetta un upload multipart, e impostare Content-Type da soli su una richiesta multipart impedisce a requests di aggiungere il boundary, cosa che fallisce in un modo fastidioso da diagnosticare.

1. Estrai il testo

/augment/text-parser accetta un file PDF, DOCX, XLSX o testo semplice fino a 25 MB e restituisce il testo con un conteggio dei token. I documenti vengono elaborati in memoria e il contenuto non viene conservato.
Il conteggio tokens è la parte utile di quella risposta. Ti dice quanto ti costerà il documento nella prossima richiesta prima ancora di farla, il che conta perché un PDF lungo può facilmente crescere oltre quanto avevi previsto di spendere.

2. Descrivi il record che vuoi

Chiedere JSON a un modello ti dà JSON con più o meno la forma che hai chiesto. Passare uno schema ti dà JSON che corrisponde, perché lo schema vincola la generazione piuttosto che consigliarla.
Vale la pena impostare additionalProperties: False a ogni livello. Senza di esso un modello che trova qualcosa di interessante può aggiungere una chiave che non avevi mai previsto, e il codice che legge il risultato non se l’aspetterà.

3. Estrai

Una singola chiamata, con response_format che porta lo schema e strict attivato:
Ogni estrazione in questo tutorial passa attraverso un unico piccolo reader, perché i due modi in cui questa chiamata fallisce arrivano entrambi come HTTP 200:
Guarda l’ultimo autore. Il paper non fornisce alcuna affiliazione per Illia Polosukhin, e lo schema dice che affiliation è richiesto, quindi il modello ha restituito una stringa vuota invece di ometterlo. È lo schema che fa esattamente ciò che gli hai detto di fare.
Una stringa vuota e un valore mancante sono fatti diversi, e required li fa collassare. Se hai bisogno di distinguere “il documento non lo dice” da “il documento non dice nulla qui”, tipizza il campo come {"type": ["string", "null"]} e chiedi null nel system prompt. La modalità strict accetta l’unione, e ottieni null invece di "".

Spegni il ragionamento

disable_thinking è la riga di quella richiesta su cui vale la pena discutere, quindi ecco l’argomentazione. Il modello di testo predefinito ragiona prima di rispondere, e il ragionamento è tratto dallo stesso budget di completion del JSON. Esegui la stessa estrazione quattro volte e osserva cosa spende il modello: Alzare il budget non risolve il primo problema, si limita ad alzare il tetto che il modello può raggiungere. L’esecuzione che ha speso 4003 token è tornata con finish_reason uguale a length e una stringa vuota. Spegnere il thinking ha reso questa estrazione cinque volte più economica e, cosa più utile, l’ha resa uguale ogni volta. Lo schema sta già facendo il lavoro che farebbe il ragionamento, cioè decidere che forma prende la risposta.
Quando il budget si esaurisce davvero, il modello di solito ha già scritto un po’ di JSON, quindi ottieni un oggetto troncato piuttosto che un errore. json.loads poi fallisce su una stringa non terminata da qualche parte nel mezzo, il che sembra un bug di parsing ma non lo è. read_record controlla finish_reason per primo così il messaggio dice cosa è successo davvero.

4. Quando non c’è testo da estrarre

Un PDF prodotto da uno scanner contiene immagini di pagine, non testo. Nulla nel nome del file lo dice, e nemmeno la dimensione del file lo rivela. Non devi rilevarlo tu, perché lo fa il parser:
Questo arriva come HTTP 400, ed è un segnale di routing piuttosto che un fallimento. La strada del testo non è disponibile per questo file, quindi prendi l’altra: renderizza la pagina e lascia che un modello la guardi.
Ora le due strade possono essere collegate insieme, con l’errore stesso del parser a scegliere tra loro:
Il fallback legge una sola pagina. Va bene per un modulo, una fattura o una pagina del titolo, ed è sbagliato per qualsiasi cosa più lunga, perché il resto del documento silenziosamente non esiste. Renderizza ogni pagina e inviale come diverse immagini quando la risposta potrebbe non essere a pagina uno.

5. Su cosa le due strade non sono d’accordo

Eseguile entrambe sulla stessa prima pagina e i record tornano quasi identici. Il quasi è la parte interessante: La strada del testo ha preservato la Ł. La strada vision ha restituito una L ASCII, perché sta leggendo forme di lettere piuttosto che codici di caratteri, e un diacritico è un piccolo dettaglio visivo che sopravvive male. Se stai confrontando nomi estratti contro un database, quella differenza decide se la riga viene trovata. L’ottavo autore conta di più. La pagina non riporta alcuna affiliazione per Illia Polosukhin, e la strada del testo lo riferisce fedelmente come stringa vuota ogni volta. La strada vision, in alcune esecuzioni, ha riempito il campo con un vicino plausibile della stessa pagina. Leggere pixel lascia più spazio per inferire di quanto ne lasci leggere caratteri, e un campo richiesto è un invito a riempirlo. Quando non puoi verificare l’output a mano, questo è un motivo per preferire il testo analizzato ovunque il documento lo offra. Il costo è più vicino di quanto sembri. Con il thinking spento su entrambi i lati, le due strade hanno usato più o meno la stessa dimensione di prompt su questa pagina: L’immagine era di 923.732 caratteri di base64, e niente di questo è ciò che paghi. Le immagini vengono tokenizzate in base alla dimensione, non alla lunghezza della loro codifica, quindi un PNG grande non costa quanto sembra. Preferisci il testo analizzato quando il documento ha testo. Preserva i caratteri esatti, non costa nulla in più per arrivare oltre la prima pagina, e non gli importa come è impaginata la pagina. Ricorri alla vision quando il parser dice che non c’è nulla da leggere, o quando il significato è nel layout, come in un grafico, un timbro o una firma.

Estrarre qualcos’altro

Nulla di quanto sopra è specifico ai paper. Sostituisci lo schema e il system prompt, e la pipeline estrae fatture:
I campi description stanno facendo del lavoro reale. Una data è univoca solo quando hai detto quale formato vuoi, e 03/04/2026 significa due giorni diversi a seconda di chi l’ha scritta.

Prossimi passi

  • Convalida il risultato contro lo schema con pydantic o jsonschema, così un record malformato fallisce al confine piuttosto che tre funzioni dopo.
  • Memorizza il testo estratto con gli Embedding per cercare tra i documenti invece di riestrarli.
  • Allega documenti direttamente a una chat completion con i File Input quando vuoi risposte piuttosto che record.
  • Fornisci l’estrattore a un agente come strumento, usando Costruire un agente che usa strumenti con il function calling.

Elaborazione documenti

Riferimento per l’endpoint text-parser.

Risposte strutturate

Come json_schema vincola una completion.

Vision

Inviare immagini a un modello chat.

File Input

Allega un documento senza analizzarlo da solo.