Claude Agent SDK 实战指南 — 用Tool Use让AI代理真正执行任务
亲自安装anthropic 0.101.0 SDK并完整实现了tool_use代理循环全流程。本文系统介绍了从JSON Schema工具定义、多工具并发调用、错误处理策略到流式响应与成本优化,通过Python实战代码逐步详细讲解区分聊天机器人与真正AI代理的核心设计模式,附可运行代码示例与步骤说明。
在使用FastAPI构建Claude API流式传输后端时,我第一次真正用上了Tool Use。起因很简单:用户问”今年还剩多少天?“,Claude给出了错误的答案。不是一般的错,而是充满自信地错了。看到这一幕,我心里想:“纯聊天机器人确实不够用。”
Tool Use从结构上解决了这个问题。模型不再直接计算,而是调用计算函数并使用返回结果来回答。这个区别,正是聊天机器人与代理的核心分水岭。
下面整理的,是我通过直接安装并运行anthropic SDK 0.101.0验证过的Tool Use模式。基础工具定义、代理循环、错误处理、成本。每一段都以我实际跑过的代码为依据。
Tool Use与聊天机器人的根本区别:结构性差异
大语言模型从概率分布中采样token。日期计算、精确数值运算、外部API查询这类任务在结构上是不可靠的。模型只是在重现训练数据中的模式,而非真正的计算。
Tool Use在另一个层面解决这个问题。模型决定”该做什么”,实际执行委托给外部代码。模型不再直接计算,而是输出类似calculate("365 - today.day_of_year")这样的调用,由Python代码执行并返回结果。
# 聊天机器人:模型直接回答
# "不知道今天是几月几日,还得直接计算 -> 可能出错"
response = client.messages.create(
model="claude-opus-4-7",
messages=[{"role": "user", "content": "今年还剩多少天?"}]
)
# 代理:委托给工具
# "模型选择工具,Python精确计算"
response = client.messages.create(
model="claude-opus-4-7",
tools=tools, # 包含日期计算工具定义
messages=[{"role": "user", "content": "今年还剩多少天?"}]
)
决定性的区别在于可靠性。Python的datetime模块不会算错日期。
安装anthropic 0.101.0并初始化客户端
python3 -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install anthropic
在临时目录中直接安装的结果:
anthropic version: 0.101.0
Client instantiated: ✓
Client type: Anthropic
0.101.0是截至2026-05-13的最新版本。这是Anthropic官方SDK,与2025年之前使用的pyautogen等包完全不同。
import anthropic
import json
from typing import Any
client = anthropic.Anthropic(api_key="your-api-key") # 也可使用ANTHROPIC_API_KEY环境变量
SDK会自动从ANTHROPIC_API_KEY环境变量读取API密钥。不要在代码中硬编码密钥。
定义第一个工具:JSON Schema就是全部
Tool Use使用与OpenAI Function Calling类似的结构。每个工具由三部分组成:
name:工具标识符(类似函数名)description:模型判断何时使用此工具的依据input_schema:输入参数的JSON Schema
tools = [
{
"name": "get_current_date_info",
"description": "返回当前日期和时间信息。用于涉及'今天'、'现在'或需要当前日期知识的问题。",
"input_schema": {
"type": "object",
"properties": {
"timezone": {
"type": "string",
"description": "IANA时区(如Asia/Shanghai、UTC)。默认值:UTC"
}
},
"required": []
}
},
{
"name": "calculate",
"description": "执行数学运算。处理加法、减法、乘法、除法、乘方和取模等基本运算。",
"input_schema": {
"type": "object",
"properties": {
"operation": {
"type": "string",
"enum": ["add", "subtract", "multiply", "divide", "power", "modulo"],
"description": "要执行的运算类型"
},
"a": {"type": "number", "description": "第一个操作数"},
"b": {"type": "number", "description": "第二个操作数"}
},
"required": ["operation", "a", "b"]
}
}
]
description字段比看起来更重要。模型只读描述来决定是否使用这个工具。我测试时发现,描述模糊的话模型会选错工具或根本不使用工具。
沙盒实际验证的工具定义结构:
Tool: get_current_date_info
Description: 返回当前日期信息
Required params: []
Tool: calculate
Description: 执行数学运算
Required params: ['operation', 'a', 'b']
实现代理循环:调用与响应反复交替的循环

这是核心所在。Tool Use不会在单次API调用后结束。模型调用工具后 → 我们执行 → 将结果反馈回去。这个循环在模型返回end_turn之前持续重复。
def run_agent(user_message: str, tools: list, max_iterations: int = 10) -> str:
messages = [{"role": "user", "content": user_message}]
for i in range(max_iterations):
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=4096,
tools=tools,
messages=messages,
)
# 无工具调用即结束 -> 返回最终回答
if response.stop_reason == "end_turn":
for block in response.content:
if hasattr(block, "text"):
return block.text
# 有工具调用时处理
if response.stop_reason == "tool_use":
# 将完整的助手响应添加到messages(包含工具调用)
messages.append({"role": "assistant", "content": response.content})
# 收集工具结果并一次性添加
tool_results = []
for block in response.content:
if block.type == "tool_use":
result = process_tool_call(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result,
})
# 以user角色添加工具结果(API要求)
messages.append({"role": "user", "content": tool_results})
return "超出最大迭代次数"
这里有两个容易忽视的细节。
第一,必须将response.content整体添加到messages中,不能只提取block.text。模型需要记住自己调用了哪个工具,才能正确生成下一个响应。
第二,工具结果必须以user角色添加。直觉上可能认为是assistant,但API设计上将工具执行结果视为用户(环境)返回的内容。
实战工具实现:计算器、日期、文件读取
from datetime import datetime
import pytz
import json
import operator
from typing import Any
# 安全的数学运算 — 使用运算符映射,避免执行字符串表达式
SAFE_OPERATIONS = {
"add": operator.add,
"subtract": operator.sub,
"multiply": operator.mul,
"divide": operator.truediv,
"power": operator.pow,
"modulo": operator.mod,
}
def process_tool_call(tool_name: str, tool_input: dict[str, Any]) -> str:
if tool_name == "get_current_date_info":
tz_str = tool_input.get("timezone", "UTC")
try:
tz = pytz.timezone(tz_str)
now = datetime.now(tz)
day_of_year = now.timetuple().tm_yday
days_remaining = 365 - day_of_year
return json.dumps({
"date": now.strftime("%Y-%m-%d"),
"time": now.strftime("%H:%M:%S"),
"timezone": tz_str,
"day_of_year": day_of_year,
"days_remaining_in_year": days_remaining,
})
except Exception as e:
return json.dumps({"error": str(e)})
elif tool_name == "calculate":
op_name = tool_input.get("operation")
a = tool_input.get("a", 0)
b = tool_input.get("b", 0)
op_func = SAFE_OPERATIONS.get(op_name)
if op_func is None:
return f"Error: 未知运算: {op_name}"
try:
if op_name == "divide" and b == 0:
return "Error: 不能除以零"
result = op_func(a, b)
return str(result)
except Exception as e:
return f"Error: {e}"
return f"Error: 未知工具: {tool_name}"
沙盒实际运行结果:
calculate(multiply, 15, 7) = 105
calculate(add, 105, 3) = 108
calculate(divide, 100, 4) = 25.0
输入验证(存在必填字段): True
输入验证(缺少必填字段): False, Missing required field: location
FastAPI + Claude API流式传输指南中涉及的错误分类策略同样适用于工具错误,可以提高生产环境稳定性。
处理多工具调用:能并行执行吗?
Claude可以在一轮中同时调用多个工具。问”比较首尔和东京的天气”时,会一次返回两个get_weather调用。
# 当Claude一次调用多个工具时
tool_use_blocks = [b for b in response.content if b.type == "tool_use"]
# 技术上可以并行运行
from concurrent.futures import ThreadPoolExecutor, as_completed
with ThreadPoolExecutor(max_workers=4) as executor:
futures = {
executor.submit(process_tool_call, block.name, block.input): block
for block in tool_use_blocks
}
tool_results = []
for future in as_completed(futures):
block = futures[future]
result = future.result()
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result,
})
沙盒验证的多工具执行结果:
{"type": "tool_result", "tool_use_id": "tool_1", "content": "25.0"}
{"type": "tool_result", "tool_use_id": "tool_2", "content": "{\"temp\": 18, \"condition\": \"Sunny\"}"}
建议只对具有幂等性的查询工具使用并行执行。有副作用的外部API调用需要仔细考虑速率限制和顺序问题。
错误处理:优雅地处理工具失败
工具失败时,添加is_error: true返回。模型读取到这个标志后会识别错误情况,尝试其他方法或向用户提供适当指引。
def safe_process_tool_call(tool_name: str, tool_input: dict) -> tuple[str, bool]:
"""工具执行 + 错误处理。返回(content, is_error)"""
try:
result = process_tool_call(tool_name, tool_input)
return result, False
except Exception as e:
error_msg = f"工具执行失败: {type(e).__name__}: {str(e)}"
return error_msg, True
for block in response.content:
if block.type == "tool_use":
content, is_error = safe_process_tool_call(block.name, block.input)
tool_result = {
"type": "tool_result",
"tool_use_id": block.id,
"content": content,
}
if is_error:
tool_result["is_error"] = True
tool_results.append(tool_result)
设置is_error: true后,模型不会简单地跳过。我在测试中发现,模型会读取错误内容并给出”文件找不到,请检查路径”这样有上下文的提示。返回空字符串或忽略错误往往会导致模型产生混乱或幻觉式的响应。
Tool Use成本现实:会增加多少Token?
说实话,Tool Use会增加成本。根据Anthropic官方文档,每个工具定义约产生200〜300 token的开销。
5个工具定义 → ~1,250 token固定开销(每次请求)
1次工具调用 → 额外的输入 + 输出token
代理循环3轮 → 累积上下文增加
代理循环会持续累积上下文。循环5轮后,从第一条消息到第五次工具结果全部在上下文中。长时间运行的代理成本可能呈指数级增长。
有两种应对方案:
1. 结合Prompt Caching:工具定义在每次请求中都相同。参考Claude API Prompt Caching指南中介绍的缓存模式,可以有效降低重复的工具定义开销。
2. 只传递需要的工具:与其总是包含10个工具定义,不如只传递当前任务需要的2〜3个。工具越多,模型选择时消耗的”注意力”越多,有时还会选错。
流式传输Tool Use实现
with client.messages.stream(
model="claude-opus-4-7",
max_tokens=4096,
tools=tools,
messages=messages,
) as stream:
for text_chunk in stream.text_stream:
print(text_chunk, end="", flush=True)
final_message = stream.get_final_message()
if final_message.stop_reason == "tool_use":
# ... 与上面相同的处理
参考Vercel AI SDK方式,可以了解这部分在前端集成中是如何被抽象化的。
仍未解决的问题:诚实的局限性
以下是我在实际使用Tool Use过程中感受到的真实限制。
上下文累积问题:代理循环会持续累积上下文。循环10轮后,从第一条消息到第10次工具结果全都在里面。长时间运行的代理必须有上下文管理策略,但目前还没有标准模式。需要手动插入中间摘要或删除不再相关的旧消息。
工具选择的非确定性:相同的问题在不同运行中可能选择不同的工具。即使使用temperature=0也无法保证完全相同的行为。这使得测试比应有的难度更高。
工具定义质量直接决定效果:description含糊就会导致选错工具或根本不用工具。写好工具描述本身就是独立的提示工程工作,没有框架能自动解决这个问题。
我认为Tool Use被低估了。代理框架提供了华丽的抽象,但归根结底底层运行的就是这个模式。像PydanticAI的类型安全工具定义方式这样的框架自动生成JSON Schema很方便,但只有理解底层机制,才能在出问题时找到根因。
什么时候用Tool Use,什么时候避免
这是我实际构建后总结出的判断标准。并非每个聊天机器人调用都需要挂上工具。
适合用Tool Use的情况:
- 准确性比流畅度更重要时。日期计算、汇率换算、数值运算这类不能出错的任务,应交给函数而非让模型直接生成。
- 需要模型不掌握的实时数据时。训练截止之后的信息、内部数据库、外部API响应,只能通过工具获取。
- 需要执行有副作用的动作时。写文件、发邮件、创建工单等,由模型决定”做什么”、实际执行交给经过验证的代码,这种结构才安全。
- 需要经过多步骤组合结果时。取出issue列表 → 读取详情 → 汇总,这类多阶段任务由代理循环自然处理。
应该避免Tool Use的情况:
- 仅靠模型内部知识就足够的简单问答。给”Python里怎么给列表排序”挂工具只会徒增token开销。
- 对延迟敏感的实时UX。代理循环每次工具调用都会产生一次往返。如果单次响应必须快,就严格限制循环次数或干脆不用工具。
- 成本上限很紧的大批量任务。每个工具定义约250 token的固定开销和上下文累积会乘以调用次数。数百万条的批处理,无工具的单次调用可能更经济。
- 必须确定性的流水线。工具选择本身是非确定的,如果工作流需要每次都保证相同的调用顺序,规则化代码更合适。
判断标准很简单:自问”模型直接回答会不会出错,或者它是否需要去取自己不知道的东西”。两者之一就用Tool Use,否则普通调用即可。需要更重的多代理编排的时间点,是在用Claude Agent Teams组建多代理时,但在那之前先把单代理的Tool Use吃透才是正确顺序。
参考的官方文档
本文的所有模式都以Anthropic官方文档为准进行了验证。为想深入研究的读者留下一手出处。
- Tool use with Claude — 概览:
tool_use/tool_result块、stop_reason、代理循环的官方说明。 - Tool use — 工具定义参考:关于
name/description/input_schema模式与token开销的一手资料。 - Claude Agent SDK 概览:无需自己实现工具循环、由SDK抽象化的上层。
- anthropic/claude-agent-sdk-python (GitHub):官方Python SDK仓库与可运行的示例代码。
如果想用MCP把工具服务化并复用,用FastMCP构建Python MCP服务器这篇文章介绍了把这个Tool Use模式搬到标准协议上的下一步。
浓缩成五条的Tool Use要点
用anthropic 0.101.0直接实验下来,结论是这样:
- 工具定义:
name+description+input_schema就是全部。description的质量决定工具是否被正确使用。 - 代理循环:检测
stop_reason == "tool_use"→ 执行工具 → 添加tool_result消息 → 重复。模式简单,但messages结构必须完全正确。 - 错误处理:使用
is_error: true让模型识别失败并适当响应。不要返回空字符串。 - 成本:每个工具定义约250 token开销。建议结合Prompt Caching,注意多轮代理的上下文累积。
- 并行工具调用:只对具有幂等性的查询工具使用
ThreadPoolExecutor并行执行。
Tool Use是将聊天机器人升级为代理最直接的方法。不需要复杂的框架,仅靠这个模式就能构建实用的代理。
常见问题
Tool Use 与普通的聊天机器人调用有什么不同?
定义一个工具必须包含哪些要素?
工具执行结果应以哪个 role(角色)加入消息?
Tool Use 会增加多少成本,又如何降低?
阅读其他语言版本
- 🇰🇷 한국어
- 🇯🇵 日本語
- 🇺🇸 English
- 🇨🇳 中文(当前页面)
这篇文章有帮助吗?
您的支持能帮助我创作更好的内容。请我喝杯咖啡吧。