拆.
把 AI 工具做成系统 · 第 03

中心命题:AI 产品 = 显式建模的事件流

贯穿全系列的一句话

3273读完约 18 分钟craft:B+发布于 2026-06-20

第三章 中心命题:AI 产品 = 显式建模的事件流

一、Demo 中途,team lead 问「卡在哪一步了」

某个 AI 产品的内部 demo。PM 站在投影前演示,agent 开始跑 tool。屏幕上转了 30 秒的 spinner。team lead 问了一句:「现在卡在哪一步了?」

PM 看屏幕。看了 5 秒,没答上来。

那 5 秒不是表达能力的问题。是产品 UX 决策的回声——团队从来没有定义过「正在发生什么」该如何向用户表达。所有的 loading 都被合并成一个 spinner,事件流被压成了一个 viewing 状态。

判断「这是系统」(Ch 1)+ 三道筛全过(Ch 2)只是产品定位完成。接下来要解决的是更具体的问题:用户怎么看见你的 AI 在做什么?

答案是一句贯穿全系列的中心命题:AI 产品 = 显式建模的事件流

这一章拆这句话里的三个关键词。

别人没法替你看见你的产品。你的产品自己不说话,最忠诚的用户也只能猜。

二、显式 + 建模 + 事件流

中心命题里的三个词,每个都有反义。读懂反义就读懂命题。

「显式」的反义是「藏」。

工具型产品藏一切。用户给输入,等结果,结果出现。中间发生了什么不重要——一个 API 调用 800ms 还是 8 秒,对工具的产品体验影响在边缘。

系统型产品不能藏。AI 跑 30 秒 tool、用户等结果时,用户不知道发生了什么,会发生两件事:要么按 Ctrl+C 取消,认定产品坏了;要么怀疑产品不可信,下次不来了。

「建模」的反义是「流水账」。

最朴素的「显式」是把所有日志打印给用户看。这是流水账,不是建模。

建模意味着事件有 schema——每个事件有类型、有阶段(开始 / 进行中 / 结束 / 失败 / 取消)、有数据。用户能预期下一个事件该出现什么。

opencode 的 packages/sdk/js 把这种事件流暴露成 OpenAPI——这意味着事件不是日志,是产品契约。

「事件流」的反义是「快照」。

快照是某一时刻的状态。事件流是状态随时间变化的过程。

工具型产品用快照——"loading"、"done"、"error" 三状态机就够。系统型产品要事件流——状态机有几十个状态,状态切换是产品语义,状态切换的过程比状态本身更值钱。

显式 + 建模 + 事件流,三个词的反义合起来就是工具型产品的默认形态:藏、流水账、快照。系统型产品要把每一个反义都翻过来。

三、opencode 怎么表达这条命题

opencode 把这条命题贯彻到代码结构。三个证据。

证据 1:8 类基础事件被显式定义。

把 opencode 的 SDK 调用展开,能看到这些事件类型——tool call started、tool call finished、LLM turn started、LLM message delta、permission requested、context source changed、session compaction、user interruption。

每一个事件都有 schema,能被 TUI / desktop / web / SDK 同时消费。

证据在 packages/opencode/src/cli/cmd/tui.ts:1-80:TUI 用 EventSource 订阅这套事件流。packages/sdk/js 把同一套事件暴露给外部 SDK 用户。packages/opencode/src/cli/cmd/serve.ts:1-24 让 headless server 把事件流 expose 成 HTTP SSE。

证据 2:事件流是 UI 与 agent 解耦的契约。

opencode 有 5 个 UI 端:TUI / desktop / web / console / SDK。它们都消费同一份事件流。

packages/opencode/src/cli/cmd/tui.ts 把 TUI 跑在 worker thread 里,跟主进程靠 RPC bridge + EventSource 通信。这意味着 TUI 不是 agent,是 agent 事件的渲染层。

换句话说,opencode 把 UI 视为消费者,把事件流视为产品。

证据 3:用户中断是事件流的一等公民。

packages/opencode/src/tool/tool.ts:36-46Tool.Context 包含一个 abort signal。用户按 Ctrl+C 触发的 abort 是显式事件——它沿 effect chain 传播,让运行中的 tool 知道用户改主意了。

工具型产品里「用户中断」是异常路径。系统型产品里它是正常路径。事件流要承认这一点。

当 UI 跟 agent 用同一份事件契约通信时,agent 的内部状态就成了产品语言。

四、4 个对手的「显式度」对比

把中心命题应用到对手身上,能看到不同 AI 产品的显式度差距:

opencode:显式度最高。TUI 底部 RunFooter 实时显示当前 tool 名、参数、token 用量、permission 询问。用户每一秒都能看见 AI 在做什么。

Claude Code:显式度高。TUI 紧凑,tool 调用过程显式,permission 询问内联,中断响应即时。但事件流的 SDK 表面比 opencode 窄——它不打算让外部开发者自己写 UI 消费它的事件流。

Cursor:显式度中。Composer / Agent 模式有「正在做什么」反馈,但事件流被吸收进了编辑器状态——你看到的是文件变化、diff、改了哪一行,不是 agent 在做什么。这是产品判断:Cursor 卖的是「编辑器变好用」,不是「agent 让你看见」。

Aider:显式度中下。stdout 是文字事件流——Editing file foo.pySent X tokensGot reply——但没有 schema。用户能看见但无法预期下一步该出现什么。

ChatGPT:显式度低。「思考中...」是一个 spinner,不是事件流。用户不知道 AI 在用什么 tool、调用了几次 API、是否在 retry。最近加的 reasoning 展开能看一段中间思考,但属于半显式。

这张对比给 PM 一个具体的位置感:你的产品现在在哪个档位?想往上挪一档要做哪些事?

显式度不是越高越好。是要跟用户的「想知道多少」对齐——开发者要全显式;普通消费者要「显式得有节制」;某些场景要「显式得有戏剧感」。

五、把 8 件事做成事件,写进 PRD

PRD 里「事件流」这件事如果只用一句「用户能看到 AI 在做什么」带过,就会失控。最小可行版本:在 PRD 里显式列出 8 类基础事件,每类有 schema、有触发条件、有 UI 表达。

8 类事件清单:

  1. Tool 开始 / 结束:哪个 tool、什么参数、运行多久。结束时附结果摘要。
  2. LLM turn 开始 / message delta / 结束:模型在思考、token 流动、本轮结束。
  3. Permission 请求 / 响应:AI 想做某件需要授权的事,用户 allow / deny / ask later。
  4. Context 变化通知:「事实变了」类事件——文件被外部改了、用户切了 agent、模型换了。
  5. 失败 / 重试:哪一步失败、为什么、是否在自动重试。
  6. 恢复 / Resume:用户回到中断的 session,承诺记得哪些状态。
  7. Agent / 模型切换:从 build 切到 plan、从 Claude 切到 GPT。
  8. 用户中断:Ctrl+C / Esc / 关浏览器,承诺保留什么。

8 件事不一定全都让用户看到。但都要在 PRD 里被显式建模——schema 写清楚、UI 表达定义清楚、跨端一致性约束清楚。

没在 PRD 里被建模的事件,会在生产里变成 bug。因为不同 UI 端、不同工程师会用不同方式表达同一个事件,用户在不同端会看到矛盾的事。

PRD 里这一节通常 4-6 页。是系统型 PRD 跟工具型 PRD 第二大的体积差异来源(最大那个是「故障 / 失忆 / 恢复」状态机,见 Ch 1 第五节)。

事件流的 schema 比事件流的展示更重要。展示可以重做,schema 错了要重写整个 UI 层。

六、反方:过度显式反而是负担

显式度不是越高越好。三种场景里过度显式有损产品力。

第一种:工具型产品。 用户来完成一件事就走,事件流对他是噪声。DeepL 不需要告诉你它正在「调用模型 #3、token 1024/2048、retry 1/3」——他要的是翻译结果,不是过程。

第二种:消费级产品。 Apple-style 的产品力来自「复杂藏起来」。普通消费者不想知道 AI 在做什么,他们想要「它能做」。把所有事件都显式化会让产品看起来像开发者工具。

第三种:戏剧性需要的产品。 有些场景里「看不到」比「看得到」更值钱——比如创意类 AI 产品的「在你眼前生成」那种 reveal 感。把全过程显式化反而破坏期待。

三种场景的共同特征:用户想要的不是「我看见 AI 做什么」,是结果或感受。

折中方案:把事件流分级。基础事件(开始、结束、失败)默认显示,高级事件(每个 tool call、token 流)藏在「详细模式」开关后面。用户按需打开。

opencode 给开发者用,全显式。FinanceMate 给老板用,要分级——下一节看怎么分。

显式度是产品决策不是工程决策。决定「用户该看见什么」的人,应该是 PM 不是工程师。

七、自检 + 偷走清单

5 道自检:

  1. 8 件事都被建模了吗? 我的 PRD 里有没有 8 类基础事件的 schema 表?没有——下周写。
  2. 同一个事件在不同 UI 端表达一致吗? Tool 开始事件,在 web 端、移动端、API 里看到的内容一致吗?不一致——schema 没共用。
  3. 用户中断是一等公民吗? Ctrl+C / Esc / 关浏览器后,session 状态是什么?这是显式定义的还是工程默认的?
  4. 「思考中」的 spinner 背后是什么? 我的产品里那个旋转的圈,对应几类事件?如果只对应「loading」——你正在藏事件流。
  5. 显式度跟用户匹配吗? 我的用户是开发者还是消费者?是想看过程还是想看结果?显式度跟这个匹配吗?
#你能立刻做的难度收益
1在 PRD 里加一节「8 类基础事件清单」,每类列 schema2 小时把「AI 在做什么」从口头默契变成产品契约
2把「思考中...」 spinner 拆开,看背后对应几类事件30 分钟暴露藏起来的事件
3跟工程对齐:事件 schema 是 SDK 表面的一部分15 分钟防止不同 UI 端漂移
4让团队 demo 时永远能回答「现在卡在哪一步了」持续内部 demo 是产品力的体检
5给对手画显式度档位图(高 / 中 / 中下 / 低)30 分钟看清自己在哪一档、对手往哪里走

一个产品的 demo 能不能讲清楚「现在卡在哪一步」,等于它的事件流被建模到了什么程度。

推演场景 · 虚构

八、推演场景(虚构):FinanceMate 的 8 类事件

继续 FinanceMate(虚构的中小企业 AI 财务助手)。Ch 1 判它是系统,Ch 2 揭示它是低频系统。Ch 3 落到具体——FinanceMate 的事件流长什么样?

FinanceMate 的 8 类基础事件

事件触发UI 表达
Upload 开始 / 结束老板上传银行流水或发票照片Progress bar + 结束后「识别了 X 笔流水」
OCR / 分类 turnAI 处理新上传内容进度条 + 当前正在分类的科目
规则应用AI 用历史规则自动分类每条规则被用时显示「用了规则 #12:餐厅类支出归招待」
异常检测AI 识别出本月异常红点标记 + 卡片「这笔比同期高 3 倍」
失败 / 重试OCR 识别失败 / 分类置信度低显式提示「识别不准,需要你确认」
Resume / 月度小结用户登录时(低频系统的关键事件)首屏卡片「上次到这里 + 本月还需要做的 X 件事」
规则学习用户教 AI 新规则显式确认「已记住 X 条规则」
用户中断用户关页面、断网静默保留草稿,下次回来 resume

8 件事 FinanceMate 都该建模。但不是 8 件都该默认全显式给老板看——这是上一节反方的具体落地。

分级显式:FinanceMate 给老板看什么、藏什么

事件类默认详细模式
Upload 开始 / 结束显示显示
OCR / 分类 turn仅进度,不显示细节显示每笔的分类决策
规则应用隐藏(累计后月底汇报)实时显示每条规则被用
异常检测显示(红点 + 卡片)显示
失败 / 重试显示显示
Resume / 月度小结默认首屏默认首屏
规则学习显式确认显式确认
用户中断静默保留草稿提示「已保留」

默认模式跟老板的「我要的是结果不是过程」对齐。详细模式给那些「想看 AI 怎么干的」老板(少数)。

低频系统的事件流后果

FinanceMate 是低频系统(Ch 2 揭示)。这给事件流加一层特殊约束。

Resume 事件是产品的核心 UX。

高频系统里,「resume 上次」可能只是 click 一下 thread。低频系统里,「resume 上次」要被精心设计——上次到哪了、这周还要做什么、上次教的规则是否有更新。这是 FinanceMate 首屏第一块的全部内容。

月度小结是事件流的「摘要」层。

低频用户不会一次次回看事件流。他们需要事件流的摘要——「这个月 AI 帮你处理了 X 笔,用了 Y 条历史规则,发现 Z 笔异常」。这个摘要本质上是事件流的「月度回看」UI。

Email 通知是「主动推送的事件」。

低频用户不主动来,所以事件流要主动找他——「上周三发现一笔异常,本周三还没确认」「本月分类规则触发了 18 次,符合上月趋势」。Email 不是营销,是事件流的 outbound 通道。

团队下周该做的事

#行动触发的命题维度
1在 PRD 里加一节「8 类基础事件」+ schema 表显式 + 建模
2设计默认 / 详细 两档显式度的开关显式度跟用户对齐
3把 Resume 事件做成首屏第一块(不是「打开 dashboard」)低频系统 + 事件流
4设计「月度小结」UI 作为事件流的摘要层事件流 + 低频
5设计 4 类 outbound Email:异常未确认 / 规则触发统计 / 月结提醒 / 规则学习确认事件流的主动推送

5 件事里 3 件直接来自「低频系统 + 事件流」的交叉。Ch 2 跟 Ch 3 第一次叠加,第一次落进 PRD。

Ch 1 给你方向。Ch 2 给你坐标。Ch 3 给你 verbs——具体动词,每一秒在做什么。


下一章,我们离开思想框架,进入证据 —— 28 个 package 是什么、各自承载什么产品判断。