工具执行机制:并行调度、懒加载 ToolSearch 与纵深防御
一句话定位:工具执行机制是 Harness 部件 ②工具 的运行本体——从模型吐出一个工具调用、到执行结果躺回上下文,中间的注册、发现、校验、执行、回填五站全部由 Harness 手写。前置:什么是 Harness、模型调用与容错(校验打回重试与它同一思想)、MCP(工具从哪来)、上下文压缩(工具结果是上下文里最大的一块,压缩体系清的正是它)。视角锚点:工具调用本身是模型采样输出的一串结构化 token(见采样),执行结果又变回 token 进入下一轮输入——机制层的一切设计,都是在为这笔 token 账做预算。
先从一次「行云流水」说起
你让 Claude Code「同时看看这三个配置文件,然后跑一遍测试」。它一口气并排发出三个读文件调用,几秒内全部返回,接着才串行执行测试命令——独立的事并行干、有依赖的事排队干,一轮里安排得明明白白。
而当你给自己的 Agent 写同样的逻辑,状况百出:工具一个接一个慢得像串行队列;或者放开并发后,两个调用同时改同一个文件,后一个把前一个的改动覆盖掉;更常见的是某个工具吐回五万行日志,几轮下来上下文被撑爆,Agent 开始「变笨」。
同样的模型,为什么一个行云流水、一个状况百出?从「模型说我要调工具」到「结果躺回上下文」,这中间到底发生了什么、哪些步骤能并行、哪些必须串行?
答案是:工具执行是一条次序严格的流水线——注册在前(决定模型「见过」哪些工具)、发现在中(决定每轮请求带多少定义)、校验与执行在后、回填在末(决定结果以多大体积进上下文)。每一环都有各自的失效模式,也各有一层防御。
图:工具执行流水线五站——「站 1」到「站 5」是本篇编号(不与 Harness 五部件的 ①–⑤ 混用);权限关卡在站 3 与站 4 之间,下篇展开
渐进拆解
把工具执行想象成一家餐厅的后厨,五站各有岗位:
- 站 1 · 注册台 = 设备与菜谱进场登记:每口锅能做什么、要什么原料、有什么禁忌,先造册——没登记的设备,前台根本不知道它存在
- 站 2 · 懒加载 = 菜谱几百页不往上端:客人报个口味,服务员去翻找对应章节,找到了才把那一页菜谱递进厨房——厨房里永远只放正在做的那几页
- 站 3 · 校验站 = 下单核验:订单写着「微辣程度:非常非常辣」,与菜谱写的「微辣/中辣/特辣」对不上,打回重填,不让后厨猜
- 站 4 · 执行间 = 后厨开火:三桌客人各点各的凉菜,三个灶眼同时开火(并行);两道菜共用一口锅,只能排队(串行)
- 站 5 · 回填口 = 出餐摆盘:整盆菜端上桌会撑爆桌面(撑爆上下文),分装小碟、桌上留取餐牌,要整盆的去取餐口自取
例子:并行为什么是「有条件」的
看一轮三个工具调用的时序——Harness 怎么决定谁并行、谁排队:
图:单轮并行调度——依赖分析分出并行组与串行组,全部凑齐才统一回填
为什么一轮内并行是安全的
三个调用在模型输出的那一刻就全部确定了:读 A 不需要知道读 B 的结果,跑测试虽然依赖前两者,但它排在下一批。并行的窗口只在「同一轮已确定的调用之间」——一旦某个调用的输入要等另一个调用的输出,它就必须串行。这不是 Harness 小气,而是因果:拿不到输入的活没法开工。
站 1 · 注册台(Tool Registry)
启动时把所有可用工具登记造册——名字、描述、参数 schema(JSON Schema 写成的参数合同)、版本、启停位。本地工具与 MCP 服务器带来的工具走同一张注册表,模型看到的统一是一个工具清单。注册台是后续一切的依据:清单里没有的,模型说了也不算。
站 2 · 懒加载(官方叫工具搜索 Tool Search)
工具一多,全量定义本身就成了上下文灾难。懒加载的解法是渐进披露:每轮请求只带一个轻量搜索工具(约 500 token;BM25——一种按关键词相关性打分的经典检索算法——与正则两种实现开箱可选)和少数核心工具的完整定义,其余工具的完整定义完全不进上下文;模型要用了,先搜后展开,命中的那几个定义才进入本轮请求。顺带一笔缓存账:正因为延迟定义从不进初始请求,请求前缀保持稳定,提示词缓存照常命中(官方口径)。
官方文档给过一笔账:50 多个工具的定义合计约 72K token、加上其余开销一趟请求约 77K,Anthropic 内部甚至见过定义吃掉 134K 的会话;懒加载之后这笔账缩成什么样,见下文动手节的对比图。
「都有哪些工具可搜」怎么感知?实现分两派:平台做法是彻底不进上下文、搜索即发现;Claude Code 则把一份只有名字、没有描述的清单放进消息里,占几百 token,让模型对可搜范围有数(逆向口径)——无论哪派,完整定义都是「要用才付钱」。
站 3 · 校验站
两道检查——类型校验(参数结构是否符合 schema:该传数组的传了字符串,打回)与数值校验(范围、枚举:分页页码传了 −1,打回)。打回不是报错了事,而是把「哪里不合法」作为结果回填给模型,让它改了重提——与调用层的结构化输出校验同一思想:错误信息是修错的原料。
站 4 · 执行间
依赖分析分出并行组与串行组(判据见上面的时序图),随后在沙箱边界内落地——文件系统访问圈定在项目目录、Shell 命令过白名单、网络访问可禁可限,重隔离的部署直接上容器。
执行中的三种异常各有去处:
- 用户中断:调用标记为已取消,取消事件本身作为结果回填,模型知道这活被叫停了。
- 执行报错:错误信息原样回填——模型看得到栈才能自纠,静默吞掉才是灾难。
- 权限拒绝:拒绝理由回填,模型可以换路径重试;裁决流程见权限机制。
站 5 · 回填口
结果按预算进上下文——超大结果整体落盘、上下文只留预览与路径(这笔账在上下文压缩篇算过);被拒绝或被取消的调用同样要回填一条结构化结果。这条铁律的缘由:协议要求每个工具调用必须有一条配对的结果消息,缺了配对,下一轮请求直接被 API 拒收——回填不完整,会话就断了。
纵深防御
五站各自设防,合起来是多层的纵深——类型校验挡住畸形参数,沙箱挡住越界行为,权限挡住高危动作,回填预算挡住上下文爆炸。任何一层失守,下一层还接着:这正是安全工程的 defense-in-depth 思想在工具链上的落地。单层防御的失效是家常便饭——校验漏了边界值、白名单漏了某个危险命令——层与层互相补位,才把单点失效降为小概率事件。
大白话:模型只负责「说」——说要用什么工具、填什么参数;Harness 负责「做」的三件事:能不能给(校验与权限)、怎么给(并行还是排队)、给完放哪(落盘还是直进上下文)。记住一句:并行是提速手段不是默认状态,彼此不看对方输出才配并行。
业界现状(2025-11 起)
Anthropic 把这条流水线上的三件武器做成了平台能力(beta 标识 advanced-tool-use-2025-11-20),三件各管一段:
| 武器 | 管什么 | 机制 | 官方口径数字 |
|---|---|---|---|
| 工具搜索 | 找到对的工具 | 每轮只载轻量搜索工具与少量核心工具,其余定义按需搜索展开 | 某真实会话 50 多个工具定义原本占约 72K token,切换后合计约 8.7K;MCP 评测工具选对率从 49% 升到 74%(Opus 4;更新的 Opus 4.5 为 79.5%→88.1%) |
| 程序化工具调用 | 高效执行 | 模型写一段编排代码在沙箱里跑,工具结果由脚本消化、只有最终输出进上下文 | 复杂研究任务平均 token 降 37%;一块代码可编排 20 多个调用 |
| 调用示例 | 参数填得对 | 在定义里附标准用例 | 内部测试选对参数的精度从 72% 升到 90% |
工具少了、选择反而更准,这就是「最简工具集」的统计学注脚。
执行层还有更激进的路线:把 MCP 工具当作代码 API,Agent 写代码按需加载与消化中间结果。两小时的会议记录在两个系统间传递,原文口径要额外流经模型 5 万 token;改走代码执行后官方口径最高省 98.7%——工具定义加载从 150K token 压到 2K。
动手:单轮并行调度器
伪代码实现上面时序图的调度逻辑(注释里的站号呼应流水线):
def run_tool_round(calls): # calls: 模型单轮吐出的 N 个调用
results = {}
for c in calls:
err = schema_check(c) # 站 3 校验站:类型/数值
results[c.id] = err # 不过关:错误本身就是要回填的结果
groups = dependency_groups(calls) # 站 4 依赖分析:互不依赖的进同组
for group in groups: # 组间串行(依赖次序)
todo = [c for c in group if results[c.id] is None]
for call, out in parallel_map(todo, sandbox.run):
results[call.id] = out # 组内并行,沙箱内落地
return backfill(calls, results) # 站 5 回填:每个调用都要有配对结果
def backfill(calls, results):
filled = []
for c in calls: # 铁律:每个调用必须有配对结果
out = results.get(c.id, CANCELLED) # 被取消的也回填取消标记
if size(out) > RESULT_BUDGET: # 超预算:落盘留预览(压缩篇 L3)
out = to_disk_with_preview(out)
filled.append(pair(c, out))
return filled # 凑齐才允许发起下一轮请求
同一概念的叫法对照:
| 本篇用词 | 文献中的其他叫法 | 深挖去处 |
|---|---|---|
| 工具执行五站 | 工具生命周期 · 工具分发机制 | 本篇 |
| 注册台 | Tool Registry · 工具注册表 | 本篇 |
| 懒加载 | 延迟加载 · Tool Search Tool · 渐进披露 | Skill 机制 |
| 依赖分析并行 | 单轮并行 · 并发工具执行 | 本篇 |
| 结果回填 | Backfill · 调用与结果配对 | 本篇 |
| 纵深防御 | Defense-in-Depth · 多层防御 | 安全 |
把懒加载这笔账画成图,宽度差一目了然(数字为上一节的官方口径):
图:懒加载上下文账——77K 到 8.7K 的差距,就是「全部背下来」与「要用再查」的差距
边界与取舍:五站不是每站都要满配
并行的边界写在协议里:只有「这一轮内互不依赖」才并行
还有一层隐含约束是读写属性——两个「读」并行天然安全,两个「写」并行就是在赌运气(后写覆盖前写)。成熟的 Harness 对写操作倾向串行或加锁,把并行红利留给只读操作。如果你的 Agent 并发之后出现「改了又改回去」的灵异现象,先查的不是权限也不是模型,是并行组里混进了写操作。
沙箱的硬度决定执行哲学的激进程度
沙箱边界有四道常见闸门:文件系统访问圈定在项目目录、Shell 命令过白名单、网络访问可禁可限、重隔离的部署直接关进容器。四道闸门的松紧,直接决定你敢不敢上「高吞吐、少确认」的执行哲学——沙箱够硬(容器 + 白名单),执行间就可以放开跑、事后抽查;沙箱形同虚设,每一笔执行都得人事先点头,Agent 退化成一问一答的问答机。先修沙箱,再谈放权——顺序反了的教训,在权限机制里会再见到。
Bash 命令为什么要单独分类
四道闸门里最难的就是命令执行——rm -rf 和 ls 在「跑了一条 Shell 命令」这个层面长得一模一样,白名单按命令逐条写永远写不全。业界的通行做法是在执行间前加一层命令分类器,给每条命令定风险级:
- 只读(查看、搜索):直接放行。
- 需确认(写文件、装依赖):交给人。
- 危险(删除、改系统配置):直接拦。
激进的自助执行模式(社区戏称 YOLO 模式)本质就是「分类器说安全就自动跑」,拿分类准确率换吞吐量;沙箱是它的安全网,分类器是它的加速器,两者缺一不可。分类器的具体边界各家实现不公开,但它的位置是确定的:在权限裁决之前先做一次粗筛——裁决怎么裁,是下一篇的主角。
「最简工具集」什么时候不成立
法则的完整表述是:在覆盖任务的前提下,工具越少越好——它防的是名字相似导致的选错、定义堆积导致的上下文膨胀。但「复杂任务缺少现成组合工具」时,法则会走向另一个极端:让模型用十几个原语工具拼装一个本可以一步到位的操作,每一步都是一次推理往返与一次出错机会。
此时的解法不是堆工具,而是按任务做组合层——要么写一个封装了固定流程的组合工具,要么走程序化工具调用那条路(模型写编排代码,一段代码顶十几次往返)。判据看重复度:某个十步流程每周都要跑,就值得做成一个组合工具;只跑一次的,原语拼装反而更诚实。
懒加载不是免费午餐
它省上下文的代价是多一次「搜索→展开→再调用」的往返(延迟换空间),搜索命中不了时模型会漏用本该用的工具。官方给出的适用线也直白:工具定义合计超过 1 万 token、工具数上十个、接了多台 MCP 服务器,才值得上懒加载;工具不到十个、个个常用,全量装载反而省心。
坏味道预警
如果发现 Agent 总在「该问的时候直接做了」,问题不在执行间而在校验与执行之间那道权限关卡——它的模式设计、规则匹配与多路裁决,是下一篇权限机制的主题。
自测
合上文章先自己答,再展开对照:
ToolSearch 为什么只给名字不给完整 Schema?这笔上下文账怎么算?
因为完整 Schema(参数合同)才是上下文大头:官方口径 50 多个工具的定义合计约 72K token、一趟请求约 77K,而一个搜索工具只要约 500 token。懒加载之后,其余工具的完整定义完全不进上下文,模型按需搜索、命中才展开——同一场景从约 77K 降到约 8.7K,官方口径 token 降约 85%。「都有哪些工具」的感知分两派:平台做法连名字都不进(搜索即发现),Claude Code 把只有名字、没有描述的清单放进消息(几百 token,逆向口径)——无论哪派,「参数怎么填」都推迟到决定用之后才付 token。附带收益:候选变短后选错工具的概率也降了(官方 MCP 评测选对率 49%→74%),且延迟定义不进初始请求、提示词缓存照常命中。代价是多一轮搜索往返、搜索不命中会漏工具——工具定义合计不足 1 万 token、数量不上十个时不值得上懒加载。
「最简工具集」法则什么时候不成立?复杂任务缺少组合工具时怎么办?
法则的适用前提是「有工具覆盖任务」,失效场景恰恰是任务需要的能力没有现成工具:让模型用十几个原语工具拼装一个本可一步到位的流程,每步都是一次推理往返与出错机会,法则反而放大了成本。此时的解法是补组合层,两条路:某流程重复出现,就写一个封装固定流程的组合工具(一次调用顶十几次往返);流程多变或要并行编排,就走程序化工具调用——模型写一段编排代码在沙箱里跑,中间结果由脚本消化、只有最终输出进上下文(官方口径复杂研究任务平均 token 降 37%)。判据看重复度:高频十步流程值得做成组合工具,一次性任务原语拼装更诚实。法则的本意是防名字相似选错与定义堆积撑爆上下文,不是工具越少越好——覆盖任务始终是前提。
参考与延伸
- Anthropic《Introducing advanced tool use on the Claude Developer Platform》(2025-11):工具搜索、程序化工具调用、调用示例三件套的官方口径与全部数字 — anthropic.com/engineering/advanced-tool-use
- Anthropic《Code execution with MCP》(2025-11):MCP 工具的 token 账(数千工具的定义可达数十万 token、中间结果两跳 5 万 token)与代码执行解法(官方口径最高省 98.7%) — anthropic.com/engineering/code-execution-with-mcp
- MCP 官方规范:工具的发现、描述与调用协议 — modelcontextprotocol.io
- Anthropic《Building Effective Agents》(2024-12):工具设计的可操作性原则 — anthropic.com/engineering/building-effective-agents
- pi issue #6278「模型-工具共生」实证卡(2026-10-24):Claude 新模型在 pi 的 edit schema 上批量产幻觉键(约 20% 编辑失败),根因指向后训练在霸主 harness 的宽容工具生态内进行——「工具 schema 不是中性的,形制=训练分布」;含缓解实证(去 thinking 块减半/strict invocation 归零)与 joshka 不变量论 — digest/2026-W43/pi-issue-6278-model-tool-symbiosis.md
本系列的后续学习路线(推荐顺序):权限机制 → 记忆机制 · Skill 机制 → 状态机与 Checkpoint → 多 Agent 编排。