本文由Zishan Ali Khan用英文撰写;此为机器翻译。AI发展速度快于任何语言,因此某些技术术语特意保留英文,带有虚线底线的术语在悬停或点击时显示原文。 阅读原文 →

实地指南 AG-UI

AG-UI:流式传输界面,不只是答案

一份关于代理-用户交互协议的实地指南,以及当流程引擎驱动流式传输时会发生什么

2026年7月10日 · 18 min · 原始 .md

我每周都在大型金融机构工作,观察团队演示“AI 代理”,但仔细一看,它们不过是带有系统提示的聊天框。界面是文本流。用户看到的是令牌。代理看到的是返回的令牌。除此之外别无他物。然后,运营部门的人会提出一个问题,直接扼杀了演示:“审批在哪里发生?”

因为真正的工作不是对话。财富客户入职流程不是“告诉我你自己”。它是身份验证,然后是风险评估,然后是路由决策,然后是需要顾问审查的案例,最后是开户。每个步骤都有状态、输出和决策。当代理计算出风险分数时,用户不想要一句描述分数的句子。他们想要的是仪表盘。当它提出资产配置建议时,他们想要可以点击进入的甜甜圈图,而不是一段近似的文字。

本指南介绍 AG-UI,这是一个弥补上述差距的协议:一个在代理后端和面向用户的前端之间传输的类型化事件流。我为此构建了一个完整的参考实现(AURUM,一个虚构的私人银行入职演示,公共仓库链接在文末),其中使用流程引擎作为编排器,而不是聊天循环。事实证明,这种架构选择比协议本身更重要,本指南的后半部分将解释原因。这里的模式与框架无关。我使用 Camunda 8 是因为我非常熟悉它;我所说的关于外部化状态的一切,同样适用于 Temporal、带有检查点的 LangGraph,或者你自己编写的状态机。

问题:令牌不是接口

令牌流解决了延迟问题,而不是接口问题。令牌流让慢速模型感觉响应迅速。它对实际工作形态没有任何帮助。

考虑一下聊天原生代理无法向您展示的内容:

  • 流程进度。 “6 步中的第 3 步”是一个 UI 概念。令牌流没有步骤,只有更多的令牌。
  • 作为一等对象的工具调用。 代理调用了 verifyIdentity 并返回了 {status: "verified", method: "document+biometric"}。这是一个带有绿色对勾的卡片,而不是模型转述(偶尔还会转述错误)的句子。
  • 暂停。 需要人工审批。在聊天循环中,“等待人工”意味着请求要么被挂起,要么丢失。两者都不是真正的暂停。
  • 结构化状态。 入职记录有 40 个字段,代理和 UI 都关心。在每次交互中将其序列化为散文,在两个方向上都会造成信息丢失。

我观察到的每个遇到这个问题的团队都以同样的方式解决:他们在后端和前端之间发明了一套私有事件词汇。一个带有 type 字段的 JSON blob,React 中的一个 switch 语句。它能工作,然后就僵化了。事件没有文档,前端和后端逐渐偏离,第二个客户端(移动应用、运营仪表盘)不得不逆向工程这个方言。

AG-UI 就是这种私有词汇的标准化版本。这就是全部的卖点,而且这已经足够了。

AG-UI 是什么

AG-UI(代理-用户交互协议)是一个开放的、基于事件的协议,它标准化了代理后端如何与面向用户的应用程序进行通信。它起源于 CopilotKit,与 LangGraph 和 CrewAI 合作开发,现在以 ag-ui-protocol 组织的名义在 GitHub 上开源。代理端通过流发出类型化事件。应用端渲染每个事件。用户输入以结构化消息的形式回流。这就是整个模型。

事实 详情
来源 CopilotKit,与 LangGraph 和 CrewAI 合作;在 ag-ui-protocol 下开源
形式 代理到应用的单向事件流,加上应用到代理的结构化输入通道
核心事件词汇 原始规范中约 16 种事件类型,分为五类:运行生命周期、文本消息、工具调用、状态管理和特殊事件(RAW、CUSTOM)。当前规范已扩展,增加了便捷的块事件和推理事件
状态模型 共享类型化存储:STATE_SNAPSHOT 用于完整状态,STATE_DELTA 用于 RFC 6902 JSON Patch 差异
传输 传输无关。Server-Sent Events (SSE) 是默认和参考传输;普通 HTTP 和 WebSockets 也适用
人在回路 一等公民:暂停、审批、编辑、在运行中重试而不丢失状态
标准化内容 事件类型、其负载形状、排序语义和状态同步机制
开放内容 如何渲染任何内容、生成式 UI 的组件模式、身份验证、授权、持久化
集成 16+ 框架绑定:LangGraph、CrewAI、Microsoft Agent Framework、Google ADK、AWS Strands、Pydantic AI、LlamaIndex 等

设计的赌注是“松散格式匹配”:AG-UI 标准化的是代理状态和 UI 意图的流动,而不是像素。一个 TOOL_CALL_START 事件不会告诉您的前端在一个圆角卡片中渲染一个加载指示器。它告诉您的前端一个工具调用开始了,带有名称和 ID,其余的都是您的问题。这是正确的选择。我所见过的所有试图标准化渲染层本身的尝试,都因其自身组件库的重压而失败。

它在协议栈中的位置

大约十八个月以来,代理协议格局根据交易方自行演变为四个层次。我发现这是唯一能让这些缩写词深入人心的框架:

协议 连接代理到… 来源 一句话概括
MCP 工具和上下文 Anthropic 赋予代理“双手”:标准化地访问系统和数据
A2A 其他代理 Google 让代理在不同供应商和运行时之间进行委托和协调
AG-UI 人类 CopilotKit 最后一公里:将代理活动转化为人类可见和可点击的内容
AP2 资金 Google + 支付合作伙伴 为代理发起的支付提供加密可审计的授权令

按交易方划分的四层代理协议栈:MCP 用于工具,A2A 用于其他代理,AG-UI 用于人类(本指南涵盖的层),AP2 用于资金

一个生产系统经常同时使用其中三个,并且越来越多地使用全部四个。在 AURUM 中,代理通过编排层(Camunda 的 AI Agent Connector 扮演 MCP 的工具代理角色)访问其工具,用户看到的一切都基于 AG-UI。我在 A2A 现场指南AP2 现场指南 中深入介绍了其他层;本文档专注于人类层。

这些层是真正正交的。AG-UI 不假定其下方有 MCP,A2A 也不假定存在 AG-UI 界面。这种正交性使得您可以独立采用它们,这也是企业内部实际采用的方式:每个预算周期采用一个协议。

事件剖析

AG-UI 事件是扁平的 JSON 对象,带有一个 type 鉴别器。五种类别完成了工作。

运行生命周期

RUN_STARTEDRUN_FINISHEDRUN_ERROR,以及用于运行中命名阶段的 STEP_STARTED / STEP_FINISHED。这些是您的进度 UI 所依赖的事件。一个运行带有 threadIdrunId;步骤带有 stepName。在 AURUM 中,操作员控制台本质上是生命周期事件的渲染器:每个 BPMN 活动都映射到一个步骤,因此原始线路读起来就像一个流程日志,因为它就是。

文本流式传输

每条消息三个事件,通过 messageId 相关联:

{ "type": "TEXT_MESSAGE_START", "messageId": "msg_042", "role": "assistant" }
{ "type": "TEXT_MESSAGE_CONTENT", "messageId": "msg_042", "delta": "Your risk profile " }
{ "type": "TEXT_MESSAGE_CONTENT", "messageId": "msg_042", "delta": "suggests a balanced allocation." }
{ "type": "TEXT_MESSAGE_END", "messageId": "msg_042" }

启动/内容/结束三元组在“只流式传输字符串”旁边看起来很官僚,但明确的边界正是让客户端能够交错多个并发消息,并确定性地知道消息何时完成而不是停滞的关键。

工具调用

TOOL_CALL_START(名称和 ID)、TOOL_CALL_ARGS(流式参数增量)、TOOL_CALL_ENDTOOL_CALL_RESULT。这一类别将代理 UI 与聊天 UI 区分开来。前端可以将调用渲染为一个具有自身生命周期的对象:待处理、运行中、已解决,并可检查参数和结果。在 AURUM 中,parseApplicationForm → {fieldsParsed: 12} 在客户视图中渲染为已完成的步骤芯片,在操作员视图中渲染为原始事件。同一个事件,两种渲染方式。

状态同步

STATE_SNAPSHOT 携带整个共享状态对象。STATE_DELTA 携带一个 RFC 6902 JSON Patch 数组:

{ "type": "STATE_DELTA", "delta": [
  { "op": "replace", "path": "/application/riskScore", "value": 72 },
  { "op": "add", "path": "/application/flags/-", "value": "PEP_SCREEN_REQUIRED" }
] }

连接时快照,流式传输时增量。这种模式是将事件溯源应用于 UI 状态,这也是大多数自制方言出错的地方,它们在每次更改时都发送完整状态,然后疑惑为什么流比实际需要的大 40 倍。

生成式 UI

在这里,规范故意不发表意见:生成式 UI 依赖于渲染为组件的工具调用事件,或依赖于 CUSTOM 事件。我的演示方言明确地命名了它,规范会将其归类为自定义事件:

{ "type": "GENERATIVE_UI", "pid": "2251799813737463", "ts": 1719849302144,
  "component": "PortfolioAllocationChart",
  "props": { "equities": 60, "fixedIncome": 30, "alternatives": 10 } }

前端维护一个注册表,将 component 名称映射到真实的 React 组件(IdentityResultCardPortfolioAllocationChartProjectedGrowthChartAccountOpenedCard)。遇到未知组件名称?渲染一个带有原始属性的备用卡片。这一个防御性决策在开发过程中两次挽救了演示,它是一个会降级的方言和一个会白屏的方言之间的区别。

排序规则简单且至关重要:按到达顺序处理事件,并将共享 ID(messageIdtoolCallId)的事件视为一个逻辑流。客户端中的其他一切都遵循这两个规则。

我验证的模式:流程编排的生成式 UI

这是我最关心的一部分,因为这是协议文档无法告诉您的部分。

AG-UI 标准化了线路。它没有说明线路背后的状态归谁所有。在每个 CopilotKit 风格的快速入门中,答案都是代理循环本身:一个聊天运行时维护对话,调用模型,发出事件。这在流程崩溃、人类需要三天才能做出决定,或者审计员询问 3 月 14 日发生了什么之前都有效。

在 AURUM 中,我颠倒了这种做法。编排器是一个流程引擎(Camunda 8,BPMN),AG-UI 流是流程状态的投影。后端是一个带有 SSE hub 的 Node BFF;Zeebe worker 和任务轮询器将引擎事件转换为 AG-UI 事件。代理(Camunda 的 AI Agent Connector 运行一个临时子流程,使用 Bedrock 上的 Claude 和 Gemini 作为可切换模型)在每次轮询中从 13 种异构工具中选择:服务任务、FEEL 脚本、用于实时外汇汇率的 HTTP 连接器、人工交接。流程是:接收、身份验证、风险评估、用于路由的 DMN 决策表,然后是直接开户或顾问审查。

当编排器拥有状态时,UI 流继承了聊天原生架构无法具备的三个特性:

可恢复。 如果 BFF 或代理在流程中途崩溃,流程实例仍然停留在引擎中它所到达的确切活动处。重启后端,从流程变量中重放一个 STATE_SNAPSHOT,流就会继续。状态存在于引擎中,而不是 LLM 的上下文窗口中,也不是 Node 进程的堆中。

可暂停。 DMN 表将超过 150 万美元的案例路由到人工用户任务。在那一刻,流程令牌停止。不是“代理循环轮询”,也不是“我们通过长超时保持 HTTP 请求开放”。令牌被持久地停放,可能是一分钟,也可能是一个月。AG-UI 流发出 USER_TASK 事件,客户看到一张诚实的“您的顾问正在审查此项”卡片,顾问在他们的队列中看到一张审批卡片。当顾问做出决定时,令牌移动,流恢复。人在回路 是编排器的一等构造,而不是附加在聊天循环上的一个 hack。关键是,AI 没有决定等待。是流程决定了。您不希望“暂停以进行合规审查”成为您需要提示的行为。

可审计。 每次状态转换、每次工具调用、每次路由决策都由引擎作为执行的副作用记录下来。在 Camunda 中,这个 审计追踪 在 Operate 中可见,无需任何自定义日志代码。当审计员询问为什么 James Rodriguez 的申请进入了人工审查而 Sarah Chen 的没有时,答案是一个带有命中策略语义和带时间戳的令牌历史的 DMN 表,而不是对模型记录进行 grep 搜索。

评论:如果您的审计故事依赖于开发人员记住在每次工具调用旁边添加日志行,那么您就没有审计故事。您只有一本日志。

这些都不是 Camunda 特有的。这个特性来自于将状态外部化到持久存储中,而不是模型上下文或 Web 层。Temporal 通过事件溯源的工作流历史为您提供了相同的可恢复性。LangGraph 通过检查点为您提供了较弱但真实的版本。你自己编写的由 Postgres 支持的状态机也符合条件。设计规则是:AG-UI 流应该是持久状态的视图,绝不是记录系统。 做到这一点,线路上的协议选择几乎是偶然的。如果做错了,任何协议都救不了您。

一个流,多个界面

事件流是类型化和语义化的,因此它不假定只有一个读取器。AURUM 通过同一条线路运行三种角色:

界面 路由 从流中渲染的内容
客户 #/customer 礼宾文本,生成式卡片逐一流式传输,诚实的“等待审查”状态
顾问 #/advisor 审批队列:USER_TASK 事件变为可操作卡片;决策回流到流程中
操作员 #/operator/{pid} 原始事件日志,未渲染:每个生命周期事件、工具调用和状态增量按到达顺序显示

一个代理后端通过 SSE 发出类型化的 AG-UI 事件流,其中 TEXT_MESSAGE、TOOL_CALL、STATE_DELTA 和生成式 UI 事件在链路上流动,并分发到客户、顾问和操作员视图

操作员视图作为“不信你看”的视图存在。在客户会话旁边打开它,您会实时看到原始线路生成精美的 UI。在客户演示中,这比任何架构幻灯片都更有说服力,因为它证明了这些卡片不是精心编排的前端表演。它们是操作控制台显示原始事件的渲染结果。

更深层次的观点是:在大多数企业中,客户 UI、后台队列和运营仪表盘是三个团队、三个代码库和三个集成项目。一个类型化的事件流将其整合为一个合同和三个渲染器。一旦词汇表存在,顾问台视图(#/desk#/pipeline)几乎是免费获得的,而“几乎免费”并不是我随意使用的短语。

实施的诚实:大约 150 行代码能带来什么

AURUM 前端没有代理框架。没有 CopilotKit,也没有 ChatKit。AG-UI 客户端是一个小型的 EventSource 包装器加上一个 useAGUI React hook,总共大约 150 行代码。我这样做是故意的,目的是找出客户端真正需要的最低限度。以下是诚实的说明。

这大约 150 行代码实际处理的内容: 为每个流程实例打开 SSE 连接,解析每个事件,根据 type 分派到 React 状态(追加文本增量,按 ID 更新工具调用,推送生成式卡片,设置用户任务标志),并依赖 EventSource 内置的自动重连。这确实是演示客户端所需的全部功能,这也是我告诉团队 AG-UI 采用的客户端成本很低的原因。代理 UX 的昂贵部分是后端语义,而不是套接字处理。

它们不处理,以及我会在生产环境中加强的内容:

  • 带连续性的重连。 EventSource 会重连,但我的服务器没有实现 Last-Event-ID,因此在断开连接期间发出的事件会直接丢失。解决方案是标准的:分配单调递增的事件 ID,保留一个短期的重放缓冲区(或从引擎重新派生,引擎是真正的记录系统),并在重连时从客户端的最后一个 ID 开始重放。当缓冲区过期时回退到新的 STATE_SNAPSHOT 可以解决其余问题。
  • 背压。 SSE 没有背压。慢速客户端意味着服务器会缓冲。对于一个演示浏览器来说这不可见;但对于数千个并发流来说,这是一个内存问题。生产环境的解决方案:每个连接的队列容量限制,溢出时快照并重置,或者合并状态增量(JSON Patch 可以组合,因此 N 个增量可以合并为一个)。
  • 跨源排序。 在一个 SSE 连接中,排序是有保证的。但我的 BFF 会从多个 worker 和一个任务轮询器中扇入事件。两个 worker 几乎同时发出事件时,它们的交错顺序可能与引擎顺序不同。演示忽略了这一点;生产环境应该在 hub 处使用引擎侧排序(来自流程实例的位置或时间戳)进行排序,然后再写入套接字。
  • 幂等渲染。 重放加上至少一次交付意味着重复。通过 messageId/toolCallId 进行更新可以解决大部分问题;我的生成式卡片处理器是追加的,因此重放的 GENERATIVE_UI 事件会渲染两次。卡片也需要稳定的 ID。
  • 流认证。 演示中的角色是哈希路由。SSE 端点上根本没有认证。生产环境需要一个绑定到流程实例和角色的短生命周期令牌,在订阅时检查,并在重连时重新检查。

关于演示本身,还有一点要诚实说明:它在没有任何 API 密钥的情况下运行。没有 ANTHROPIC_API_KEY,代理 worker 会重放一个确定性的脚本场景(“影子模式”),因此完整的 AG-UI 流、人在回路 暂停和引擎编排都能在您花费任何令牌费用之前工作。我构建它是为了演示的可靠性,但它作为测试工具也发挥了作用:整个事件管道在模型零变异的情况下得到验证,这正是您在 CI 中所需要的。如果您从本节中采纳一个想法,就采纳这个。

评论:脚本化的“影子模式”并非次等演示。它是您事件契约的固定装置,它将比任何提示评估都能捕获更多的前端回归。

AG-UI 不解决什么

协议通过明确其边界来赢得信任。您将自行负责三个空白:

UI 中显示的操作的授权。 AG-UI 可以向顾问流式传输审批卡片。它对该顾问是否被允许审批没有意见。事件是渲染指令,而不是权限授予,将“卡片出现”视为授权的前端存在 IDOR(不安全的直接对象引用)漏洞。检查必须在操作执行的地方进行。在 AURUM 中,这发生在引擎中:用户任务完成 API 强制执行谁可以完成它,而 UI 只是这一事实的一个漂亮界面。无论您的操作在哪里执行,都在那里强制执行,并将每个入站的“用户决定 X”消息视为不受信任的输入。

生成式组件的模式治理。 后端发出器和前端注册表之间的 component + props 契约的强度完全取决于您的规范。AG-UI 中没有任何内容可以验证 PortfolioAllocationChart 在上周重构后是否仍然接受 alternatives。您需要像对待任何 API 一样:共享模式定义(我将使用 zod 或 JSON Schema,在发出和渲染时进行验证)、未知组件的备用渲染器,并将组件属性视为版本化的公共 API,因为它们确实如此。

跨部署的事件版本控制。 流是长寿命的;部署不是。在滚动部署期间,针对昨天事件形状构建的客户端可能会收到今天的事件,而上周启动的流程实例可能会恢复流式传输到重新设计的前端。规范为您提供了 RAWCUSTOM 逃生舱口,但没有版本控制方案。最低可行答案:事件负载只进行添加性更改,自定义事件中包含版本字段,以及在进行破坏性更改之前排空长寿命流的策略。流程编排的设置对此感受更深,而不是更少,因为一个停放的用户任务可以轻松地等待六次部署。

我会再补充一个软性的第四点:AG-UI 不会让您的代理变得优秀。一个糟糕流程的类型化流只是一个格式良好的糟糕流程。该协议将我的问题从“如何将结构传递给前端”转移到“这个工作流实际需要什么结构”,这是一个正确的问题,但仍然需要努力。

一段式总结

AG-UI 是一个开放的、类型化的事件协议,用于代理后端和面向用户的前端之间:包括运行生命周期、流式文本、带结果的工具调用、JSON Patch 状态增量,以及用于生成式 UI 组件的开放通道,通常通过 SSE 传输。它位于协议栈的代理到人类层,与 MCP(工具)、A2A(其他代理)和 AP2(支付)并列,一个功能强大的客户端很小(我的大约 150 行,不依赖框架)。该协议标准化了线路,但故意不标准化渲染、授权或模式治理,因此这些仍然是您的职责。最具杠杆作用的决策根本不在链路上:将流作为持久的、外部化状态(流程引擎、Temporal、带有检查点的图)的投影,这样 UI 在崩溃后变得可恢复,可以作为一等构造暂停以供人工审批,并作为执行的副作用可审计。我通过 AURUM 证明了这种模式,这是一个虚构的私人银行入职流程,其中一个事件流同时为客户视图、顾问审批队列和原始操作员控制台提供数据,并且 150 万美元的审批暂停是一个停放的流程令牌,而不是一个提示。

参考资料

关注作品

电子报即将推出。在此之前,新的指南和剧集将首先发布在领英和RSS订阅中。

> esc