本教程通过五个同心环构建一个日历管理智能体。每个环都是一个完整的、可运行的程序,在前一个环的基础上恰好增加一个概念。到最后,您将手动编写智能体循环,然后用 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-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?