跳到主要内容

工具使用 / 函数调用

进阶

工具使用 让 Claude 调用 你 定义的函数——搜索、计算器、你的数据库、任意 API——并使用其结果。它是每一个 智能体 的基础。

大多数工具使用教程讲完四步循环就结束了。循环是最容易的部分。真正让生产环境集成崩掉的东西范围更窄、也更少被讨论:会返回 400 的消息形态、能让 Claude 自我恢复而不是道歉的那个错误字段,以及在最新的 Claude 模型上强制调用工具已经不再有效这一事实。本页把这些全部讲清。

What you'll learn
  • 四步智能体循环是如何工作的,从工具定义到最终答案
  • 如何写一个工具定义——以及如何写出真正能引导 Claude 何时调用它的描述
  • 会返回 400 的三条消息形态规则,以及告诉你违反了哪一条的错误文本
  • 为什么 tool_choice "any" 和 "tool" 现在在最新模型上会失败,以及两种替代方案
  • 如何用 strict: true 保证工具输入符合 schema
  • 为什么工具结果是提示词注入最可能触达你的智能体的地方

循环​

工具使用是一场对话,而非单次调用。你向 Claude 递上一份工具菜单;Claude 挑选其中一个并暂停;你执行它并回报结果;Claude 将结果融入它的答案中——按需重复。

Guided walkthrough1 of 4
  1. 你包含一个工具定义列表——每个都带有名称、描述和 JSON-Schema 输入。

注意第 2 步里的复数:Claude 可以在一次响应中返回多个 tool_use 块。请按每个块的 type 字段分发,而不要假设 content[0]——响应通常以一个 text 块开头,而在最新模型上它可能以 thinking 块开头。

定义一个工具​

一个工具定义就是一个名称、一段通俗语言的描述,以及一份用于输入的 JSON Schema。

字段是否必需说明
name是必须匹配正则 ^[a-zA-Z0-9_-]{1,128}$
description是它做什么、何时使用、何时不该使用。目标至少 3–4 句
input_schema是一个描述参数的 JSON Schema 对象
input_examples否一个示例输入对象的数组,每个都对该 schema 有效
strict否设为 true 以强制输入符合 schema——见下文
cache_control否跨回合缓存该定义(参见 提示词缓存)

get_weather 工具 + 首次调用(Python)

tools = [{
  "name": "get_weather",
  "description": (
      "Get the current weather for a city. Use this when the user asks about "
      "present conditions or temperature in a named place. Returns temperature "
      "in the requested unit plus a one-word sky condition. It does NOT return "
      "forecasts — use get_forecast for anything in the future."
  ),
  "input_schema": {
      "type": "object",
      "properties": {
          "city": {"type": "string", "description": "City and region, e.g. 'Rome, IT'"},
          "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
      },
      "required": ["city"],
      "additionalProperties": False,
  },
}]

msg = client.messages.create(
  model="claude-opus-5-5", max_tokens=1024,
  tools=tools,
  messages=[{"role": "user", "content": "What's the weather in Rome?"}],
)

if msg.stop_reason == "tool_use":
  for block in msg.content:            # iterate — never index content[0]
      if block.type == "tool_use":
          result = run_tool(block.name, block.input)
          # send back a tool_result carrying block.id

描述就是提示词​

description 字段是对工具使用质量影响最大的杠杆——它是给模型读的,不是给你的同事读的。Anthropic 自己的指南对什么才算好描述毫不含糊:

  • 说明何时不该用它。 大多数选错工具的情况,都是某个工具被叫去做了相邻工具负责的活。
  • 合并而非增殖。 与其提供 create_pr、review_pr、merge_pr,不如发布一个带 action 参数的工具。工具更少、更宽泛,就能减少选择上的歧义。
  • 跨服务加命名空间。 一旦你的工具面变大,github_list_prs 和 slack_send_message 就胜过 list_prs 和 send_message。
  • 返回高信噪比的结果。 稳定、语义化的标识符(slug、UUID),以及只包含 Claude 挑选下一步所需的字段。臃肿的工具输出会烧掉上下文,并把信号埋起来。
  • 对于难缠的输入,加上 input_examples。 经过 schema 校验的示例,简单对象每个大约花费 20–50 个 token,复杂嵌套对象则是 100–200 个,而一个无效示例会返回 400。

处理响应:会返回 400 的三条规则​

这里是正常运行的代码通常会崩掉的地方。tool_result 是一条普通的 user 消息,但 API 会强制执行三条位置规则。

Guided walkthrough1 of 3
  1. 在助手的 tool_use 消息和你的 tool_result 消息之间不能夹着任何消息。违反这一条,API 就会报告:tool_use ids were found without tool_result blocks immediately after。
// ❌ 400 — text before tool_result
{"role": "user", "content": [
{"type": "text", "text": "Here are the results:"},
{"type": "tool_result", "tool_use_id": "toolu_01", "content": "15 degrees"}
]}

// ✅ correct — results first, text after (client tools only)
{"role": "user", "content": [
{"type": "tool_result", "tool_use_id": "toolu_01", "content": "15 degrees"},
{"type": "text", "text": "What should I do next?"}
]}

一个 tool_result 的 content 可以是一个纯字符串,也可以是一个由 text、image、document 或 search_result 块组成的列表。对于不返回任何内容的工具,它也可以整体省略。

把失败作为结果返回,而不是作为异常​

当一个工具抛出异常时,不要中断循环——用 is_error 把失败交回去:

{"role": "user", "content": [{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "Rate limit exceeded. Retry after 60 seconds.",
"is_error": true
}]}

为那个必须决定下一步做什么的读者来写这条消息。"failed" 什么也没告诉 Claude;"Rate limit exceeded. Retry after 60 seconds." 则告诉它应该等待,而不是立刻重试。当一次工具调用无效或缺少参数时,Claude 通常会带着修正重试两到三次,然后才放弃并向用户道歉——所以一条有指导性的错误,往往就是恢复与死路之间的差别。

保证输入符合 schema:strict: true​

在一个工具定义上设置 strict: true,会把模型的 token 采样约束到符合 schema 的输出(语法约束采样)。此后每个 tool_use 块中的 input 都可被证明匹配你的 input_schema,而 name 永远是一个真实存在的工具——不会在你需要 2 的地方出现 "2",不会缺少必填字段,也不需要一层校验并重试的逻辑。

严格的工具定义

{
"name": "search_flights",
"description": "Search bookable flights for a route and date.",
"strict": true,
"input_schema": {
  "type": "object",
  "properties": {
    "destination": {"type": "string"},
    "departure_date": {"type": "string", "format": "date"},
    "passengers": {"type": "integer", "enum": [1, 2, 3, 4, 5, 6]}
  },
  "required": ["destination", "departure_date"],
  "additionalProperties": false
}
}

在你到处打开它之前,有三条值得知道的注意事项:

  • schema 必须留在受支持的 JSON Schema 子集之内——和 结构化输出 使用的是同一个子集。
  • 计算机使用和浏览器使用的工具集条目拒绝 strict: true;在任一者上设置它的请求都会被拒绝。
  • 编译后的 schema 与消息内容分开缓存,最长保留 24 小时。Anthropic 的指南很明确:不要把受保护健康信息放进 schema——不要放在属性名、enum 值、const 值或 pattern 正则里。PHI 属于消息内容,而消息内容带有缓存 schema 所不具备的 HIPAA 保护措施。

tool_choice——以及为什么现在强制调用工具会失败​

tool_choice 控制 Claude 可以、必须 还是 不可以 调用工具。它接受四个值:

值含义
auto由 Claude 决定。只要存在 tools 就是默认值
anyClaude 必须调用你的某个工具,但由它挑选哪一个
toolClaude 必须调用指定名称的那个工具
noneClaude 不可以调用任何工具。没有 tools 时的默认值

下面这部分是目前所有其他工具使用指南都漏掉的。any 和 tool 并非被普遍支持,而在最新模型上它们会返回 400。

模型或设置限制改用
手动扩展思考(thinking 类型为 enabled)any 和 tool 报错auto 或 none。自适应思考本身不会阻止强制工具使用
Claude Opus 5.5、Sonnet 5.5、Fable 5.1、Mythos 5.1无论思考设置如何,any 和 tool 都返回 400auto 加上 strict: true,或者 结构化输出。none 仍然可用

错误信息很明确:

tool_choice: type "tool" and "any" are not supported for this model.

同样的校验也适用于 token 计数端点,所以一个通过了 token 计数的请求不会在之后给你意外。

应该改用什么​

把两种动机拆开,迁移路径就很明显了,因为强制工具使用其实在干两件互不相关的活:

  • 「我需要符合 schema 的 JSON。」 这其实从来就不是为了强制一次调用。把 tool_choice 保持为 auto 并设置 strict: true;或者如果你想要的是一个响应而不是一次调用,就把 schema 搬到 结构化输出。
  • 「我需要它去调用工具,而不是跟我闲聊。」 那就在提示词里说清楚。点明该工具适用的场合——「任何关于当前状况的问题都使用 get_weather」——仍然能引导 auto 挑选哪个工具。

放弃强制工具使用还有一个额外好处:any 和 tool 的实现方式是预填充助手消息,这会压制 tool_use 块之前的任何自然语言前言。在 auto 之下,Claude 可以解释它接下来要做什么——而在面向用户的智能体里,这通常正是你想要的。

缓存的坑

在对话中途更改 tool_choice 会让已缓存的消息块失效。工具定义和系统提示词仍然保持缓存,但消息内容必须重新处理。如果你之前是按回合切换 tool_choice,那它一直在悄悄让你多花钱——参见 提示词缓存。

客户端工具 vs. 服务端工具​

并不是每个工具都需要在你的代码库里有一个处理器。

  • 客户端工具——你自己的。你定义 schema,你执行,你返回一个 tool_result。就是上面那个循环。
  • Anthropic 预置 schema 的客户端工具——bash、文本编辑器、记忆。schema 是预先定义好的,但仍然由你的应用来执行它们。
  • 服务端工具——网页搜索、网页抓取、代码执行、工具搜索。它们在 Anthropic 的基础设施上运行,你这边不需要处理器代码,错误也会被透明地处理:你永远不要为服务端工具返回 is_error。
  • 客户端工具集——计算机使用和浏览器使用是一个没有 name 的单一条目,它声明了一组固定的成员工具。它们的 tool_use 块会多带一个 toolset_name 字段("computer" 或 "browser"),而 name 是被调用的那个成员,比如 screenshot 或 navigate。要同时按这两个字段分发,并在结果上把 toolset_name 回传——省略它的成员结果会被拒绝。

常见错误​

  • 索引 content[0],而不是遍历并按 type 分支。响应常常以一个 text 块开头,在最新模型上则以 thinking 块开头。
  • 每次响应只处理一个 tool_use。 Claude 可以在一个回合里调用多个工具;只回答第一个而丢掉其余的循环,会留下没有结果的 tool_use id,这在下一次请求时就是一个 400。
  • 把你的说明文字放在结果之前。 tool_result 块永远排在最前面。
  • 吞掉工具异常。 你的处理器里抛出的异常会结束循环;一个 is_error 结果则让 Claude 得以适应。
  • 出于习惯去用 tool_choice: "any"。 在当前的旗舰模型上这是一个 400——而 strict: true 本来就是更好的答案。
  • 把工具输出当作可信内容。 见下文。
工具结果是你的智能体最大的注入面

工具结果经常携带你无法控制的内容:网页、收到的邮件、用户上传的文件、第三方 API。任何能影响其中之一的攻击者,都可以嵌入针对你的模型的指令——这就是间接提示词注入。请把不可信内容保持在 tool_result 块内部,绝不要把它拼接进 system 提示词或一个普通的用户 text 块,并阅读 提示词注入 和 保护智能体与工具安全。能采取真实行动的工具同样继承了一套安全模型:最小权限,以及对任何不可逆的操作保持人在回路。

工具使用词汇
按 Enter 或空格键翻转卡片。使用左右方向键在卡片之间切换。已显示术语。
1 / 6

自我检测

0/4
  1. 你在 claude-opus-5-5 上把 tool_choice 设为 {"type": "tool", "name": "get_weather"}。会发生什么?
  2. 你的用户消息里包含一个解释结果的 text 块,后面跟着两个 tool_result 块。API 会怎么做?
  3. 你定义的某个工具在运行时抛出了一个连接错误。推荐的做法是什么?
  4. 为什么不可信的网页内容应该留在 tool_result 块里,而不是被粘贴进系统提示词?
Key takeaways
  • 工具使用是一个循环:发送工具定义,Claude 返回一个或多个 tool_use 块并停止,你执行并返回 tool_result,Claude 继续。请按 type 遍历内容块——绝不要索引 content[0]。
  • 三条位置规则造成了大多数 400:结果必须紧跟在它们的 tool_use 之后,tool_result 块在 content 数组中排在最前面,而一个未解决的服务端工具调用意味着只能发结果。
  • 强制工具使用在最新模型上已经没了:tool_choice "any" 和 "tool" 在 Opus 5.5、Sonnet 5.5、Fable 5.1 和 Mythos 5.1 上返回 400。请使用 auto 加 strict: true,或者结构化输出,并从提示词里引导工具选择。
  • strict: true 通过语法约束采样保证输入符合 schema——但不要把 PHI 放进 schema,也不要在计算机/浏览器工具集条目上设置它。
  • 把失败作为带 is_error 和有指导性消息的 tool_result 返回,让 Claude 能够调整而不是道歉。
  • 工具结果是间接提示词注入触达你的智能体最可能的路径——把不可信内容保持在其中,套用最小权限,并对不可逆的操作加上人工把关。

下一步​