query.ts 工具分发链
目标
这页只回答一个问题:
在
query()主循环里,模型产出的tool_use是如何被系统分发、执行,再回灌成tool_result的?
一句话结论
query() 本身不直接执行每个工具,而是把工具执行委托给工具调度层:
toolOrchestration.tsStreamingToolExecutor.tstoolExecution.ts
关键角色
1. query.ts
负责主循环:
- 调模型
- 收流式输出
- 识别
tool_use - 进入工具执行阶段
- 把结果重新拼回消息流
2. toolOrchestration.ts
负责“传统分发”逻辑:
- 把一批 tool calls 按 concurrency-safe 与否分组
- 并发安全的批次并发跑
- 非并发安全的批次串行跑
3. StreamingToolExecutor.ts
负责“流式工具执行”逻辑:
- 工具一边流入一边执行
- 可并发的工具可同时跑
- 非并发工具需要独占
- 结果按原始顺序产出
- progress 可抢先产出
4. runToolUse()
真正跑某一个具体工具。
toolOrchestration.ts 的核心思路
分批规则:partitionToolCalls()
它会先看每个 tool 是否 isConcurrencySafe:
- 如果是并发安全工具,就可以和相邻并发安全工具归为同一批
- 如果不是,就单独成批,串行执行
也就是把一串 tool_use 切成这样:
[并发批][单个独占工具][并发批][单个独占工具]并发批:runToolsConcurrently()
并发批内部:
- 多个工具同时执行
- 并发上限由
CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY控制 - 每个工具最后还是独立调用
runToolUse()
串行批:runToolsSerially()
串行批内部:
- 一个一个执行
- 每执行完一个工具,可能更新
currentContext - 再带着更新后的 context 进入下一个工具
为什么 context 需要回传?
因为有些工具不只是“产出消息”,还可能修改上下文。
所以调度层不仅要返回:
message
还要返回:
newContext
也就是说,tool execution 不只是 side effect,还可能改变后续工具和后续 query 的运行环境。
StreamingToolExecutor.ts 的价值
相比传统“整批执行”,StreamingToolExecutor 更适合流式场景。
它做了几件很重要的事
1. 工具一到就排队
addTool() 收到新的 tool_use 后:
- 先找工具定义
- 判断是否 concurrency-safe
- 放进内部队列
- 立刻尝试
processQueue()
2. 用队列状态控制调度
每个工具都有状态:
queuedexecutingcompletedyielded
这让它可以既并发执行,又按顺序吐结果。
3. 允许 progress 抢先输出
工具运行中的 progress 不需要等最终结果 ready 才给上层,
会放进 pendingProgress 并尽快 yield。
4. Bash 错误会级联取消兄弟工具
实现里有个很有意思的策略:
- 如果某个工具报错,不一定取消其它工具
- 但如果是 BashTool 报错,会触发 sibling abort
原因写得挺直白:
- bash 常常有隐式依赖链
- 前一条命令炸了,后面一串并发命令继续跑很可能没意义
5. 用户中断和 streaming fallback 会生成 synthetic error
也就是:
- 不是简单粗暴消失
- 而是给模型补一个明确的错误型
tool_result
这样主循环和模型都能感知:
- 这个工具没正常完成
- 原因是被拒绝 / 被中断 / streaming fallback
和 AgentTool 的关系
AgentTool 并不绕过这套分发系统。
也就是说,当模型输出:
tool_use(name=Agent)调度层做的事跟 BashTool / ReadTool 本质一样:
- 找到
AgentTool - 调
runToolUse() runToolUse()内部再进入AgentTool.call()AgentTool.call()再决定如何起子 agent
所以:
agent 调用本质上是“经过统一 tool dispatch 的一种特殊工具调用”。
关键设计意义
1. 主循环不用知道每个工具的细节
query() 只需要知道:
- 有 tool_use
- 扔给工具调度器
2. 并发控制被抽离出来
主循环不需要自己处理:
- 哪些工具能并行
- 哪些要串行
- progress 何时吐
- 中断如何传播
3. AgentTool 能自然接入现有工具基础设施
因为 agent 就是工具,所以它自动获得:
- 权限检查链路
- 并发调度机制
- progress 通道
- 错误回传机制
当前理解
如果把主循环看成大脑, 那工具调度层就是它的手和神经系统:
query.ts决定“该去用工具了”toolOrchestration / StreamingToolExecutor决定“这些工具怎么排队、怎么跑、怎么回消息”runToolUse()决定“具体执行哪个工具实现”
后续待追
toolExecution.tsquery.ts内调用工具调度器的具体位置AgentTool.call()如何与runToolUse()的协议对接