本教學以五個同心環的方式建構一個行事曆管理代理程式。每個環都是一個完整、可執行的程式,並在前一個環的基礎上恰好新增一個概念。完成後,您將親手撰寫出代理迴圈,然後再以 Tool Runner SDK 抽象層取代它。
範例工具是 create_calendar_event。它的結構描述(schema)使用了巢狀物件、陣列和選用欄位,因此您將看到 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-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.",
}
],
)
# 當 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-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),
}
],
},
],
)
# 取得工具結果後,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 Monday, March 30 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-5",
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-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)預期結果
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-5",
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-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)預期結果
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-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:
# 發出失敗訊號,讓 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-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)預期結果
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,並在迴圈完成後取得最終訊息。錯誤包裝、結果格式化和對話管理都在內部處理。
每個 SDK 都提供一個輔助工具,可將一般函式轉換為可執行的工具,並從其函式簽章推導出輸入結構描述;下方的分頁顯示了每種語言的慣用寫法。
# 第 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-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)預期結果
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?