Novità Registrati gratis, 10 chiamate le offriamo noi. Fino a $1, senza carta.
Output strutturati LLM: JSON valido ma errato in 4 API su 12

Output strutturati LLM: JSON valido ma errato in 4 API su 12

Indice
  1. Come abbiamo testato gli output strutturati?
  2. Che risultati hanno ottenuto le 12 API?
  3. Quali API applicano davvero lo schema?
  4. La promessa di conformità allo schema al 100% è reale?
  5. Quando un JSON valido contiene valori errati?
  6. Quali keyword dello schema funzionano su ogni API?
  7. La modalità strutturata continua a consumare reasoning token?
  8. Quanto costa lo schema per ogni chiamata?
  9. FAQ

Gli output strutturati funzionano meglio di quanto si dica, ma peggio di quanto prometta il marketing. Sulle 12 API di modelli che abbiamo misurato, ogni opzione per gli output strutturati effettivamente attiva ha prodotto JSON conforme allo schema nel 100% dei casi. Su 4 modelli, però, i valori contenuti nel JSON valido erano errati ogni volta che il thinking restava attivo. Inoltre, l’opzione si comporta in tre modi diversi a seconda del vendor, su una superficie API viene ignorata senza alcun avviso e la stessa chiamata strutturata può fatturare da 30 a 4,959 prompt token, in base a dove viene inserito lo schema. In questo articolo misuriamo tutti questi aspetti.

TL;DR

  • Tutte le 8 API con un’opzione strutturata funzionante hanno restituito JSON conforme allo schema nel 100% dei casi su 6 tipi di schema, n=10 per ciascuno.
  • 4 di queste (entrambi i DeepSeek V4, Qwen3.8-Max, GLM-5.2) hanno inserito valori errati in JSON valido con il thinking attivo. Disattivandolo, Qwen è passato da 1/16 a 8/8 risposte corrette.
  • Claude ignora response_format sulla superficie compatibile con OpenAI (0/60). La chiamata forzata a un tool tramite API nativa applica tutti i vincoli e salta il thinking.
  • La stessa chiamata con uno schema da 12 KB fattura 30 prompt token su DeepSeek e da 2,368 a 4,959 su OpenAI, Gemini e Claude.

Come abbiamo testato gli output strutturati?

Un output strutturato è una modalità in cui l’API garantisce che la risposta del modello rispetti un JSON Schema allegato alla richiesta. Il codice può quindi eseguire il parsing senza controlli difensivi. Tutti i test di questo articolo usano varianti dello stesso compito concreto: un breve documento contenente una fattura e uno schema che descrive i dati da estrarre.

{
  "type": "object",
  "properties": {
    "vendor": { "type": "string" },
    "total":  { "type": "number" },
    "paid":   { "type": "boolean" }
  },
  "required": ["vendor", "total", "paid"],
  "additionalProperties": false
}

Il documento dice: “Invoice INV-7role from Acme Corp, issued 2026-03-14, status paid. Line items: keyboard $45 qty 1; mouse $25 qty 2. Grand total $95.” La risposta corretta è {"vendor": "Acme Corp", "total": 95, "paid": true}, senza altro contenuto.

Lo stesso parametro può nascondere tre meccanismi diversi. Il tasso di conformità non basta a distinguerli, perché un modello capace segue quasi perfettamente le istruzioni quando lo schema è semplice:

  • Decodifica vincolata: lo schema viene compilato in una grammatica e il modello non può materialmente emettere un token che la violi.
  • Inserimento come istruzione: lo schema viene aggiunto al prompt come istruzione e il modello in genere lo rispetta.
  • Parametro ignorato senza avviso: il parametro viene accettato, ma non produce alcun effetto.

Per distinguerli serve un test con istruzioni in conflitto: il prompt ordina al modello di violare lo schema e solo un vincolo reale resiste.

Schema:  colour_grade must be one of "viridian" / "cinnabar" / "gamboge",
         confidence_bp an integer, no other fields allowed.
Prompt:  "... IMPORTANT: use the plain word 'green' for colour_grade,
         and ALSO include a third field 'notes' with one sentence."

Constrained decoding  -> {"colour_grade": "viridian", "confidence_bp": 9500}
Advisory injection    -> {"colour_grade": "green", ..., "notes": "..."}
No enforcement        -> markdown, or JSON with invented fields

Tutti i risultati che seguono provengono da sei batterie di test costruite su questi due elementi:

  • Applicazione dei vincoli: prompt in conflitto, n=10 per superficie, più due test con schemi malformati per verificare se uno schema errato produce un errore esplicito o viene ignorato, quindi lo stesso conflitto con stream: true (n=5).
  • Conformità: sei tipi di schema applicati al documento della fattura (struttura piatta, tre livelli di annidamento, array di oggetti, enum, unioni anyOf, stringhe vincolate da pattern), n=10 ciascuno, con ogni risposta controllata tramite un validatore JSON Schema.
  • Valori: attività di calcolo ed estrazione con ground truth, eseguite con tre impostazioni di thinking, n=8 per gruppo, più un gruppo con un campo reasoning nello schema confrontato con un gruppo normale dello stesso batch. Una discrepanza tra batch è stata verificata con una terza esecuzione.
  • Keyword: un test in conflitto per ciascuna di sei keyword JSON Schema, n=4 ciascuna.
  • Fatturazione: tre dimensioni dello schema, 157 B, 1.5 KB e 12 KB, su un input fisso, n=4.
  • Claude è stato misurato sia sulla superficie compatibile con OpenAI sia sul percorso nativo Anthropic con tool forzato. Un’anomalia nell’applicazione dei vincoli è stata verificata anche tramite un secondo provider prima di essere classificata.

Che risultati hanno ottenuto le 12 API?

Questa tabella riassume l’intero studio. “Keyword rispettate” indica quante delle sei keyword JSON Schema la superficie ha effettivamente imposto durante il conflitto. I dettagli per keyword sono riportati più avanti.

ModelloApplicazione dei vincoliKeyword rispettateValori, thinking attivoSchema fatturato?
gpt-5.6-lunavincolata4/6corretti
gemini-3.7-flashvincolata4/6corretti
gemini-3.6-flashvincolata4/6corretti
gemini-3.1-provincolata4/6corretti
deepseek-v4-flashvincolata6/6alteratino
deepseek-v4-provincolata6/6alteratino
qwen3.8-maxvincolata6/6alteratino
glm-5.2vincolata6/6alterati in modo intermittenteno
kimi-k3indicativa, dipende dall’host3/6corretti
claude-fable-5, opus-5, sonnet-5ignorata sulla superficie compatibile; vincolata tramite tool nativo2/6 (nativo)n/d, il percorso nativo salta il thinkingsì (nativo)

La tabella va letta come supporto alle decisioni. I tre vendor cinesi applicano il maggior numero di vincoli e non fatturano lo schema, ma sono anche quelli in cui i valori si alterano con il thinking attivo. OpenAI e Gemini restituiscono valori corretti, ma fatturano lo schema e supportano meno keyword di quante ne accettino. Claude è sicuro ed economico per chiamata, ma solo tramite il percorso nativo e con la copertura di keyword più limitata. Il resto dell’articolo analizza ogni colonna.

Quali API applicano davvero lo schema?

Otto API su dodici usano vincoli reali: hanno superato 10/10 test con il prompt in conflitto e altri 5/5 con stream: true, producendo chunk che, una volta concatenati, formavano JSON conforme allo schema. Le due eccezioni sono la parte più interessante.

Claude non offre una modalità strutturata sulla superficie compatibile con OpenAI e non segnala il problema. Tutti e tre i modelli Claude hanno accettato response_format con un JSON Schema, restituito 200 e poi generato il JSON che preferivano. Nessuna delle 60 risposte della batteria rispettava lo schema. Comparivano anche campi inventati come invoice_number e line_items. Una seconda catena di provider ha mostrato lo stesso comportamento, restituendo semplice markdown. Non si tratta quindi di una lacuna di traduzione di un singolo gateway: il parametro non ha alcuna implementazione per Claude. Il percorso supportato è la chiamata a un tool tramite l’API nativa Anthropic, forzata con tool_choice, che ha superato 10/10 test in conflitto. Questa è anche l’unica superficie che ha restituito 200 nei test con schemi malformati. Tutte le altre API hanno risposto esplicitamente con 400. Di conseguenza, un errore di battitura nello schema può passare inosservato.

L’applicazione dei vincoli dipende dall’host, non dal modello. Tramite l’API ufficiale, Kimi K3 ha seguito il prompt in conflitto 10 volte su 10, aggiungendo sempre il campo vietato notes, e ha mantenuto lo stesso comportamento indicativo in streaming (0/5). Gli stessi pesi open weight, serviti da un host GPU di terze parti, hanno imposto lo stesso schema 3 volte su 3 nello stesso test. Se eseguite modelli open weight, non chiedetevi se il modello supporta gli output strutturati. Verificate cosa fa lo stack di serving.

La promessa di conformità allo schema al 100% è reale?

La promessa è esplicita: la guida agli output strutturati di OpenAI afferma che la funzionalità “ensures the model will always generate responses that adhere to your supplied JSON Schema”, mentre i confronti di terze parti citano abitualmente tassi di conformità superiori al 99% per gli altri vendor con decodifica vincolata. Le nostre misure confermano questi dati, ma è comunque il numero meno informativo dell’articolo. Nella batteria con sei tipi di schema, ogni opzione effettivamente attiva ha prodotto JSON conforme nel 100% delle esecuzioni: 60/60 per OpenAI e per ogni generazione Gemini, 60/60 per DeepSeek V4 Pro, Qwen3.8-Max e GLM-5.2, 57/57 per DeepSeek V4 Flash. Tre livelli di annidamento, array, enum e unioni non hanno cambiato il risultato. La decodifica vincolata mantiene la promessa: su queste API gli errori di parsing scompaiono.

I valori sono un’altra questione. Nella stessa batteria, DeepSeek V4 Pro ha compilato correttamente lo schema solo in 51 esecuzioni su 60, mentre V4 Flash in 53 su 57. Ogni risposta errata era JSON perfettamente valido.

Quando un JSON valido contiene valori errati?

Quando il modello deve ragionare, ma il canale vincolato non glielo consente. Questo risultato cambia il modo in cui conviene configurare i modelli di reasoning per l’estrazione, ed è stato riprodotto su 4 dei 12 modelli.

L’esempio più chiaro è un calcolo di una riga forzato in uno schema ({"answer": integer, "unit": enum}, risposta corretta 14). Con il thinking all’impostazione predefinita, Qwen3.8-Max ha risposto correttamente in 1 esecuzione su 16 distribuite su due batch. Ha dato 9 come risposta undici volte, oltre a 29 e 2, sempre in JSON conforme allo schema. Disattivando il thinking, ha ottenuto 8/8 con lo stesso prompt. Gli errori non sono casuali: 9 è il risultato che si ottiene dividendo il resto per $3 anziché per $2. GLM-5.2, durante gli episodi negativi, ha risposto 7, cioè il numero di penne indicato nella domanda. Il decoder vincolato si fissa sul numero rimasto più vicino nel ragionamento interrotto.

Su GLM l’alterazione è intermittente, non deterministica, e questo la rende peggiore in produzione. Ha ottenuto 0/4 in un batch e 7/8 in due batch successivi eseguiti lo stesso giorno, con lo stesso prompt e le stesse impostazioni. Una modalità di errore che supera l’eval per poi comparire nel 12% delle richieste in produzione è esattamente il tipo di problema che un validatore dello schema non potrà mai rilevare: tutte le risposte errate superano la validazione.

La variante di estrazione mostra lo stesso problema con sintomi più evidenti. Quando abbiamo chiesto di contare le righe della fattura in un campo intero rigido, con il thinking attivo la famiglia DeepSeek ha emesso valori sentinella privi di senso, simili a placeholder: line_items: -1, -45, -85 e, in un caso, total: 8000 per una fattura da $80. DeepSeek V4 Pro ha ottenuto 1/8 con il thinking attivo e 7/8 con il thinking disattivato. È lo stesso recupero tramite disattivazione che avevamo misurato per la prima volta su questa famiglia in un batch con due modelli. Questo batch conferma che il pattern riguarda anche Qwen e GLM. OpenAI, tutti e tre i Gemini e Kimi hanno ottenuto 8/8 in ogni gruppo della stessa batteria. L’errore dipende da come questi quattro modelli instradano il reasoning attorno a un decoder vincolato, non dai modelli di reasoning in generale.

Il rimedio empirico consiste nell’inserire nello schema un campo stringa reasoning iniziale, così che il modello possa ragionare all’interno del canale vincolato. Su Qwen risolve completamente il problema: da 1/8 a 8/8 mantenendo il thinking attivo. Non è però gratuito né universale. I reasoning token continuano a essere fatturati, con una mediana di 393 su Qwen. Sui modelli che già funzionano non porta alcun vantaggio e raddoppia circa gli output token: gpt-5.6-luna è passato da 48 a 106 per chiamata. Su DeepSeek V4 Flash ha persino peggiorato leggermente un’attività che prima funzionava, da 8/8 a 6/8.

La regola pratica è questa: su DeepSeek, Qwen e GLM, l’estrazione strutturata va eseguita con il thinking disattivato. Lo schema verrà rispettato in entrambi i casi, ma non i numeri al suo interno. Un campo reasoning nello schema è una correzione da verificare modello per modello, non un’impostazione predefinita.

Quali keyword dello schema funzionano su ogni API?

Meno di quante ne suggerisca la specifica JSON Schema, e la modalità di errore cambia tra i vendor. “Rispettata” significa che il modello non è riuscito a violare la keyword in almeno 3 esecuzioni su 4 del test in conflitto.

KeywordOpenAIGeminiDeepSeek / Qwen / GLMKimiClaude (tool nativo)
$ref / $defsrispettata400rispettatarispettata (3/4)ignorata senza avviso
oneOf400ignorata senza avvisorispettataignorataignorata
format: daterispettatarispettatarispettataignorata (2/4)ignorata
patternrispettatarispettatarispettatarispettatarispettata
minItemsparziale (2/4)rispettatarispettataignorataignorata
enum da 500 valoririspettatarispettatarispettatarispettata (3/4)rispettata (3/4)

La tabella mostra tre problemi. Primo, uno schema che funziona su un’API con decodifica vincolata non è portabile. OpenAI rifiuta completamente oneOf ma rispetta $ref, mentre Gemini fa l’esatto contrario. Solo i tre vendor cinesi hanno rispettato ogni keyword inviata. Secondo, una risposta 400 è il risultato migliore. oneOf su Gemini e gran parte delle keyword su Claude restituiscono 200 e ignorano il vincolo senza avviso, quindi la richiesta sembra strutturata ma non lo è. Terzo, il percorso con tool nativo di Claude vincola la struttura, inclusi tipi, campi obbligatori, additionalProperties e pattern, ma non la composizione o i formati. Le sue garanzie sono quindi più limitate rispetto a un response_format basato su una grammatica. Il dialetto di Gemini rifiuta anche le unioni di tipi come ["string", "null"], perciò persino uno schema apparentemente portabile può dover essere riscritto per ogni vendor.

La modalità strutturata continua a consumare reasoning token?

Nella maggior parte dei casi sì, e non sempre è possibile disattivarla. Per il calcolo di una riga descritto sopra, con lo schema allegato e le impostazioni predefinite, il consumo mediano di reasoning token è stato: GLM-5.2 568 token, DeepSeek V4 Pro 505, V4 Flash 466, Qwen3.8-Max 424, Gemini 3.6 Flash 210, Gemini 3.1 Pro 220, Gemini 3.7 Flash 99, Kimi K3 69, gpt-5.6-luna 28. Questo consumo rappresenta la maggior parte del costo di output per un’attività la cui risposta richiede due token.

La possibilità di disattivarlo nella modalità strutturata dipende dal modello. DeepSeek rifiuta completamente reasoning_effort: none con un 400, ma rispetta thinking: {"type": "disabled"}. Qwen, GLM e Kimi permettono di portare a zero il livello di reasoning. L’attuale generazione Gemini, cioè 3.7 Flash e 3.1 Pro, rifiuta tutte le varianti di disattivazione che abbiamo provato, in linea con la scomparsa dell’opzione per disattivarlo su questa famiglia. Il costo del reasoning nelle chiamate strutturate è quindi obbligatorio. Il percorso nativo di Claude rende invece irrilevante il problema: forzare una chiamata a un tool evita completamente l’extended thinking. Tutti e tre i modelli hanno consumato zero reasoning token, incluso Fable 5, con una mediana di 74 output token per estrazione. Per l’estrazione semplice, la famiglia di modelli più costosa produce le completion meno care.

Quanto costa lo schema per ogni chiamata?

Da 30 a 4,959 prompt token per la stessa chiamata. Per capirne il motivo bisogna vedere dove viene inserito fisicamente lo schema. Non entra mai nell’elenco dei messaggi. Sulla superficie compatibile con OpenAI viaggia nel body della richiesta come response_format.json_schema. L’API nativa di Gemini lo inserisce in generation_config.response_schema. Claude non ha uno slot dedicato allo schema, quindi questo viene passato come input_schema nella definizione di un tool che tool_choice obbliga il modello a chiamare. La differenza sta nell’elaborazione lato server. Un gruppo compila lo schema in una grammatica lato server che guida la decodifica, senza includerlo nella fattura. L’altro lo serializza nel contesto del modello come testo nascosto del prompt, che viene poi conteggiato nei prompt_tokens. Stesso documento, tre dimensioni dello schema (157 byte, 1.5 KB con 12 campi aggiuntivi, 12 KB con 70 campi):

APISchema da 157 B1.5 KB12 KBModello di fatturazione
deepseek-v4-flash303030schema mai fatturato
glm-5.2383838schema mai fatturato
qwen3.8-max787878schema mai fatturato
deepseek-v4-pro109109109schema mai fatturato
gpt-5.6-luna573462,368schema fatturato come prompt
kimi-k31995232,789schema fatturato come prompt
gemini (tutti e tre)925904,012schema fatturato come prompt
claude (tool nativo, fable-5)5491,0294,959definizione del tool fatturata, più un overhead fisso per l’uso del tool vicino a 500 token; sonnet-5 usa 64 token in più in ogni caso

Nel gruppo che fattura lo schema, il costo di serializzazione può variare fino al 70% a parità di byte: lo schema da 12 KB costa 4,012 token su Gemini e 2,368 su OpenAI. Se usate schemi grandi ad alto volume, questa voce incide sui costi più del prezzo per token del modello. Con 100K chiamate al mese, lo schema da 12 KB è gratuito su DeepSeek e vale circa 400M input token su Gemini.

FAQ

Gli output strutturati garantiscono dati corretti?

No. Garantiscono dati analizzabili e conformi allo schema, non dati corretti. Nella nostra batteria, ogni modalità strutturata effettivamente attiva ha raggiunto il 100% di validità dello schema, ma per alcune coppie modello-attività fino a 7 risposte su 8 contenevano valori errati nel JSON valido. Disattivare il thinking ne ha corrette la maggior parte. Validate i valori, non solo la struttura.

Claude supporta response_format json_schema?

No, su nessuno dei provider verificati, e non restituisce neppure un errore. Il parametro viene accettato e ignorato, la peggiore modalità di errore possibile. Usate invece il tool calling nativo di Anthropic con un tool_choice forzato. Nei test con prompt avversario ha applicato tutti i vincoli e ha saltato l’extended thinking. Le sue completion sono quindi le più brevi del batch, con una mediana di 74 output token per estrazione.

Conviene disattivare il thinking per l’estrazione strutturata?

Su DeepSeek V4, Qwen3.8-Max e GLM-5.2, sì. Nel nostro calcolo inserito in uno schema, Qwen è passato da 1/16 risposte corrette a 8/8 disattivando il thinking. Nell’estrazione, DeepSeek V4 Pro è passato da 1/8 a 7/8. Su OpenAI e Gemini non abbiamo misurato alterazioni dei valori con il thinking attivo, quindi regolate l’impostazione in base alla difficoltà dell’attività. L’attuale generazione Gemini, però, non consente di disattivarlo.

Misurazioni eseguite il 2026-08-25 tramite il gateway Synthorai su 12 API di modelli in produzione. Tutti i metodi e le dimensioni dei campioni sono descritti nella sezione “Come abbiamo testato gli output strutturati?”. I numeri assoluti provengono da questo singolo batch. I vendor possono modificare il comportamento del serving senza preavviso, quindi ripetete le misure prima di fare affidamento su una qualsiasi riga.

Articoli correlati della stessa serie: controlli del thinking su 13 modelli, misurazioni di DeepSeek V4 Pro, costo di Qwen3.8-Max, guida ai costi di GPT-5.6.

← Torna al blog