A2UI:AI Agent 为什么不应该只返回一段文字
A2UI 把 Agent 的界面意图与客户端的组件实现分开:模型选择要展示的卡片、表单或动作,宿主应用负责白名单、权限、渲染和安全。本文用订票场景讲清 A2UI 的四层结构、跨端复用、流式更新与落地护栏。

从文字到界面:Agent 为什么需要一层 UI 协议
聊天机器人返回一段文字,用户还要自己判断下一步做什么;但在真实产品里,用户往往需要的是一组可以直接操作的选项:选择日期、确认金额、填写地址、比较方案,或者继续编辑一份表单。
这就是 A2UI(Agent-to-User Interface)试图解决的问题。它不是让模型随意生成 HTML,也不是把整个前端交给模型,而是让 Agent 用一种声明式格式表达“我想给用户展示什么”,再由宿主应用用自己已有的组件库把它渲染出来。
Google 在 2026 年发布的 A2UI 0.9,强调了一个很实用的分工:Agent 负责理解意图和选择界面结构,应用负责组件实现、样式、权限与交互安全。A2UI 可以通过 MCP、WebSocket、REST、A2A 等不同传输方式工作,但它本身更像“界面声明层”,而不是又一个网络传输协议。可以先看官方介绍,再把它放回自己的前端架构里理解。
为什么纯文本不够用了
假设用户说:“帮我订下周去上海的高铁,最好下午出发。”纯文本 Agent 可能返回一串车次。用户还需要复制车次、确认时间、选择座位,再回到对话里输入“就这个”。
如果 Agent 能返回一个经过审核的车次卡片,卡片里有“出发时间”“到达时间”“余票状态”和“确认”按钮,交互就从“读一段答案”变成了“完成一个任务”。
但直接让模型生成 HTML 或 JavaScript 有三个问题:
- 模型可能生成宿主应用没有实现的组件。
- 任意脚本会扩大 XSS、数据外传和权限越界风险。
- Web、Flutter、原生移动端各自有不同的组件体系,HTML 很难直接复用。
A2UI 的关键取舍是:只允许 Agent 从一个组件目录里选择组件,并用数据绑定和动作描述它们如何组合。模型表达的是 UI 意图,最终执行仍由客户端掌控。
A2UI 的四层结构
可以把一次 A2UI 响应拆成四层:
- Agent 层:理解用户问题,决定需要展示卡片、表单还是列表。
- Schema 层:用版本化的结构描述组件树、数据和动作。
- Catalog 层:规定当前应用允许使用哪些组件,以及每个组件需要什么字段。
- Renderer 层:把声明转换成 React、Lit、Angular、Flutter 或其他宿主框架的真实组件。
这四层让“生成内容”和“执行内容”分开。Agent 可以说“需要一个日期选择器”,但不能凭空执行一个未登记的浏览器 API。
一个具体例子:订票卡片怎么生成
Agent 不需要返回完整 HTML,可以只表达类似下面的意图:
{
"surface": "train_options",
"components": [
{
"type": "option_card",
"data": {
"departure": "2026-08-12 15:20",
"arrival": "2026-08-12 19:48",
"price": 553,
"available": true
},
"actions": ["select_train"]
}
]
}
真正的客户端还要做几件事:检查 option_card 是否在白名单里,验证价格和车次数据来自可信工具,确认 select_train 是否需要登录或二次确认,然后才把它映射成产品自己的卡片组件。
这也是 A2UI 与“模型直接写前端代码”的根本区别:模型输出的是受限的数据,不是可以立即执行的程序。
为什么组件目录比“万能组件”更重要
组件目录不是简单的 UI 列表,它实际上是 Agent 的能力边界。
一个金融应用可以只开放余额卡片、转账表单和收款人选择器;一个客服系统可以开放订单时间线、退款原因选项和人工转接按钮。不同用户、设备和权限,还可以使用不同的目录。
这样做有三个好处:
- 安全:Agent 不能调用目录之外的组件和动作。
- 一致:生成式交互仍然遵守产品的设计系统。
- 可演进:升级客户端组件时,不必要求 Agent 学会新的 HTML 细节。
但组件目录也会带来维护成本。每新增一个组件,都要补齐 schema、校验规则、渲染器、无障碍语义和失败回退。如果目录太小,Agent 只能输出僵硬的卡片;如果目录太大,模型更容易选错或生成难以测试的组合。
声明的不只是组件,还有数据和动作
把 A2UI 简化成“模型返回一棵组件树”还不够。一个真正能工作的界面声明,至少要回答三件事:显示什么、数据从哪里来、用户操作后发生什么。
| 部分 | 解决的问题 | 典型约束 |
|---|---|---|
| Component | 画出卡片、表单、列表还是进度状态 | 必须存在于 Catalog |
| Data | 给组件填充价格、时间、状态和选项 | 字段类型、来源和权限可验证 |
| Action | 用户点击后向谁发送什么事件 | 动作白名单、参数校验、确认级别 |
例如“确认订票”不应该只是一个叫 confirm 的字符串。客户端至少要检查车次 ID 是否来自当前查询结果、价格是否仍然有效、用户是否登录,以及这个动作是否需要二次确认。A2UI 负责表达动作意图,但最终的业务授权仍然应该发生在服务端。
还要区分两类数据:Agent 生成的数据和工具返回的事实数据。前者可以是标题、解释和排序建议;后者则可能是余额、库存和订单状态。后者不能因为模型把它写进 JSON 就自动变成可信事实,最好携带来源标识和过期时间,由客户端或服务端再次验证。
A2UI 与三种相邻方案有什么区别
直接生成 HTML / JavaScript:自由度最高,但安全边界最差。模型生成的代码需要经过沙箱、静态检查和运行时隔离,复杂度很快超过“做一个界面”的收益。
固定 JSON Schema:比 HTML 安全,也容易解析,但如果 schema 只描述数据、不描述组件语义,前端仍要为每一种业务自定义协议。A2UI 更强调组件目录、版本协商和跨端渲染。
MCP Apps 一类的工具 UI:可以让工具返回一个交互式资源,适合把工具自己的小界面带进宿主。A2UI 更像一层面向 Agent 的通用界面声明,适合由宿主统一控制组件和设计系统。二者可以组合,并不是非此即彼。
实际选型可以这样判断:如果 UI 主要属于某个工具,优先考虑工具资源;如果 UI 要跨多个 Agent、多个设备,并且必须服从宿主设计系统,A2UI 的抽象更合适。
流式渲染与跨端复用
A2UI 0.9 的一个重要方向是流式更新:客户端不一定要等 Agent 生成完整结果后才渲染,可以先显示骨架,再逐步补齐数据或组件。对于需要搜索、比价和多轮工具调用的任务,这会明显降低“什么都没发生”的等待感。
跨端复用则依赖各端的 Renderer。Web 端可以映射到 React,移动端可以映射到 Flutter,二者共享的是组件语义和数据,而不是具体的 DOM。前提是各端的组件目录足够一致,否则同一个“日期选择器”可能在不同设备上出现不同能力。
流式界面还要处理“半成品状态”。例如 Agent 先生成了一个空的结果卡片,后来工具调用失败;客户端应该把卡片标记为“暂时无法获取”,而不是保留一个看起来像最终结果的旧状态。一个成熟的声明格式通常需要区分 loading、partial、complete 和 error,并携带可恢复动作,例如“重试查询”或“改用文字回答”。
这会改变前端测试方式。过去测试的是“点击按钮后组件是否出现”,现在还要测试:声明版本不兼容时是否降级、数据缺失时是否显示错误、Agent 重复发送同一事件时是否幂等、用户在流式更新中点击时状态是否一致。
无障碍与设计系统不能交给模型猜
生成式 UI 很容易只关注“能不能显示”,忽略“能不能被所有人操作”。组件目录应该直接绑定无障碍语义:按钮的可访问名称、表单字段的标签、错误提示与输入框的关联、键盘焦点顺序和屏幕阅读器状态。
同样,颜色、间距、字体和交互反馈最好来自设计系统 token,而不是让模型自由生成。Agent 可以选择“警告状态”或“强调操作”,但不应该自己决定用什么十六进制颜色。这样既保持视觉一致,也避免模型在不同回答中生成一套套互相冲突的 UI。
一条更稳的落地路线
不要一开始就让 Agent 生成任意页面。可以按四步推进:
- 选一个闭环任务,例如筛选商品或填写报销单。
- 只开放 5–8 个组件,每个组件配 schema、示例和失败状态。
- 先让 Agent 只生成“组件选择 + 数据填充”,动作由固定代码处理。
- 用真实任务记录无效组件率、校验失败率、用户完成率和人工接管率,再扩大目录。
这样可以把问题拆开:如果用户没完成任务,到底是 Agent 选错组件、数据不可信、动作失败,还是流程本身设计得太长。没有这些指标,生成式 UI 很容易变成一组看起来漂亮但无法完成业务的卡片。
适合什么时候使用
A2UI 更适合这些场景:
- Agent 需要引导用户完成多步骤任务。
- 同一套 Agent 要服务 Web、移动端和桌面端。
- 产品已经有成熟设计系统,希望 AI 复用现有组件。
- 需要对 Agent 能展示和执行的 UI 做权限控制。
如果只是问答、摘要或一次性文本生成,直接返回 Markdown 通常更简单。不要为了“看起来像 AI”而把每个回答都包装成动态界面。
落地时的四个护栏
第一,所有组件和动作都采用白名单。未知类型直接拒绝,不要尝试“宽松解析”。
第二,把数据权限放在工具和服务端。UI 声明里的 price、balance、status 只能作为展示数据,不能成为业务决策的最终依据。
第三,动作需要幂等和确认机制。支付、下单、删除、发消息等副作用操作,不应因为 Agent 重试或用户重复点击而执行两次。
第四,始终保留文本回退。客户端版本过旧、schema 不兼容、数据校验失败时,用户至少应该得到一段清楚的文字说明,而不是空白区域。
结语:Agent 的下一层抽象是“意图”,不是“代码”
A2UI 的价值不在于让模型生成更漂亮的卡片,而在于重新划分边界:Agent 负责决定“用户此刻需要什么交互”,客户端负责决定“这个交互以什么安全、可访问、可维护的方式呈现”。
如果你准备尝试它,建议先选一个窄流程,例如“筛选商品”或“填写报销单”,建立小型组件目录,给每个动作加校验和审计,再逐步扩大范围。生成式 UI 的可靠性,最终取决于目录和边界设计,而不只是模型聪不聪明。
动手前的检查清单:
- 是否能把每个可生成组件写成明确 schema?
- 是否有未知组件、未知动作的拒绝路径?
- 是否支持客户端版本协商和文本降级?
- 是否对副作用动作做了确认、幂等和审计?
- 是否用真实用户任务而不是 Demo 截图评估体验?



