Claude Code 的工具按需加载
用 Claude Code 写代码的时候,你大概不会注意到一件事:它注册了超过 40 个工具,但你让它读个文件、改几行代码,它只用到三四个。剩下那三十多个工具的定义,每个大约 500 个 token,全塞进上下文就是一万多 token 的固定开销。你只想改一行 CSS,却要为 WebSearch、NotebookEdit、CronCreate 这些完全用不到的工具买单。
这个问题在传统软件里有现成的解决方案:动态链接。程序启动时不加载所有共享库,等到第一次调用某个函数时再去加载。Claude Code 做了一件类似的事,只不过它管理的不是内存地址空间,而是 token 预算。
问题有多大
一个 LLM Agent 能力越强,注册的工具就越多。读文件、写文件、搜索、执行命令是基础,再加上笔记本编辑、定时任务、计划模式、Web 搜索、各种 MCP 扩展工具,轻松超过 40 个。每次 API 请求,所有工具的名称、描述、参数定义都要作为上下文发给模型。
对于一个 200K 的上下文窗口,光是按需工具的定义就占了将近 7%。这不是一次性成本,是每轮对话都要付的。而且 Anthropic 按 token 计费,即使缓存命中,这些工具定义也在占用缓存空间,影响其他内容的缓存效率。
更关键的是,工具定义占的 token 越多,留给实际对话和代码的空间就越少。在长会话里,这 7% 的固定开销可能就是触发一次额外压缩和不触发之间的差别。
常驻和按需两类工具
Claude Code 的解决方案是把工具分成两类。
第一类是常驻工具,每次请求都带完整定义:
这些是高频工具,几乎每个任务都会用到。注意 ToolSearch 本身也是常驻的,因为它是加载其他工具的入口,不能被按需加载,否则就没人能加载别的工具了。
第二类是按需工具,只发送名字,不发送完整定义。模型知道有这些工具存在,但看不到参数和用法。这类包括 WebSearch、TodoWrite、NotebookEdit、各种 Cron 工具、Plan 模式工具,以及所有 MCP 扩展工具。
判断标准很直觉:第一轮对话就可能需要的工具常驻,可能整个会话都用不到的工具按需。MCP 工具天然适合按需加载,它们是用户按项目配置的,数量不可控,有些项目接了十几个 MCP 服务器,工具定义轻松破万 token。
发现机制
模型怎么加载一个按需工具?通过调用 ToolSearch。
比如用户说"帮我搜一下 Claude Code 的更新日志",模型判断需要 WebSearch,但上下文里只有这个工具的名字,没有参数定义。模型先调用 ToolSearch,指定要加载 WebSearch。ToolSearch 返回一个特殊的引用标记,API 服务端看到这个标记后,把 WebSearch 的完整定义注入模型的上下文。模型随后就能正常调用 WebSearch 了。
整个流程大概是这样的:
| |
代价是多一轮 ToolSearch 调用,大约 200 token 的输入。但省下的是 30 多个工具定义的固定开销,一万多 token。只要不是每轮都在搜索新工具,净收益是正的。而且一旦加载过的工具会被记住,后续请求不需要重复加载。
ToolSearch 还支持模糊搜索。模型不确定工具叫什么名字的时候,可以用关键词查询,比如搜索"notebook jupyter"就能找到 NotebookEdit,搜索"slack send"就能找到对应的 MCP 工具。
压缩后不丢工具
上一篇聊过 Claude Code 的上下文压缩机制。压缩会把历史消息压成摘要,但之前加载过的工具引用也会跟着丢失。如果不处理,压缩完模型就忘了自己加载过 WebSearch,下次用的时候又要重新搜索一遍。
| |
Claude Code 用两层机制解决这个问题。第一层是在压缩边界消息里记录已加载的工具列表,压缩后系统扫描到这个边界就能恢复之前的状态。第二层是增量通知:每轮对话前,系统对比当前可用的按需工具和已通知过的工具,如果有变化(比如某个 MCP 服务器断开了,或者新的服务器连上了),就生成一条消息告诉模型。
两层配合的效果是:API 层面保证工具过滤正确,模型认知层面保证它知道哪些工具可用。压缩不会造成工具状态的断裂。
自适应启用
并不是所有场景都需要按需加载。如果项目只用了两三个 MCP 工具,总共不到 1500 token,多一轮 ToolSearch 调用反而浪费时间。
Claude Code 有一个自动模式:先计算所有可按需加载的工具定义总 token 数,如果超过上下文窗口的 10%,就启用按需加载;不超过就全部内联。小项目工具少,全部常驻更高效;大项目工具多,按需加载省得多。系统自己判断,用户不需要操心。
第三方 API 代理默认不启用这个功能,因为工具引用标记是 Anthropic 的特有能力,代理可能不支持。但如果用户确认代理能处理,可以手动开启。
为什么这个设计有意思
工具按需加载看起来是个很具体的工程优化,但它背后的矛盾其实很普遍:Agent 能力越强,注册的工具越多,上下文开销就越大,留给干活的空间就越小。能力本身成了负担。
这个矛盾在传统软件里早就出现过。程序能调用的函数越多,全部打包进来,体积就越大,启动就越慢。后来的解法是延迟加载,用到哪个再加载哪个,用一次额外的间接调用换来大幅减少的资源占用。Claude Code 对工具做的事情一模一样,只不过省的不是内存,是 token。