Tool use, function calling, structured output
~ min di lettura
Riepilogo in 30 secondi
- Il tool use (o function calling: stesso meccanismo, due nomi) fa sì che il modello, invece di rispondere, ti chieda di eseguire una funzione che gli hai descritto tu. Il modello non esegue niente: chiede, esegui tu.
- Ogni strumento lo descrivi con nome, descrizione e uno schema JSON degli argomenti. Il modello decide se e quale chiamare, e con quali valori.
- Il giro è a più turni: la sua richiesta di chiamata, tu esegui e gli rimandi il risultato, lui produce la risposta finale.
- L’output strutturato è l’altra faccia: quando ti serve un dato in una forma fissa (JSON), non un’azione. Il trucco portabile è un solo strumento il cui schema è la forma che vuoi, forzato con
tool_choice. - Il modello può sbagliare o inventare gli argomenti: validali sempre. E ogni strumento che gli dai è una capacità in più, quindi anche una superficie di rischio in più.
Il modello di base sa fare una cosa sola: produrre testo. Ma spesso ti serve altro. Che compia un’azione (interrogare un database, chiamare un’API, fare un conto affidabile) o che ti dia un dato in una forma precisa che il tuo codice legge senza indovinare. Tool use e output strutturato sono i due meccanismi per questo, e sotto sono lo stesso meccanismo usato in due modi.
Cos’è il tool use
Sezione intitolata “Cos’è il tool use”«Tool use» e «function calling» sono la stessa cosa con due nomi diversi: Anthropic la chiama tool use, OpenAI function calling. L’idea è controintuitiva: il modello non esegue niente. Tu gli descrivi degli strumenti che potrebbe usare, e lui, invece di rispondere in prosa, può decidere di chiederti di eseguirne uno, dicendoti quale e con quali argomenti. L’esecuzione la fai tu, nel tuo codice, e gli rimandi il risultato. Lui prosegue da lì.
Ogni strumento lo dichiari con tre cose: un nome, una descrizione, e uno schema JSON degli argomenti che accetta. La descrizione conta più di quanto sembri: è il testo con cui il modello capisce quando quello strumento è quello giusto.
Il giro completo
Sezione intitolata “Il giro completo”Un esempio. Dai al modello un solo strumento, get_weather, e gli chiedi il
meteo di Roma.
tools = [ { "name": "get_weather", "description": "Restituisce il meteo attuale di una città.", "input_schema": { "type": "object", "properties": { "city": {"type": "string", "description": "Nome della città, es. Roma"}, }, "required": ["city"], }, }]
conversazione = [{"role": "user", "content": "Che tempo fa a Roma?"}]
message = client.messages.create( model="claude-sonnet-5", max_tokens=1024, tools=tools, messages=conversazione,)
# stop_reason == "tool_use": il modello chiede di chiamare lo strumentoblocco = next(b for b in message.content if b.type == "tool_use")risultato = get_weather(**blocco.input) # la TUA funzione, es. "24 gradi, sereno"
conversazione.append({"role": "assistant", "content": message.content})conversazione.append({"role": "user", "content": [ {"type": "tool_result", "tool_use_id": blocco.id, "content": risultato},]})
finale = client.messages.create( model="claude-sonnet-5", max_tokens=1024, tools=tools, messages=conversazione,)
print(finale.content[0].text) # -> A Roma ci sono 24 gradi e cielo sereno.const tools = [ { name: "get_weather", description: "Restituisce il meteo attuale di una città.", input_schema: { type: "object", properties: { city: { type: "string", description: "Nome della città, es. Roma" }, }, required: ["city"], }, },];
const conversazione: any[] = [{ role: "user", content: "Che tempo fa a Roma?" }];
const message = await client.messages.create({ model: "claude-sonnet-5", max_tokens: 1024, tools, messages: conversazione,});
// stop_reason === "tool_use": il modello chiede di chiamare lo strumentoconst blocco = message.content.find((b) => b.type === "tool_use");if (blocco && blocco.type === "tool_use") { const args = blocco.input as { city: string }; const risultato = getWeather(args.city); // la TUA funzione
conversazione.push({ role: "assistant", content: message.content }); conversazione.push({ role: "user", content: [{ type: "tool_result", tool_use_id: blocco.id, content: risultato }], });
const finale = await client.messages.create({ model: "claude-sonnet-5", max_tokens: 1024, tools, messages: conversazione, });
const testo = finale.content[0]; if (testo.type === "text") console.log(testo.text); // -> A Roma ci sono 24 gradi e cielo sereno.}Il punto chiave è stop_reason. Vale tool_use invece di end_turn: è il
segnale che il modello vuole uno strumento, non che ha finito. Accodi la sua
richiesta e il tuo risultato alla lista dei messaggi, la stessa che rimandi a
ogni chiamata senza stato, come nella prima lezione del
modulo, e richiami. In un caso reale
questo diventa un ciclo: finché stop_reason è tool_use, esegui e rimandi;
quando è end_turn, hai la risposta.
Output strutturato
Sezione intitolata “Output strutturato”L’altra faccia del tool use non fa agire il modello: lo costringe a darti un dato in una forma fissa. Ti serve quando vuoi un oggetto JSON che il tuo codice legge direttamente, non una frase da cui estrarre i campi con una regex fragile.
Il modo più portabile è un trucco. Definisci un solo strumento il cui schema è
la forma che vuoi, e forzi il modello a chiamarlo con tool_choice. Non esegui
niente: quello che ti interessa sono gli argomenti che il modello ha riempito.
tools = [{ "name": "salva_contatto", "description": "Salva i dati di contatto estratti dal testo.", "input_schema": { "type": "object", "properties": { "nome": {"type": "string"}, "email": {"type": "string"}, "azienda": {"type": "string"}, }, "required": ["nome", "email"], },}]
message = client.messages.create( model="claude-sonnet-5", max_tokens=256, tools=tools, tool_choice={"type": "tool", "name": "salva_contatto"}, # forza la chiamata messages=[{"role": "user",)
dati = message.content[0].input# -> {"nome": "Anna Rossi", "email": "[email protected]", "azienda": "Acme"}const tools = [{ name: "salva_contatto", description: "Salva i dati di contatto estratti dal testo.", input_schema: { type: "object", properties: { nome: { type: "string" }, email: { type: "string" }, azienda: { type: "string" }, }, required: ["nome", "email"], },}];
const message = await client.messages.create({ model: "claude-sonnet-5", max_tokens: 256, tools, tool_choice: { type: "tool", name: "salva_contatto" }, // forza la chiamata});
const blocco = message.content[0];if (blocco.type === "tool_use") console.log(blocco.input);// -> { nome: "Anna Rossi", email: "[email protected]", azienda: "Acme" }Ora dati è un oggetto già strutturato, con i campi che hai chiesto. Niente
parsing del testo libero, niente casi limite del tipo «e se la mail va a capo».
Lo schema JSON è anche un contratto: required dice quali campi devono
esserci, i tipi dicono cosa aspettarti. Per un JSON semplice esiste anche la
scorciatoia vista in System prompt e
ruoli: chiudere con un turno
assistant che contiene {. Funziona, ma lo schema di uno strumento è più
solido, perché dichiara i campi e i tipi invece di sperare che il modello
indovini la forma.
Cosa tenere a mente
Sezione intitolata “Cosa tenere a mente”- Valida sempre gli argomenti. Il modello riempie lo schema, ma può sbagliare un valore o inventarlo. Lo schema garantisce la forma, non che il contenuto sia giusto: tratta gli argomenti come input non fidato.
- Puoi forzare o lasciar scegliere. Con
tool_choiceobblighi il modello a usare un tool, utile per l’estrazione; senza, decide lui se e quale chiamare, utile quando alcune domande non richiedono strumenti. - Gli strumenti costano token. Le definizioni entrano nella finestra a ogni chiamata, come hai visto in Il contesto in token. Molti strumenti descritti a lungo pesano: tienili essenziali.
- Uno strumento è una capacità, quindi un rischio. Se un tool scrive su un database o manda una mail, un input malevolo che convince il modello a chiamarlo nel modo sbagliato diventa un problema di sicurezza. È il tema di Prompt injection e sicurezza.
Verifica cosa hai capito
Sezione intitolata “Verifica cosa hai capito”Cosa viene dopo
Sezione intitolata “Cosa viene dopo”Il tool use fa arrivare al modello dati e capacità che decidi tu. Un caso frequente è dargli accesso a una tua base di documenti: recuperare i pezzi giusti e passarglieli nel prompt, invece di infilare tutto nel contesto. È la prossima lezione.