BUZZ AI Gateway
文档 · 核心概念 · Tool Use

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}

三件事要记住:

  1. content_block_start 里的 input 是空对象。不要解析它,等 delta。
  2. 每个 input_json_delta 携带的是 JSON 字符串片段。把片段拼起来,在 content_block_stop 时整体解析。
  3. 如果模型同一响应里既输出文本又调用工具,会出现不同 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。

相关链接