Immagina un assistente AI che non si limita a risponderti, ma che può fare cose: cercare informazioni online, leggere file, eseguire calcoli, chiamare API esterne. Questo è il potere del Tool Use nella Claude API — la capacità di dotare Claude di strumenti reali che trasformano un modello linguistico in un agente operativo.
In questa guida pratica vedremo come implementare il tool use passo dopo passo, con esempi completi in Python e JavaScript. Se hai già esplorato Claude Code come strumento di sviluppo, il tool use ti permetterà di costruire agenti simili nel tuo codice. E se stai esplorando l’ecosistema Anthropic più in profondità, questo si connette strettamente al Model Context Protocol, che standardizza proprio l’integrazione tra LLM e strumenti esterni.
Cos’è il Tool Use (Function Calling)
Il Tool Use — noto anche come “function calling” in altri LLM — è un meccanismo che permette al modello di richiedere l’esecuzione di funzioni esterne durante una conversazione. Il flusso base è semplice:
- Tu definisci una lista di “tools” disponibili (nome, descrizione, schema degli input)
- Claude riceve il messaggio dell’utente insieme alla lista di tools
- Claude decide autonomamente se e quale tool usare
- Claude restituisce un blocco
tool_usecon i parametri scelti - Il tuo codice esegue il tool e restituisce il risultato
- Claude usa il risultato per formulare la risposta finale
La chiave è che Claude non esegue il codice — decide solo quale tool usare e con quali parametri. Il controllo sull’esecuzione rimane sempre nel tuo codice. Questo pattern è fondamentale per la sicurezza degli agenti AI.
💡 La differenza tra un chatbot e un agente è proprio questa: un chatbot risponde, un agente agisce. Il tool use è il meccanismo che abilita l’azione.
Definire i Tools con JSON Schema
Ogni tool viene definito attraverso un oggetto JSON con tre campi obbligatori: name, description e input_schema. La description è cruciale: Claude la usa per decidere quando e come usare il tool. Una descrizione vaga porta a un uso impreciso del tool.
tools = [
{
"name": "get_weather",
"description": "Recupera le condizioni meteo attuali per una città specifica. "
"Usalo quando l'utente chiede informazioni sul tempo, temperatura, "
"pioggia o condizioni atmosferiche di una località.",
"input_schema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "Nome della città (es: 'Roma', 'Milano', 'London')"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Unità di misura per la temperatura"
}
},
"required": ["city"]
}
},
{
"name": "calculate",
"description": "Esegue calcoli matematici. Usalo per operazioni aritmetiche, "
"percentuali, conversioni e qualsiasi calcolo numerico preciso.",
"input_schema": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "L'espressione matematica da calcolare (es: '15% di 240', '(100 * 1.22) / 12')"
}
},
"required": ["expression"]
}
},
{
"name": "save_file",
"description": "Salva contenuto testuale in un file.",
"input_schema": {
"type": "object",
"properties": {
"filename": {"type": "string"},
"content": {"type": "string"}
},
"required": ["filename", "content"]
}
}
]Nota l’uso di enum per il campo unit: vincolare i valori possibili riduce gli errori e migliora la qualità dell’output. Usa required per i campi indispensabili. La documentazione ufficiale di Anthropic sul tool use copre tutti i tipi JSON Schema supportati.
Struttura della Request e Gestione della Response
La request alla Claude API con tools è simile a una normale chiamata, con l’aggiunta del parametro tools. Vediamo la struttura completa in Python e JavaScript:
import anthropic
client = anthropic.Anthropic()
# Prima chiamata: Claude decide se usare un tool
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=[
{
"role": "user",
"content": "Che tempo fa a Roma oggi? E salvami il risultato in un file."
}
]
)
print(f"Stop reason: {response.stop_reason}") # "tool_use"
# Analisi della response
for block in response.content:
if block.type == "text":
print(f"Testo: {block.text}")
elif block.type == "tool_use":
print(f"Tool richiesto: {block.name}")
print(f"Input: {block.input}")
print(f"Tool use ID: {block.id}")Quando Claude vuole usare un tool, la response ha stop_reason: "tool_use" e il content contiene uno o più blocchi di tipo tool_use. Ogni blocco ha un id univoco che devi usare quando restituisci il risultato.
// JavaScript con @anthropic-ai/sdk
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const response = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
tools: tools,
messages: [
{
role: "user",
content: "Che tempo fa a Roma oggi?",
},
],
});
const toolUseBlocks = response.content.filter((b) => b.type === "tool_use");
for (const block of toolUseBlocks) {
console.log(`Tool: ${block.name}`);
console.log("Input:", block.input);
}Pattern Multi-Turn: Agente Completo con Tool Results
Il vero potere emerge nel pattern multi-turn: dopo che Claude richiede un tool, tu esegui il tool reale, poi restituisci il risultato in una seconda chiamata. Claude usa il risultato per continuare il ragionamento — e può richiedere altri tools in cascata.
import anthropic, json, math, os
client = anthropic.Anthropic()
def get_weather(city: str, unit: str = "celsius") -> dict:
# Demo: dati fittizi. In produzione: chiamata a OpenWeatherMap
return {"city": city, "temperature": 22, "condition": "Soleggiato", "humidity": 45}
def calculate(expression: str) -> dict:
try:
result = eval(expression, {"__builtins__": {}}, {"math": math})
return {"result": result, "expression": expression}
except Exception as e:
return {"error": str(e)}
def save_file(filename: str, content: str) -> dict:
with open(f"/tmp/{filename}", "w") as f:
f.write(content)
return {"success": True, "filepath": f"/tmp/{filename}"}
TOOL_MAP = {"get_weather": get_weather, "calculate": calculate, "save_file": save_file}
def run_agent(user_message: str) -> str:
messages = [{"role": "user", "content": user_message}]
while True:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=2048,
tools=tools,
messages=messages
)
if response.stop_reason == "end_turn":
return " ".join(b.text for b in response.content if b.type == "text")
messages.append({"role": "assistant", "content": response.content})
tool_results = []
for block in response.content:
if block.type == "tool_use":
func = TOOL_MAP.get(block.name)
if func:
result = func(**block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps(result)
})
messages.append({"role": "user", "content": tool_results})
result = run_agent("Che tempo fa a Roma? Salvami il report in meteo-report.txt")
print(result)🔧 La struttura
tool_resultdeve sempre includere iltool_use_idcorrispondente. Claude usa questo ID per capire quale tool ha prodotto quale risultato, specialmente quando più tools vengono usati in parallelo.
Error Handling e Best Practices
Un agente robusto deve gestire i casi in cui il tool fallisce. Il modo corretto è restituire l’errore nel tool_result e lasciare che Claude decida come procedere.
def execute_tool_safely(block) -> dict:
func = TOOL_MAP.get(block.name)
if not func:
return {
"type": "tool_result",
"tool_use_id": block.id,
"is_error": True,
"content": f"Tool '{block.name}' non trovato nel sistema."
}
try:
result = func(**block.input)
return {
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps(result)
}
except Exception as e:
return {
"type": "tool_result",
"tool_use_id": block.id,
"is_error": True,
"content": f"Errore durante l'esecuzione: {str(e)}"
}Alcune best practices fondamentali per il tool use in produzione:
- Descrizioni precise: Investi tempo nella descrizione dei tools. È ciò che Claude usa per decidere quando e come usarli.
- Validazione input: Prima di eseguire il tool, valida sempre i parametri ricevuti da Claude.
- Timeout e retry: I tools che chiamano servizi esterni possono fallire. Implementa timeout ragionevoli.
- Logging completo: Logga ogni chiamata tool — nome, input, output, durata.
- Principio del minimo privilegio: Dai ai tools solo le permissioni strettamente necessarie.
- Limite di iterazioni: Aggiungi un contatore di max_iterations nel loop dell’agente per evitare loop infiniti.
🎯 La qualità di un agente AI dipende per il 70% dalla qualità delle descrizioni dei tools e per il 30% dal codice. Un tool mal descritto porta a usi sbagliati anche con il miglior modello disponibile.
FAQ
Qual è la differenza tra tool use e Model Context Protocol (MCP)?
Il tool use è un’API-level feature: definisci tools direttamente nelle tue chiamate API. MCP è un protocollo standardizzato per connettere LLM a sorgenti di dati tramite server dedicati. Per progetti con molti tools condivisi tra più agenti, MCP è più scalabile. Per integrazioni semplici, il tool use diretto è più rapido.
Claude può usare più tools in parallelo?
Sì. Claude può restituire più blocchi tool_use nella stessa response quando i tools sono indipendenti tra loro. Il tuo codice deve essere in grado di eseguirli e restituire tutti i risultati in un unico blocco user con array di tool_result.
Come posso impedire che Claude usi un tool in modo inaspettato?
Puoi usare il parametro tool_choice: {"type": "auto"} lascia decidere Claude (default), {"type": "any"} forza l’uso di almeno un tool, {"type": "tool", "name": "nome_tool"} forza un tool specifico.
I tools funzionano con il prompt caching di Anthropic?
Sì. Puoi applicare il prompt caching ai tools aggiungendo {"cache_control": {"type": "ephemeral"}} all’ultimo tool della lista. Questo è utile quando hai una lista lunga di tools che rimane costante tra le chiamate — riduce latenza e costi significativamente.
È sicuro far eseguire codice ai tools?
Dipende dall’implementazione. Non usare mai eval() diretto su input non sanitizzato. Per tool di esecuzione codice, usa sandbox isolate (Docker containers, AWS Lambda con risorse limitate). La sicurezza dell’infrastruttura è responsabilità tua, non del modello.

