工具调用链对比
工具调用链对比
1. 这份文档回答什么问题
这份文档只回答一个问题:
当模型输出了工具调用之后,Claude Code 和 Opencode 分别如何执行工具、回填结果,并决定任务要继续还是停下。
这也是理解两边“为什么会自动多跑几轮”最关键的一条链路。
2. 先给结论
2.1 Claude Code
Claude Code 的工具链更像:
Query Loop 内部的一条内建执行支路。
也就是说:
- 模型流式输出里一旦出现
tool_use query.ts当场收集工具块- 然后直接在同一个 Query Loop 里执行工具
- 再把
tool_result拼回消息流 - 接着推进下一轮
工具执行不是外挂模块,而是 Query Loop 状态机本身的一部分。
2.2 Opencode
Opencode 的工具链更像:
LLM 流 -> SessionProcessor 消费 -> Session 状态更新 -> 外层 loop 决定下一步。
也就是说:
LLM.stream()负责把工具能力接进模型流SessionProcessor负责消费tool-call / tool-result / tool-error- 工具状态以
part的形式写入 session - 最后由
processor.process()返回continue / compact / stop - 再由
prompt.ts的外层 loop 决定下一轮是否继续
工具执行在 Opencode 里是会话流水线的一环,而不是单个总循环里的独立内脏。
3. Claude Code:工具调用链怎么走
3.1 工具调用是在 Query Loop 里被收集的
Claude Code 的核心文件是:
claude-code-rev/src/query.ts
在模型流式输出阶段,只要 assistant 消息里出现 tool_use:
- 会把对应 block 收集进
toolUseBlocks - 同时把
needsFollowUp = true
这代表:
本轮不能直接结束,必须先走工具执行分支。
3.2 工具执行有两条路径
Claude Code 主要有两种工具执行方式:
StreamingToolExecutorrunTools(...)
对应文件:
claude-code-rev/src/services/tools/StreamingToolExecutor.tsclaude-code-rev/src/services/tools/toolOrchestration.ts
两者的共同点是:
- 都围绕
tool_use执行真实工具 - 都产出
tool_result - 都可能更新
toolUseContext
区别在于:
StreamingToolExecutor更激进,工具可以随着流式输出边到边执行runTools(...)更像批量执行器,把一组工具调用按并发安全性分批跑完
3.3 Claude Code 会把工具结果重新拼回消息流
工具执行完成后,query.ts 会把工具产物塞进:
toolResults
随后还会补充:
- queued command attachments
- memory attachments
- skill attachments
然后把下一轮消息重建成:
messagesForQueryassistantMessagestoolResults
这意味着 Claude Code 的工具链不是“一轮里做完就丢”,而是:
把工具结果重新回灌进 transcript,让下一轮模型继续消费。
3.4 Claude Code 如何判断继续
Claude Code 的继续判断,核心不是“工具有没有成功”,而是:
- 这一轮有没有
needsFollowUp - 恢复路径是否要求继续
- stop hook / budget / 边界条件是否要求继续
所以工具链在 Claude Code 里的意义是:
它是 Query Loop 自我续跑的直接燃料。
4. Opencode:工具调用链怎么走
4.1 工具是先在 LLM 层装配进去的
Opencode 的关键文件有三层:
packages/opencode/src/session/llm.tspackages/opencode/src/session/processor.tspackages/opencode/src/session/prompt.ts
在 llm.ts 里:
- 会先
resolveTools(...) - 根据权限和用户设置删掉不可用工具
- 再把
tools、activeTools、toolChoice一起交给streamText(...)
也就是说,Opencode 的工具系统首先表现为:
模型调用参数的一部分。
4.2 工具事件是在 SessionProcessor 中被消费的
当模型开始流式输出后,SessionProcessor.process() 会消费这些事件:
tool-input-starttool-calltool-resulttool-error
它不会像 Claude Code 那样手工维护一组 toolUseBlocks 再去显式跑一轮 orchestrator,而是:
- 在工具开始时创建
tool类型的 session part - 在运行中把 part 状态更新为
running - 在结果返回时更新为
completed - 出错时更新为
error
所以 Opencode 的工具链重点不是“本地 transcript 回填块数组”,而是:
把工具执行过程结构化落进 session state。
4.3 工具错误会直接影响 stop 判定
在 processor.ts 里,如果工具错误属于:
Permission.RejectedErrorQuestion.RejectedError
就会把:
blocked = true
而在一轮结束时:
needsCompaction返回compactblocked返回stopassistantMessage.error返回stop- 否则返回
continue
这意味着 Opencode 的工具链和停止判定绑得很紧:
工具层一旦把会话打进 blocked/error,外层 loop 就会停。
4.4 最终是否续跑由外层 loop 裁决
prompt.ts 不负责逐个工具执行,但它负责决定:
stop:直接结束compact:先压缩再继续continue:进入下一轮
同时它还会看模型 finish reason:
- 如果模型自然完成,且没有明确继续理由,就更倾向于停下
所以 Opencode 的工具链不是“工具执行器自己拉起下一轮”,而是:
工具流先改变 session 状态,再由外层会话编排器决定要不要继续。
5. 两者最本质的差异
5.1 Claude Code
Claude Code 更像:
Query Loop 中心化工具编排。
特点是:
- tool use 在
query.ts里被直接收集 - 工具执行与 Query Loop 同轴
tool_result直接回灌 transcript- 下一轮继续与工具 follow-up 强绑定
5.2 Opencode
Opencode 更像:
Session 流水线式工具处理。
特点是:
- 工具先作为 LLM 能力注入
- 工具事件由
SessionProcessor流式消费 - 状态写进 session parts
- 下一轮继续与
continue / compact / stop三分结果绑定
6. 用一句话总结
如果压成一句话:
- Claude Code:工具链是 Query Loop 的内脏
- Opencode:工具链是 Session Pipeline 的一环