跳到主要内容
A2UI 技术原理深度解析:AI Agent 如何安全生成富交互 UI | 极客日志
Python AI 大前端
A2UI 技术原理深度解析:AI Agent 如何安全生成富交互 UI 综述由AI生成 Google 开源的 A2UI 协议通过声明式 JSON 描述 UI,解决了 AI Agent 跨信任边界生成富交互界面的安全问题。其核心采用三层解耦架构(组件树、数据模型、组件目录),利用邻接表模型替代嵌套树,降低 LLM 生成难度并支持流式传输。协议定义了 surfaceUpdate、dataModelUpdate 等消息类型,实现 UI 结构与数据的分离。安全机制依赖客户端预定义组件白名单及目录协商,LLM 仅能输出符合 Schema 约束的数据,无法执行恶意代码。业务逻辑由 Agent 通过工具或子代理处理,确保渲染层纯净可控。
晚风告白 发布于 2026/4/5 更新于 2026/5/20 26 浏览
本文深入解析 Google 开源的 A2UI 协议,探讨其核心架构、数据流设计以及为何它是 LLM 生成 UI 的最佳实践。
一、A2UI 是什么?
A2UI (Agent-to-User Interface) 是 Google 于 2025 年开源的声明式 UI 协议。它解决了一个核心问题:
如何让 AI Agent 安全地跨信任边界发送富交互 UI?
传统的 Agent 交互往往是纯文本对话,效率低下。而直接让 LLM 生成 HTML/JS 代码又存在严重的安全风险。A2UI 提供了一个中间方案:Agent 发送声明式 JSON 描述 UI 意图,客户端使用自己的原生组件渲染 。
安全性:像数据一样安全 表达力:像代码一样丰富
二、核心设计理念
2.1 三层解耦架构
A2UI 的核心哲学是将三个关键元素解耦:
┌─────────────────────────────────────────────────────────┐
│ A2UI 三层架构 │
├─────────────────────────────────────────────────────────┤
│ 1. 组件树 (Structure) - Agent 提供的抽象 UI 结构 │
│ 2. 数据模型 (State) - 动态填充 UI 的应用状态 │
│ 3. 组件目录 (Catalog) - 客户端定义的可信组件映射 │
└─────────────────────────────────────────────────────────┘
这种设计带来的好处:
安全性 :Agent 只能使用客户端预定义的组件,无法注入恶意代码
灵活性 :同一份 UI 描述可在不同框架(Angular/Flutter/React)上渲染
高效性 :数据变更无需重发整个 UI 结构
2.2 邻接表模型 vs 嵌套树
这是 A2UI 最精妙的设计之一。传统 UI 描述使用嵌套 JSON 树:
{
"type" : "Column" ,
"children" : [ { "type" : "Text" , "text" : "Hello" } , { "type" : "Row" , "children"
:
[
{
"type"
:
"Button"
,
"child"
:
{
"type"
:
"Text"
,
"text"
:
"Cancel"
}
}
,
{
"type"
:
"Button"
,
"child"
:
{
"type"
:
"Text"
,
"text"
:
"OK"
}
}
]
}
]
}
{
"surfaceUpdate" : {
"components" : [
{ "id" : "root" , "component" : { "Column" : { "children" : { "explicitList" : [ "greeting" , "buttons" ] } } } } , { "id" : "greeting" , "component" : { "Text" : { "text" : { "literalString" : "Hello" } } } } , { "id" : "buttons" , "component" : { "Row" : { "children" : { "explicitList" : [ "cancel-btn" , "ok-btn" ] } } } } , { "id" : "cancel-btn" , "component" : { "Button" : { "child" : "cancel-text" , "action" : { "name" : "cancel" } } } } , { "id" : "cancel-text" , "component" : { "Text" : { "text" : { "literalString" : "Cancel" } } } } , { "id" : "ok-btn" , "component" : { "Button" : { "child" : "ok-text" , "action" : { "name" : "ok" } } } } , { "id" : "ok-text" , "component" : { "Text" : { "text" : { "literalString" : "OK" } } } } ]
}
}
特性 嵌套树 邻接表 LLM 生成难度 高(需一次性正确嵌套) 低(逐个组件生成) 增量更新 困难 简单(按 ID 更新) 流式传输 不支持 原生支持 错误恢复 整体失败 单组件失败不影响其他
三、协议消息类型详解 A2UI 定义了 4 种服务端到客户端的消息类型:
3.1 surfaceUpdate - 定义 UI 结构 {
"surfaceUpdate" : {
"surfaceId" : "booking-form" ,
"components" : [
{ "id" : "title" , "component" : { "Text" : { "text" : { "literalString" : "预订餐厅" } , "usageHint" : "h1" } } } , { "id" : "date-picker" , "component" : { "DateTimeInput" : { "value" : { "path" : "/reservation/date" } , "enableDate" : true , "enableTime" : true } } }
]
}
}
surfaceId:标识 UI 区域,支持多个独立 Surface
components:扁平组件列表,通过 ID 引用建立父子关系
组件属性支持字面值 或数据绑定
3.2 dataModelUpdate - 填充数据 {
"dataModelUpdate" : {
"surfaceId" : "booking-form" ,
"path" : "/reservation" ,
"contents" : [ { "key" : "date" , "valueString" : "2025-12-20T19:00:00Z" } , { "key" : "guests" , "valueInt" : 2 } , { "key" : "restaurant" , "valueMap" : [ { "key" : "name" , "valueString" : "川味轩" } , { "key" : "rating" , "valueNumber" : 4.8 } ] } ]
}
}
数据与 UI 结构分离,修改数据无需重发组件定义
支持嵌套数据结构(valueMap)
类型安全(valueString/valueInt/valueBoolean/valueNumber)
3.3 beginRendering - 触发渲染 {
"beginRendering" : {
"surfaceId" : "booking-form" ,
"root" : "title" ,
"catalogId" : "https://github.com/google/A2UI/.../standard_catalog_definition.json"
}
}
防止"闪烁":客户端缓冲组件,等待明确信号再渲染
指定根组件:从哪个组件开始构建树
指定组件目录:告诉客户端使用哪套组件定义
3.4 deleteSurface - 清理 UI {
"deleteSurface" : {
"surfaceId" : "booking-form"
}
}
四、完整数据流解析 ┌──────────────────────────────────────────────────────────────────┐
│ A2UI 数据流 │
└──────────────────────────────────────────────────────────────────┘
用户:"帮我预订明晚7点的餐厅,2人"
▼
┌─────────────────┐
│ AI Agent │ ← 接收用户请求,调用 LLM
│ (Python/Java) │
└────────┬────────┘
│ 生成 A2UI JSON (JSONL 流)
▼
┌─────────────────────────────────────────────────────────────────┐
│ {"surfaceUpdate": {"surfaceId": "booking", "components": [...]}}│
│ {"dataModelUpdate": {"surfaceId": "booking", "contents": [...]}}│
│ {"beginRendering": {"surfaceId": "booking", "root": "form"}} │
└─────────────────────────────────────────────────────────────────┘
│ 通过 SSE/WebSocket/A2A 传输
▼
┌─────────────────┐
│ Client App │ ← 解析 JSONL,构建组件缓冲
│ (Angular/Lit) │
└────────┬────────┘
│ 收到 beginRendering 后
▼
┌─────────────────┐
│ A2UI Renderer │ ← 从 root 开始递归构建组件树
│ │ ← 解析数据绑定,查询 WidgetRegistry
└────────┬────────┘
│
▼
┌─────────────────┐
│ Native UI │ ← 渲染为原生组件(Material/Cupertino等)
│ (用户可见) │
└────────┬────────┘
│ 用户点击"确认预订"按钮
▼
┌─────────────────────────────────────────────────────────────────┐
│ {"userAction": {
│ "name": "confirm_booking",
│ "surfaceId": "booking",
│ "context": {"date": "2025-12-20T19:00", "guests": 2}
│ }}
└─────────────────────────────────────────────────────────────────┘
│ 通过 A2A 消息发送回 Agent
▼
┌─────────────────┐
│ AI Agent │ ← 处理用户操作,可能更新 UI 或完成任务
└─────────────────┘
4.1 用户点击"确认预订"后发生了什么? 这是一个关键问题:A2UI 只负责 UI 层,真正的业务逻辑由 Agent 决定 。
当用户点击按钮后,Client 会将 userAction 发送回 Agent。Agent 收到后有多种处理方式:
┌─────────────────────────────────────────────────────────────────┐
│ 用户点击"确认预订"后的处理流程 │
└─────────────────────────────────────────────────────────────────┘
userAction 到达 Agent
┌───────────────┼───────────────┐
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 方案 A │ │ 方案 B │ │ 方案 C │
│ 模拟预订 │ │ 调用 API │ │ 委托子Agent │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
▼ ▼ ▼
直接返回确认UI 调用餐厅真实API 通过 A2A 委托
(Demo 场景) (MCP/HTTP) 专业预订 Agent
方案 A:模拟预订(Demo 场景) 当前示例代码采用的就是这种方式——Agent 直接生成确认 UI,不调用真实预订系统:
{
"surfaceUpdate" : {
"components" : [ { "id" : "confirm-title" , "component" : { "Text" : { "text" : { "path" : "title" } } } } , { "id" : "confirm-details" , "component" : { "Text" : { "text" : { "path" : "bookingDetails" } } } } ] } ,
"dataModelUpdate" : {
"surfaceId" : "confirmation" ,
"contents" : [ { "key" : "title" , "valueString" : "预订成功!" } , { "key" : "bookingDetails" , "valueString" : "川味轩 | 2人 | 明晚7点" } ] }
}
方案 B:调用真实 API(生产场景) 在真实生产环境中,Agent 需要调用外部服务完成预订。这可以通过以下方式实现:
def book_restaurant ( restaurant_id:str , date:str , time:str , guests:int , tool_context: ToolContext )->str :
"""调用餐厅预订 API"""
response = requests.post("https://api.restaurant.com/bookings" , json={"restaurant_id" : restaurant_id,"datetime" :f"{date} T{time} " ,"party_size" : guests }, headers={"Authorization" :f"Bearer {API_KEY} " })
return response.json()
agent = LlmAgent( tools=[get_restaurants, book_restaurant],
方式 2:通过 MCP (Model Context Protocol) 调用
mcp_client = MCPClient("restaurant-booking-server" )
result = await mcp_client.call_tool("create_booking" ,{"restaurant_id" :"chuanwei-001" ,"datetime" :"2025-12-20T19:00:00" ,"guests" :2 })
方案 C:委托专业子 Agent(多 Agent 协作) 在复杂的多 Agent 系统中,主 Agent 可能将预订任务委托给专业的预订 Agent:
┌─────────────────────────────────────────────────────────────────┐
│ 多 Agent 协作预订流程 │
└─────────────────────────────────────────────────────────────────┘
┌──────────────┐ A2A 协议 ┌──────────────────┐
│ 主 Agent │──────────>│ 预订专业 Agent │
│ (对话协调) │ │ (OpenTable集成) │
└──────────────┘ └────────┬─────────┘
│ │
│ 调用真实 API
▼
┌──────────────────┐
│ OpenTable API │
│ 或其他预订平台 │
└──────────────────┘
async def delegate_booking (booking_details:dict ):
a2a_client = A2AClient("https://booking-agent.example.com" )
response = await a2a_client.send_message({"message" :{"role" :"user" ,"parts" :[{"kind" :"data" ,"data" :{"task" :"create_booking" ,"details" : booking_details }}]}})
return response
4.2 A2UI 的职责边界 ┌─────────────────────────────────────────────────────────────────┐
│ 职责分离 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ A2UI 协议负责: │
│ ├── UI 结构描述 (surfaceUpdate) │
│ ├── 数据绑定 (dataModelUpdate) │
│ ├── 用户交互事件传递 (userAction) │
│ └── 渲染控制 (beginRendering) │
│ │
│ A2UI 协议不负责: │
│ ├── 业务逻辑执行(预订、支付等) │
│ ├── 外部 API 调用 │
│ ├── 数据持久化 │
│ └── 身份认证 │
│ │
│ 业务逻辑由 Agent 通过以下方式实现: │
│ ├── 内置 Tools(函数调用) │
│ ├── MCP 服务器 │
│ ├── A2A 委托给专业子 Agent │
│ └── 直接 HTTP/gRPC 调用 │
│ │
└─────────────────────────────────────────────────────────────────┘
简单来说:A2UI 是 UI 层协议,业务逻辑由 Agent 自行决定如何实现 。
五、标准组件目录 类别 组件 说明 布局 Row, Column, List 排列子组件 展示 Text, Image, Icon, Video, AudioPlayer, Divider 展示内容 交互 Button, TextField, CheckBox, DateTimeInput, Slider, MultipleChoice 用户输入 容器 Card, Tabs, Modal 组织内容
组件示例:动态列表
{
"surfaceUpdate" : {
"components" : [ { "id" : "restaurant-list" , "component" : { "List" : { "children" : { "template" : { "dataBinding" : "/restaurants" , "componentId" : "restaurant-card-template" } } } } , { "id" : "restaurant-card-template" , "component" : { "Card" : { "child" : "card-content" } } } ] }
}
}
六、组件目录协商机制 这是 A2UI 安全模型的核心:Agent 如何知道可以使用哪些组件?
6.1 协商流程 ┌─────────────────────────────────────────────────────────────────┐
│ 组件目录协商流程 │
└─────────────────────────────────────────────────────────────────┘
步骤 1: Agent 在 Agent Card 中声明支持的目录
↓
┌─────────────────────────────────────────────────────────────────┐
│ {
│ "name": "Restaurant Finder",
│ "capabilities": {
│ "extensions": [{
│ "uri": "https://a2ui.org/a2a-extension/a2ui/v0.8",
│ "params": {
│ "supportedCatalogIds": [
│ "https://github.com/google/A2UI/.../standard_catalog",
│ "https://my-company.com/custom_catalog"
│ ],
│ "acceptsInlineCatalogs": true
│ }
│ }]
│ }
│ }
└─────────────────────────────────────────────────────────────────┘
步骤 2: Client 在每条消息中声明自己支持的目录
↓
┌─────────────────────────────────────────────────────────────────┐
│ {
│ "metadata": {
│ "a2uiClientCapabilities": {
│ "supportedCatalogIds": [
│ "https://github.com/google/A2UI/.../standard_catalog"
│ ],
│ "inlineCatalogs": [
│ {
│ "catalogId": "my-app:custom-charts",
│ "components": {
│ "PieChart": { "type": "object", "properties": {...}}
│ }
│ }
│ ]
│ }
│ },
│ "message": { "prompt": { "text": "找餐厅" } }
│ }
└─────────────────────────────────────────────────────────────────┘
步骤 3: Agent 选择双方都支持的目录,在 beginRendering 中指定
↓
┌─────────────────────────────────────────────────────────────────┐
│ {
│ "beginRendering": {
│ "surfaceId": "main",
│ "catalogId": "https://github.com/google/A2UI/.../standard",
│ "root": "root-component"
│ }
│ }
└─────────────────────────────────────────────────────────────────┘
6.2 LLM 如何被约束只使用已知组件? 关键在于 Prompt Engineering + JSON Schema 约束 :
catalog = load_catalog("standard_catalog_definition.json" )
resolved_schema = {
"properties" :{
"surfaceUpdate" :{
"properties" :{
"components" :{
"items" :{
"properties" :{
"component" :{
"properties" : catalog["components" ]
}
}
}
}
}
}
}
}
response = llm.generate(
prompt="生成一个餐厅预订表单" ,
response_schema=resolved_schema
)
层级 约束方式 说明 LLM 层 JSON Schema / Structured Output 现代 LLM(GPT-4、Gemini)支持强制输出符合 Schema 的 JSON Agent 层 Prompt 中包含组件目录 告诉 LLM 可用的组件类型和属性 协议层 目录协商 Client 声明支持的目录,Agent 只能选择其中之一 渲染层 组件白名单 Client 渲染器只渲染已注册的组件类型
6.3 如果 Agent 发送了未知组件会怎样?
{
"surfaceUpdate" : {
"components" : [ { "id" : "evil" , "component" : { "ScriptExecutor" : {
"code" : "alert('hacked')" } } }
]
}
}
忽略未知组件 :渲染器在 WidgetRegistry 中找不到 ScriptExecutor,跳过该组件
显示占位符 :渲染一个错误提示组件
发送错误消息 :通过 error 消息通知 Agent
{
"error" : {
"type" : "unknown_component" ,
"componentId" : "evil" ,
"componentType" : "ScriptExecutor" ,
"message" : "Component type 'ScriptExecutor' is not in the supported catalog"
}
}
6.4 自定义组件的安全扩展 如果业务需要自定义组件(如图表、地图),流程如下:
┌─────────────────────────────────────────────────────────────────┐
│ 自定义组件安全流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. Client 开发者实现自定义组件(本地代码,完全可控) │
│ class PieChartComponent { render(data) { ... } } │
│ │
│ 2. 在 WidgetRegistry 中注册 │
│ registry.register("PieChart", PieChartComponent) │
│ │
│ 3. 定义组件 Schema,加入自定义目录 │
│ { "PieChart": { "properties": { "data": {...} } } } │
│ │
│ 4. 在 a2uiClientCapabilities 中声明支持该目录 │
│ │
│ 5. Agent 现在可以安全地使用 PieChart 组件 │
│ │
└─────────────────────────────────────────────────────────────────┘
安全保证 :自定义组件的实现代码在 Client 侧,Agent 只能传递数据参数,无法注入逻辑。
七、安全模型总结 ┌────────────────────────────────────────────────────────┐
│ A2UI 安全边界 │
├────────────────────────────────────────────────────────┤
│ Agent 侧(不可信) │ Client 侧(可信) │
│ ───────────────── │ ───────────────── │
│ • 生成 JSON 描述 │ • 定义组件目录 │
│ • 只能引用已知组件类型 │ • 实现组件渲染逻辑 │
│ • 无法执行任意代码 │ • 控制样式和行为 │
│ • 受 JSON Schema 约束 │ • 验证数据绑定 │
└────────────────────────────────────────────────────────┘
声明式数据 :Agent 发送的是数据,不是代码
组件白名单 :只能使用客户端预定义的组件
目录协商 :双向声明,取交集
Schema 约束 :LLM 输出受 JSON Schema 强制约束
无 eval/innerHTML :客户端渲染器不执行任意字符串
数据绑定验证 :路径解析在客户端控制
八、总结 A2UI 通过精巧的协议设计,解决了 AI Agent 生成 UI 的核心挑战:
挑战 A2UI 解决方案 安全性 声明式 JSON + 组件白名单 LLM 生成难度 邻接表模型 + 流式传输 跨平台 抽象组件 + 客户端渲染 性能 数据/结构分离 + 增量更新
如果你正在构建 AI Agent 应用,A2UI 值得深入研究。它代表了 Agent UI 领域的最佳实践。
相关免费在线工具 RSA密钥对生成器 生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
Mermaid 预览与可视化编辑 基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
随机西班牙地址生成器 随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online
curl 转代码 解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online