如果你最近在关注 AI Agent 生态,大概率听过 MCP(Anthropic 搞的,让 Agent 调用工具)和 A2A(Google 搞的,让 Agent 之间对话)。
但有一个问题,两个协议都没回答:Agent 怎么跟用户交互?
Agent 跑了半天,推理了一堆中间结果,调了三个工具,最后——怎么把这些东西实时、流畅地呈现给坐在浏览器前的用户?用户又怎么在执行过程中插手、审批、修改?
这就是 AG-UI 要解决的事。它是 Agent 协议栈里缺失的最后一块拼图——连接 Agent 和用户的标准化交互协议。
一、先搞清楚:三个协议各管哪段
在聊 AG-UI 之前,先把三个协议的分工理清。
| 协议 | 连接谁 | 解决什么 |
|---|---|---|
| MCP | Agent ↔ 工具/数据 | Agent 怎么安全地调用外部工具、获取上下文 |
| A2A | Agent ↔ Agent | 多个 Agent 之间怎么协调、分工 |
| AG-UI | Agent ↔ 用户(前端) | Agent 怎么把结果推给用户、用户怎么在流程中插手 |
三大 Agent 协议栈:AG-UI 负责最上层的用户交互
这和通信领域的 OSI 分层模型是同一个思路——每一层解决一个问题,层与层之间通过标准接口对接。MCP 管"向下连工具",A2A 管"横向连同伴",AG-UI 管"向上连用户"。
一个 Agent 可以同时使用这三个协议。
这不是三选一的问题。一个智能体通过 MCP 调搜索引擎、通过 A2A 委派子任务给其他 Agent、通过 AG-UI 把进度和结果实时推送给用户——三个协议各司其职,协同工作。
二、AG-UI 到底是什么
一句话:AG-UI 是一个开放、轻量、基于事件流的协议,用于标准化 Agent 和前端应用之间的双向通信。
它由 CopilotKit 团队在 2025 年 5 月正式发布,目前已获得微软、Google、AWS、LangChain 等主流框架的官方集成支持。
为什么传统 API 搞不定 Agent 交互?
传统前后端的通信模型很简单:请求 → 响应 → 渲染 → 结束。
但 Agent 应用有四个特点,让这个模型彻底失灵:
| 特点 | 说明 |
|---|---|
| 长时间运行 | Agent 不是毫秒级返回结果,它可能思考几十秒甚至几分钟,期间需要持续推送中间状态 |
| 非确定性 | Agent 可以在运行时动态决定调用什么工具、生成什么 UI,前端无法预知 |
| 混合 I/O | 同时产生文本、工具调用、状态更新、推理过程,不是单一数据类型 |
| 需要人介入 | 敏感操作需要人审批,Agent 需要能"暂停等人"再继续 |
所以 AG-UI 的核心设计选择是:不用请求/响应,用事件流。
底层基于 HTTP SSE(Server-Sent Events)或 WebSocket,构建了一套类型化的事件协议。Agent 通过发射一连串事件来和前端通信,前端也可以反向发送事件给 Agent。
就像 REST 之于 API,AG-UI 是 Agent 之于用户界面的流式交互协议。
三、16 种事件 + 3 种流模式:AG-UI 的骨架
AG-UI 的核心是事件系统。所有 Agent 和前端之间的交互,都通过类型化事件传递。
先看总览表:
| 事件类别 | 核心事件 | 用途 |
|---|---|---|
| 生命周期 | RUN_STARTED / RUN_FINISHED / RUN_ERROR | 标记 Agent 运行的开始、结束、异常 |
| 步骤 | STEP_STARTED / STEP_FINISHED | 标记内部执行步骤(可选) |
| 文本消息 | TEXT_MESSAGE_START / CONTENT / END | 流式推送文本(打字机效果) |
| 工具调用 | TOOL_CALL_START / ARGS / END / RESULT | 流式推送工具调用过程和结果 |
| 状态管理 | STATE_SNAPSHOT / STATE_DELTA / MESSAGES_SNAPSHOT | 前后端状态同步 |
| 推理 | REASONING_START / CONTENT / END | 展示思考过程 |
| 活动 | ACTIVITY_SNAPSHOT / ACTIVITY_DELTA | 展示结构化进行中活动 |
| 特殊 | RAW / CUSTOM | 扩展机制 |
这些事件遵循三种流模式:
模式一:Start → Content → End(文本/工具/推理)
这是最常用的模式,用于流式传输文本消息、工具调用参数和推理内容:
TEXT_MESSAGE_START → TEXT_MESSAGE_CONTENT(多次) → TEXT_MESSAGE_END
TOOL_CALL_START → TOOL_CALL_ARGS(多次) → TOOL_CALL_END
REASONING_MSG_START → REASONING_MSG_CONTENT(多次) → REASONING_MSG_END
前端收到 Start 就创建容器,每个 Content 追加内容(类似打字机),End 标记完成。
拿文本消息举例,一次完整的事件流长这样:
// 1. 消息开始 —— 创建一个新消息容器
{ type: "TEXT_MESSAGE_START", messageId: "msg-1", role: "assistant" }
// 2. 流式内容 —— 逐块追加(这就是 ChatGPT 打字效果的底层原理)
{ type: "TEXT_MESSAGE_CONTENT", messageId: "msg-1", delta: "让我来" }
{ type: "TEXT_MESSAGE_CONTENT", messageId: "msg-1", delta: "帮你分析" }
{ type: "TEXT_MESSAGE_CONTENT", messageId: "msg-1", delta: "这个问题。" }
// 3. 消息结束 —— 标记完成
{ type: "TEXT_MESSAGE_END", messageId: "msg-1" }
工具调用也一样——TOOL_CALL_ARGS 流式推送 JSON 参数片段,前端拼接后执行:
// Agent 要调用搜索工具
{ type: "TOOL_CALL_START", toolCallId: "tc-1", toolCallName: "search" }
{ type: "TOOL_CALL_ARGS", toolCallId: "tc-1", delta: '{"quer' }
{ type: "TOOL_CALL_ARGS", toolCallId: "tc-1", delta: 'y":"AG-UI 协议"}' }
{ type: "TOOL_CALL_END", toolCallId: "tc-1" }
模式二:Snapshot → Delta(状态管理/活动)
状态同步用另一种模式——先发一个完整快照建立基线,之后只发增量补丁:
STATE_SNAPSHOT(完整状态)→ STATE_DELTA(JSON Patch 增量,多次)
增量更新使用 RFC 6902 JSON Patch 标准:
[ { "op": "replace", "path": "/progress", "value": 75 }, { "op": "add", "path": "/results/0", "value": { "title": "搜索结果1" } }]
为什么不每次都发完整状态?因为 Agent 运行时状态更新频率很高,完整快照太浪费带宽。 只传"变了什么",效率高一个数量级。
这和数据库的 WAL(Write-Ahead Log)、Git 的 diff 是同一个思想——存增量而非全量,在正确性和效率之间取得最优解。
模式三:Lifecycle(生命周期)
最外层的包裹:
RUN_STARTED → (各种事件) → RUN_FINISHED / RUN_ERROR
每次 Agent 运行必须以 RUN_STARTED 开始,以 RUN_FINISHED 或 RUN_ERROR 结束。这是协议级别的约束,确保前端始终能知道"Agent 是在跑还是跑完了"。
四、最精彩的部分:前端定义工具,Agent 来调
AG-UI 有一个让我眼前一亮的设计:工具由前端定义,传给 Agent,Agent 通过事件来调用。
传统思路是工具在后端注册,Agent 直接调。但 AG-UI 反过来了:
// 前端定义一个"确认操作"工具
const confirmTool = {
name: "confirmAction",
description: "请求用户确认一个操作",
parameters: {
type: "object",
properties: {
action: { type: "string", description: "需要确认的操作" },
importance: { type: "string", enum: ["low", "medium", "high", "critical"] }
},
required: ["action"]
}
}
// 把工具传给 Agent
agent.runAgent({
tools: [confirmTool],
// ...
})
当 Agent 决定调用这个工具时,它发出一系列 TOOL_CALL_* 事件;前端接收后执行工具(比如弹出确认对话框),再把结果作为 tool 角色消息发回给 Agent。
这意味着敏感操作的控制权在前端,不在 Agent。
Agent 可以请求"发一封邮件给 CEO",但前端决定是直接执行还是弹窗让用户确认。这就是 AG-UI 原生支持 Human-in-the-Loop(人在回路)的基础。
五、中断机制:Agent 能"暂停等人"
这是 AG-UI 比大多数 Agent 框架更成熟的地方——内置了完整的中断/恢复协议。
当 Agent 遇到需要人工决策的节点,它不是报错退出,而是:
发出所有当前状态(StateSnapshot + MessagesSnapshot)
发出 RunFinished,但 outcome 类型为 "interrupt"
附带中断描述:原因、期望的响应格式、过期时间等
{
"type": "RUN_FINISHED",
"outcome": {
"type": "interrupt",
"interrupts": [{
"id": "int-1",
"reason": "confirmation",
"message": "即将给 CEO 发邮件,确认继续?",
"responseSchema": {
"type": "object",
"properties": {
"approved": { "type": "boolean" },
"editedContent": { "type": "string" }
}
},
"expiresAt": "2026-05-16T23:00:00Z"
}]
}
}
用户看到这个中断后,可以批准、拒绝、甚至修改内容。前端在恢复时发送:
{
"resume": [{
"interruptId": "int-1",
"status": "resolved",
"payload": { "approved": true, "editedContent": "修改后的邮件内容" }
}]
}
Agent 收到后从中断点继续执行。整个过程状态不丢失、可审计、支持超时。
这让我想到经济学中的委托-代理问题:如何让代理人(Agent)在拥有自主权的同时,保留委托人(用户)的否决权?AG-UI 的中断机制就是一个精确的技术解答——给 Agent 自主权,但在关键节点设置"审批门"。
六、两个真实的实现案例
案例一:Microsoft Agent Framework + AG-UI
微软的 Agent Framework 已经原生支持 AG-UI 协议。实现方式非常优雅:
浏览器 → HTTP POST + SSE → ASP.NET Core (MapAGUI) → AIAgent → Azure OpenAI
核心映射表:
| Agent Framework 概念 | AG-UI 事件 |
|---|---|
agent.RunStreamingAsync() | SSE 事件流 |
AIFunctionFactory.Create() | 后端工具,结果通过事件流推送 |
ApprovalRequiredAIFunction | 自动转为 AG-UI 中断协议 |
ChatResponseFormat.ForJsonSchema<T>() | 转为 STATE_SNAPSHOT 事件 |
微软还提供了一个在线演示:AG-UI Dojo,覆盖了流式聊天、后端工具渲染、人机审批、共享状态、生成式 UI 等 7 大场景。
案例二:Express + 纯前端 CSR 实现
来自社区的轻量级实现,演示了 AG-UI 的最小可行方案:
后端(Express) :
app.post("/agent", async (req, res) => {
// 设置 SSE headers
res.setHeader("Content-Type", "text/event-stream")
res.setHeader("Connection", "keep-alive")
const encoder = new AGUIEncoder()
// 按协议发射事件
res.write(encoder.encode({ type: "RUN_STARTED", threadId, runId }))
res.write(encoder.encode({ type: "TEXT_MESSAGE_START", messageId, role: "assistant" }))
// 流式转发 LLM 输出
for await (const chunk of llmStream) {
res.write(encoder.encode({
type: "TEXT_MESSAGE_CONTENT",
messageId,
delta: chunk.content
}))
}
res.write(encoder.encode({ type: "TEXT_MESSAGE_END", messageId }))
res.write(encoder.encode({ type: "RUN_FINISHED", threadId, runId }))
res.end()
})
前端:用 fetch + ReadableStream 解析 SSE 数据流,逐行解析 data: 前缀的事件。
核心依赖只有两个包:@ag-ui/core(类型定义)和 @ag-ui/encoder(事件编码器)。整个协议层足够轻量,不绑定任何框架。
七、AG-UI 的中间件系统:AOP 思想的完美应用
AG-UI 还有一个值得单独说的设计——中间件系统。
它和 Express/Koa 的中间件思路一模一样:在 Agent 和前端之间插入处理层,拦截、转换、增强事件流。
// 函数式中间件:给所有 AI 回复加前缀
const prefixMiddleware = (input, next) => {
return next.run(input).pipe(
map(event => {
if (event.type === "TEXT_MESSAGE_CONTENT") {
return { ...event, delta: `[AI]: ${event.delta}` }
}
return event
})
)
}
// 链式组合
agent.use(loggingMiddleware, authMiddleware, filterMiddleware)
还有内置的 FilterToolCallsMiddleware,可以按白名单/黑名单过滤工具调用。
这就是 AOP(面向切面编程)在协议层的应用。 日志、鉴权、过滤、指标采集——这些横切关注点不侵入 Agent 核心逻辑,通过中间件链式组合。
如果你写过 Express middleware 或 Redux middleware,这套模式你一定不陌生。
八、未来走向
AG-UI 目前处于快速迭代期,有几个明确的发展方向:
| 方向 | 现状 | 趋势 |
|---|---|---|
| 生成式 UI | 草案阶段,支持静态和声明式两种模式 | Agent 不仅返回数据,还能"提议" UI 结构 |
| 多模态消息 | 草案阶段,支持图片/音频/视频/文档 | 从纯文本走向富媒体交互 |
| MCP/A2A 握手 | 已实现基础握手 | AG-UI 作为统一入口,代理 MCP 和 A2A 协议的 Agent |
| 更多框架集成 | OpenAI Agent SDK、Cloudflare 进行中 | 目标是覆盖所有主流 Agent 框架 |
| 更多客户端 | CopilotKit(Web)、CLI 已支持 | React Native 移动端在招募贡献者 |
最值得关注的是生成式 UI 方向——Agent 不仅告诉你结果,还能描述"用什么组件展示这个结果"。这意味着未来前端可能不再需要为每种 Agent 输出手写 UI 组件,Agent 自己就能提议 UI 结构,前端验证后直接渲染。
九、什么时候该用 AG-UI
一个快速判断清单:
| ✅ 适合 | ❌ 不适合 |
|---|---|
| 需要实时流式展示 Agent 推理过程 | 简单的一问一答(直接调 API 就行) |
| Agent 运行时间长,需要推送中间状态 | 纯后端 Agent,不需要用户交互 |
| 需要 Human-in-the-Loop 审批机制 | 已有完善的自定义通信协议 |
| 前后端需要共享状态、双向同步 | 纯静态内容展示 |
| 要对接多个 Agent 框架到同一个前端 | 单一框架内部使用 |
| 需要展示工具调用过程和结果 | 工具调用完全在后端闭环 |
如果你只想带走一句话,我建议记这个:
MCP 让 Agent 有了手,A2A 让 Agent 有了同伴,AG-UI 让 Agent 有了嘴和眼睛——它终于能跟用户说话了。
三个协议拼在一起,才是完整的 Agent 基础设施。而 AG-UI 解决的"最后一公里"问题——如何让 Agent 的能力优雅地呈现在用户面前——恰恰是决定 Agent 应用能否真正落地的关键一环。
参考原文:
• AG-UI Protocol — AG-UI Official Docs
• Microsoft — AG-UI Integration with Agent Framework
• 维李设论 — AG-UI 实践及原理浅析