本教學透過五個同心環來建立一個行事曆管理代理程式。每個環都是一個完整、可執行的程式,並在前一個環的基礎上恰好新增一個概念。完成後,您將親手撰寫代理迴圈,然後用 Tool Runner SDK 抽象層取代它。
範例工具是 create_calendar_event。其結構描述使用了巢狀物件、陣列和選用欄位,因此您將看到 Claude 如何處理實際的輸入形態,而非單一的扁平字串。
每個環都可獨立執行。將任何一個環複製到新檔案中,它都能在不需要先前環的程式碼的情況下執行。
最小可行的工具使用程式:一個工具、一則使用者訊息、一次工具呼叫、一個結果。程式碼附有大量註解,讓您可以將每一行對應到工具使用生命週期。
請求會將 tools 陣列與使用者訊息一起傳送。當 Claude 決定呼叫工具時,回應會帶有 stop_reason: "tool_use" 以及一個 tool_use 內容區塊,其中包含工具名稱、唯一的 id 和結構化的 input。您的程式碼執行該工具,然後在 tool_result 區塊中將結果傳回,該區塊的 tool_use_id 必須與呼叫中的 id 相符。
# 第 1 環:單一工具,單一回合。
import json
import anthropic
# 建立一個用戶端。它會從環境變數讀取 ANTHROPIC_API_KEY。
client = anthropic.Anthropic()
# 定義一個工具。input_schema 是一個 JSON Schema 物件,用於描述
# Claude 呼叫此工具時應傳遞的引數。此 schema
# 包含巢狀物件(recurrence)、陣列(attendees)以及選用
# 欄位,這比扁平的字串引數更接近真實世界的工具。
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"],
},
}
]
# 將使用者的請求連同工具定義一起傳送。Claude 會根據
# 請求內容與工具描述來決定是否呼叫該工具。
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.",
}
],
)
# 當 Claude 呼叫工具時,回應的 stop_reason 為 "tool_use",
# 且 content 陣列中會包含一個 tool_use 區塊以及任何文字內容。
print(f"stop_reason: {response.stop_reason}")
# 尋找 tool_use 區塊。回應中可能在 tool_use 區塊之前包含文字
# 區塊,因此請掃描 content 陣列,而非假設其位置。
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}")
# 執行該工具。在真實系統中,這會呼叫您的行事曆 API。
# 此處結果為硬編碼,以保持範例的獨立完整性。
result = {"event_id": "evt_123", "status": "created"}
# 將結果回傳。tool_result 區塊需放在 user 訊息中,且其
# tool_use_id 必須與上方 tool_use 區塊的 id 相符。同時
# 納入助理先前的回應,讓 Claude 擁有完整的對話歷史。
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),
}
],
},
],
)
# 取得工具結果後,Claude 會產生最終的自然語言
# 回答,且 stop_reason 變為 "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)預期結果
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.第一個 stop_reason 是 tool_use,因為 Claude 正在等待行事曆結果。在您傳送結果後,第二個 stop_reason 是 end_turn,內容則是給使用者的自然語言。
環 1 假設 Claude 只會呼叫工具一次。實際任務通常需要多次呼叫:Claude 可能會建立一個事件、讀取確認訊息,然後再建立另一個。解決方法是使用 while 迴圈,持續執行工具並將結果回傳,直到 stop_reason 不再是 "tool_use" 為止。
另一個變更是對話歷史記錄。不要在每次請求時從頭重建 messages 陣列,而是保留一個持續更新的清單並附加到其中。每個回合都能看到完整的先前上下文。
# 第 2 環:代理迴圈。
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}"}
# 將完整的對話歷史保存在清單中,讓每一輪都能看到先前的上下文。
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,
)
# 持續迴圈直到 Claude 停止請求工具。每次迭代會執行所請求的
# 工具,將結果附加到歷史記錄,並請 Claude 繼續。
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)預期結果
I've set up your weekly team standup for the next 4 Mondays at 9am with Alice, Bob, and Carol invited.迴圈可能執行一次或多次,取決於 Claude 如何拆解任務。您的程式碼不再需要事先知道。
代理程式很少只有一種能力。新增第二個工具 list_calendar_events,讓 Claude 可以在建立新事件之前先檢查現有的排程。
當 Claude 有多個獨立的工具呼叫要執行時,它可能會在單一回應中傳回多個 tool_use 區塊。您的迴圈需要處理所有這些區塊,並在一則使用者訊息中一起傳回所有結果。請迭代 response.content 中的每個 tool_use 區塊,而不只是第一個。
# 第 3 環:多個工具,平行呼叫。
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":
# 單一回應可包含多個 tool_use 區塊。請處理所有區塊,
# 並將所有結果一併放入單一使用者訊息中回傳。
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)預期結果
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.如需更多關於並行執行和順序保證的資訊,請參閱平行工具使用。
工具會失敗。行事曆 API 可能會拒絕參與者過多的事件,或者日期格式可能不正確。當工具引發錯誤時,請使用 is_error: true 將錯誤訊息傳回,而不是讓程式當機。Claude 會讀取錯誤,並可以使用修正後的輸入重試、向使用者要求澄清,或說明該限制。
# 第 4 環:錯誤處理。
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:
# 發出失敗訊號,讓 Claude 可以重試或請求澄清。
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)預期結果
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.is_error 旗標是與成功結果的唯一差異。Claude 會看到該旗標和錯誤文字,並據此回應。請參閱處理工具呼叫以取得完整的錯誤處理參考。
環 2 到環 4 都是手動撰寫相同的迴圈:呼叫 API、檢查 stop_reason、執行工具、附加結果、重複。Tool Runner 會為您完成這些工作。將每個工具定義為函式,將清單傳遞給 tool_runner,並在迴圈完成後取得最終訊息。錯誤包裝、結果格式化和對話管理都在內部處理。
Python SDK 使用 @beta_tool 裝飾器從型別提示和 docstring 推斷結構描述。TypeScript SDK 使用 betaZodTool 搭配 Zod 結構描述。其他 SDK 遵循相同的模式並使用各自的輔助工具:C# 和 PHP 中的 BetaRunnableTool、Java 和 Ruby 中的型別化工具類別,以及 Go 中的 toolrunner.NewBetaToolFromJSONSchema。
Tool Runner 在所有七個 SDK 中都可使用:Python、TypeScript、C#、Go、Java、PHP 和 Ruby。請參閱 Tool Runner 以取得完整參考。cURL 和 CLI 分頁顯示的是說明而非程式碼;對於基於 curl 或 CLI 的指令碼,請保留環 4 的迴圈。
# 第 5 環: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)預期結果
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.輸出與環 3 相同。差異在於程式碼:大約只有一半的行數、沒有手動迴圈,且結構描述與實作放在一起。
您從單一硬編碼的工具呼叫開始,最終建立了一個具有生產環境架構的代理程式,能夠處理多個工具、平行呼叫和錯誤,然後將所有這些收攏到 Tool Runner 中。在此過程中,您看到了工具使用協定的每個部分:tool_use 區塊、tool_result 區塊、tool_use_id 比對、stop_reason 檢查,以及 is_error 訊號。
Was this page helpful?