我对 AG-UI 的理解分几次补上的:先记住 MCP 连工具,后来补上 A2A 连 Agent,最后才把 AG-UI 连前端这条线接上。Agent 到了界面上,要处理的是流式文本、工具调用可视化、共享状态和中断恢复。
Q1: 为什么 Agent 需要 AG-UI?传统 REST / WebSocket 不够吗?
Agent 的交互形态和传统 CRUD 有本质差异:
常见的三种”自研协议”及其问题:
- 轮询任务状态。 实现简单,但延迟高、请求量大,且拿不到 token 级增量,用户看到的是”转圈很久,然后整段出现”。
- 自定义 WebSocket 消息。 能实时,但每个团队定义一套
{type, data},前端要写大量 if/else;断线重连、乱序、回放全靠自己。 - 直接把模型 SSE 透传给前端。 前端拿到的是 Provider 的 delta 格式,不是业务事件;工具调用、状态同步、人工确认都无处安放。
AG-UI 是”Agent 的过程协议”,不是”UI 组件协议”。它让前端知道”现在在思考、正在调哪个工具、参数是什么、状态变成了什么、需不需要用户确认”,而不是只等一个最终字符串。
Q2: 一次完整 Run 的事件序列长什么样?
请求:RunAgentInput
前端发起一次执行,POST 一个结构化输入:事件流
三条不变量
- 每个 run 必须以
RUN_STARTED开始,以RUN_FINISHED或RUN_ERROR结束。 中间可以有任意多个 step、消息、工具调用。 - 同一
messageId的内容只能追加。 前端按到达顺序拼接delta;要改内容只能开新消息。 - 同一
toolCallId的TOOL_CALL_START→ARGS→END→RESULT必须配套。TOOL_CALL_END只表示”参数发完了”,不表示工具执行成功;结果要等TOOL_CALL_RESULT。
Q3: AG-UI 的事件类型怎么分类?每类的失败模式是什么?
事件字段的共同点
所有事件都有type,可选 timestamp、rawEvent、metadata,以及可选的 subagentRunId(标识由哪个子 Agent 产生)。metadata 是开放键值对象,适合放 token usage、trace id、finish reason;消费端按键合并,后写覆盖先写。
便捷事件:CHUNK
TEXT_MESSAGE_CHUNK、TOOL_CALL_CHUNK、REASONING_MESSAGE_CHUNK 是”缩写”:客户端流转换器会自动展开成 Start → Content → End 三件套。第一条 chunk 必须带 id,切换 id 或流结束时自动补 End。服务端可以只发 CHUNK,但客户端必须同时支持展开后的完整事件,否则换个 Producer 就崩。
状态事件的两条规则
STATE_SNAPSHOT是替换,不是合并。前端收到后应整体替换本地状态。STATE_DELTA是 RFC 6902 JSON Patch 数组,必须按顺序应用;检测到不一致时应主动请求或等待新的 Snapshot。
MESSAGES_SNAPSHOT 是整段对话的快照,用于初始化历史和断线重连;它不是增量,不能拿来做”追加一条消息”。
Q4: SSE 和 WebSocket 怎么选?断线重连怎么做?
SSE 模式(最常见)
- 因为是 POST + 自定义 body,浏览器原生
EventSource用不了(它只支持 GET),要用fetch+ReadableStream自己解析。 - SSE 是单向的:服务端推事件,客户端想发消息只能再发一个 POST(例如 resume)。
- 事件
id可用于续传,但 AG-UI 本身不规定服务端必须支持Last-Event-ID重放;重连后的状态一致性要靠 Snapshot 保证。
WebSocket 模式
适合需要双向实时交互的场景:语音、协同编辑、用户频繁打断。代价是要自己定义连接生命周期、心跳、鉴权、重连和消息封装。部分托管平台(例如 AWS Bedrock AgentCore Runtime)同时支持 SSE 与 WebSocket 两种传输。选型表
断线重连的正确姿势
SSE 断开 ≠ 任务取消 这一点和 MCP 的 Streamable HTTP 一致:取消必须是显式动作。
生产环境的五个坑
Q5: 共享状态怎么同步?Snapshot 和 Delta 各自什么时候用?
AG-UI 的状态同步是快照 + 增量模型:什么时候必须发 Snapshot
- Run 开始,前端可能持有旧状态;
- 断线重连后;
- 前端报告 patch 应用失败或版本不连续;
- 中断边界:在发出带
interrupt的RUN_FINISHED之前,必须先把恢复所需的状态通过STATE_SNAPSHOT和MESSAGES_SNAPSHOT发出去。
前端实现
applyPatch 用标准 JSON Patch 库(RFC 6902),不要手写。应用前校验 path 是否以 / 开头、是否越界;服务端也要限制 patch 只能作用于白名单路径,防止恶意 patch 改到敏感字段。
失败模式
Q6: human-in-the-loop 怎么做?工具调用确认的完整契约是什么?
AG-UI 的中断模型是终态式中断:run 会以 interrupt 结果结束,不会挂起在连接上,用户响应后由客户端发起一个新的 run 来恢复。中断事件
恢复:同一个 thread,新的 run
- 同一个
threadId; interruptId必须来自被中断 run 的interrupts[];- 必须覆盖所有未决中断,不支持部分恢复;
- 线程里有未决中断时,任何新输入都必须带
resume,否则应返回RUN_ERROR; - 相同的
(threadId, interruptId, status, payload)必须可安全重放(幂等); - 声明了
responseSchema时,payload 要校验,不匹配返回RUN_ERROR; - 超过
expiresAt不得恢复; - 拒绝要表达在 payload 里(如
{"approved": false}),不是另一个 status。
status 只有两种:resolved(用户给了有意义输入)和 cancelled(用户放弃,payload 应省略)。
工具确认的三段审计
当reason: "tool_call" 且带 toolCallId 时,一次工具调用跨两个 run:
TOOL_CALL_START/ARGS/END,只发 TOOL_CALL_RESULT。前端要靠原 toolCallId 把结果挂回原来的卡片。如果工具由服务端执行,生产方可以用 metadata: {"ag-ui:tool-execution": "server"} 标记;前端不得自己合成工具结果,服务端仍必须校验决定。
前端审批代码骨架
失败模式
Q7: AG-UI 和 Generative UI 的边界在哪里?
这是最容易被混淆的一对概念。
三种常见实现方式:
- 固定组件 + 工具结果渲染。 前端为每个工具注册一个卡片组件(如
get_order→ 订单卡片),AG-UI 的TOOL_CALL_*事件驱动卡片状态。可控性最好。 - 前端注册”动作/组件”映射。 例如 CopilotKit 的
useCopilotAction/ 渲染钩子,把某个工具调用映射到自定义 React 组件。仍然是前端决定 UI,模型只决定调用。 - 模型生成 UI DSL。 模型输出组件树或 DSL,前端按白名单渲染。灵活但风险最高:组件注入、XSS、版本漂移、可访问性都难保证。
边界原则
- AG-UI 只传事件,不传可执行 UI 代码。 如果要传”UI 意图”,用
CUSTOM事件 + 业务自己的 schema,并做白名单校验。 - 组件映射由前端拥有。 后端不应该知道”订单卡片长什么样”。
- 生成式 UI 必须有沙箱。 只允许预注册组件、限制 props schema、禁止内联脚本和任意 URL。
- 不要把所有状态都塞进
state。 只同步”渲染需要且需要跨端一致”的部分,组件内部状态留在前端。
Q8: CopilotKit 这类实现扮演什么角色?自研还是采用?
CopilotKit 是 AG-UI 的发起方之一,也是主要的参考前端实现:它提供 React 侧的聊天组件、工具渲染、共享状态和人工确认能力,底层用 AG-UI 事件流与后端通信。后端侧则有各框架的 adapter(LangGraph、CrewAI、Mastra、Pydantic AI、Google ADK、AWS Strands、Microsoft Agent Framework 等)。
采用时的三个检查点:
- 协议版本对齐。 SDK 版本与后端事件结构必须匹配,尤其是 interrupt、reasoning、activity 这些较新的能力。
- 传输能力对齐。 前端要支持 POST + SSE(不是只支持
EventSource),并处理代理缓冲。 - 状态所有权明确。 哪些状态由 Agent 拥有、哪些由前端拥有,要在接入前定好,否则会出现”两边都在改同一个字段”。
Q9: A2A 是什么?它和 MCP 的本质区别在哪?
A2A(Agent2Agent)解决的是互不透明的 Agent 之间如何委派任务。核心假设是:你不能访问对方的内部状态、记忆、工具和推理过程,只能通过声明式能力和消息/产物交互。Agent Card
supportedInterfaces 可以同时声明多个绑定与版本,便于灰度迁移;capabilities 声明是否支持流式与推送;skills 是能力目录,不是工具 Schema。v1.0 还引入了签名 Agent Card 与多租户支持。
Task 生命周期
INPUT_REQUIRED 和 AUTH_REQUIRED 是”暂停等待”,用于中途补充信息或凭据;contextId 把多个相关任务和消息归到一个会话。
消息与产物
- Message:一轮通信,
role为user或agent,包含一个或多个 Part。 - Part:最小内容单元。v1.0 对 Part 的序列化做了破坏性调整:据公开迁移说明,取消了
kind字段,改为按携带的字段区分(text、url、raw、data)。从 v0.3 升级时必须逐条核对官方迁移文档,不要按旧解析器上线。 - Artifact:任务产出,例如文档、图片、结构化 JSON,由多个 Part 组成。
三种消费方式
协议绑定与版本
A2A v1.0 支持三种绑定:JSON-RPC 2.0 over HTTP、gRPC、HTTP+JSON,都跑在 HTTP(S) 上;客户端用A2A-Version 头声明版本,不支持时服务端返回版本错误。操作命名在 v1.0 统一为 SendMessage、GetTask、ListTasks、CancelTask、SubscribeToTask、GetExtendedAgentCard 等(完整列表以官方规范为准)。
A2A 的失败模式
Q10: 三者怎么组合?画一张分层图,并说清每层的失败模式。
选型表
三个常见误用
- 用 MCP 做 Agent 间协作。 MCP 没有任务状态机、没有 Artifact、没有
INPUT_REQUIRED,最后会在工具层手写一套任务协议。 - 用 A2A 做前端流。 A2A 的任务状态粒度是”任务级”,不是 token 级,前端会失去逐字输出和细粒度工具事件。
- 用 AG-UI 传工具定义。 AG-UI 的
tools是”客户端工具”,生命周期属于一次 run;把它当成能力注册中心,会与 MCP 的发现机制冲突。
MCP 让 Agent 有手,A2A 让 Agent 有同事,AG-UI 让 Agent 有表情。三者解决的是不同层的问题,叠在一起才是完整的 Agent 协议栈。
Q11: 端到端实战:AG-UI + MCP 的完整链路
时序
服务端骨架(Node / TypeScript)
恢复分支
前端消费骨架
错误处理矩阵
这条链路的关键设计点
- 工具执行在服务端,不在前端。 前端只负责展示和审批,不能持有下游凭据。
- 中断是”结束一个 run + 开一个新 run”,不是长连接挂起。这样服务端可以无状态扩展。
- 状态快照先于中断。 否则用户刷新页面后无法恢复上下文。
- 恢复必须幂等。 用
threadId + interruptId + payload做去重键,写操作再加下游幂等键。 - 错误分级。 工具错误回灌给模型/用户;协议错误终止 run;基础设施错误由编排层降级。
Q12: 深度追问(AG-UI / A2A)
AG-UI 为什么用事件流,而不是直接返回一个完整结果?
AG-UI 为什么用事件流,而不是直接返回一个完整结果?
及格回答: 因为 Agent 执行慢,流式体验更好。满分回答: 不只是体验,而是可观测性和可干预性。Agent 的一次执行包含规划、多次工具调用、状态变化,用户需要知道”现在在做什么、为什么卡住、要不要确认”。如果只返回最终结果,前端无法展示工具调用、无法在危险操作前拦截、无法在断线后恢复中间状态。事件流把内部过程变成一等公民:文本增量、工具调用、状态快照、错误、中断都能被消费。代价是前端要处理乱序、重连、去重和状态一致性,所以协议必须定义清楚事件边界和快照语义。
TOOL_CALL_END 和 TOOL_CALL_RESULT 有什么区别?
TOOL_CALL_END 和 TOOL_CALL_RESULT 有什么区别?
及格回答: 一个是工具结束,一个是结果。满分回答:
TOOL_CALL_END 只表示参数传输结束,此时工具可能刚开始执行,也可能还在排队;TOOL_CALL_RESULT 才是执行结果,带 content 并挂到同一个 toolCallId。这个区分对 UI 很重要:参数发完可以把卡片从”生成中”切到”执行中”,但只有收到 RESULT 才能显示”完成”。在人工确认场景里更明显:TOOL_CALL_ARGS 是 Agent 的提议,TOOL_CALL_RESULT 可能要到下一个 run 才出现,中间隔着用户的决定。AG-UI 的中断为什么设计成"结束 run",而不是保持连接挂起?
AG-UI 的中断为什么设计成"结束 run",而不是保持连接挂起?
及格回答: 因为用户可能很久才回复,连接不能一直挂着。满分回答: 核心是可扩展性和可恢复性。如果中断时保持连接或协程挂起,服务端就要为每个待审批任务保留内存和连接,跨实例、跨重启都不可靠。终态式中断把”等待”变成”已结束的 run + 持久化的 pending interrupt”,用户响应后开一个新 run,带上
resume 数组恢复。协议还要求中断前先发 STATE_SNAPSHOT 和 MESSAGES_SNAPSHOT,这样”重放式恢复”和”检查点式恢复”在外部观察上一致。代价是前端要持久化未决中断,服务端要保证 resume 幂等和过期校验。STATE_DELTA 丢了怎么办?
STATE_DELTA 丢了怎么办?
及格回答: 再发一次全量状态。满分回答: 先要能发现丢了。做法是给状态加快照版本号或事件序号,客户端应用 patch 时检查连续性,发现缺口就不应用并请求新的
STATE_SNAPSHOT。快照是替换语义,收到后整体覆盖本地状态,不合并。还要处理交错问题:旧版本的 delta 不能应用到新快照上,所以 patch 要带基准版本。服务端侧则要限制 patch 的 path 白名单,防止恶意 patch 改到敏感字段。最终原则是:事件流可以丢,状态必须能重建。如何防止前端伪造工具执行结果?
如何防止前端伪造工具执行结果?
及格回答: 后端再校验一次。满分回答: 首先明确信任边界:前端传来的任何东西都不可信,包括
resume.payload、客户端工具结果、state。工具执行必须在服务端或受控执行器里完成,前端只提交”批准/拒绝/修改后的参数”。服务端要校验 payload 是否符合 responseSchema、interruptId 是否属于该 thread、是否过期、是否重复;写操作再加幂等键。AG-UI 里 metadata: {"ag-ui:tool-execution": "server"} 只是告诉前端”这个工具由服务端执行,不要合成结果”,它不是授权凭据,服务端仍必须自己校验。A2A 和 MCP 都能"调能力",为什么还要两个协议?
A2A 和 MCP 都能"调能力",为什么还要两个协议?
及格回答: A2A 是 Agent 之间,MCP 是 Agent 和工具之间。满分回答: 抽象不同。MCP 的抽象是”能力调用”:一次请求、一个结果、有明确的 Schema,适合工具和数据;A2A 的抽象是”任务委派”:有 Task 状态机(SUBMITTED、WORKING、INPUT_REQUIRED、AUTH_REQUIRED、终态)、有 Message 和 Artifact、支持长任务、流式、推送和人工补充信息,而且对方 Agent 是不透明的,你不共享它的记忆和工具。用 MCP 模拟这些,最后会在工具层重新发明任务状态机和产物协议。反过来,A2A 的任务粒度也不适合前端逐字流式展示。所以常见组合是”MCP inside agents,A2A between agents,AG-UI to users”。
A2A 的 Task 卡在 WORKING,你怎么处理?
A2A 的 Task 卡在 WORKING,你怎么处理?
及格回答: 超时后取消任务。满分回答: 先分清是”对方真的在干活”还是”状态没更新”。看
GetTask 的 status.timestamp、最近一次消息时间、是否支持流式;如果对方支持 SubscribeToTask,优先用流式而不是轮询。设置分层超时:单次请求超时、任务整体超时、无进展超时(一段时间没有新消息或产物)。超时后发 CancelTask,并记录 taskId、contextId、最后一次状态,便于对方排查。对 INPUT_REQUIRED 要单独处理:通知用户、设置过期、允许重新发起。最后在客户端做幂等,避免重试导致重复任务。设计一个支持多会话、断线恢复的 AG-UI 前端架构。
设计一个支持多会话、断线恢复的 AG-UI 前端架构。
及格回答: 每个会话一个连接,断了就重连。满分回答: 按
threadId 建会话模型,每个会话保存:消息列表、状态快照及版本、未决中断、正在运行的 runId、事件序号。连接层与渲染层解耦:连接层负责 POST + SSE 解析、心跳、重连、去重;渲染层只消费归一化后的事件。断线后不重放所有事件,而是拉一次 MESSAGES_SNAPSHOT + STATE_SNAPSHOT 重建,再继续接收增量。多会话要限制并发连接数,后台会话可以降级为轮询或只保留快照。未决中断要持久化(localStorage 或服务端),刷新后还能继续审批。最后加可观测性:记录 runId、最后事件序号、重连次数,才能定位”用户说卡住了”到底是网络、服务端还是渲染层。延伸阅读与引用参考
- AG-UI 官方文档:协议定位、三种 Agentic Protocol 的分层。
- AG-UI Events:事件类型、字段、Snapshot/Delta、CHUNK 与子 Agent 事件。
- AG-UI Interrupts:中断结果、resume 契约、工具确认与 approve-with-edits。
- AG-UI 官网:生态与集成列表。
- CopilotKit 文档:AG-UI 的参考前端实现与各框架集成。
- A2A Protocol 官方规范:Agent Card、Task、Message、Artifact、传输绑定。
- A2A v1.0 发布说明:多绑定、签名 Agent Card、多租户与 MCP 的分工。
- AWS Bedrock AgentCore AG-UI 协议契约:SSE/WebSocket 传输与
RunAgentInput的实际形态。 - MCP 规范:与 AG-UI/A2A 相邻的工具协议层。
