降低工具密集型工作流的成本
一个使用工具的智能体会在每次请求中重新发送所有内容。编程化工具调用和提示缓存分别降低了其成本的不同部分。
本视频中的演示录制于 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,因为这些计数会告诉您成本出在哪里。
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 部署中运行。
启用编程化工具调用需要四处改动。
- 将代码执行工具添加到您的
tools列表中。 - 标记脚本可以调用的工具。 为每个工具添加一个
allowed_callers字段,指定该代码执行工具的版本code_execution_20260120或更高版本(在新标签页中打开)。 - 将容器的 ID 回传。 从每个因工具调用而停止的响应中读取它,并随返回工具结果的请求一起发送。
- 描述每个工具的结果。 在描述中说明结果的形式,因为脚本需要解析它。以下是 Meridian 为
get_employee_hours所写的描述。
Returns one engineer's hours for the month as JSON: {"name": str, "billable_hours": float, "utilization": float 0-1}
以下是这些改动之后 Meridian 的循环。
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。脚本可以并行运行工具,因此一个响应中可以包含多个工具调用。
以下是用两种方式回答的同一个问题。
| 运行方式 | 请求数 | 工具调用数 | 输入令牌 | 令牌成本 |
|---|---|---|---|---|
| 经典循环 | 42 | 41 | 1,124,799 | 2.29 美元 |
| 编程化工具调用 | 3 | 41 | 28,486 | 0.0667 美元 |
两次运行使用了相同的问题、相同的工具和 claude-sonnet-5。成本使用的是截至 2026 年 9 月的价格(在新标签页中打开),代码执行时间与令牌分开计费。
编程化工具调用适用于彼此不相互依赖的查询。当 Claude 必须在每个结果上进行推理才能做出下一次工具调用时,它的帮助就很有限。
缓存重复出现的工具定义
借助提示缓存,API 会存储您请求中的前缀部分,即直到您标记的缓存点为止的部分。再次读取该前缀的成本要低得多。
Meridian 的第二个智能体有 14 个工具,其定义共有 3,305 个令牌,每个请求都要重新发送一遍。
在最后一个工具上放置 cache_control 即可标记缓存点,整个列表都会被缓存。 只需一行代码:
TOOLS[-1]["cache_control"] = {"type": "ephemeral"}在 Claude Sonnet 5 上,读取该前缀的成本是正常输入价格的十分之一(在新标签页中打开)。写入它的成本是正常输入价格的 1.25 倍。
默认情况下,缓存的前缀会保留五分钟,每次使用都会刷新该期限。对于间隔更长的请求,可在 cache_control 中添加 "ttl": "1h",将其保留一小时。写入一小时缓存的成本是正常输入价格的两倍(在新标签页中打开)。
Meridian 用同样的八个问题分别在启用缓存和不启用缓存的情况下运行。其总输入成本从 0.0554 美元降至 0.0154 美元。
借助自动缓存(在新标签页中打开),您只需在请求的顶层放置一个 cache_control 字段,API 就会为您放置缓存点。
启用缓存后,请留意以下三点:
- 更改任何工具定义,您就会失去整个缓存。
- 在 Sonnet 5 上,被缓存的前缀必须至少有 1,024 个令牌。如果更短,就不会缓存任何内容,也不会有任何错误提示。
- 启用缓存后,
input_tokens不包含缓存的前缀,因此一个请求的真实总量是input_tokens、cache_read_input_tokens和cache_creation_input_tokens三者相加。
将各功能组合使用到合适之处
按以下顺序添加它们。
- 先从缓存开始。 只需一行代码。
- 测试编程化工具调用。 用有无
allowed_callers两种方式运行您的一部分常规流量,然后比较每次运行计费的输入令牌数。
针对下面这两个 Meridian 请求,找出占据大部分输入令牌的那一部分,然后选出能降低它的功能。
关键要点
- 每个请求都会重新发送所有内容,因此您会为旧的工具结果反复付费。
- 每次响应返回时都打印
usage.input_tokens,这样您就知道请求中哪部分花费最多。 - 先从缓存开始。将
cache_control添加到您列表中的最后一个工具上。 - 当您的工具调用彼此不相互依赖时,使用代码执行工具和
allowed_callers设置编程化工具调用。在之后的每个请求中都要发送容器的 ID。