18 – JSON corretto, informazione sbagliata: i limiti degli output strutturati

Copertina avorio di Dentro l’IA 18: una tessera con triangolo dorato entra in una cornice quadrata, mentre una lente mostra un cerchio su una seconda tessera. Titolo JSON corretto, dati sbagliati; libri, quaderno e mappamondo sulla scrivania.

Un assistente legge un documento e restituisce un oggetto JSON impeccabile: nomi dei campi corretti, numeri al posto dei numeri, nessuna virgola fuori posto. L’applicazione lo accetta. Poi scopriamo che il totale è sbagliato, oppure che il documento diceva tutt’altro. Il formato ha funzionato; il controllo dell’informazione era incompleto.

La domanda di questo episodio è precisa: che cosa possiamo considerare verificato quando un output rispetta uno schema? Per rispondere separiamo la conformità del dato al contratto, la fedeltà alla fonte e le regole applicative. Gli output strutturati sono molto utili proprio quando sappiamo quale parte del problema risolvono e quali verifiche restano da eseguire.

In breve

  • Un JSON leggibile dal parser può comunque violare lo schema richiesto.
  • Uno schema valido può ammettere dati falsi, anche aritmeticamente coerenti.
  • Alcune regole di dominio si esprimono nello schema; altre richiedono codice, archivi o autorizzazioni.
  • La generazione vincolata limita i token ammessi durante il decoding, senza consultare automaticamente la realtà.
  • Il passaggio all’azione deve dipendere dall’insieme dei controlli necessari, non dal solo esito del validatore.

Dal testo libero al contratto

JSON è una rappresentazione di dati: oggetti, liste, stringhe, numeri, valori booleani e null. Un parser controlla che il testo sia interpretabile secondo la sintassi prevista. Per esempio, {"total": "10000"} è un oggetto JSON perfettamente leggibile, ma il totale è una stringa. Se l’applicazione si aspetta un intero, il parsing non basta.

Uno schema aggiunge un contratto: quali proprietà devono esistere, quali tipi e valori sono consentiti, quali vincoli vanno rispettati. Come chiarisce la documentazione JSON Schema sugli oggetti, elencare i campi in properties non li rende obbligatori: serve required. Per vietare proprietà ulteriori si può usare additionalProperties: false.

Il contratto può quindi rifiutare un totale scritto come testo o una proprietà inattesa. Ma se ammette un intero non negativo, sia 9.000 sia 10.000 possono essere valori conformi. Per sapere quale dei due corrisponde al documento, bisogna confrontarli con una fonte.

Come viene vincolata la generazione

Chiedere nel prompt “rispondi in JSON” e applicare un vincolo al campionamento sono interventi diversi. Nel primo caso il modello riceve un’istruzione. Nel secondo il motore può escludere, a ogni passo, token incompatibili con la grammatica dell’output.

La documentazione di XGrammar 0.2.8, descrive una maschera applicata ai punteggi dei token: le opzioni vietate ricevono probabilità zero. Lo stato della grammatica viene aggiornato con i token già generati. Una stringa numerica può così essere esclusa dove è richiesto un numero.

La garanzia va letta rispetto a grammatica, schema supportato e corretta integrazione del motore. Non ogni sistema implementa l’intero linguaggio JSON Schema; errori di esecuzione, interruzioni e limiti di lunghezza vanno gestiti separatamente. Il paper originale di XGrammar, versione del 12 maggio 2025, studia l’esecuzione efficiente dei vincoli. Questo meccanismo non costituisce, da solo, una verifica documentale o fattuale.

Un esempio concreto: estrarre un preventivo

Costruiamo una fonte interamente inventata, il preventivo Q7: “Quantità: 2. Prezzo unitario: 50 euro. Totale: 100 euro. Valuta: EUR”. Per mantenere esatti i conti monetari, nell’esempio rappresentiamo gli importi in centesimi interi. Non trattiamo imposte, sconti o arrotondamenti.

{
"doc": "Q7",
"qty": 2,
"unit": 5000,
"total": 10000,
"currency": "EUR"
}

Il nostro schema richiede tutti e cinque i campi; doc è una stringa non vuota, qty un intero almeno pari a uno, unit e total interi non negativi. La valuta deve essere EUR e non sono ammesse proprietà aggiuntive.

Separatamente fissiamo una regola applicativa ipotetica: soltanto importi fino a 9.000 centesimi possono proseguire nel percorso automatico; oltre quella soglia serve revisione. È una scelta del nostro esempio, non una norma o una soglia economica reale. L’esito di questa singola regola non autorizza da solo alcuna operazione.

Quattro candidati che separano i controlli

Il candidato A riproduce la fonte. B cambia soltanto il totale in 9.000. C cambia quantità e totale in 1 e 5.000. D lascia i valori di A, ma trasforma il totale nella stringa "10000". Tutti possono essere rappresentati con sintassi JSON valida; non tutti rispettano lo schema.

CasoSchemaContiFonteSoglia
ASìSìSìNo
BSìNoNoSì
CSìSìNoSì
DNo———
Prova su dati costruiti. Conti: total = qty × unit. Fonte: coincidenza con Q7. Soglia: total ≤ 9.000 centesimi. Dopo il fallimento dello schema, gli altri controlli non vengono eseguiti.

B mostra un limite immediato: 2 × 5.000 fa 10.000, non 9.000. Il tipo del totale è corretto, il calcolo no. C è più insidioso: 1 × 5.000 fa proprio 5.000, quindi i conti tornano; ma il preventivo diceva due unità. Coerenza interna e fedeltà alla fonte sono proprietà diverse.

A completa il quadro: il dato è fedele e coerente, ma supera la soglia del percorso automatico. L’estrazione non deve cambiare un importo autentico per farlo rientrare nel limite. Il sistema dovrebbe conservare il dato e segnalare la necessità di revisione.

Una piccola prova verificabile

Il codice seguente è stato eseguito con jsonschema 4.25.1, usando il validatore Draft 2020-12. Per riprodurlo si può installare la dipendenza con python -m pip install jsonschema==4.25.1. Lavora su oggetti Python equivalenti al JSON già interpretato: non chiama modelli e non misura l’efficacia di un decoder vincolato.

from jsonschema import Draft202012Validator
# Schema e fonte INVENTATI per il laboratorio.
schema = {
'type': 'object',
'properties': {
'doc': {'type': 'string', 'minLength': 1},
'qty': {'type': 'integer', 'minimum': 1},
'unit': {'type': 'integer', 'minimum': 0},
'total': {'type': 'integer', 'minimum': 0},
'currency': {'enum': ['EUR']},
},
'required': [
'doc', 'qty', 'unit', 'total', 'currency'
],
'additionalProperties': False,
}
Draft202012Validator.check_schema(schema)
v = Draft202012Validator(schema)
source = dict(doc='Q7', qty=2, unit=5000,
total=10000, currency='EUR')
cases = {
'A': source.copy(),
'B': dict(source, total=9000),
'C': dict(source, qty=1, total=5000),
'D': dict(source, total='10000'),
}
results = []
for name, data in cases.items():
valid = v.is_valid(data)
if not valid:
result = (False, None, None, None)
else:
math_ok = data['total'] == (
data['qty'] * data['unit']
)
source_ok = data == source
rule_ok = data['total'] <= 9000
result = (True, math_ok, source_ok, rule_ok)
results.append(result)
print(name, result)
assert results == [
(True, True, True, False),
(True, False, False, True),
(True, True, False, True),
(False, None, None, None),
]

Le tuple stampate seguono l’ordine schema, conti, fonte, soglia e corrispondono alla tabella. None significa “controllo non eseguito”. La verifica della fonte usa qui l’uguaglianza con un record costruito manualmente: in un’applicazione reale occorre ottenere quel riferimento da documenti o archivi affidabili, mantenendo provenienza e versione.

La documentazione del validatore distingue il controllo dello schema stesso da quello dei dati. Anche un contratto scritto male può produrre risultati fuorvianti: prima si verifica che lo schema sia ben formato, poi che rappresenti davvero le esigenze del sistema.

Lo schema può fare più della sola forma

Sarebbe sbagliato concludere che JSON Schema controlli soltanto parentesi e tipi. Intervalli, enumerazioni e condizioni fra proprietà permettono di esprimere molte regole. La soglia del nostro esempio potrebbe diventare un maximum in un contratto dedicato ai soli dati ammessi al percorso automatico.

Bisogna però scegliere il contratto giusto. Lo schema di estrazione deve poter rappresentare anche un preventivo autentico sopra soglia; lo schema del successivo comando applicativo può essere più restrittivo. Mescolare i due scopi può costringere il sistema a rifiutare estrazioni corrette o a gestire male casi del tutto legittimi.

La documentazione introduttiva What is a schema? ricorda che alcuni vincoli richiedono una fase semantica in un linguaggio generale. Il prodotto fra campi variabili del nostro laboratorio viene controllato in Python. L’esistenza e la validità attuale del documento Q7 richiederebbero invece accesso a un archivio: scrivere un identificativo non dimostra che il documento esista.

Dove si nascondono altri falsi positivi

Un campo obbligatorio può comunque essere vuoto, se lo schema lo consente. Un codice appartenente a un elenco può essere quello sbagliato per il caso specifico. Un URL può avere una forma plausibile e puntare a una pagina che non sostiene l’affermazione. Aggiungere un campo “fonte” facilita la tracciabilità, ma occorre controllarne il contenuto.

Anche i formati richiedono attenzione: secondo la documentazione di JSON Schema, format è normalmente un’annotazione. Il validatore Python consente di attivare controlli con un FormatChecker, con supporto e dipendenze che variano per formato. Una data deve essere verificata sia come valore rappresentabile sia rispetto a ciò che dice il documento.

Infine, uno schema che richiede sempre una risposta numerica può lasciare senza rappresentazione il caso “dato assente”. Conviene progettare esplicitamente stati come mancante, ambiguo o da verificare, eventualmente con valori null ammessi e motivazioni. Un campo riempito per completare la struttura non diventa informazione acquisita.

Dal dato conforme all’operazione ammessa

Per una pipeline documentale distinguerei l’acquisizione della fonte, l’estrazione, la validazione del contratto, i controlli aritmetici e documentali, infine le regole per l’operazione richiesta. Ogni fase dovrebbe conservare un esito separato, con messaggi utili a capire cosa correggere.

Un errore di tipo può richiedere una nuova estrazione; un totale incoerente richiede il confronto con le righe del documento; un importo sopra soglia richiede il percorso previsto dalla procedura. Chiedere al modello di riprovare finché tutto passa può trasformare un problema reale in un dato apparentemente accettabile. La correzione deve restare collegata alla fonte.

Per valutare il sistema misurerei separatamente conformità allo schema, accuratezza dei campi rispetto a riferimenti verificati e rispetto delle regole applicative. Nel laboratorio tre candidati su quattro superano lo schema, ma fra questi soltanto uno riproduce il record di riferimento: sono conteggi di casi costruiti, non tassi di prestazione di un LLM. Nei test reali inserirei documenti incompleti, dati ambigui e casi fuori dal percorso automatico.

Una checklist pratica

  • Lo schema dichiara campi richiesti, tipi, unità e valori mancanti?
  • Conosco versione e sottoinsieme supportato dal generatore e dal validatore?
  • Controllo i dati contro fonti versionate, oltre ai calcoli interni?
  • Le regole di estrazione sono distinte da quelle per l’azione?
  • Errori, interruzioni e necessità di revisione hanno un percorso esplicito?

Uno schema stabilisce che cosa il sistema è disposto a leggere. Fonti, verifiche e regole stabiliscono che cosa può considerare attendibile e utilizzare.

DALLA MIA BIBLIOTECA

Per approfondire


← Torna al percorso Dentro l’IA

Commenti

Lascia un commento