Questo tutorial costruisce un agente per la gestione del calendario in cinque anelli concentrici. Ogni anello è un programma completo ed eseguibile che aggiunge esattamente un concetto all'anello precedente. Alla fine avrai scritto il ciclo agentico a mano e poi lo avrai sostituito con l'astrazione Tool Runner dell'SDK.
Lo strumento di esempio è create_calendar_event. Il suo schema utilizza oggetti annidati, array e campi opzionali, così potrai vedere come Claude gestisce forme di input realistiche anziché una singola stringa piatta.
Ogni anello funziona in modo autonomo. Copia qualsiasi anello in un nuovo file e verrà eseguito senza il codice degli anelli precedenti.
Il programma più piccolo possibile che usa strumenti: uno strumento, un messaggio utente, una chiamata allo strumento, un risultato. Il codice è ampiamente commentato così puoi mappare ogni riga al ciclo di vita dell'uso degli strumenti.
La richiesta invia un array tools insieme al messaggio utente. Quando Claude decide di chiamare uno strumento, la risposta ritorna con stop_reason: "tool_use" e un blocco di contenuto tool_use che contiene il nome dello strumento, un id univoco e l'input strutturato. Il tuo codice esegue lo strumento, poi invia il risultato in un blocco tool_result il cui tool_use_id corrisponde all'id della chiamata.
Cosa aspettarsi
stop_reason: tool_use
Tool: create_calendar_event
Input: {'title': 'Sync', 'start': '2026-03-30T10:00:00', 'end': '2026-03-30T10:30:00', 'attendees': ['[email protected]', '[email protected]']}
stop_reason: end_turn
I've scheduled your 30-minute sync with Alice and Bob for next Monday at 10am.Il primo stop_reason è tool_use perché Claude sta aspettando il risultato del calendario. Dopo aver inviato il risultato, il secondo stop_reason è end_turn e il contenuto è linguaggio naturale per l'utente.
L'Anello 1 presupponeva che Claude chiamasse lo strumento esattamente una volta. I compiti reali spesso richiedono diverse chiamate: Claude potrebbe creare un evento, leggere la conferma, poi crearne un altro. La soluzione è un ciclo while che continua a eseguire strumenti e a restituire i risultati finché stop_reason non è più "tool_use".
L'altra modifica riguarda la cronologia della conversazione. Invece di ricostruire l'array messages da zero a ogni richiesta, mantieni una lista in esecuzione e aggiungi elementi ad essa. Ogni turno vede il contesto precedente completo.
Cosa aspettarsi
I've set up your weekly team standup for the next 4 Mondays at 9am with Alice, Bob, and Carol invited.Il ciclo potrebbe essere eseguito una o più volte a seconda di come Claude scompone il compito. Il tuo codice non ha più bisogno di saperlo in anticipo.
Gli agenti raramente hanno una sola capacità. Aggiungi un secondo strumento, list_calendar_events, così Claude può controllare il calendario esistente prima di creare qualcosa di nuovo.
Quando Claude ha più chiamate a strumenti indipendenti da effettuare, potrebbe restituire diversi blocchi tool_use in una singola risposta. Il tuo ciclo deve elaborarli tutti e inviare tutti i risultati insieme in un unico messaggio utente. Itera su ogni blocco tool_use in response.content, non solo sul primo.
Cosa aspettarsi
I checked your calendar for next Monday and found an existing meeting from 2pm to 3pm. I've scheduled the planning session for 10am to 11am to avoid the conflict.Per maggiori informazioni sull'esecuzione concorrente e sulle garanzie di ordinamento, consulta Uso parallelo degli strumenti.
Gli strumenti falliscono. Un'API di calendario potrebbe rifiutare un evento con troppi partecipanti, oppure una data potrebbe essere malformata. Quando uno strumento genera un errore, invia il messaggio di errore con is_error: true invece di andare in crash. Claude legge l'errore e può riprovare con input corretto, chiedere chiarimenti all'utente o spiegare la limitazione.
Cosa aspettarsi
I tried to schedule the all-hands but the calendar only allows 10 attendees per event. I can split this into two sessions, or you can let me know which 10 people to prioritize.Il flag is_error è l'unica differenza rispetto a un risultato riuscito. Claude vede il flag e il testo dell'errore, e risponde di conseguenza. Consulta Gestire le chiamate agli strumenti per il riferimento completo sulla gestione degli errori.
Gli Anelli da 2 a 4 hanno scritto lo stesso ciclo a mano: chiama l'API, controlla stop_reason, esegui gli strumenti, aggiungi i risultati, ripeti. Il Tool Runner fa questo per te. Definisci ogni strumento come una funzione, passa la lista a tool_runner e recupera il messaggio finale una volta completato il ciclo. Il wrapping degli errori, la formattazione dei risultati e la gestione della conversazione sono gestiti internamente.
L'SDK Python usa il decoratore @beta_tool per inferire lo schema dai type hint e dalla docstring. L'SDK TypeScript usa betaZodTool con uno schema Zod. Gli altri SDK seguono lo stesso pattern con i propri helper: BetaRunnableTool in C# e PHP, classi di strumenti tipizzate in Java e Ruby, e toolrunner.NewBetaToolFromJSONSchema in Go.
Tool Runner è disponibile in tutti e sette gli SDK: Python, TypeScript, C#, Go, Java, PHP e Ruby. Consulta Tool Runner per il riferimento completo. Le schede cURL e CLI mostrano una nota invece del codice; mantieni il ciclo dell'Anello 4 per gli script basati su curl o CLI.
Cosa aspettarsi
I checked your calendar for next Monday and found an existing meeting from 2pm to 3pm. I've scheduled the planning session for 10am to 11am to avoid the conflict.L'output è identico all'Anello 3. La differenza è nel codice: circa la metà delle righe, nessun ciclo manuale, e lo schema risiede accanto all'implementazione.
Hai iniziato con una singola chiamata a uno strumento hardcoded e sei arrivato a un agente strutturato per la produzione che gestisce strumenti multipli, chiamate parallele ed errori, poi hai compresso tutto questo nel Tool Runner. Lungo il percorso hai visto ogni parte del protocollo di uso degli strumenti: blocchi tool_use, blocchi tool_result, corrispondenza di tool_use_id, controllo di stop_reason e segnalazione con is_error.
Specifica dello schema e best practice.
Il riferimento completo dell'astrazione SDK.
Risolvi gli errori comuni nell'uso degli strumenti.
Was this page helpful?
# Anello 1: Singolo strumento, singolo turno.
import json
import anthropic
# Crea un client. Legge ANTHROPIC_API_KEY dall'ambiente.
client = anthropic.Anthropic()
# Definisci uno strumento. L'input_schema è un oggetto JSON Schema che descrive
# gli argomenti che Claude deve passare quando chiama questo strumento. Questo schema
# include oggetti annidati (recurrence), array (attendees) e campi
# opzionali, più vicino agli strumenti reali rispetto a un semplice argomento stringa.
tools = [
{
"name": "create_calendar_event",
"description": "Create a calendar event with attendees and optional recurrence.",
"input_schema": {
"type": "object",
"properties": {
"title": {"type": "string"},
"start": {"type": "string", "format": "date-time"},
"end": {"type": "string", "format": "date-time"},
"attendees": {
"type": "array",
"items": {"type": "string", "format": "email"},
},
"recurrence": {
"type": "object",
"properties": {
"frequency": {"enum": ["daily", "weekly", "monthly"]},
"count": {"type": "integer", "minimum": 1},
},
},
},
"required": ["title", "start", "end"],
},
}
]
# Invia la richiesta dell'utente insieme alla definizione dello strumento. Claude decide
# se chiamare lo strumento in base alla richiesta e alla descrizione dello strumento.
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
tools=tools,
tool_choice={"type": "auto", "disable_parallel_tool_use": True},
messages=[
{
"role": "user",
"content": "Schedule a 30-minute sync with [email protected] and [email protected] next Monday at 10am.",
}
],
)
# Quando Claude chiama uno strumento, la risposta ha stop_reason "tool_use"
# e l'array content contiene un blocco tool_use insieme a eventuale testo.
print(f"stop_reason: {response.stop_reason}")
# Trova il blocco tool_use. Una risposta può contenere blocchi di testo prima del
# blocco tool_use, quindi scansiona l'array content invece di presumere la posizione.
tool_use = next(block for block in response.content if block.type == "tool_use")
print(f"Tool: {tool_use.name}")
print(f"Input: {tool_use.input}")
# Esegui lo strumento. In un sistema reale questo chiamerebbe la tua API calendario.
# Qui il risultato è hardcoded per mantenere l'esempio autonomo.
result = {"event_id": "evt_123", "status": "created"}
# Invia il risultato. Il blocco tool_result va in un messaggio user e
# il suo tool_use_id deve corrispondere all'id del blocco tool_use sopra. La
# risposta precedente dell'assistente è inclusa così Claude ha la cronologia completa.
followup = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
tools=tools,
tool_choice={"type": "auto", "disable_parallel_tool_use": True},
messages=[
{
"role": "user",
"content": "Schedule a 30-minute sync with [email protected] and [email protected] next Monday at 10am.",
},
{"role": "assistant", "content": response.content},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": json.dumps(result),
}
],
},
],
)
# Con il risultato dello strumento a disposizione, Claude produce una risposta finale
# in linguaggio naturale e stop_reason diventa "end_turn".
print(f"stop_reason: {followup.stop_reason}")
final_text = next(block for block in followup.content if block.type == "text")
print(final_text.text)# Anello 2: Il ciclo agentico.
import json
import anthropic
client = anthropic.Anthropic()
tools = [
{
"name": "create_calendar_event",
"description": "Create a calendar event with attendees and optional recurrence.",
"input_schema": {
"type": "object",
"properties": {
"title": {"type": "string"},
"start": {"type": "string", "format": "date-time"},
"end": {"type": "string", "format": "date-time"},
"attendees": {
"type": "array",
"items": {"type": "string", "format": "email"},
},
"recurrence": {
"type": "object",
"properties": {
"frequency": {"enum": ["daily", "weekly", "monthly"]},
"count": {"type": "integer", "minimum": 1},
},
},
},
"required": ["title", "start", "end"],
},
}
]
def run_tool(name, tool_input):
if name == "create_calendar_event":
return {"event_id": "evt_123", "status": "created", "title": tool_input["title"]}
return {"error": f"Unknown tool: {name}"}
# Conserva l'intera cronologia della conversazione in una lista così ogni turno vede il contesto precedente.
messages = [
{
"role": "user",
"content": "Schedule a weekly team standup every Monday at 9am for the next 4 weeks. Invite the whole team: [email protected], [email protected], [email protected].",
}
]
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
tools=tools,
tool_choice={"type": "auto", "disable_parallel_tool_use": True},
messages=messages,
)
# Itera finché Claude non smette di richiedere strumenti. Ogni iterazione esegue lo
# strumento richiesto, aggiunge il risultato alla cronologia e chiede a Claude di continuare.
while response.stop_reason == "tool_use":
tool_use = next(block for block in response.content if block.type == "tool_use")
result = run_tool(tool_use.name, tool_use.input)
messages.append({"role": "assistant", "content": response.content})
messages.append(
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": json.dumps(result),
}
],
}
)
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
tools=tools,
tool_choice={"type": "auto", "disable_parallel_tool_use": True},
messages=messages,
)
final_text = next(block for block in response.content if block.type == "text")
print(final_text.text)# Anello 3: Strumenti multipli, chiamate parallele.
import json
import anthropic
client = anthropic.Anthropic()
tools = [
{
"name": "create_calendar_event",
"description": "Create a calendar event with attendees and optional recurrence.",
"input_schema": {
"type": "object",
"properties": {
"title": {"type": "string"},
"start": {"type": "string", "format": "date-time"},
"end": {"type": "string", "format": "date-time"},
"attendees": {
"type": "array",
"items": {"type": "string", "format": "email"},
},
"recurrence": {
"type": "object",
"properties": {
"frequency": {"enum": ["daily", "weekly", "monthly"]},
"count": {"type": "integer", "minimum": 1},
},
},
},
"required": ["title", "start", "end"],
},
},
{
"name": "list_calendar_events",
"description": "List all calendar events on a given date.",
"input_schema": {
"type": "object",
"properties": {
"date": {"type": "string", "format": "date"},
},
"required": ["date"],
},
},
]
def run_tool(name, tool_input):
if name == "create_calendar_event":
return {"event_id": "evt_123", "status": "created", "title": tool_input["title"]}
if name == "list_calendar_events":
return {"events": [{"title": "Existing meeting", "start": "14:00", "end": "15:00"}]}
return {"error": f"Unknown tool: {name}"}
messages = [
{
"role": "user",
"content": "Check what I have next Monday, then schedule a planning session that avoids any conflicts.",
}
]
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
tools=tools,
messages=messages,
)
while response.stop_reason == "tool_use":
# Una singola risposta può contenere più blocchi tool_use. Elaborali tutti
# e restituisci tutti i risultati insieme in un unico messaggio utente.
tool_results = []
for block in response.content:
if block.type == "tool_use":
result = run_tool(block.name, block.input)
tool_results.append(
{
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps(result),
}
)
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
tools=tools,
messages=messages,
)
final_text = next(block for block in response.content if block.type == "text")
print(final_text.text)# Anello 4: Gestione degli errori.
import json
import anthropic
client = anthropic.Anthropic()
tools = [
{
"name": "create_calendar_event",
"description": "Create a calendar event with attendees and optional recurrence.",
"input_schema": {
"type": "object",
"properties": {
"title": {"type": "string"},
"start": {"type": "string", "format": "date-time"},
"end": {"type": "string", "format": "date-time"},
"attendees": {
"type": "array",
"items": {"type": "string", "format": "email"},
},
"recurrence": {
"type": "object",
"properties": {
"frequency": {"enum": ["daily", "weekly", "monthly"]},
"count": {"type": "integer", "minimum": 1},
},
},
},
"required": ["title", "start", "end"],
},
},
{
"name": "list_calendar_events",
"description": "List all calendar events on a given date.",
"input_schema": {
"type": "object",
"properties": {
"date": {"type": "string", "format": "date"},
},
"required": ["date"],
},
},
]
def run_tool(name, tool_input):
if name == "create_calendar_event":
if "attendees" in tool_input and len(tool_input["attendees"]) > 10:
raise ValueError("Too many attendees (max 10)")
return {"event_id": "evt_123", "status": "created", "title": tool_input["title"]}
if name == "list_calendar_events":
return {"events": [{"title": "Existing meeting", "start": "14:00", "end": "15:00"}]}
raise ValueError(f"Unknown tool: {name}")
messages = [
{
"role": "user",
"content": "Schedule an all-hands with everyone: " + ", ".join(f"user{i}@example.com" for i in range(15)),
}
]
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
tools=tools,
messages=messages,
)
while response.stop_reason == "tool_use":
tool_results = []
for block in response.content:
if block.type == "tool_use":
try:
result = run_tool(block.name, block.input)
tool_results.append(
{"type": "tool_result", "tool_use_id": block.id, "content": json.dumps(result)}
)
except Exception as exc:
# Segnala il fallimento così Claude può riprovare o chiedere chiarimenti.
tool_results.append(
{
"type": "tool_result",
"tool_use_id": block.id,
"content": str(exc),
"is_error": True,
}
)
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
tools=tools,
messages=messages,
)
final_text = next(block for block in response.content if block.type == "text")
print(final_text.text)# Anello 5: l'astrazione Tool Runner SDK.
import json
import anthropic
from anthropic import beta_tool
client = anthropic.Anthropic()
@beta_tool
def create_calendar_event(
title: str,
start: str,
end: str,
attendees: list[str] | None = None,
recurrence: dict | None = None,
) -> str:
"""Create a calendar event with attendees and optional recurrence.
Args:
title: Event title.
start: Start time in ISO 8601 format.
end: End time in ISO 8601 format.
attendees: Email addresses to invite.
recurrence: Dict with 'frequency' (daily, weekly, monthly) and 'count'.
"""
if attendees and len(attendees) > 10:
raise ValueError("Too many attendees (max 10)")
return json.dumps({"event_id": "evt_123", "status": "created", "title": title})
@beta_tool
def list_calendar_events(date: str) -> str:
"""List all calendar events on a given date.
Args:
date: Date in YYYY-MM-DD format.
"""
return json.dumps({"events": [{"title": "Existing meeting", "start": "14:00", "end": "15:00"}]})
final_message = client.beta.messages.tool_runner(
model="claude-opus-4-8",
max_tokens=1024,
tools=[create_calendar_event, list_calendar_events],
messages=[
{
"role": "user",
"content": "Check what I have next Monday, then schedule a planning session that avoids any conflicts.",
}
],
).until_done()
for block in final_message.content:
if block.type == "text":
print(block.text)