原文:03-advanced-tool-use.md 来源:https://www.anthropic.com/engineering/advanced-tool-use
Claude 开发者平台高级工具使用
发布于 2025 年 11 月 24 日
我们新增了三项 beta 功能,让 Claude 能够动态地发现、学习并执行工具。
AI Agent 的未来是模型能在数百乃至数千个工具之间无缝工作。比如一个 IDE 助手集成 git 操作、文件操作、包管理器、测试框架和部署管线;一个运维协调员同时连接 Slack、GitHub、Google Drive、Jira、公司数据库和数十个 MCP 服务器。
要构建有效 Agent,它们需要在不预先把每个定义都塞进上下文的情况下使用无限工具库。我们之前关于"用 MCP 做代码执行"的文章讨论过:工具结果和定义有时在 Agent 读取请求之前就消耗了 50,000+ token。Agent 应该按需发现和加载工具,只保留与当前任务相关的部分。
Agent 还需要从代码中调用工具的能力。使用自然语言工具调用时,每次调用都需要一次完整推理,中间结果不论是否有用都会堆积在上下文中。代码天然 适合做编排逻辑(循环、条件、数据变换)。Agent 需要根据任务在代码执行和推理之间灵活选择。
Agent 还需要从示例中学习正确的工具用法,而不仅仅从 schema 定义。JSON Schema 定义了什么是结构上有效的,但无法表达使用模式:什么时候带可选参数、哪些组合合理、API 期望什么约定。
今天我们发布三项功能:
- 工具搜索工具(Tool Search Tool):让 Claude 通过搜索访问数千个工具,不消耗上下文窗口
- 程序化工具调用(Programmatic Tool Calling, PTC):让 Claude 在代码执行环境中调用工具,降低对模型上下文窗口的影响
- 工具使用示例(Tool Use Examples):提供演示如何有效使用某个工具的通用标准
在内部测试中,这些功能让我们做出了过去用传统工具使用模式做不到的东西。例如 Claude for Excel 用程序化工具调用读取和修改数千行的电子表格而不耗尽模型上下文窗口。
工具搜索工具(Tool Search Tool)
挑战
MCP 工具定义提供了重要的上下文,但随着接入更多服务器,这些 token 会快速累积。看一个五服务器的设置:
- GitHub:35 个工具(约 26K token)
- Slack:11 个工具(约 21K token)
- Sentry:5 个工具(约 3K token)
- Grafana:5 个工具(约 3K token)
- Splunk:2 个工具(约 2K token)
那是 58 个工具消耗了约 55K token——对话还没开始就消耗了。再加上 Jira(光它就用约 17K token),很快就接近 100K+ token 的开销。Anthropic 内部曾见过工具定义在优化前消耗 134K token。
但 token 成本并不是唯一问题。最常见的失败是工具选择错误和参数错误——尤其是工具名相似时,比如 notification-send-user 和 notification-send-channel。
解决方案
不在前期加载所有工具定义,Tool Search Tool 按需发现工具。Claude 只看到当前任务真正需要的工具。
传统方法:
- 所有工具定义前期加载(50+ MCP 工具约 72K token)
- 对话历史和系统提示与剩余空间竞争
- 总上下文消耗:开工前约 77K token
用 Tool Search Tool:
- 只加载 Tool Search Tool 本身(约 500 token)
- 工具按需发现(3-5 个相关工具,约 3K token)
- 总上下文消耗:约 8.7K token,保留 95% 的上下文窗口
这是 token 使用减少 85%,同时保持对完整工具库的访问能力。在大工具库的 MCP 评估中,内部测试显示准确率显著提升:Opus 4 从 49% 提升到 74%,Opus 4.5 从 79.5% 提升到 88.1%。
工作机制
你把所有工具定义提供给 API,但用 defer_loading: true 标记可按需发现的工具。被推迟的工具初始不会加载到 Claude 上下文里。Claude 只看到 Tool Search Tool 本身,加上任何 defer_loading: false 的工具(你最常用、最关键的工具)。
当 Claude 需要特定能力时,它搜索相关工具。Tool Search Tool 返回匹配工具的引用,引用在 Claude 上下文中展开为完整定义。
例如 Claude 需要与 GitHub 交互时,搜索 "github",只有 github.createPullRequest 和 github.listIssues 被加载——而不是来自 Slack、Jira、Google Drive 的其他 50+ 个工具。
Prompt 缓存说明:Tool Search Tool 不会破坏 prompt 缓存,因为被推迟的工具完全被排除在初始 prompt 之外。它们只在 Claude 搜索后才被加入上下文,所以系统提示和核心工具定义保持可缓存。
实现示例:
{
"tools": [
{"type": "tool_search_tool_regex_20251119", "name": "tool_search_tool_regex"},
{
"name": "github.createPullRequest",
"description": "Create a pull request",
"input_schema": {...},
"defer_loading": true
}
]
}
对于 MCP 服务器,可以推迟加载整个服务器同时保留特定高频工具:
{
"type": "mcp_toolset",
"mcp_server_name": "google-drive",
"default_config": {"defer_loading": true},
"configs": {
"search_files": {"defer_loading": false}
}
}
Claude 开发者平台开箱即用提供基于 regex 和 BM25 的搜索工具,你也可以用 embedding 等策略实现自定义搜索工具。
何时使用
适合:
- 工具定义消耗 >10K token
- 出现工具选择准确率问题
- 构建多服务器 MCP 系统
- 10+ 个工具可用
不太适合:
- 小工具库(<10 个工具)
- 每个会话都频繁使用所有工具
- 工具定义紧凑
程序化工具调用(Programmatic Tool Calling)
挑战
随着工作流变复杂,传统工具调用产生两个根本问题:
-
中间结果污染上下文:Claude 分析 10MB 日志找错误模式时,整个文件进入上下文,即使 Claude 只需要错误频率摘要。跨多张表抓客户数据时,每条记录都会累积在上下文中,无论是否相关。这些中间结果消耗大量 token 预算,可能把重要信息挤出上下文窗口。
-
推理开销与人工综合:每次工具调用都需要一次完整模型推理。收到结果后,Claude 必须"目测"数据提取相关信息、推理各部分如何组合、决定下一步——全部通过自然语言处理。一个五工具流意味着五次推理加上 Claude 解析每个结果、对比数值、综合结论。慢且易错。
解决方案
PTC 让 Claude 通过代码而非逐次 API 往返来编排工具。Claude 不再一次请求一个工具,等结果回到上下文,而是写代码调用多个工具、处理它们的输出、控制什么信息真正进入上下文窗口。
Claude 擅长写代码——让它用 Python 表达编排逻辑而非通过自然语言工具调用,能获得更可靠、精确的控制流。循环、条件、数据变换、错误处理在代码中显式存在,而非隐含在 Claude 的推理里。
示例:预算合规检查
业务任务:"Q3 出差预算超支的团队成员有哪些?"
可用工具:
get_team_members(department)— 返回团队成员列表get_expenses(user_id, quarter)— 返回某用户的支出条目get_budget_by_level(level)— 返回某员工级别的预算上限
传统方法:
- 取团队成员 → 20 人
- 对每个人取 Q3 支出 → 20 次工具调用,每次返回 50-100 条目
- 取员工级别预算上限
- 全部进入 Claude 上下文:2000+ 条支出(50KB+)
- Claude 手动对每个人求和、查预算、对比
- 大量模型往返、显著上下文消耗
用 PTC:
Claude 写一段 Python 脚本编排整个流程,脚本在 Code Execution 工具(沙箱环境)中运行,需要工具结果时暂停。当你通过 API 返回工具结果时,结果由脚本处理,而非模型消耗。脚本继续执行,Claude 只看到最终输出。
Claude 的编排代码大致这样:
team = await get_team_members("engineering")
levels = list(set(m["level"] for m in team))
budget_results = await asyncio.gather(*[
get_budget_by_level(level) for level in levels
])
budgets = {level: budget for level, budget in zip(levels, budget_results)}
expenses = await asyncio.gather(*[
get_expenses(m["id"], "Q3") for m in team
])
exceeded = []
for member, exp in zip(team, expenses):
budget = budgets[member["level"]]
total = sum(e["amount"] for e in exp)
if total > budget["travel_limit"]:
exceeded.append({
"name": member["name"],
"spent": total,
"limit": budget["travel_limit"]
})
print(json.dumps(exceeded))
Claude 上下文只收到最终结果——超预算的两三个人。2000+ 条目、中间求和、预算查询都不影响 Claude 上下文,把消耗从 200KB 原始支出数据降到 1KB 结果。
效率收益:
- Token 节省:复杂研究任务平均使用从 43,588 降到 27,297,减少 37%