01 V1 单体循环:系统提示组装与流式处理 本节摘要:OpenCode 的会话核心目前正处在一个有意思的时期——它有一套正在运行的 V1(一个规模可观的循环函数,把整个回合编排塞在一处),同时有一套正在建设的 V2(事件溯源式,把录入与执行分离)。本节先讲 V1,因为它是「当前可运行的真相」:你今天每一次对话,实际跑的就是它。我们要拆解这个单体循环如何编排出一次完整回合——从组装系统提示、调底层 AI SDK、流式处理工具调用、权限询问,到压缩判断与续跑。读懂 V1,你既掌握了现状,也为理解 V2 为什么要演进打下了基础。 一、V1 是什么:一个塞满一切的循环 V1 的核心是一个规模可观的循环函数(单体),它把一个回合要做的事几乎全塞在一起。
本节摘要:OpenCode 的会话核心目前正处在一个有意思的时期——它有一套正在运行的 V1(一个规模可观的循环函数,把整个回合编排塞在一处),同时有一套正在建设的 V2(事件溯源式,把录入与执行分离)。本节先讲 V1,因为它是「当前可运行的真相」:你今天每一次对话,实际跑的就是它。我们要拆解这个单体循环如何编排出一次完整回合——从组装系统提示、调底层 AI SDK、流式处理工具调用、权限询问,到压缩判断与续跑。读懂 V1,你既掌握了现状,也为理解 V2 为什么要演进打下了基础。
V1 的核心是一个规模可观的循环函数(单体),它把一个回合要做的事几乎全塞在一起。这种写法在项目早期很常见——先把功能跑通,再谈架构。它的样子大致是这样:
循环开始 ├─ 组装系统提示(System Context + 工具描述 + 权限提示) ├─ 调用底层 AI SDK(流式) │ └─ 流式处理:文本增量 / 工具调用请求 ├─ 遇到工具调用? │ ├─ 是 → 权限询问 → 执行工具 → 工具结果作为新消息 │ └─ 否 → 继续 ├─ 压缩判断(超窗口?) │ ├─ 是 → 压缩(第 9 章) │ └─ 否 → 继续 ├─ 续跑判断(模型还要继续吗?) │ ├─ 是 → 回到「循环开始」 │ └─ 否 → 结束
💡 为什么先讲 V1:尽管 V2 是未来,但 V1 是你现在每次对话实际跑的代码。讲清楚 V1,你才知道「现状是什么」,也才能理解 V2 为什么要做那次演进。这不是技术考古,而是理解一个项目如何从「能跑」走向「能 durable、能并发」。
循环的第一件事,是把喂给模型的「初始指令」组装好。在 V1 里,这部分主要是拼一段(或多段)系统提示文本,包含:
这一步的产物是「这一回合模型看到的初始上下文」。注意它每回合都会重新组装——这是 V1 的简单之处,也是 V2 要优化的地方(V2 引入了 Epoch 来避免每回合重算)。
组装好系统提示后,V1 把系统提示 + 历史消息 + 用户输入 + 工具描述一起交给底层 AI SDK,发起一次流式调用。
系统提示 + 历史消息 + 用户输入 + 工具描述 │ ▼ 底层 AI SDK(流式) │ ▼ 流式返回 token 文本增量 / 工具调用请求 / (后续)工具结果
这里有个细节值得记:V1 用的是一套通用的 AI SDK,而 V2 直接用项目自研的 LLM 抽象层(第 6 章)。这个差异不是偶然——它是 V1→V2 迁移的一部分,V2 想更彻底地掌控「一个 provider turn」的每个细节。
⚠️ 术语提示:「回合(turn)」和「循环(loop)」别混。一次循环是「调一次模型 + 处理它的工具调用」;一个用户输入可能触发多次循环(续跑)。第 7 章剩下三节会反复用到这两个词。
模型流式返回时,V1 边收边处理。关键是处理「工具调用请求」:
这一步是「Agent 能动手」的实现现场。第 5 章讲过工具系统的三层抽象,这里就是那套抽象被调用的地方——V1 从注册表里找到对应工具,带上工具上下文(含权限询问函数),执行它。
如果模型请求的工具是写操作(改文件、执行命令),且权限规则是「询问(ask)」,V1 会在执行前暂停,向用户请求授权。这正是第 4 章权限三态的体现。
模型请求:改文件 oldName → newName │ ▼ 权限求解:规则 = 询问(ask) │ ▼ 暂停,问用户:允许吗? ├─ 允许 → 执行 └─ 拒绝 → 工具失败,结果告知模型,模型决定下一步
这个暂停是同步的——V1 会一直等到用户响应。这在交互式 TUI 里没问题,但在某些自动化场景里是个局限(V2 用 durable 录入改善了这点)。
回合快结束时,V1 做两件判断:
这两个判断共同决定了「一次用户输入要循环几次」。一个复杂任务可能循环很多次——每次循环都是「调模型 + 处理工具 + 判断」。
诚实地说,V1 既不是「坏代码」,也不是「完美设计」,它是一次务实的工程选择:
| 优点 | 缺点 |
|---|---|
| 简单直接,一个函数读完就懂全貌 | 录入与执行耦合,难 durable |
| 当前可运行,经过大量实战 | 并发处理弱(同步暂停) |
| 迭代快(改一处即生效) | 难以恢复、难以重放 |
它的缺点正是 V2 要解决的。下一节我们就讲 V2 为什么要把「录入」和「执行」分开——这不是为重构而重构,而是为了换来 durability、可恢复、可并发。
理解了 V1 的局限,你就能看懂 V2 为什么要把「录入」和「执行」彻底分开——这正是下一节的主题。