AG-UI — 架构与原理
30 秒导读: AG-UI 是一个开放的、基于事件的协议,专门解决「AI agent 后端怎么把它正在做的事,实时、标准地告诉用户界面」这件事。后端只管吐出一串约 16 种类型的事件(
TEXT_MESSAGE_CONTENT、TOOL_CALL_START、STATE_DELTA……),前端 SDK 把这串事件回放成「聊天消息 + 一份共享状态」。它和 MCP(给 agent 工具)、A2A(agent 之间通信)是互补的三块协议。
1. 这是什么(零基础也能懂)
一句话定义: AG-UI(Agent-User Interaction Protocol,「智能体—用户交互协议」)是一套事件格式 + 事件顺序规则 + 客户端 SDK,让任意 AI agent 后端能用统一的方式,把「我正在打字 / 我要调一个工具 / 我改了共享状态 / 我跑完了」这些过程流式地推给前端。
解决什么问题 / 给谁用。 假设你做了个 LangGraph / CrewAI / 自研的 agent,想把它接到一个聊天 UI 里。痛点是:
- agent 后端的输出五花八门(OpenAI 风格、各家框架自有格式),前端要为每一种写一套解析。
- 你想要「打字机效果」「工具调用进度」「实时改一个表单」,这些都需要增量(一个 token 一个 token、一个 JSON Patch 一个 Patch)地传,而不是等全部算完再返回。
AG-UI 把这层「中间语言」标准化:后端只要会吐 AG-UI 事件,任何 AG-UI 前端就能渲染它。给谁用——做 in-app agent 聊天 / copilot 的应用开发者,以及做 agent 框架集成的人(LangGraph、Mastra、Pydantic-AI 等都有官方集成)。
它能做什么(协议层面的功能):
- 实时流式聊天(逐 token)。
- 工具调用的全过程可视化(开始 / 参数流 / 结束 / 结果)。
- 双向共享状态同步(后端改一个 JSON,前端跟着变;反之亦然)。
- 「思考过程」(reasoning)流式展示,支持加密(零数据保留模式)。
- Human-in-the-loop:agent 跑到一半「中断」,等人批准/编辑后再「恢复」。
- 生成式 UI / 结构化消息、多模态输入(图/音/视频/文档)。
用起来什么样。 前端把一个 agent 包成对象,调 runAgent(),然后订阅事件:
// 示意,非源码:典型的前端调用方式
import { HttpAgent } from "@ag-ui/client";
const agent = new HttpAgent({ url: "https://my-backend/agui" });
agent.subscribe({
onTextMessageContentEvent: ({ textMessageBuffer }) => {
render(textMessageBuffer); // 打字机效果:每来一个 delta 就重画
},
onStateDeltaEvent: ({ state }) => {
syncForm(state); // 后端改了共享状态,前端表单跟着更新
},
});
await agent.runAgent({ tools, context }); // 跑一轮,Promise resolve 时这轮结束
// 跑完后 agent.messages / agent.state 就是最新的完整状态
后端那边吐的是一条 SSE 流,每行长这样(真实 wire 格式):
data: {"type":"RUN_STARTED","threadId":"t1","runId":"r1"}
data: {"type":"TEXT_MESSAGE_START","messageId":"m1","role":"assistant"}
data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"m1","delta":"Hel"}
data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"m1","delta":"lo"}
data: {"type":"TEXT_MESSAGE_END","messageId":"m1"}
data: {"type":"RUN_FINISHED","threadId":"t1","runId":"r1"}
一句话直觉/类比。 把 AG-UI 想成 agent 世界的「视频流协议 + 字幕轨」:后端不是一次性返回成片,而是边算边推帧(每个 delta 是一帧);前端是个播放器,按帧累积出完整画面(消息)和一份随时间变化的元数据(共享状态)。协议的核心价值,就是规定了「帧的类型」和「帧的合法播放顺序」。
2. 顶层全景(它大概怎么转)
AG-UI 的代码分两半:协议契约(纯类型/schema,各语言一份)和客户端运行时(把事件流回放成状态的引擎,目前 TypeScript 最完整)。
一次完整对话的数据流(以 HTTP+SSE 为例,从左到右):
后端 agent 前端 (@ag-ui/client)
┌──────────┐ SSE 事件流 ┌──────────────────────────────────────────────┐
│ 吐 AG-UI │ ─────────────▶ │ ① 解码 ② chunk展开 ③ verify ④ apply │
│ 事件 │ data:{...}\n\n │ SSE→JSON CHUNK→START/ 顺序 事件→消息/ │
└──────────┘ │ CONTENT 合法? 状态(reduce) │
└───────────────────────────────┬──────────────┘
│ ⑤ 写回 agent.messages
▼ / agent.state
订阅者回调 (渲染 UI)
主要部件一句话职责:
| 部件 | 干什么 | 在哪个包/文件 |
|---|---|---|
事件 schema(EventType + 各 *EventSchema) | 定义 ~16 种事件的字段与校验(Zod) | packages/core/src/events.ts |
数据模型(Message/Tool/RunAgentInput/Interrupt) | 定义消息、工具、运行输入、中断的形状 | packages/core/src/types.ts |
能力声明(AgentCapabilities) | agent 自报支持哪些特性(流式/工具/中断…) | packages/core/src/capabilities.ts |
AbstractAgent | 客户端运行时核心:runAgent() 串起整条流水线 | packages/client/src/agent/agent.ts |
transformChunks | 把「chunk 事件」展开成标准的 start/content/end | packages/client/src/chunks/transform.ts |
verifyEvents | 流的「语法检查」状态机:顺序非法就报错 | packages/client/src/verify/verify.ts |
defaultApplyEvents | reducer:把事件折叠进 messages/state | packages/client/src/apply/default.ts |
HttpAgent + transport | 发 HTTP 请求、按 content-type 选 SSE/protobuf 解码 | packages/client/src/agent/http.ts、transform/* |
主线走一遍(高层,不进代码):
- 你调
agent.runAgent(params)。AbstractAgent把当前messages/state/你传的tools/context打包成一个RunAgentInput(agent.ts:380prepareRunAgentInput)。 - 子类的
run(input)真正去取事件流。HttpAgent发一个 POST,拿回 SSE,解码成一串BaseEvent。 - 这串事件依次过 chunk 展开 → verify 顺序校验 → apply 折叠成状态(
agent.ts:194的pipe(...))。 - 每产生一次状态变化(消息变了 / 状态变了),就写回
agent.messages/agent.state,并通知所有订阅者(agent.ts:347processApplyEvents)。 - 收到
RUN_FINISHED且没有 outstanding 的消息/工具,流结束,runAgent()的 Promise resolve,返回这轮新增的消息。
阅读地图(建议顺序)
01-event-protocol.md— 先搞懂「协议本体」:那 ~16 种事件、它们的字段、以及什么顺序才合法(生命周期)。这是理解后面一切的基础。02-agent-loop.md— 客户端怎么把事件流跑起来:runAgent()背后的 5 道 rxjs 工序、中间件、订阅者、中断/恢复。03-verify-and-apply.md— 两个最值得读的算法:verifyEvents(流的状态机校验)和defaultApplyEvents(事件→消息/状态的 reducer)。精华在这。04-transport-cross-language.md— 底层:SSE 与 protobuf 解码、chunk 事件存在的理由、TS 和 Python 怎么共享同一份协议契约。
想快速判断相关性:只关心「协议有哪些事件、字段、顺序」→ 读 ch01;关心「前端怎么消费/集成」→ 读 ch02;关心「实现精华/算法」→ 读 ch03;关心「 传输与多语言一致性」→ 读 ch04。