Structured outputs e JSON mode, che differenza c’è?
Cosa garantiscono JSON mode, tool calling e structured outputs, e come usarli con OpenAI, Claude, Gemini e agenti di programmazione.
In sintesi. Ci sono tre modi per avere JSON da un LLM: chiedere JSON al modello (JSON mode), fargli compilare gli argomenti di un tool (tool calling) oppure vincolare la risposta a uno schema (structured outputs). Il 20 ottobre 2026 abbiamo letto la documentazione delle API di OpenAI, Anthropic e Google e la documentazione da riga di comando di Claude Code, Codex di OpenAI, GitHub Copilot (la CLI e VS Code), Cursor e Antigravity CLI di Google.
- Tre modi, tre garanzie diverse. Il JSON mode dà solo JSON valido; in una chiamata strict a un tool, gli argomenti rispettano lo schema; gli structured outputs vincolano allo schema la risposta stessa. Nessuno dei tre garantisce che la risposta sia corretta.
- Su Claude il vecchio metodo ora restituisce un errore. Su Opus 5.5 e Sonnet 5.5, un
tool_choiceche obbliga il modello a chiamare un tool restituisce un errore 400. La strada consigliata da Anthropic è lo strict tool use conauto, oppure gli structured outputs. - Lo stesso schema non si comporta allo stesso modo con ogni provider. OpenAI e Anthropic rifiutano con un errore una regola di schema non supportata; Gemini la ignora. Anche l’ordine dei campi, la regola sui campi obbligatori e i limiti dello schema cambiano da provider a provider.
- Negli agenti di programmazione lo schema si passa con un flag. Claude Code e Antigravity CLI hanno
--json-schema, Codex ha--output-schema. La documentazione di Copilot CLI e di Cursor CLI descrive un output JSON, ma non cita nessuna opzione per lo schema.
Cosa sono gli structured outputs e in cosa differiscono dal JSON mode?
Se un programma deve leggere la risposta di un modello, la risposta deve arrivare ogni volta nella stessa forma. Se manca un campo, se un numero arriva come testo o se il JSON si interrompe a metà, il programma si blocca. Nell’articolo sul prompt per la code review abbiamo spiegato come chiedere un rilievo con una citazione testuale, e nell’articolo su LLM-as-a-judge come registrare il verdetto del giudice; in entrambi i casi è il codice a leggere la risposta del modello. Questo articolo parla della forma di quella risposta: i modi per avere JSON, cosa garantisce ciascuno e cosa è cambiato su Claude questo autunno.

Con il JSON mode chiedi JSON al modello e l’API promette che la risposta sarà JSON valido. Non promette quali campi conterrà. La documentazione di OpenAI lo scrive chiaramente: il JSON mode garantisce che la risposta si possa parsare senza errori, non che rispetti uno schema.
Con il tool calling (detto anche function calling) descrivi un tool al modello. Quando il modello vuole chiamare quel tool, ne scrive gli argomenti in JSON. Se il tool non è strict, è solo best effort, non una garanzia: secondo la documentazione di Anthropic, con un tool non strict il modello può scrivere "2" invece di 2 o saltare un campo obbligatorio. Con un tool strict, gli argomenti rispettano lo schema.
Con gli structured outputs dai lo schema alla risposta stessa. A ogni token, il modello sceglie solo tra i token che rispettano lo schema; si chiama constrained decoding. Lo strict tool use applica la stessa tecnica agli argomenti di un tool. Il risultato è un JSON valido che contiene i campi dello schema.
Quindi, quale per cosa? La documentazione di Google riassume la distinzione: gli structured outputs servono a formattare la risposta finale, il function calling a far fare qualcosa nel mezzo della conversazione. Se la risposta va scritta in un database, structured outputs; se il modello vuole leggere un file o eseguire un comando, tool calling.
Come si attivano gli structured outputs nei tre provider?
Tutti e tre i provider offrono gli stessi tre modi, ma cambiano i nomi, il modo di attivarli e i limiti. Abbiamo ricavato la tabella dalla documentazione di ciascun provider il 20 ottobre 2026:
| Provider | Vincolare la risposta a uno schema | Argomenti dei tool | JSON senza schema | Regola non supportata | Forzare un tool |
|---|---|---|---|---|---|
| OpenAI | text.format: json_schema e strict: true | strict: true; predefinito nella Responses API quando possibile | JSON mode: json_object | Restituisce un errore | required o una sola funzione per nome |
| Anthropic | output_config.format: json_schema | strict: true | La documentazione non lo dice | Restituisce 400 | Opus 5.5 e Sonnet 5.5: 400; Haiku 5.5 lo accetta ma salta il ragionamento |
response_format o response_json_schema | Modalità validated | La documentazione non lo dice | Il modello la ignora | Modalità any |
Fonte: la documentazione di OpenAI, Anthropic e Google, 20 ottobre 2026. «La documentazione non lo dice»: nelle pagine che abbiamo letto quel giorno non abbiamo trovato una frase che descriva questo caso. Su Google, response_format è un campo della Interactions API, response_json_schema uno di generateContent. La Responses API di OpenAI porta in modalità strict, quando possibile, una funzione non marcata come strict; in Chat Completions la modalità strict non è predefinita.
Su OpenAI gli structured outputs si attivano nella Responses API dentro text.format, con type: "json_schema" e strict: true. La documentazione pone due condizioni: ogni campo deve essere required e ogni oggetto deve avere additionalProperties: false. Se vuoi un campo opzionale, aggiungi null al suo tipo. Anche i limiti dello schema sono scritti: al massimo 5.000 campi, 10 livelli di oggetti annidati, 1.000 valori di enum e 120.000 caratteri sommando tutti i nomi e i valori. Una parola chiave non supportata restituisce un errore. La prima richiesta con uno schema è più lenta, perché l’API elabora lo schema; le successive non lo sono.
Nel tool calling, strict: true fa rispettare lo schema agli argomenti, e OpenAI consiglia di attivarlo sempre. Obbligare il modello a chiamare un tool è ancora possibile su OpenAI: se imposti tool_choice su required, il modello chiama almeno una funzione; se indichi il nome di una funzione, chiama esattamente quella.
Su Anthropic gli structured outputs hanno due metà: output_config.format per la risposta (JSON outputs) e strict: true per i tool (strict tool use). Le due si possono usare insieme nella stessa richiesta. Lo schema viene compilato in una grammatica. Per questo la prima richiesta con un certo schema è più lenta; lo schema compilato viene conservato per 24 ore dopo l’ultimo uso. I limiti valgono per richiesta: al massimo 20 tool strict, 24 campi opzionali in tutto tra tutti gli schemi e 16 campi che usano anyOf o più di un tipo; la compilazione ha inoltre un timeout di 180 secondi. Una regola non supportata, come minimum o maxLength, restituisce un errore 400. I campi arrivano nell’ordine dello schema, ma quelli obbligatori vengono scritti prima degli opzionali. Un ultimo dettaglio: cambiare output_config.format invalida la cache del prompt di quella conversazione. Abbiamo spiegato perché la cache si rompe nell’articolo sul prompt caching.
Anche il cambiamento principale di questo autunno riguarda Anthropic. Un modo noto per avere JSON da Claude era definire un solo tool e obbligare il modello a chiamarlo con tool_choice. Secondo le note di rilascio, su Opus 5.5, uscito il 22 settembre, e su Sonnet 5.5, uscito il 28 settembre, i tipi any e tool di tool_choice restituiscono un errore 400. La strada consigliata da Anthropic è lo strict tool use con auto, e gli structured outputs se ti serve una forma JSON fissa. Secondo la documentazione dei tool, i modelli che ragionano per impostazione predefinita, come Haiku 5.5, accettano ancora la forzatura, ma in una chiamata forzata il modello chiama direttamente il tool, senza ragionare. Su Haiku 5.5, uscito il 7 ottobre, un altro vecchio metodo, scrivere tu l’inizio del messaggio dell’assistente (prefill), può restituire un errore 400; in ogni caso gli structured outputs non funzionano con il prefill.
Su Google la guida attuale descrive la Interactions API: a response_format dai un mime_type: "application/json" e uno schema. In generateContent si fa lo stesso con response_json_schema. Secondo la documentazione, il modello scrive un JSON sintatticamente valido che rispetta lo schema e mette i campi nell’ordine dello schema; l’API può rifiutare schemi molto grandi o molto annidati. Qui c’è una differenza importante: OpenAI e Anthropic rifiutano con un errore una regola di schema che non supportano, mentre Gemini la ignora. Se il tuo schema contiene una regola che Gemini non supporta, la richiesta va comunque a buon fine, ma la regola non viene applicata. Nel function calling, la modalità validated fa rispettare lo schema agli argomenti; la modalità any, invece, obbliga il modello a chiamare una funzione ogni volta.
Cosa non garantiscono gli structured outputs?
Uno schema garantisce la forma della risposta, non il suo contenuto. Google lo scrive chiaramente: non è garantito che i valori siano corretti nel significato. OpenAI dice anche che gli structured outputs possono contenere errori, e aggiunge un avvertimento: se l’input non ha niente a che fare con lo schema, il modello può inventare pur di rispettarlo. Anthropic, dal canto suo, elenca i casi in cui lo schema può non reggere affatto. Cosa controllare:

I due casi più frequenti sono il rifiuto e la risposta troncata. Su Anthropic una richiesta rifiutata per motivi di sicurezza non arriva come errore, ma come una risposta normale con stop_reason: "refusal": restituisce HTTP 200, i token generati vengono addebitati e la risposta può non rispettare lo schema. Su OpenAI il rifiuto arriva in un campo a parte, refusal. Una risposta che si ferma a max_tokens resta incompleta con entrambi i provider; Anthropic consiglia di riprovare con un max_tokens più alto. Quindi il tuo codice deve prima guardare perché la risposta è finita, e solo dopo parsare il JSON.
Una trappola tipica di Claude è il campo di ragionamento nello schema. Mettere nello schema un campo reasoning e chiedere al modello di ragionarci dentro prima di rispondere può portare a un rifiuto su Claude. La documentazione di Anthropic sui rifiuti mette un campo chiamato reasoning, thinking o trace, in un output JSON o nell’input di un tool, tra le cause frequenti di un rifiuto di tipo reasoning_extraction. Consiglia invece di chiedere una breve spiegazione, le prove su cui si basa il risultato o un riassunto di ciò che è stato fatto. Dal 24 settembre viene addebitato anche un rifiuto di questo tipo che arriva prima che sia stato prodotto qualsiasi output. La regola vale solo per questo provider: uno degli esempi della guida di OpenAI è uno schema che dà la risposta con spiegazioni passo per passo. Su Claude quanto ragiona il modello lo decide l’impostazione di effort, non lo schema; ne abbiamo parlato nell’articolo sull’effort.
Quanto sia solido lo schema nelle mani del provider è un’altra questione. Nei nostri registri, abbiamo mandato uno schema JSON strict a un endpoint che il catalogo di un servizio di routing indicava come compatibile con response_format, ma non con gli structured outputs. La risposta non era un errore: HTTP 200 e 1.639 caratteri di markdown. E il contenuto era giusto: nominava il bug reale nel diff usato per la prova. Mancava solo il JSON. È il registro di una sola prova, non un tasso. Ma mostra che la promessa di uno schema da parte di un’API non sostituisce il controllo nel codice che legge la risposta. In diffox una risposta che non si può parsare come JSON non conta come successo; la chiamata fallisce e finisce nel registro della review, insieme a quale agente o quale passaggio è caduto e perché.
Come si ottiene JSON da un agente di programmazione da riga di comando?
La stessa domanda vale per gli agenti di programmazione. Se esegui un agente in CI o in uno script e passi il suo risultato a un altro passaggio, devi poterti fidare della forma della risposta. Tutti e cinque gli strumenti si possono lanciare da riga di comando, ma non danno tutti la stessa cosa: alcuni mettono la risposta in un involucro JSON, altri chiedono che la risposta stessa segua uno schema. Abbiamo ricavato la tabella dalla documentazione di ciascuno strumento il 20 ottobre 2026:
| Strumento | Comando | Output | Schema |
|---|---|---|---|
| Claude Code | claude -p --output-format json --json-schema '…' | Involucro JSON; il risultato è nel campo structured_output | Lo convalida; se non corrisponde lo richiede, e al limite dei tentativi restituisce un errore |
| Codex | codex exec --output-schema schema.json | Eventi JSONL con --json; -o scrive l’ultimo messaggio in un file | Chiede una risposta finale conforme allo schema; come viene applicato, la documentazione non lo dice |
| GitHub Copilot CLI | copilot -p '…' --output-format json | Eventi JSONL | La documentazione non lo dice |
| Copilot, VS Code | code chat '…' | Apre la chat nell’editor | La documentazione non lo dice |
| Cursor CLI | agent -p --output-format json | Un unico involucro JSON; la risposta è testo nel campo result | La documentazione non lo dice |
| Antigravity CLI | agy -p '…' --output-format json --json-schema '…' | Involucro JSON; il risultato è nel campo structured_output | Applica lo schema; come, la documentazione non lo dice |
Fonte: la documentazione di ciascuno strumento, 20 ottobre 2026. «La documentazione non lo dice»: nelle pagine che abbiamo letto quel giorno non abbiamo trovato una frase che descriva questo caso. Che Claude Code convalidi rispetto allo schema e richieda di nuovo l’output lo spiega la documentazione dell’Agent SDK; claude -p esegue quell’SDK da riga di comando. In Cursor CLI, se l’esecuzione fallisce, non viene scritto alcun JSON e il processo esce con un codice diverso da zero.
La distinzione della tabella conta. Un involucro JSON vuol dire che la risposta arriva dentro un oggetto JSON; la risposta lì dentro resta testo libero. Se il passaggio successivo deve leggere dei campi dalla risposta, ti serve uno strumento con un’opzione per lo schema oppure un passaggio che convalidi la risposta nel codice. La documentazione di Claude Code è chiara anche qui: anche se il risultato sembra riuscito, consideralo un errore se manca il campo structured_output. Claude Code segnala un altro dettaglio: la parola chiave format di uno schema, per esempio "format": "email", la tratta solo come un’annotazione e non la applica.
Se usi ancora Gemini CLI, gemini -p --output-format json restituisce la risposta come testo nel campo response; questa documentazione non cita un’opzione per lo schema. Per gli utenti del livello gratuito e di Google One, Google ha sostituito Gemini CLI con Antigravity CLI il 18 giugno 2026; con chiavi API a pagamento Gemini CLI resta utilizzabile.
Come si ottiene JSON alla fine del loop di un agente?
Nel loop di un agente il modello prima legge, cerca e prova con i tool; alla fine dà un risultato. Perché quel risultato fosse JSON su Claude, bastava obbligare il modello a chiamare un tool «submit» nell’ultimo passaggio. Siccome su Opus 5.5 e Sonnet 5.5 questa strada restituisce un errore 400, ora si procede così:
- Lascia
tool_choicesuauto. Secondo Anthropic il prompt influenza ancora quale tool viene scelto, e un modello non forzato può ragionare prima di chiamare il tool. - Dai al modello un tool finale con
strict: true, per esempiosubmit, e nel prompt chiedigli di chiamarlo esattamente una volta, per ultimo, quando il lavoro è finito. Dato che gli argomenti rispettano lo schema, non devi parsare il risultato a parte. - Chiudi il loop quando arriva quella chiamata. Se il modello si ferma senza chiamare il tool, se la risposta viene troncata a
max_tokenso se arriva un rifiuto, non contarlo come risultato; solleva un errore esplicito. - Metti un limite alle chiamate ai tool. Raggiunto il limite, invece di eseguire il tool, di’ al modello di decidere subito. Taglia anche i turni con un tetto massimo.
- Se vuoi una motivazione, metti nello schema un breve campo
evidence, nonreasoning.
Per la risposta finale puoi anche usare gli structured outputs al posto di un tool: la documentazione di Anthropic scrive che i tool strict e output_config.format funzionano insieme nella stessa richiesta. Le chiamate ai tool rispettano lo schema, e anche la risposta finale in testo arriva vincolata allo schema. L’esempio qui sotto mostra la prima strada, con l’SDK Python di Anthropic:
# Esempio: tool_choice resta su auto, il ciclo finisce quando arriva submit.
import anthropic
client = anthropic.Anthropic()
SUBMIT = {
"name": "submit",
"description": "Give the final answer. Call exactly once, last.",
"strict": True,
"input_schema": {
"type": "object",
"properties": {
"verdict": {"type": "string", "enum": ["HOLDS", "REFUTED", "UNSURE"]},
"evidence": {"type": "string"},
},
"required": ["verdict", "evidence"],
"additionalProperties": False,
},
}
def run(model, messages, tools, run_tool, max_calls=8):
calls = 0
for _ in range(max_calls + 3):
response = client.messages.create(
model=model, max_tokens=8000, tools=tools + [SUBMIT], messages=messages)
if response.stop_reason in ("max_tokens", "refusal"):
raise RuntimeError("no answer: " + response.stop_reason)
messages.append({"role": "assistant", "content": response.content})
uses = [b for b in response.content if b.type == "tool_use"]
for b in uses:
if b.name == "submit":
return b.input # il risultato, conforme allo schema
if not uses:
raise RuntimeError("the model stopped without calling submit")
results = []
for b in uses:
calls += 1
out = (run_tool(b.name, b.input) if calls <= max_calls
else "Tool budget spent: call submit now.")
results.append({"type": "tool_result", "tool_use_id": b.id, "content": out})
messages.append({"role": "user", "content": results})
raise RuntimeError("turn limit reached without submit")In diffox funziona così il passaggio che controlla un rilievo sul repository stesso prima di scriverlo nella pull request. La nota nel codice lo dice chiaramente: «A MANUAL LOOP, tool_choice left at auto». Il modello può fare al massimo 8 chiamate con tool git in sola lettura, e il prompt gli chiede di chiamare il tool submit_verdict esattamente una volta, per ultimo, quando ha finito. submit_verdict è un tool strict: il verdetto è uno di tre valori (HOLDS, REFUTED, UNSURE), con accanto un campo per le prove; lo schema non ha un campo di ragionamento. Raggiunto il limite, il tool non viene eseguito e al modello si chiede di dare il suo verdetto; i turni si fermano 3 oltre il limite. Se il modello si ferma senza dare un verdetto, nel registro si scrive «nessun verdetto» e il rilievo resta com’è: nessun rilievo viene considerato confutato per impostazione predefinita.
Quale usare, e in quale caso?
La regola è semplice: scegli in base a ciò che serve a chi leggerà il JSON, e controlla comunque ogni risposta che arriva. Un riepilogo per i casi più comuni:

Il punto in comune delle schede: lo schema garantisce la forma, il resto spetta al tuo codice. Le regole che uno schema non sa esprimere, come la lunghezza di un testo, l’intervallo di un numero o la coerenza di un campo con un altro, si controllano nel codice quando arriva la risposta. Se il controllo fallisce, richiedi una volta; se fallisce anche il secondo tentativo, non ingoiare l’errore, sollevalo in modo esplicito. Il Claude Agent SDK funziona allo stesso modo: richiede di nuovo l’output che non corrisponde e restituisce un errore quando raggiunge il limite di tentativi. Abbiamo descritto controlli di questo tipo, che girano dopo la risposta del modello, nell’articolo sull’harness engineering.
Domande frequenti
Cosa sono gli structured outputs?
Una funzionalità delle API che vincola la risposta di un LLM allo schema JSON che fornisci. Mentre genera token, il modello non può uscire dallo schema (constrained decoding); la risposta arriva come JSON valido e con i campi dello schema. Si attivano su OpenAI dentro text.format con json_schema e strict: true, su Anthropic con output_config.format e su Google con response_format o response_json_schema. Lo schema garantisce la forma della risposta, non che il contenuto sia corretto.
Che differenza c’è tra JSON mode e structured outputs?
Il JSON mode assicura solo che la risposta sia JSON valido; non garantisce quali campi conterrà. Gli structured outputs vincolano la risposta allo schema che fornisci: i campi obbligatori non mancano, e i tipi e i valori di enum sono quelli dello schema. OpenAI offre entrambi e consiglia gli structured outputs dove sono supportati. Su OpenAI, se il JSON mode è attivo e la parola «JSON» non compare nel prompt, l’API restituisce un errore.
Che differenza c’è tra function calling e structured outputs?
Il function calling è il modello che chiama un tool per far fare qualcosa; il JSON sono gli argomenti di quel tool. Gli structured outputs sono invece la forma della risposta finale del modello. La documentazione di Google traccia il confine così: structured outputs per formattare la risposta finale, function calling per far fare qualcosa nel mezzo della conversazione. Perché anche gli argomenti dei tool rispettino lo schema, si usa strict: true su OpenAI e Anthropic e la modalità validated su Gemini.
Perché tool_choice any dà errore su Claude?
Su Opus 5.5 e Sonnet 5.5 i tipi di tool_choice che obbligano il modello a chiamare un tool, any e tool, restituiscono un errore 400. La strada consigliata da Anthropic è lasciare tool_choice su auto e usare lo strict tool use, e gli structured outputs se ti serve una forma JSON fissa. Il prompt influenza ancora quale tool viene chiamato. Haiku 5.5 accetta ancora la forzatura, ma in una chiamata forzata il modello chiama direttamente il tool, senza ragionare.
Come si passa uno schema JSON a Gemini?
Nella Interactions API, a response_format dai un type: "text", un mime_type: "application/json" e uno schema; in generateContent si fa lo stesso con response_json_schema (responseFormat.text.schema in REST). Gemini supporta un sottoinsieme di JSON Schema e ignora le regole che non supporta; OpenAI e Anthropic, invece, restituiscono un errore per una regola del genere. Google consiglia di convalidare comunque i valori della risposta nella tua applicazione.
Gli structured outputs garantiscono che il contenuto sia corretto?
No. Google e OpenAI lo dicono chiaramente nella loro documentazione: lo schema garantisce la forma della risposta, non che i valori siano corretti. Anche Anthropic elenca i casi in cui lo schema può non reggere: un rifiuto, una risposta troncata a max_tokens e maiuscole e minuscole dei valori di enum. Guarda prima perché la risposta è finita, poi convalida la risposta nel codice; se non passa, richiedila una volta.
Come ottiene diffox JSON da un modello?
diffox è uno strumento di code review con IA e chiede ai modelli le risposte con uno schema JSON. Il passaggio che controlla un rilievo sul repository stesso è un loop dell’agente: tool_choice è su auto, le chiamate ai tool sono limitate a 8 e il loop finisce con un tool strict, submit_verdict. Una risposta che non si può parsare come JSON non conta come successo; finisce come errore nel registro della review. Se il modello si ferma senza dare un verdetto, il rilievo resta com’è.