EP05. "Tool Use 进阶:多工具、批次调用与结构化抽取"
🔒 登录后可标记已读- 有了 EP04 那套「写函数 → 写 schema → 执行 → 送回结果」的基础设施后,加第二、第三个工具只剩两步:把 schema 塞进
tools列表、在run_tool里加一个分支——不用重新搭一遍架构 - Claude 明明能一次触发多个独立的工具调用,实际上却常常一个一个慢慢调——batch tool 是一个「工具中的工具」,专门哄 Claude 把互不依赖的调用打包成一次并行请求
- 想要 Claude 稳定吐出结构化数据,比 EP01 的 prefill + stop sequence 更可靠的做法是「把 schema 包成一个工具,强制 Claude 调用它」——数据直接从 tool call 的参数里拿,不用自己 parse 文字
- 写详细 JSON schema 很花时间,
to_json这种通用 schema + 在 prompt 里描述要什么结构,能用十分之一的功夫拿到九成的效果 - Text Editor Tool 是 Claude 内建的工具——schema 已经有了,你只需要写「怎么真正读写文件」这部分,不用自己设计 schema
重点内容
加第二、第三个工具:只有两步
延续 EP04 的提醒设置场景,再加两个工具:add_duration_to_datetime(日期加减)、set_reminder(设提醒,目前先用 print 模拟,之后可以接真正的提醒系统)。
第一步:把 schema 加进 tools 数组
tools=[
get_current_datetime_schema,
add_duration_to_datetime_schema,
set_reminder_schema
]
第二步:在 run_tool 里加分支
def run_tool(tool_name, tool_input):
if tool_name == "get_current_datetime":
return get_current_datetime(**tool_input)
elif tool_name == "set_reminder":
return set_reminder(**tool_input)
elif tool_name == "add_duration_to_datetime":
return add_duration_to_datetime(**tool_input)
else:
raise Exception(f"Unknown tool name: {tool_name}")
📌 基础设施搭好之后,往后加任何新工具都只需要这两步:schema 加进数组、run_tool 加个分支——不用碰 run_tools()、对话循环这些已经写好的部分。
测试一个需要连续调用三个工具的复杂请求:「Set a reminder to go to the doctor. The appointment is in 100 days」,Claude 会自动依序:
- 用
get_current_datetime拿到今天日期 - 用
add_duration_to_datetime加上 100 天 - 用
set_reminder建立提醒
三步全部串起来,最后输出算好的未来日期和提醒确认。
Batch Tool:哄 Claude 把独立调用打包并行
Claude 原生就有能力一次触发多个工具,但常常不会主动这么做。比如问「March 12th, 2025 + 50 days 是几号?还有 +100 days 呢?」,这两个计算彼此互不依赖,理论上该并行算,但 Claude 经常还是一个一个顺序处理,效率打折扣。
解法:写一个叫 batch_tool 的工具,它的工作就是「一次触发好几个别的工具」——Claude 只需要调用这一个工具,参数里塞一串子调用列表:
batch_tool_schema = {
"toolSpec": {
"name": "batch_tool",
"description": "Invoke multiple other tool calls simultaneously",
"inputSchema": {
"json": {
"type": "object",
"properties": {
"invocations": {
"type": "array",
"description": "The tool calls to invoke",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name of the tool to invoke"
},
"arguments": {
"type": "string",
"description": "The arguments to the tool, encoded as a JSON string"
}
},
"required": ["name", "arguments"]
}
}
},
"required": ["invocations"]
}
}
}
}
run_batch() 把 invocations 列表里的每一项,逐个转成实际的工具调用:
def run_batch(tool_input):
batch_output = []
for invocation in tool_input["invocations"]:
tool_name = invocation["name"]
args = json.loads(invocation["arguments"])
tool_output = run_tool(tool_name, args)
batch_output.append({"tool_name": tool_name, "output": tool_output})
return batch_output
接进 run_tool:
elif tool_name == "batch_tool":
return run_batch(tool_input)
⚠️ 跟别的工具不一样,这里不要用 展开 tool_input**——batch_tool 要处理的是整个未拆解的输入结构本身,不是单一函数的参数。
用了 batch_tool 之后,同样那句两个日期计算的问题,Claude 会改成只发一次 batch_tool 调用,把两个日期计算当成子调用塞在里面一起并行处理,不再是两次分开的顺序调用。
用工具抽取结构化数据:比 prefill 更可靠
EP01 讲过用 prefill + stop sequence 拿干净 JSON,但更可靠的做法是:把想要的数据结构写成一个工具的输入参数,逼 Claude「调用」这个工具,数据直接从 tool call 的参数里拿,不用自己 parse 文字。
📌 两种做法各有定位:prompt 式(prefill + stop sequence)设置快,适合简单场景;工具式更可靠,适合要求高一致性的场景——两个都学起来,可以按情况选。
流程五步:
- 写一份描述目标数据结构的 JSON schema
- 拿这份 schema 当输入规格,做成一个工具
- 把数据和这个工具 schema 一起发给 Claude
- 用
toolChoice强制 Claude 一定要调用这个工具 - 从 tool call 的参数里直接拿到结构化数据
toolChoice 三种写法:
{"toolChoice": {"auto": {}}} # Claude 自行判断要不要用工具(默认)
{"toolChoice": {"any": {}}} # Claude 一定要用某个工具,但自己选用哪个
{"toolChoice": {"tool": {"name": "tool-name"}}} # 强制 Claude 用指定的这个工具
抽取结构化数据要用第三种,逼 Claude 一定调用你指定的抽取工具。
实例:从文章里抽取标题/作者/主题
article_details_schema = {
"toolSpec": {
"name": "article_details",
"description": "Extracts key information from an article",
"inputSchema": {
"json": {
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "The title of the article"
},
"author": {
"type": "string",
"description": "The author's name"
},
"topics": {
"type": "array",
"items": {"type": "string"},
"description": "List of key topics mentioned"
}
},
"required": ["title", "author", "topics"]
}
}
}
}
messages = []
add_user_message(messages, f"""
Analyze the article below and extract key data. Then call the article_details tool.
<article_text>
{article_text}
</article_text>
""")
result = chat(messages, tools=[article_details_schema], tool_choice="article_details")
💡 课程封装的 chat() 帮助函数直接接受工具名字符串当 tool_choice 参数,不用自己手动拼 toolChoice 字典。
好处:比 prompt 式抽取可靠得多;结构保证跟 schema 一致;不需要 prefill 或 stop sequence;schema 本身自带校验。代价是要多写一份详细 schema、多处理 tool response 的结构,换来的是稳定、可靠的结构化输出。
灵活抽取:一个通用 to_json 工具
📌 每个任务都写一份详细 schema 很花时间。折衷做法:只做一个通用的 to_json 工具,接受任意 object 结构,真正要什么字段写在 prompt 里,不是写进 schema 里。
代价:效果不如专门写的 schema 精准,但设置量小很多,换来的还是高质量的 JSON 输出。
Prompt 示例:
Analyze the article below and extract key data. Then call the to_json tool.
<article_text>
{result["text"]}
</article_text>
When you call to_json, pass in the following structure:
{
"title": str # title of the article,
"author": str # author of the article,
"topics": List[str] # List of topics mentioned in the article
}
调用方式一样是强制指定工具:
flexible_result = chat(messages, tools=[to_json_schema], tool_choice="to_json")
💡 要改结构(比如加一个 "num_topics": int)只要改 prompt 里那段说明,完全不用碰 schema。
方法怎么选:专用 Schema vs 通用 to_json
| 场景 | 适合用 |
|---|---|
| 快速原型/实验 | 通用 to_json |
| 简单的数据抽取任务 | 通用 to_json |
| 数据需求常常在变 | 通用 to_json |
| 生产环境的关键抽取任务 | 专用 schema |
| 复杂的嵌套数据结构 | 专用 schema |
| 要求最高准确度 | 专用 schema |
课程的总结是:灵活方案能用十分之一的设置工作量,换来九成的输出质量。
Text Editor Tool:内建工具,只用写「怎么执行」
跟前面自己定义的工具不一样,Text Editor Tool 的 schema 是 Claude 内建的,不用自己写 schema——只需要写函数去实际处理 Claude 发来的 view/edit/create 请求。
这个工具让 Claude 能像工程师一样操作文件系统:
- 查看文件或目录内容
- 查看文件里指定的行数范围
- 替换文件里的文字
- 创建新文件
- 在指定行插入文字
设置工具版本(要跟你用的模型对上):
# Claude 4 及之后的模型
text_editor = "text_editor_20250728"
# 更早的 Claude 模型
text_editor = "text_editor_20250124"
⚠️ 更早版本的工具名是 str_replace_editor,还多一个 undo_edit 命令;Claude 4+ 版本没有 undo,工具名也不一样。
处理 Claude 发来的命令,要覆盖四种:view、str_replace、create、insert:
def run_tool(tool_name, tool_input):
if tool_name == "str_replace_based_edit_tool":
command = tool_input.get("command", "")
if command == "view":
path = tool_input.get("path", "")
return text_editor_tool.view(path)
elif command == "str_replace":
path = tool_input.get("path", "")
old_str = tool_input.get("old_str", "")
new_str = tool_input.get("new_str", "")
return text_editor_tool.str_replace(path, old_str, new_str)
# ... 处理其他命令
实例:请 Claude 分析 ./main.py,Claude 会发一个 {"command": "view", "path": "./main.py"} 的工具请求,你的 server 用 TextEditorTool 类读文件内容回传,Claude 再基于拿到的代码给出分析。
更进一步,像是「在 main.py 里写一个算圆周率到小数点后 5 位的函数,再建一个 test.py 测试它」这种请求,Claude 会自己读现有文件、加新函数、建测试文件、写完整测试——相当于一个内建 AI 功能的代码编辑器后端。
常见错误
- ❌ 给
run_batch传参数时用**tool_input展开——batch_tool要处理的是整个输入结构,不能像其他工具那样展开 - ❌ 想要结构化输出时只靠 prompt 式的 prefill + stop sequence——遇到要求高一致性的场景,改用「工具抽取 + 强制 toolChoice」更可靠
- ❌ 每个抽取任务都从头写一份详细 schema——先评估是不是用通用
to_json+ prompt 描述就够了,能省下大量设置时间 - ❌ 用错 Text Editor Tool 的版本号——模型版本和工具版本要对上,旧模型的工具名(
str_replace_editor)跟新模型(str_replace_based_edit_tool)不一样,命令支持也不同
Sources
官方文档:
- Claude with Amazon Bedrock(Claude Academy 课程)— https://academy.claude.com/courses/claude-with-amazon-bedrock