Salta al contenuto
18/30Capitolo 18 di 30

Tool Calling e output strutturati: il contratto che regge

24 chiamate, zero JSON rotti e due date utilizzabili. Poi lo stesso endpoint con una descrizione migliore e ciò che uno schema non corregge.

In questa pagina

Dai a un modello un tool di ricerca voli e chiedigli di trovare un volo da Madrid a Berlino. Ecco cosa torna:

TEXT
<tool_call>
{"name": "search_flights",
 "arguments": {"from": "Madrid", "to": "Berlin", "date": "3rd October 2026"}}
</tool_call>

Il JSON è valido. Il nome del tool è corretto. Tutti i campi obbligatori sono presenti. E la chiamata è inutile: nessuna API di voli accetta "Madrid" dove vuole un codice aeroportuale, o "3rd October 2026" dove vuole una data.

Quel divario — sintatticamente perfetto, semanticamente inutilizzabile — è il tema di questo capitolo, e la prima cosa da chiarire è che non è un problema di JSON. In ventiquattro richieste con questo tool, il modello ha prodotto 24 chiamate tool valide e zero JSON rotti. Non ha mai fallito nemmeno una volta nella parte che tutti fanno debug.

Prima della meccanica, la frase che evita più confusione: una tool call è una richiesta, non un’azione.

Il modello emette un messaggio strutturato che dice vorrei che search_flights venisse chiamato con questi argomenti. Poi si ferma. Il tuo codice riceve quel messaggio, decide se onorarlo, chiama ciò che deve chiamare e rimanda indietro il risultato come un altro messaggio. Il modello non ha mai toccato il tuo database, non ha mai fatto una richiesta HTTP, non ha mai avuto credenziali.

Tutto ciò che riguarda la sicurezza degli agent in Capitolo 30 deriva da questa divisione, e lo stesso vale per tutto ciò che riguarda la progettazione degli agent in Capitolo 23: il modello propone e il tuo codice dispone, e il codice è il luogo in cui vive ogni garanzia.

Quindi un tool, spogliato del vocabolario, è due cose:

Uno schema. Un JSON Schema che descrive una funzione: il suo nome, cosa fa e quali argomenti accetta, con tipi e vincoli. Questo è ciò che entra nel prompt, ed è l’unica cosa che il modello vede mai.

Un endpoint. Una funzione nel tuo codice che prende quegli argomenti e restituisce qualcosa. Il modello non la vede mai, non sa mai in che linguaggio sia scritta e non sa distinguere una query al database da una stringa hardcoded.

Le definizioni dei tool vanno nel prompt, serializzate nel formato su cui il modello è stato addestrato. Costano tokens a ogni singola chiamata — un fatto che più avanti in questo capitolo torna con un numero.

Il modello risponde con una chiamata invece che con testo

Link alla sezione: Il modello risponde con una chiamata invece che con testo

Invece di prosa, la risposta contiene una richiesta strutturata, e l’API segnala un motivo di chiusura che lo indica. Il motivo conta: è il modo in cui il tuo codice sa che deve eseguire un tool invece di mostrare una risposta all’utente.

Questo è il passaggio in cui il modello non c’è. Valida gli argomenti rispetto allo schema, decidi se questo chiamante è autorizzato a farlo ed esegui.

Il risultato diventa un altro turno nella conversazione, in un ruolo riservato a questo. Il modello lo legge come qualsiasi altro context.

Il modello risponde, oppure chiede un altro tool

Link alla sezione: Il modello risponde, oppure chiede un altro tool

Questo è il ciclo del Capitolo 23, e il motivo per cui una singola richiesta può trasformarsi in una dozzina di andate e ritorni.

Niente di tutto questo è emergente. Come stabilito nel Capitolo 11, il tool calling è un comportamento addestrato:1 durante il post-training il modello ha visto migliaia di conversazioni strutturate esattamente così. Ecco perché il formato è specifico del modello, perché l’affidabilità varia così tanto tra modelli di dimensioni simili, e perché un modello può chiamare un tool che non ha mai visto: la forma è stata addestrata, il tool specifico arriva dal tuo prompt.

Quanto costa uno schema cattivo, misurato

Link alla sezione: Quanto costa uno schema cattivo, misurato

Ecco il tool come lo scrive la maggior parte delle persone all’inizio. Nota che non c’è nulla di sbagliato; è solo sottile:

tools/badFlights.tsTS
{
  name: "search_flights",
  description: "Search for flights.",
  parameters: {
    type: "object",
    properties: {
      from: { type: "string", description: "Airport." },   
      to:   { type: "string", description: "Airport." },   
      date: { type: "string", description: "The date." },  
    },
    required: ["from", "to", "date"],
  },
}

Ventiquattro richieste, sei coppie di città incrociate con quattro modi di esprimere una data («il 3 del mese prossimo», «venerdì prossimo», «15 dicembre», «domani»), greedy decoding in modo che i risultati siano riproducibili:

tool chiamatoJSON rottodata in ISOaeroporti come IATAtutto corretto
lo schema sopra24/2402/244/241/24

Leggi le prime due colonne prima delle ultime tre. Il modello chiama il tool giusto ogni volta e produce JSON ben formato ogni volta. Il fallimento è interamente nei valori, e i valori sono inutilizzabili: "Madrid" invece di MAD, "3rd October 2026" invece di 2026-10-03.

Vale la pena insistere su questo perché determina dove guardi quando qualcosa si rompe. L’istinto è aggiungere un parser JSON con un retry, o chiedere al modello con più decisione di produrre JSON valido. Nessuna delle due cose affronta ciò che è successo qui.

Stesso endpoint. Stesso codice dietro. Stesso modello, stessi prompts, stesso decoding. L’unica cosa che cambia è il testo nello schema:

tools/goodFlights.tsTS
{
  name: "search_flights",
  description: "Search scheduled flights between two airports on a given day.",
  parameters: {
    type: "object",
    properties: {
      from: {
        type: "string",
        description: "Departure airport as a three-letter IATA code, e.g. MAD for Madrid. Never a city name.",   
        pattern: "^[A-Z]{3}$",
      },
      to: { /* same */ },
      date: {
        type: "string",
        description: "Departure date as an ISO 8601 calendar date, YYYY-MM-DD. Resolve relative dates against today before calling.",   
        format: "date",
        pattern: "^\\d{4}-\\d{2}-\\d{2}$",
      },
    },
    required: ["from", "to", "date"],
  },
}
FORMATO dataVALORE dataFORMATO aeroportoVALORE aeroporto
schema sottile2/241/244/244/24
schema descritto24/2412/2416/248/24

Il formato della data passa da 2 su 24 a 24 su 24. Perfetto, con una modifica testuale, senza toccare codice e senza logica di retry. Se da questo capitolo devi portarti a casa un’abitudine operativa, è questa: quando un tool viene chiamato in modo sbagliato, la correzione è quasi sempre nella descrizione, ed è la correzione più economica del sistema.

Ora leggi la seconda colonna, che è la metà più importante.

Uno schema vincola la forma. Non può fornire conoscenza.

Link alla sezione: Uno schema vincola la forma. Non può fornire conoscenza.

La data è in formato ISO 24 volte su 24. È il giorno giusto 12 volte su 24.

Quindi metà delle chiamate ora contiene una data perfettamente formattata che è la data sbagliata. La descrizione ha detto al modello quale forma produrre, e il modello l’ha prodotta senza errori — ma trasformare «venerdì prossimo» in 2026-09-11 richiede conoscere la data di oggi e fare aritmetica di calendario, e nessuna quantità di descrizione può fornirlo. Stessa storia per gli aeroporti: il formato è passato da 4 a 16, ma il valore solo da 4 a 8, perché scrivere MAD richiede sapere che l’aeroporto di Madrid è MAD.

Questa distinzione è l’idea portante del capitolo:

Uno schema è un contratto sulla forma. Può rendere l’output del modello analizzabile, tipizzato e coerente. Non può renderlo vero, e ogni modalità di fallimento che sopravvive a un buono schema è un fallimento di conoscenza, non un fallimento di formato.

Le due cose richiedono correzioni diverse, e confonderle fa perdere settimane. I fallimenti di formato si correggono nella descrizione o con decoding vincolato, sotto. I fallimenti di conoscenza si correggono mettendo la conoscenza nel prompt — la data corrente nel messaggio di sistema, una ricerca aeroporti come secondo tool che il modello chiama per primo, un enum nello schema quando l’insieme è abbastanza piccolo da essere enumerato. Nota cosa hanno in comune tutte e tre: spostano il problema fuori dalla memoria del modello e dentro il suo input, che è tutto il Capitolo 24.

Output strutturati, e cos’è davvero il «decoding vincolato»

Link alla sezione: Output strutturati, e cos’è davvero il «decoding vincolato»

Tutto ciò che c’è sopra si basa ancora sul fatto che il modello scelga di produrre la forma giusta. Esiste una garanzia più forte, ed è il miglior ritorno del Capitolo 17.

Ricorda come funziona la generazione: a ogni passo il modello produce un logit per ogni token del vocabolario, e il sampler ne sceglie uno. Il decoding vincolato inserisce un passaggio nel mezzo. Data una grammatica — derivata dal tuo JSON Schema — calcola quali tokens potrebbero arrivare legalmente dopo, imposta i logits di tutti gli altri a meno infinito e lascia che il sampler scelga tra ciò che resta.

Se lo schema dice che la prossima cosa deve essere un {, allora ogni token che non è { ha probabilità zero. Non «improbabile»: zero. Il modello non può emettere JSON non valido perché i tokens non validi sono stati rimossi dalla distribuzione prima del sampling.

Questo è ciò che stanno sotto a «output strutturati», «JSON mode» e «guided generation», e spiega le loro due proprietà. La garanzia è totale per tutto ciò che la grammatica può esprimere — tipi, campi obbligatori, enum, nesting — perché viene applicata meccanicamente invece che richiesta con gentilezza. E non dice nulla sul contenuto: una grammatica può forzare "date" a essere una stringa che corrisponde a un pattern di data, e non può forzarla a essere il giorno giusto. Che è lo stesso muro della sezione precedente, raggiunto dall’altro lato.

Due note pratiche. Non è gratis: la maschera deve essere calcolata a ogni passo, e grammatiche complesse costano latenza misurabile. E cambia ciò che il modello sta facendo: un modello spinto lontano dal suo token preferito può produrre contenuti peggiori pur producendo una struttura perfetta, ed è per questo che «chiedere bene e validare» resta un default ragionevole per forme semplici, mentre il decoding vincolato vale il suo costo quando la forma è complessa o il consumatore è rigido.

Effetti collaterali, e l’unica proprietà che conta

Link alla sezione: Effetti collaterali, e l’unica proprietà che conta

Il Capitolo 14 misurava un timeout seguito da un retry che fatturava due generazioni per una risposta. Con i tool lo stesso fallimento peggiora, perché un tool può fare qualcosa.

Se il tuo codice chiama charge_card, va in timeout e riprova, hai due addebiti. Il modello non ha idea che sia successo qualcosa; vede un solo risultato del tool. La soluzione è la stessa di qualunque sistema distribuito e non è un problema del modello: rendi l’operazione idempotente dando alla chiamata una chiave, così la seconda esecuzione riconosce la prima e ne restituisce il risultato invece di rifare il lavoro.

La regola di progettazione che ne segue merita di essere detta chiaramente. Separa letture e scritture nel tuo catalogo di tool. Una lettura può essere ritentata liberamente, eseguita in parallelo e messa in cache. Una scrittura no, e dovrebbe portare con sé una chiave, un controllo dei permessi e — per qualsiasi cosa di cui un utente vorrebbe sapere prima che accada — un passaggio di approvazione che mette un essere umano tra la richiesta e l’azione. Quel passaggio di approvazione non è una cortesia: è una delle poche cose che separano una prompt injection da una conseguenza reale — e, come misura il Capitolo 30, la più debole tra queste.

Il folklore dice che caricare molti tool fa scegliere male il modello. Vale la pena misurarlo invece di ripeterlo, quindi: le stesse ventiquattro richieste, con il tool dei voli più un insieme crescente di altri — inclusi tre deliberatamente confondibili (orari dei treni, traversate in traghetto, tratte autobus).

tool caricatiprompt tokensha scelto search_flightsdata in ISO
135324/2424/24
573024/2424/24
101.19321/2421/24
202.11924/2424/24

La selezione non è degradata. Con venti tool, tre dei quali plausibilmente confondibili, un modello da mezzo miliardo di parametri ha scelto quello giusto ventiquattro volte su ventiquattro. Il calo a dieci sono tre chiamate che hanno nominato un tool diverso, e non sopravvive al passaggio a venti.

È un risultato negativo e va riportato come tale: su questo task, con questi tool, «troppi tool» non era il problema. Ciò che è cresciuto, monotonicamente e di un fattore sei, è il prompt: da 353 tokens a 2.119, pagati a ogni richiesta nella conversazione, per sempre, che un tool venga usato o meno.

Quindi la versione onesta del folklore riguarda costo e context, non accuratezza. Venti tool sono una tassa permanente su ogni messaggio, e il Capitolo 16 ha già mostrato cosa fa un prefisso permanente a una fattura su quaranta turni. Quando le persone riferiscono che molti tool danneggiano la qualità, il meccanismo di solito è che le definizioni hanno occupato lo spazio del context che contava — che è un problema da Capitolo 24 travestito da Capitolo 18. I tool che sono davvero quasi duplicati tra loro sono un problema reale, e la soluzione per quelli non è avere meno tool ma descrizioni e namespace migliori: prefissali per sistema (crm.search_customer, billing.search_customer) così che due cataloghi uniti da due team non entrino in collisione, e così il modello abbia qualcosa su cui discriminare.

Tre tipi di tool, e quello che apre la parte successiva

Link alla sezione: Tre tipi di tool, e quello che apre la parte successiva

Aiuta ordinare i tool in base a ciò che fanno al mondo, perché l’ingegneria cambia per ciascuno.

Data tool leggono: cercano, recuperano, interrogano. Ritentabili, parallelizzabili, cacheable. Falliscono restituendo nulla di utile, e il loro rischio principale è portare testo non fidato nel context — che è l’intera superficie d’attacco del Capitolo 30.

Action tool scrivono: inviano, creano, addebitano, eliminano. Non ritentabili senza una chiave, non parallelizzabili in sicurezza, e sono il motivo per cui esistono i flussi di approvazione.

Orchestration tool chiamano altri modelli. Un tool la cui implementazione è un altro agent, con il suo prompt, i suoi tool e il suo loop — e al modello chiamante appare esattamente come gli altri due, perché uno schema e un endpoint sono tutto ciò che vede mai.

Quel terzo tipo non è una curiosità. È il meccanismo dietro la metà agent-come-tool del Capitolo 25 — l’altra topologia, l’handoff, cede la conversazione e non la riavrà mai indietro — e funziona proprio perché l’interfaccia in questo capitolo è abbastanza stretta da poterci infilare dietro un intero agent.

Ora hai un modello che può chiedere cose, e un contratto che rende la richiesta analizzabile. Quello che non hai è qualcosa su cui chiedere oltre a ciò che entra nel suo prompt.

Il tool più comune in produzione, con ampio margine, è una ricerca su un corpo di testo che il modello non ha mai visto durante l’addestramento: la tua documentazione, i tuoi ticket, i tuoi contratti. Sembra un problema risolto — fai embedding, trova i vicini più prossimi, incollali dentro — e le parti non risolte sono quelle che decidono se la risposta è affidabile: come viene tagliato il testo prima di essere embedded, quale soglia di similarità è abbastanza bassa da significare non lo so, e come una citazione viene collegata a un’affermazione così che un lettore possa verificarla.

Il Capitolo 19 è il retrieval, ed è il capitolo in cui una risposta sbagliata smette di essere una curiosità e inizia a essere una responsabilità.


Le misurazioni in questo capitolo vengono da Qwen/Qwen2.5-0.5B-Instruct con greedy decoding, su 24 richieste generate incrociando sei coppie di città con quattro formulazioni della data, usando il chat template del modello per le definizioni dei tool. Si riproducono esattamente, e sono un piccolo modello: leggi la distinzione formato/valore come una dimostrazione del meccanismo, non come un benchmark di ciò che fanno i modelli attuali. Un modello frontier risolve «venerdì prossimo» correttamente molto più spesso — e comunque uno schema non può costringerlo a farlo, che è la parte che si generalizza.

Il vocabolario JSON Schema usato sopra (type, properties, required, pattern, format, enum) è specificato nella bozza JSON Schema nominata dalla documentazione del tuo provider; il sottoinsieme utile è piccolo e uguale tra provider, e le differenze che esistono — quali keyword vengono applicate dal decoding vincolato invece che semplicemente passate al modello — vale la pena leggerle nella guida agli output strutturati del provider, invece di darle per scontate.

Per il decoding vincolato come tecnica, le librerie in stile guidance e il progetto outlines documentano la costruzione grammatica-to-logit-mask in un modo che si mappa direttamente sul sampler del Capitolo 17. E per l’andata e ritorno in sé, la specifica più chiara non è un tutorial ma un protocollo: il Capitolo 26 lo legge riga per riga.

  1. Ouyang, L. et al. Training language models to follow instructions with human feedback. arXiv:2203.02155 (2022). Il paper che ha reso standard la ricetta del post-training; la forma di una tool call si impara lì, dalle dimostrazioni, esattamente come la forma di una risposta.

Pronto a lasciare scegliere LIA?

Crea con ogni modello AI in un unico posto — inizia gratis oggi.