Structured output o tool calling: quando chiedere JSON e quando dare un tool al modello

Stessa forma, contratti diversi. Quando chiedere uno schema JSON e quando dare un tool al modello, secondo la documentazione di OpenAI, Anthropic e Gemini: modalità strict, cosa lo schema non garantisce, quanti tool dare e cosa cambia con modelli piccoli e locali su Ollama e llama.cpp. Nessun test fatto per l'articolo.

Francesco Pio Nocerino8 minAI
Structured output o tool calling: quando chiedere JSON e quando dare un tool al modello
Indice

Structured output e tool calling producono la stessa cosa: un oggetto JSON che rispetta uno schema. Cambia il contratto. Con lo structured output il JSON è la risposta finale; con un tool il JSON è una richiesta che la tua applicazione esegue e di cui rimanda il risultato al modello. Questo confronto è costruito dalla documentazione ufficiale di OpenAI, Anthropic, Google Gemini, Ollama e llama.cpp, letta il 31 ottobre 2026. Non contiene test o misure fatti per l'articolo.

In breve:

  • Usa lo structured output quando vuoi la risposta in un formato preciso: estrazione di dati, classificazione, un oggetto da salvare o passare a un'altra API.
  • Usa il tool calling quando il modello deve far fare qualcosa al tuo sistema prima di rispondere: leggere dati, chiamare un servizio, eseguire un'azione.
  • OpenAI, Anthropic e Gemini tracciano lo stesso confine: formato della risposta da una parte, azione durante la conversazione dall'altra.
  • In entrambi i casi conviene la modalità stretta (strict o constrained decoding): lo schema viene imposto in generazione, non solo suggerito.
  • Schema valido non vuol dire valori giusti. Rifiuti, risposte troncate e valori sbagliati ma ben formati vanno gestiti nel codice.
  • In locale, con Ollama e llama.cpp, la sintassi è simile ma il modello conta di più: lo schema va descritto anche nel prompt e il template della chat deve supportare i tool.

La domanda giusta

Non "JSON o tool?". È: dopo questo JSON il modello ha ancora bisogno di qualcosa dal tuo sistema?

Se la risposta è no, il JSON chiude il lavoro. Lo leggi, lo validi, lo usi. È structured output.

Se la risposta è sì, il JSON è una richiesta. La tua applicazione esegue la funzione, rimanda il risultato e il modello continua. È tool calling, e porta con sé un loop.

La guida di OpenAI agli structured outputs lo dice in modo diretto: se stai collegando il modello a strumenti, funzioni o dati del tuo sistema, usa il function calling. Se vuoi strutturare l'output con cui il modello risponde all'utente, usa un formato di risposta strutturato. La documentazione di Gemini sugli structured outputs ha una tabella con lo stesso confine: structured output per formattare la risposta finale, function calling per agire durante la conversazione.

Structured output: il JSON è la risposta

Con lo structured output passi uno schema JSON e il modello risponde rispettandolo. I casi tipici citati dalle tre documentazioni sono gli stessi: estrarre dati da testo libero, classificare in categorie fisse, generare un oggetto pronto per un'API.

Come si attiva, secondo le docs:

  • OpenAI: lo schema va nel formato della risposta (text.format nella Responses API, response_format in Chat Completions). OpenAI consiglia di usare sempre gli Structured Outputs al posto della vecchia JSON mode, che garantisce JSON valido ma non il rispetto dello schema.
  • Anthropic: lo schema va in output_config.format con type: "json_schema". La documentazione di Claude spiega che l'output è vincolato con constrained decoding, quindi niente errori di parsing né campi obbligatori mancanti.
  • Gemini: in response_format si imposta mime_type a application/json e si passa lo schema nel campo schema. Gli SDK accettano modelli Pydantic e Zod.

Il punto in comune è che lo schema non è un'istruzione nel prompt. Il provider lo compila in una grammatica e limita i token che il modello può generare. Per questo la prima richiesta con uno schema nuovo può essere più lenta: OpenAI e Anthropic lo dicono entrambi, e Anthropic aggiunge che la grammatica compilata resta in cache per 24 ore dall'ultimo uso.

Tool calling: il JSON è una richiesta

Con il tool calling descrivi una o più funzioni (nome, descrizione, schema degli argomenti) e il modello decide se chiamarne una. Non la esegue lui. La guida di OpenAI al function calling descrive il flusso in cinque passi:

1. Richiesta al modello con i tool che può chiamare. 2. Il modello risponde con una chiamata a un tool. 3. La tua applicazione esegue il codice con gli argomenti ricevuti. 4. Seconda richiesta al modello con il risultato del tool. 5. Risposta finale, oppure altre chiamate.

Anthropic e Gemini descrivono lo stesso giro. La panoramica sul tool use di Claude distingue i client tool, che girano nella tua applicazione, dai server tool che Anthropic esegue per te, come la ricerca web. Gemini supporta chiamate in parallelo (più funzioni indipendenti nello stesso turno) e in sequenza (una chiamata che dipende dal risultato della precedente).

Il prezzo del tool calling è il loop. Ogni chiamata è un giro in più di richiesta e risposta, e le definizioni dei tool entrano nel contesto. OpenAI scrive che le funzioni sono iniettate nel messaggio di sistema, contano sul limite di contesto e sono fatturate come token di input. Anthropic aggiunge un prompt di sistema per il tool use, con il numero di token indicato per modello. Il loop e il suo costo sono il tema del pezzo sul tool engineering nel loop di un agente.

Il confronto in una tabella

Criterio

Structured output

Tool calling

Cosa rappresenta il JSON

La risposta finale

Una richiesta da eseguire

Chi agisce dopo

Il tuo codice, una volta

La tua applicazione, poi di nuovo il modello

Giri di richiesta

Uno

Almeno due per ogni chiamata

Casi tipici

Estrazione, classificazione, oggetti per un'API

Leggere dati, chiamare servizi, eseguire azioni

Dove va lo schema

Formato della risposta

Parametri di ogni tool

Garanzia sullo schema

Con la modalità strict o constrained

Con strict sui tool o la modalità validata di Gemini

Costo in token

Prompt di formato aggiunto dal provider

Definizioni dei tool più i risultati, a ogni giro

Modalità stretta: dove vale per i tool

La garanzia sullo schema non è automatica sui tool. Ogni provider ha un'opzione dedicata.

OpenAI consiglia di attivare sempre strict: true sulle funzioni. Sotto usa la stessa tecnologia degli Structured Outputs e richiede additionalProperties: false su ogni oggetto. Nella Responses API prova a normalizzare lo schema in modalità strict e, se non ci riesce, torna alla chiamata best effort; in Chat Completions il default resta non strict.

Anthropic ha la strict tool use: con strict: true nome e argomenti del tool rispettano lo schema. JSON output e strict tool use si possono usare insieme nella stessa richiesta: il primo controlla cosa dice Claude, il secondo come chiama le tue funzioni.

La guida di Gemini al function calling prevede quattro modalità: auto (default, il modello decide), any (deve chiamare una funzione), none (non può chiamarne) e validated (rispetto dello schema della funzione garantito).

C'è un limite pratico. Anthropic documenta tetti per richiesta: al massimo 20 tool strict, 24 parametri opzionali in totale tra tutti gli schemi strict, 16 parametri con tipi unione. Il consiglio è marcare strict solo i tool dove un argomento sbagliato fa danni, rendere obbligatori i parametri quando c'è un default ragionevole e appiattire gli oggetti annidati.

Cosa lo schema non garantisce

Uno schema rispettato dice che il JSON ha la forma giusta. Non dice che i valori sono giusti.

Le docs elencano tre casi da gestire:

  • Rifiuti. Se il modello rifiuta per motivi di sicurezza, l'output può non rispettare lo schema. OpenAI espone un campo refusal per riconoscerlo; Anthropic avverte che la risposta arriva comunque con status 200 e i token sono fatturati.
  • Risposte troncate. Se si raggiunge il limite di token, il JSON può essere incompleto. Entrambi i provider lo indicano come caso da controllare.
  • Valori ben formati ma sbagliati. Gemini lo scrive esplicitamente: anche se il JSON è sintatticamente corretto, i valori vanno sempre validati nell'applicazione. Gemini ignora inoltre alcuni vincoli in fase di generazione (lunghezza delle stringhe, numero di proprietà) e chiede di verificarli lato client, per esempio con Pydantic o Zod.

Anthropic aggiunge un dettaglio utile: gli enum non hanno garanzia sulle maiuscole. Claude può restituire un valore che differisce dallo schema solo nella prima lettera, senza errore. Il consiglio è confrontare gli enum senza distinguere maiuscole e minuscole.

Per i tool vale la stessa regola, con più peso perché il JSON diventa un'azione. Gemini raccomanda di validare le chiamate prima di eseguirle. Un argomento valido per lo schema può comunque cancellare il record sbagliato. Come misurare questi errori in modo ripetibile è nel pezzo sulle eval per agenti.

Quanti tool dare al modello

Più tool non vuol dire risultati migliori. OpenAI consiglia di tenere piccolo il numero di funzioni disponibili all'inizio per avere più accuratezza, e di caricare quelle usate di rado solo quando servono con la tool search. Gemini indica un insieme attivo di 10-20 tool al massimo.

Se un'operazione non ha bisogno del risultato di un'altra chiamata, spesso non serve un tool. Uno structured output con un campo azione e il tuo codice che decide cosa fare è più semplice da testare. È la stessa logica del pezzo su quando un agente non serve.

Modelli piccoli e locali: Ollama e llama.cpp

In locale la sintassi è vicina a quella delle API cloud. Cambia quanto conta il modello.

La documentazione di Ollama sugli structured outputs prevede lo schema nel campo format. Consiglia di passare lo schema anche come testo nel prompt, per orientare il modello, e di abbassare la temperatura a 0 per risposte più deterministiche. Gli structured outputs funzionano anche tramite l'API compatibile con OpenAI con response_format. La stessa pagina segnala che il cloud di Ollama, al momento, non li supporta. Il tool calling di Ollama copre chiamate singole, parallele e il loop multi turno; la documentazione suggerisce di dire al modello che è dentro un loop e può fare più chiamate.

llama.cpp lavora con le grammatiche GBNF. Il README delle grammatiche spiega che llama-server converte un sottoinsieme di JSON Schema in grammatica, passato come json_schema o dentro response_format. Due avvertenze contano:

  • Lo schema serve solo a vincolare l'output e non viene inserito nel prompt. Il modello non lo vede: se deve capire la struttura, va descritta nel prompt. Per il tool calling invece gli schemi sono iniettati.
  • Le funzioni dello schema non supportate vengono saltate senza errori. Il README consiglia di convertire lo schema con lo script Python per vedere gli avvisi.

Per i tool, la documentazione sul function calling di llama.cpp richiede di avviare llama-server con --jinja e un template della chat che supporti i tool. Alcune famiglie hanno un formato nativo, come Llama 3.x, Qwen 2.5, Mistral Nemo e Hermes; per le altre c'è un gestore generico che può usare più token ed essere meno efficiente. Le chiamate in parallelo sono disattivate di default. C'è anche un avviso diretto: quantizzazioni molto spinte della cache KV possono degradare parecchio il tool calling.

Il quadro che esce dalle docs, per i modelli piccoli: la grammatica garantisce la forma, non la decisione. Un modello locale può produrre un JSON perfetto con il campo sbagliato, o chiamare un tool quando non serve. Uno schema piccolo e piatto, descritto anche nel prompt, è la scelta più prudente. Per esporre un modello locale con un'API compatibile con OpenAI c'è la guida su Ollama e vLLM in locale.

Cosa conviene fare adesso

  • Per ogni chiamata al modello, chiediti se dopo il JSON serve ancora il tuo sistema. Se no, structured output.
  • Attiva la modalità stretta: strict su OpenAI e Anthropic, validated su Gemini, grammatica su llama.cpp.
  • Valida comunque i valori nel codice e gestisci rifiuti e risposte troncate.
  • Tieni pochi tool e schemi piatti, soprattutto con modelli piccoli.
  • In locale descrivi lo schema anche nel prompt e controlla che il template della chat supporti i tool.

Per mettere in ordine i termini della serie, il punto di partenza è l'hub AI engineering.

[ DOMANDE FREQUENTI ]

Domande frequenti

01JSON mode e structured output sono la stessa cosa?

No. Nella documentazione di OpenAI la JSON mode garantisce solo un JSON valido, mentre gli Structured Outputs garantiscono anche il rispetto dello schema. OpenAI consiglia gli Structured Outputs quando il modello li supporta.

02Posso usare structured output e tool nella stessa richiesta?

Sì, con i provider che lo supportano. Anthropic permette JSON output e strict tool use insieme: tool chiamati con argomenti validi e risposta finale in JSON. Gemini lo consente in preview con i modelli della serie 3, anche insieme ai tool integrati.

03Lo schema rende inutile la validazione nel codice?

No. Lo schema garantisce la forma, non i valori. Rifiuti e risposte troncate possono uscire dallo schema, e Gemini chiede esplicitamente di validare i valori e gestire gli output corretti nella forma ma sbagliati nel contenuto.

04Perché la prima richiesta con uno schema nuovo è più lenta?

Il provider compila lo schema in una grammatica prima di generare. OpenAI e Anthropic lo indicano entrambi. Anthropic tiene in cache la grammatica per 24 ore dall'ultimo uso; cambiare la struttura dello schema o l'insieme dei tool la invalida.

05Con un modello locale conviene il tool calling o lo structured output?

Dipende dal compito, ma le docs suggeriscono prudenza. Con llama.cpp lo schema di output non entra nel prompt e il tool calling dipende dal template della chat. Se il modello non deve davvero chiamare il tuo sistema, uno structured output piccolo è più semplice da controllare.

06Quanti tool posso dare al modello?

Il meno possibile all'inizio. OpenAI consiglia un insieme iniziale piccolo e la tool search per il resto; Gemini indica 10-20 tool attivi al massimo; Anthropic limita a 20 i tool strict per richiesta.

Condividi