加载中

降低工具密集型工作流的成本

第 8 课15 分钟
加载中

降低工具密集型工作流的成本

摘要文字记录

一个使用工具的智能体会在每次请求中重新发送所有内容。编程化工具调用和提示缓存分别降低了其成本的不同部分。

本视频中的演示录制于 2026 年 9 月,运行在 Claude Sonnet 5(claude-sonnet-5)上。结果在较新的模型上可能会有所不同。

一个使用工具的智能体会在每次请求中重新发送所有内容,因此其成本会随着每次工具调用而增长。 每个请求都携带工具定义、系统提示、问题以及迄今为止的每一个工具结果。这些是它的输入令牌,而每一个都会被计费。

编程化工具调用和提示缓存都能降低这一成本。

Meridian 是一家虚构的拥有 40 名工程师的工程咨询公司。其智能体收到了一个问题:2026 年 6 月哪些工程师的利用率低于 70%?该智能体有两个工具。list_employees 返回员工名单,get_employee_hours 返回某一工程师的工时表。

回答这个问题让该智能体耗费了 42 次请求和 1,124,799 个输入令牌。整个运行过程在令牌上花费了 2.29 美元。

测量每次请求中的输入令牌

每次响应返回时都打印 usage.input_tokens,因为这些计数会告诉您成本出在哪里。

python
resp = client.messages.create(...)
print(resp.usage.input_tokens, "input tokens")

Meridian 的智能体运行着一个经典循环,每次响应都请求一次工具调用。以下是其中两次的计数。

请求携带的内容输入令牌
请求 1工具定义、系统提示和问题606
请求 37同上,外加此前 36 个工具结果47,905

请求 1 只携带每个请求都会发送的内容。后续请求则会加上工具调用及其结果。比较这两个数字,看看哪部分增长了。

让工具结果不进入 Claude 所读取的内容

通过编程化工具调用,Claude 编写一个调用您工具的脚本,代码执行工具会在一个容器中运行它。只有脚本打印出的内容才会到达 Claude。 Meridian 的 40 份工时表留在脚本那边,而 Claude 得到的是简短的打印输出。

请先检查您的工具和您的模型。 截至 2026 年 10 月,编程化工具调用不支持标记为 strict: true 的工具。它也不支持 disable_parallel_tool_use,即限制 Claude 在每次响应中只能调用一个工具的 tool_choice 设置。tool_choice 无法强制 Claude 从脚本中调用工具。

Claude Haiku 4.5 不支持此功能。该功能可在 Claude API、AWS 上的 Claude Platform 以及托管在 Anthropic 上的 Microsoft Foundry 部署中运行。

启用编程化工具调用需要四处改动。

  1. 将代码执行工具添加到您的 tools 列表中。
  2. 标记脚本可以调用的工具。 为每个工具添加一个 allowed_callers 字段,指定该代码执行工具的版本 code_execution_20260120 或更高版本。
  3. 将容器的 ID 回传。 从每个因工具调用而停止的响应中读取它,并随返回工具结果的请求一起发送。
  4. 描述每个工具的结果。 在描述中说明结果的形式,因为脚本需要解析它。以下是 Meridian 为 get_employee_hours 所写的描述。

Returns one engineer's hours for the month as JSON: {"name": str, "billable_hours": float, "utilization": float 0-1}

以下是这些改动之后 Meridian 的循环。

python
CODE_EXECUTION = "code_execution_20260120"
TOOLS = [
    {"type": CODE_EXECUTION, "name": "code_execution"},
    {**LIST_EMPLOYEES, "allowed_callers": [CODE_EXECUTION]},
    {**GET_EMPLOYEE_HOURS, "allowed_callers": [CODE_EXECUTION]},
]

messages = [{"role": "user", "content": QUESTION}]
container = None
while True:
    resp = client.messages.create(
        model="claude-sonnet-5", max_tokens=4096,
        tools=TOOLS, messages=messages, container=container,
    )
    print(resp.usage.input_tokens, "input tokens")
    if resp.stop_reason not in ("tool_use", "pause_turn"):
        break
    container = resp.container.id
    messages.append({"role": "assistant", "content": resp.content})
    if resp.stop_reason == "tool_use":
        messages.append({"role": "user", "content": run_tools(resp)})
print("".join(b.text for b in resp.content if b.type == "text"))
  • LIST_EMPLOYEES 和 GET_EMPLOYEE_HOURS 是 Meridian 的工具定义。
  • 您自己的 run_tools 负责运行每个工具调用并返回结果。
  • 在 pause_turn 时,循环会把响应发回去并继续。

当脚本调用工具时,响应的 stop_reason 为 "tool_use"。您的代码运行该工具,结果会发送给脚本,而不是 Claude。脚本可以并行运行工具,因此一个响应中可以包含多个工具调用。

以下是用两种方式回答的同一个问题。

运行方式请求数工具调用数输入令牌令牌成本
经典循环42411,124,7992.29 美元
编程化工具调用34128,4860.0667 美元

两次运行使用了相同的问题、相同的工具和 claude-sonnet-5。成本使用的是截至 2026 年 9 月的价格,代码执行时间与令牌分开计费。

编程化工具调用适用于彼此不相互依赖的查询。当 Claude 必须在每个结果上进行推理才能做出下一次工具调用时,它的帮助就很有限。

缓存重复出现的工具定义

借助提示缓存,API 会存储您请求中的前缀部分,即直到您标记的缓存点为止的部分。再次读取该前缀的成本要低得多。

Meridian 的第二个智能体有 14 个工具,其定义共有 3,305 个令牌,每个请求都要重新发送一遍。

在最后一个工具上放置 cache_control 即可标记缓存点,整个列表都会被缓存。 只需一行代码:

python
TOOLS[-1]["cache_control"] = {"type": "ephemeral"}

在 Claude Sonnet 5 上,读取该前缀的成本是正常输入价格的十分之一。写入它的成本是正常输入价格的 1.25 倍。

默认情况下,缓存的前缀会保留五分钟,每次使用都会刷新该期限。对于间隔更长的请求,可在 cache_control 中添加 "ttl": "1h",将其保留一小时。写入一小时缓存的成本是正常输入价格的两倍。

Meridian 用同样的八个问题分别在启用缓存和不启用缓存的情况下运行。其总输入成本从 0.0554 美元降至 0.0154 美元。

借助自动缓存,您只需在请求的顶层放置一个 cache_control 字段,API 就会为您放置缓存点。

启用缓存后,请留意以下三点:

  1. 更改任何工具定义,您就会失去整个缓存。
  2. 在 Sonnet 5 上,被缓存的前缀必须至少有 1,024 个令牌。如果更短,就不会缓存任何内容,也不会有任何错误提示。
  3. 启用缓存后,input_tokens 不包含缓存的前缀,因此一个请求的真实总量是 input_tokens、cache_read_input_tokens 和 cache_creation_input_tokens 三者相加。

将各功能组合使用到合适之处

按以下顺序添加它们。

  1. 先从缓存开始。 只需一行代码。
  2. 测试编程化工具调用。 用有无 allowed_callers 两种方式运行您的一部分常规流量,然后比较每次运行计费的输入令牌数。

针对下面这两个 Meridian 请求,找出占据大部分输入令牌的那一部分,然后选出能降低它的功能。

加载中

关键要点

  • 每个请求都会重新发送所有内容,因此您会为旧的工具结果反复付费。
  • 每次响应返回时都打印 usage.input_tokens,这样您就知道请求中哪部分花费最多。
  • 先从缓存开始。将 cache_control 添加到您列表中的最后一个工具上。
  • 当您的工具调用彼此不相互依赖时,使用代码执行工具和 allowed_callers 设置编程化工具调用。在之后的每个请求中都要发送容器的 ID。