Cinque anni fa, estrarre dati strutturati dalle fatture significava una pipeline di deep learning: una fase OCR, un modello consapevole del layout (LayoutLM, Donut) ottimizzato su migliaia di documenti annotati manualmente e un onere di manutenzione ogni volta che un fornitore modificava il proprio modello. Nel 2026 l'intera pipeline si riduce a una chiamata API a un vision LLM: immagine in ingresso, JSON convalidato dallo schema in uscita — zero dati di addestramento, zero modelli ospitati e nuovi layout gestiti zero-shot. Questa guida di Kunavo — un gateway indipendente compatibile con OpenAI verso i modelli visivi su cui si basa questo approccio — mostra il pattern completo funzionante, il costo per fattura misurato sulle nostre tariffe pubblicate e come mantenere un'accuratezza adatta alla produzione.
Confronto tra gli approcci all'estrazione delle fatture: regole, machine learning, deep learning, LLM
Sono state utilizzate quattro generazioni di tecniche per ottenere dati strutturati da una fattura. Vale la pena metterle a confronto, perché il compromesso che per due decenni ha deciso la scelta tra loro — accuratezza acquistata con dati annotati — è proprio quello che ha smesso di essere applicabile.
| Approccio | Come funziona | Costo di configurazione | Gestisce un nuovo layout di fornitore |
|---|---|---|---|
| Modelli / regole (anni 1990–) | OCR, quindi zone di coordinate per fornitore ed espressioni regolari («il totale è il numero a destra di ‘TOTAL’») | Ore per fornitore, per sempre | No — richiede un nuovo modello |
| Machine learning classico (anni 2010) | OCR, quindi caratteristiche progettate manualmente (posizione, dimensione del font, parole vicine) inserite in un CRF o SVM che assegna un'etichetta a ogni token | Progettazione delle caratteristiche + alcune migliaia di etichette | Parzialmente — peggiora sui layout mai visti |
| Deep learning (2020–) | Transformer consapevoli del layout (LayoutLM, Donut, DocTR) ottimizzati per leggere congiuntamente testo, posizione e pixel | 3.000–50.000 fatture annotate + serving su GPU | Spesso no — di norma richiede rietichettatura e riaddestramento |
| LLM con capacità visive (2024–) | Una chiamata API: immagine della fattura più uno schema JSON, con output strutturato vincolato allo schema | Nessuno — uno schema e un prompt; $0,00248–$0,00497 per fattura | Sì, zero-shot |
I primi tre condividono tutti una proprietà: l'accuratezza si acquista con dati annotati. Ogni modello di fornitore, ogni caratteristica CRF, ogni fine-tuning di LayoutLM è un progetto di annotazione. Un vision LLM ha già sostenuto quel costo durante il pretraining, quindi il costo marginale di un nuovo layout è zero. Questo è l'intero cambiamento — non che i metodi precedenti abbiano smesso di funzionare, ma che ciò su cui spendevano il budget è diventato gratuito.
Deep learning ottimizzato rispetto a vision LLM, nel dettaglio
| Modello del layout ottimizzato (2021) | LLM con capacità visive (2026) | |
|---|---|---|
| Dati di addestramento | 3.000–50.000 fatture annotate | Nessuno (zero-shot) — alcuni esempi aiutano nei casi limite |
| Nuovo layout di fornitore | Spesso richiede rietichettatura + riaddestramento | Gestito zero-shot |
| Infrastruttura | Serving su GPU + fase OCR | Una chiamata HTTPS |
| Formato dell'output | Etichette dei token → decodifica personalizzata | JSON vincolato allo schema |
| Costo per fattura | Predomina il tempo di progettazione | $0,00248–$0,00497 |
Un estrattore ottimizzato può ancora prevalere su un singolo layout stabile ad alto volume, ma per la lunga coda delle fatture del mondo reale — fornitori, lingue e qualità delle scansioni differenti — il vision LLM zero-shot è più accurato nella pratica e molto più economico da gestire. Altri pattern per questo tipo di carico di lavoro sono disponibili nel caso d'uso dell'estrazione dati.
Il pattern completo: immagine → JSON convalidato dallo schema
Tutto ciò che segue viene eseguito sull'endpoint compatibile con OpenAI di Kunavo — punta base_url a https://api.kunavo.com/v1 e lo stesso codice può usare Claude o GPT modificando una sola stringa:
from openai import OpenAI
import base64, json
client = OpenAI(
base_url="https://api.kunavo.com/v1",
api_key="sk-kn-...",
)
SCHEMA = {
"name": "invoice",
"schema": {
"type": "object",
"properties": {
"vendor_name": {"type": "string"},
"vendor_tax_id": {"type": ["string", "null"]},
"invoice_number": {"type": "string"},
"invoice_date": {"type": "string", "description": "ISO 8601"},
"due_date": {"type": ["string", "null"]},
"currency": {"type": "string", "description": "ISO 4217"},
"line_items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"description": {"type": "string"},
"quantity": {"type": "number"},
"unit_price": {"type": "number"},
"amount": {"type": "number"},
},
"required": ["description", "amount"],
},
},
"subtotal": {"type": ["number", "null"]},
"tax": {"type": ["number", "null"]},
"total": {"type": "number"},
},
"required": ["vendor_name", "invoice_number", "invoice_date",
"currency", "line_items", "total"],
},
}
def extract(path: str) -> dict:
image_b64 = base64.b64encode(open(path, "rb").read()).decode()
resp = client.chat.completions.create(
model="claude-haiku-4-5", # the cheapest capable model for clean invoices
response_format={"type": "json_schema", "json_schema": SCHEMA},
messages=[
{"role": "system", "content":
"Extract the invoice into the schema. Copy values exactly as "
"printed; use null when a field is absent. Never invent data."},
{"role": "user", "content": [
{"type": "image_url",
"image_url": {"url": f"data:image/png;base64,{image_b64}"}},
]},
],
)
return json.loads(resp.choices[0].message.content)
inv = extract("invoice_0231.png")
# Cheap arithmetic guardrail: reject when the line items don't add up.
delta = abs(sum(li["amount"] for li in inv["line_items"])
+ (inv.get("tax") or 0) - inv["total"])
if delta > 0.02:
raise ValueError(f"line items disagree with total by {delta:.2f}")
print(inv["vendor_name"], inv["total"], inv["currency"])Tre dettagli svolgono la maggior parte del lavoro: lo schema JSON in response_format (su Claude il modello non può emettere altro — consulta la nota su GPT nel riferimento collegato qui sotto), la regola del prompt di sistema «copia i valori esattamente; null quando assenti; non inventare mai» (elimina gli ID fiscali inventati) e la protezione aritmetica — voci + imposta devono corrispondere al totale, altrimenti il documento passa a un modello più forte o a una persona. Consulta il riferimento sui completamenti chat per ciò che response_format impone su ciascuna famiglia di modelli.
Lo stesso schema come modello Pydantic tipizzato
Un dizionario JSON-schema grezzo va bene per un endpoint e diventa problematico per dieci. In produzione, la maggior parte dei team definisce lo schema una sola volta come modello Pydantic e da questo ricava sia il vincolo sia l'oggetto analizzato, così l'estrattore e il resto del codebase non possono mai essere in disaccordo sulla struttura:
from pydantic import BaseModel, Field
from typing import Literal
from openai import OpenAI
client = OpenAI(base_url="https://api.kunavo.com/v1", api_key=KEY)
class LineItem(BaseModel):
description: str
quantity: float | None = None
unit_price: float | None = None
amount: float
class Invoice(BaseModel):
vendor_name: str
vendor_tax_id: str | None = None
invoice_number: str
invoice_date: str = Field(description="ISO 8601")
currency: str = Field(description="ISO 4217")
line_items: list[LineItem]
tax: float | None = None
total: float
# A confidence field the model fills in itself is a cheap, surprisingly
# reliable router: low self-reported confidence correlates well with the
# documents a human should see.
confidence: Literal["high", "medium", "low"]
resp = client.chat.completions.create(
model="claude-haiku-4-5",
response_format={
"type": "json_schema",
"json_schema": {"name": "invoice", "schema": Invoice.model_json_schema()},
},
messages=[...],
)
invoice = Invoice.model_validate_json(resp.choices[0].message.content)Qui vale la pena copiare due elementi. I campi tipizzati come float | None invece di float offrono al modello un modo legale per dire «assente» — senza questa possibilità, una riga d'imposta mancante spinge fortemente a inventare uno zero. Inoltre, il campo confidence autodichiarato costa tre token di output e instrada sorprendentemente bene; non è calibrato, ma «basso» è un segnale affidabile che una persona dovrebbe controllare.
Routing a due livelli: cosa viene realmente eseguito in produzione
Usare un unico modello per ogni documento è la struttura sbagliata. Le fatture stampate pulite — la stragrande maggioranza — vengono gestite dal vision model economico disponibile; la coda difficile (grafia manuale, scansioni scadenti, layout insoliti) è il punto in cui un modello più forte giustifica la propria tariffa. Escala in base all'errore di convalida, non a una supposizione:
CHEAP, STRONG = "claude-haiku-4-5", "claude-sonnet-5"
def valid(inv: dict) -> bool:
"""Arithmetic is the guardrail no confidence score replaces."""
items = sum(li["amount"] for li in inv["line_items"])
return abs(items + (inv.get("tax") or 0) - inv["total"]) <= 0.02
def extract_routed(path: str) -> tuple[dict, str]:
inv = extract(path, model=CHEAP)
if valid(inv) and inv.get("confidence") != "low":
return inv, CHEAP
# Escalate: same prompt, same schema, stronger model. Only the failures
# pay the higher rate, which is what keeps the blended cost near the
# cheap tier's.
inv = extract(path, model=STRONG)
if not valid(inv):
raise NeedsHumanReview(path, inv)
return inv, STRONG
# Blended cost at a 90/10 split, per 10,000 invoices:
# 9,000 x $0.00248 + 1,000 x $0.00497
# = $27.29Il controllo aritmetico svolge il lavoro reale: è l'unico segnale indipendente dall'opinione che il modello ha di sé. Un documento le cui voci corrispondono al totale dichiarato è quasi sempre corretto nei modi importanti; uno che non supera il controllo merita il modello più forte o una persona, qualunque cosa dichiari il campo della confidenza.
Costo per fattura, per modello
Una fattura di una pagina ≈ 1.800 token di input (inviata come immagine) + circa 350 token di output JSON:
| Modello | Per fattura | Per 10.000 fatture | Usa per |
|---|---|---|---|
claude-haiku-4-5 | $0,00248 | $24,80 | Predefinito: fatture stampate pulite ed esecuzioni in massa più economiche |
claude-sonnet-5 | $0,00497 | $49,70 | Livello di escalation: scansioni, grafia manuale, errori |
Le tariffe sono aggiornate nella pagina dei prezzi (circa il 30% in meno rispetto ai prezzi di listino dei fornitori) e le richieste non riuscite non vengono fatturate. Con il routing a due livelli — tutto tramite Haiku, gli errori del controllo aritmetico inoltrati a Sonnet — 10.000 fatture al mese costano in genere meno di $27,29.
Accuratezza: la checklist per arrivare in produzione
- Prima lo schema. Contrassegna i campi realmente obbligatori come
required, consentinullovunque altro — imporre un valore impone un'allucinazione. - Invia l'immagine, non il testo OCR. Il layout trasmette significato (colonne, riquadri dei totali); i modelli visivi lo leggono direttamente. Ricorri al testo solo per i PDF nativamente digitali da cui puoi estrarre un livello di testo pulito.
- Convalida l'aritmetica nel codice. Voci + imposta = totale rileva gratuitamente la maggior parte degli errori di estrazione.
- Escala, non riprovare alla cieca. Gli errori passano a Sonnet con l'errore di convalida incluso nel prompt; gli errori persistenti passano a una coda umana.
- Usa few-shot per i veri casi limite. Due o tre esempi dei tuoi layout peggiori (note di credito, più valute) nel prompt di sistema sono più efficaci di qualsiasi quantità di instruction tuning.
Oltre le fatture
Lo stesso pattern vincolato allo schema copre ricevute, ordini di acquisto, bolle di consegna, moduli doganali ed estratti conto — sostituisci lo schema e mantieni le protezioni. Esplora il più ampio caso d'uso dell'estrazione di dati strutturati per pattern adiacenti al RAG, oppure la guida all'ottimizzazione dei costi per le strategie di routing quando il volume cresce. Una sola chiave Kunavo copre ogni livello di modello utilizzato qui — registrati gratuitamente e crea una chiave per eseguire lo snippet così com'è.
Domande frequenti
Mi serve un modello di deep learning per estrarre dati dalle fatture?
Non più. La pipeline classica — OCR, seguito da un modello del layout come LayoutLM ottimizzato su migliaia di fatture annotate — è stata sostituita per la maggior parte dei team da una singola chiamata a un vision LLM: invia l'immagine della fattura insieme a uno schema JSON e ricevi dati strutturati convalidati. Non c'è alcun set di addestramento da annotare, nessun modello da ospitare e le variazioni di layout che rompevano i modelli ottimizzati vengono gestite zero-shot.
Come si confronta l'estrazione delle fatture con il machine learning rispetto all'uso di un LLM?
Gli estrattori di machine learning classico (CRF o SVM sull'output OCR, più caratteristiche di posizione e font progettate manualmente) e i loro successori di deep learning (LayoutLM, Donut) acquistano accuratezza con dati annotati — migliaia di fatture annotate per distribuzione, da rietichettare quando cambiano i layout. Un vision LLM ha sostenuto quel costo durante il pretraining, quindi legge zero-shot un layout di fornitore mai visto e restituisce JSON vincolato allo schema con una sola chiamata API. Il machine learning mantiene un vantaggio su un singolo layout stabile e ad altissimo volume, quando domina il costo d'inferenza; per la lunga coda delle fatture reali, l'LLM è più accurato nella pratica e molto più economico da gestire.
Posso ancora usare LayoutLM o Donut per l'estrazione delle fatture?
Sì, e restano opzioni ragionevoli quando elabori milioni di documenti con un unico layout stabile e puoi ammortizzare l'annotazione e il serving su GPU. Ciò che è cambiato è il valore predefinito: per un nuovo progetto, un vision LLM raggiunge l'accuratezza di produzione in un pomeriggio senza set di addestramento, e il costo per documento è abbastanza basso perché l'economia del fine-tuning raramente ripaghi. Un percorso intermedio comune consiste nell'eseguire prima l'LLM e ottimizzare il modello solo in seguito, usando gli output accettati dell'LLM come corpus annotato.
Quanto costa l'estrazione delle fatture per documento?
Una fattura di una pagina contiene circa 1.800 token di input (come immagine) più circa 350 token di output JSON. Su Kunavo costa circa $0,00248 per fattura con claude-haiku-4-5 e $0,00497 con claude-sonnet-5 — cioè, anche alla qualità Sonnet, mille fatture costano pochi dollari. Le richieste non riuscite non vengono fatturate.
Quale modello dovrei usare per estrarre dati dalle fatture?
Inizia con claude-haiku-4-5: gestisce le fatture stampate pulite quasi perfettamente al prezzo più basso. Invia al modello claude-sonnet-5 solo i casi problematici — grafia manuale, scansioni a bassa risoluzione, layout insoliti o documenti che non superano il controllo aritmetico. Questo routing a due livelli mantiene in genere oltre il 90% del volume sul livello economico.
Come posso garantire che l'output sia JSON valido?
Usa gli output strutturati: passa response_format con uno schema JSON e un modello Claude viene vincolato a produrre esattamente quella struttura — senza post-processing con espressioni regolari. Aggiungi una semplice protezione a livello applicativo: verifica che le voci più l'imposta corrispondano al totale e invia le discrepanze al modello più forte o a una coda umana.
Posso estrarre dati da fatture PDF, non solo da immagini?
Sì — esegui il rendering di ogni pagina PDF in PNG (ad esempio con pdf2image) e inviala come sopra. Per i PDF nativamente digitali puoi anche estrarre il livello di testo e inviarlo come testo semplice, che costa meno; mantieni il percorso tramite immagine per le scansioni.
Quanto è accurata l'estrazione delle fatture tramite LLM?
Sulle fatture stampate pulite, l'estrazione visiva vincolata allo schema con protezione aritmetica supera regolarmente il 98% di accuratezza a livello di campo — e, a differenza di un modello ottimizzato, mantiene l'accuratezza su layout mai visti. Misura sui tuoi documenti: 100 fatture annotate costituiscono un solido set di valutazione.
I miei dati delle fatture vengono usati per l'addestramento?
No — le richieste tramite Kunavo vengono trasmesse ai fornitori dei modelli secondo i termini di utilizzo delle API (non quelli delle app consumer) e non vengono usate per addestrare i modelli. Consulta la documentazione su fatturazione e dati per i dettagli sulla conservazione.