基本想法
Agent 的回答不是一段文本,而是一场小型任务执行。所以不能只按“消息”来存,也不能只按“UI block”来存,真正要保住的是这次任务的边界。
用户问一句话之后,Agent 可能会思考、查记忆、搜文件、调用工具、等待用户确认、最后再总结。底层看起来是一堆事件:
thinking → tool call → tool result → text → finish
但对用户来说,这不是五件事,而是一件事。中间必须有一层把“过程”整理成“状态”。Agent 的执行过程本身就是事件流,而事件结束也不是返回一个 block 或者 Message,只是文本的碎片。
Reducer 的意义是把碎事件折叠成稳定状态。比如三个 text delta,UI 不应该看到三条消息,而应该看到一个正在增长的正文块。
-
Agent 回复本质上不是“内容”,而是“过程”。
开始思考 流式输出 reasoning 决定调用工具 工具参数还在流式生成 工具执行中 工具结果回来 根据工具结果继续回答 中途可能报错 / 等用户确认 / 被取消 最后输出 Markdown -
一条回复不是一个消息,一条回复由多个 block 组成:
AgentTurn { blocks: [ { kind: 'reasoning' }, { kind: 'tool' }, { kind: 'markdown' }, ] }也就是:
type不放在整条消息上,而放在可组合的内容块上。 -
Event 是事实来源:text delta、reasoning delta、tool call start、tool result、finish。
-
Reducer 负责归并:把事件折叠成当前 agent turn。
-
Block 是 UI 读模型:markdown、reasoning、tool、error 都是 block。
-
Renderer 是可插拔的:memory tool、web search tool、file tool 各自有自己的 UI。
AG-UI
Agent User Interaction Protocol(AG-UI)是一个代表性的实现,基于事件驱动架构,实现前端应用程序和 AI 代理之间的通信。
事件?
事件是对“已经发生的事实”的抽象。事件驱动进一步做的事情,是把这个“事实”变成系统协作的触发机制。
传统方式:A 发生变化 → A 主动调用 B,让 B 做某事。
事件驱动:订单服务只声明一个事实 OrderCreated(订单已创建),不需要关心 B 的调用,广播给订阅者就可以了,让订阅者自行判断。
只有事实和变化作为一个可传递的对象时,调用链才有可能解耦。这里的消息模型也使用了类似思想:把事实给到客户端,由客户端决定处理方式,不需要在意客户端是谁,仍然可以达到解耦效果。
Publisher
|
| emits ToolCallStarted
v
Event Bus
├── UI Subscriber
├── Logger Subscriber
├── Analytics Subscriber
└── Trace Subscriber
example
- 定义好 Agent 返回的事件,通过 WebSocket / SSE 连接通信,接收 Agent 传来的事件。
- 根据 Agent 事件,对最小内容单位 Message/Block 的 content 加上 Delta。
- UI 渲染处理完成后的 Message/Block,保证 UI 每次消费的是一个完整的消息单位,而不是碎片。
- 根据 Message / Block 的类型进行消息渲染。