Questo tutorial costruisce un agente di 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 loop 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 usa oggetti annidati, array e campi opzionali, quindi vedrai come Claude gestisce forme di input realistiche anziché una singola stringa piatta.
Il più piccolo programma possibile che usa gli strumenti: uno strumento, un messaggio utente, una chiamata a uno 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 determina che è necessaria una chiamata a uno strumento, la risposta ritorna con stop_reason: "tool_use" e un blocco di contenuto tool_use contenente il nome dello strumento, un id univoco e l'input strutturato. Il tuo codice esegue lo strumento, poi invia il risultato indietro in un blocco tool_result il cui tool_use_id corrisponde all'id della chiamata.
# 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-5",
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] on Monday, March 30, 2026 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-5",
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] on Monday, March 30, 2026 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)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 Monday, March 30 at 10am.Il primo stop_reason è tool_use perché Claude sta aspettando il risultato del calendario. Dopo che hai 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 loop while che continua a eseguire gli strumenti e a restituire i risultati finché stop_reason non è più "tool_use".
L'altro cambiamento è la cronologia della conversazione. Invece di ricostruire l'array messages da zero a ogni richiesta, mantieni una lista corrente e aggiungi elementi ad essa. Ogni turno vede il contesto precedente completo.
# 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-5",
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-5",
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)Cosa aspettarsi
I've set up your weekly team standup for the next 4 Mondays at 9am with Alice, Bob, and Carol invited.Il loop potrebbe essere eseguito una volta 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 loop deve elaborarli tutti e inviare indietro tutti i risultati insieme in un unico messaggio utente. Itera su ogni blocco tool_use in response.content, non solo sul primo.
# 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-5",
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-5",
max_tokens=1024,
tools=tools,
messages=messages,
)
final_text = next(block for block in response.content if block.type == "text")
print(final_text.text)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 degli strumenti in parallelo.
Gli strumenti falliscono. Un'API di calendario potrebbe rifiutare un evento con troppi partecipanti, o una data potrebbe essere malformata. Quando uno strumento genera un errore, invia il messaggio di errore indietro con is_error: true invece di andare in crash. Claude legge l'errore e può riprovare con un input corretto, chiedere chiarimenti all'utente o spiegare la limitazione.
# 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-5",
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-5",
max_tokens=1024,
tools=tools,
messages=messages,
)
final_text = next(block for block in response.content if block.type == "text")
print(final_text.text)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 loop 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 loop. L'incapsulamento degli errori, la formattazione dei risultati e la gestione della conversazione sono gestiti internamente.
Ogni SDK fornisce un helper che trasforma una funzione ordinaria in uno strumento eseguibile e deriva lo schema di input dalla sua firma; le schede qui sotto mostrano la forma idiomatica per ogni linguaggio.
# 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-5",
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)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 loop manuale, e lo schema vive accanto all'implementazione.
Hai iniziato con una singola chiamata a uno strumento codificata a mano e hai finito con un agente dalla forma pronta per la produzione che gestisce più strumenti, chiamate parallele ed errori, per poi condensare tutto questo nel Tool Runner. Lungo il percorso hai visto ogni pezzo 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 all'astrazione dell'SDK.
Correggi gli errori comuni nell'uso degli strumenti.
Was this page helpful?