Tool Use
Tool Use 是 Anthropic 对 Function Calling 的称呼。你声明一份模型可以调用的工具列表,发出 user 消息,Claude 要么直接用文本回答,要么返回一个结构化的 tool_use 块描述它想让你执行的调用。你执行工具,把结果送回去,对话继续。
基本形状
一个工具定义有三个字段:名字、自然语言描述、输入 JSON Schema。
"tools": [
{
"name": "get_weather",
"description": "获取某城市的当前天气",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string"},
"units": {"type": "string", "enum": ["c", "f"]}
},
"required": ["city"]
}
}
]
描述很重要。Claude 看不到你的函数体,它只能看到 description 和 schema,然后判断是否调用、如何调用。把 description 当成 prompt 的一部分来写。
当 Claude 决定调用工具时,响应里出现 tool_use 内容块而不是文本块,stop_reason 是 "tool_use":
{
"stop_reason": "tool_use",
"content": [
{
"type": "tool_use",
"id": "toolu_IbId2k5Cs4dpj5vgdvJJDA",
"name": "get_weather",
"input": {"city": "Tokyo", "units": "c"}
}
]
}
你执行 get_weather("Tokyo", "c"),把字符串结果作为 tool_result 块追加进对话。Claude 拿到这份数据后,才能给出最终答复。
多轮 round trip
这是最容易出错的部分。一个用户提问,在你的代码与 API 之间可能往返多次。每往返一次,对话历史就增长一段。
// 第 1 轮:用户提问
[
{"role": "user", "content": "东京天气怎么样?"}
]
// 第 2 轮:Claude 想调用工具
[
{"role": "user", "content": "东京天气怎么样?"},
{"role": "assistant", "content": [
{"type": "tool_use", "id": "toolu_X", "name": "get_weather",
"input": {"city": "Tokyo", "units": "c"}}
]}
]
// 第 3 轮:你回填结果
[
{"role": "user", "content": "东京天气怎么样?"},
{"role": "assistant", "content": [
{"type": "tool_use", "id": "toolu_X", "name": "get_weather",
"input": {"city": "Tokyo", "units": "c"}}
]},
{"role": "user", "content": [
{"type": "tool_result", "tool_use_id": "toolu_X",
"content": "晴,22°C,微风。"}
]}
]
// 第 4 轮:Claude 给出最终答复
"东京目前晴朗,大约 22°C,有微风。"
两条规则:tool_result 里的 tool_use_id 必须与 Claude 返回的 id 完全一致;tool_result 块要放在 user role 的消息里,即使它是你的代码产出的。在 Claude 的世界观里,你的代码是用户侧的一员。
模型也可以在一次响应里同时发出多个 tool_use 块(并行工具调用)。你的代码应该全部执行,然后把所有对应的 tool_result 块塞进同一条 user 消息一起送回。
三种 tool_choice 模式
tool_choice 字段控制 Claude 调用工具的积极程度。一共四个取值,常用的是其中三个:
| 取值 | 行为 | 使用场景 |
|---|---|---|
{"type": "auto"} | 默认。Claude 自主决定每轮是用工具还是直接答文本。 | 大多数助手。模型在这件事上做得不错。 |
{"type": "any"} | Claude 必须从给定工具中选一个,不能直接答文本。 | 结构化输出。"工具" 就是你的输出 schema,模型被强制按 schema 填。 |
{"type": "tool", "name": "x"} | Claude 必须调用指定名字的工具。 | 调用本身已确定,你只是用模型来填参数。 |
{"type": "none"} | 禁用工具。 | 对话中途禁用工具但保留历史。 |
any 模式是把 Anthropic Tool Use 转成可靠结构化输出的招数。定义一个工具,schema 就是你想要的 JSON 形状,把 tool_choice 设成 any,模型就被迫输出一个符合 schema 的 tool_use 块。
流式中的 tool_use
流式工具调用能用,但 JSON 是分片到达的,客户端必须自己拼起来。涉及的 SSE 事件:
event: content_block_start
data: {"type": "content_block_start", "index": 0,
"content_block": {"type": "tool_use", "id": "toolu_X", "name": "get_weather", "input": {}}}
event: content_block_delta
data: {"type": "content_block_delta", "index": 0,
"delta": {"type": "input_json_delta", "partial_json": "{\"city\":"}}
event: content_block_delta
data: {"type": "content_block_delta", "index": 0,
"delta": {"type": "input_json_delta", "partial_json": " \"Tokyo\"}"}}
event: content_block_stop
data: {"type": "content_block_stop", "index": 0}
三件事要记住:
content_block_start里的input是空对象。不要解析它,等 delta。- 每个
input_json_delta携带的是 JSON 字符串片段。把片段拼起来,在content_block_stop时整体解析。 - 如果模型同一响应里既输出文本又调用工具,会出现不同
index的内容块交错到达。不要假设一个块结束后另一个才开始。
因为这种复杂性,大多数客户端会缓冲整个 tool_use 块直到 content_block_stop 才动手。流式工具调用主要用来做 "模型正在准备调用 X" 这种 UX 提示,而不是提前解析 input。
常见坑
tool_use_id 不匹配。带错 ID 的 tool_result 直接 400,不会被静默忽略。原样回写。
漏掉 assistant 轮。对话里 user 的 tool_result 之前必须有 assistant 的 tool_use。漏掉就是 400。
在 tool_result.content 里塞 JSON。这个字段是文本。结构化数据请序列化成字符串再传,模型会自己解析回去。
Schema 漂移。如果 input_schema 标了 required 但 Claude 漏了字段,问题几乎都在 description 写得不够明确。先收紧 description,再考虑改 schema。
相关链接
POST /v1/messages— 请求参考,包括tools和tool_choice- Prompt Caching — 缓存大型工具目录
- 透明转发 — 工具块为什么字节级直通
- Claude Tool Use 透过网关 — 生产实战