如果说ChatGPT让AI学会了说话,那么A2UI就是在教AI学会"画画"------不是艺术创作,而是绘制用户界面
开篇:一个让人头疼的技术难题
想象一下,你正在和一个AI餐厅助手对话,想预订明晚7点的晚餐。传统的对话式AI会像个话痨一样问东问西:
AI: 好的,请告诉我用餐人数
你: 2个人
AI: 请告诉我日期
你: 明天
AI: 请告诉我时间
你: 晚上7点
AI: 有什么特殊需求吗?
你: 靠窗的位置...
这种"二十个问题"式的交互让人抓狂。但如果AI能直接"画"出一个预订表单呢?日期选择器、时间滑块、人数输入框,一应俱全,你只需点击和填写就能完成预订。听起来很美好,对吧?
但问题来了:如何让AI安全地生成这样的界面?
直接让AI生成HTML或JavaScript代码?那简直是在玩火。谁知道AI会不会在代码里藏个<script>标签,把你的用户数据偷偷发送到某个神秘服务器?或者更糟糕的是,AI可能会生成一段看似无害但实际上会让你的网站崩溃的代码。
这就是Google开源的A2UI(Agent-to-User Interface)项目要解决的核心问题:让AI智能体能够生成丰富的交互界面,同时保证绝对的安全性。
一、A2UI是什么?一个"翻译官"的故事
1.1 核心理念:数据的安全,代码的表达力
A2UI的设计哲学可以用一句话概括:像数据一样安全,像代码一样强大。
这听起来有点矛盾,但这正是A2UI的精妙之处。让我用一个比喻来解释:
传统的做法就像让AI直接给你写一份菜谱(代码),然后你照着做。问题是,你不知道这份菜谱里会不会有毒。
而A2UI的做法是:AI只能从一个预先审核过的"菜单"(组件目录)里选择菜品,然后告诉你"我要一份宫保鸡丁、一碗米饭、一杯可乐"(声明式数据)。你的厨房(渲染器)负责把这些菜品做出来。AI永远无法让你做菜单上没有的东西。
1.2 技术架构:三个关键角色
A2UI的架构非常清晰,涉及三个关键角色:
1. Agent(智能体):负责生成A2UI消息的服务端程序。它可以是基于Gemini、GPT、Claude等任何LLM的智能体。
2. Transport(传输层):负责将A2UI消息从智能体传递到客户端。可以是A2A协议、WebSocket、SSE等任何能传输JSON的方式。
3. Renderer(渲染器):客户端库,负责将A2UI的JSON消息转换为原生UI组件。目前支持Web Components(Lit)、Angular、Flutter等框架。
整个流程就像一条流水线:
用户输入 → 智能体处理 → LLM生成A2UI JSON → 传输到客户端 → 渲染器转换为原生组件 → 用户看到界面
1.3 为什么不直接生成HTML?
这是个好问题。很多人第一反应是:为什么不让AI直接生成HTML或React代码?
原因有三个:
安全性:HTML和JavaScript是可执行代码,AI生成的代码可能包含XSS攻击、数据泄露等安全隐患。而A2UI是纯数据格式,不包含任何可执行代码。
跨平台性:HTML只能在Web上运行。而A2UI的同一份JSON可以在Web、Flutter、React Native、SwiftUI等多个平台上渲染,每个平台使用自己的原生组件。
可维护性:AI生成的代码质量参差不齐,难以维护。而A2UI的声明式结构清晰,易于理解和调试。
二、核心技术解析:邻接表模型的巧妙设计
2.1 传统嵌套结构的噩梦
在深入A2UI之前,我们先看看传统的UI描述方式有什么问题。
传统的UI通常用嵌套的树形结构表示:
{
"type": "Column",
"children": [
{
"type": "Text",
"text": "欢迎"
},
{
"type": "Row",
"children": [
{
"type": "Button",
"text": "取消"
},
{
"type": "Button",
"text": "确定"
}
]
}
]
}
这种结构对人类来说很直观,但对LLM来说是个噩梦:
-
必须一次性生成完美的嵌套结构:LLM必须在开始输出之前就规划好整个树形结构,这对流式生成非常不友好。
-
难以增量更新:如果要修改深层嵌套的某个组件,必须重新生成整个父节点,甚至整个树。
-
容易出错:括号匹配、缩进层级,任何一个小错误都会导致整个JSON无效。
2.2 邻接表模型:扁平化的智慧
A2UI采用了一个非常聪明的设计:邻接表模型(Adjacency List Model)。
简单来说,就是把树形结构"拍扁",用ID引用来表示父子关系:
{
"surfaceUpdate": {
"components": [
{
"id": "root",
"component": {
"Column": {
"children": {"explicitList": ["greeting", "buttons"]}
}
}
},
{
"id": "greeting",
"component": {
"Text": {"text": {"literalString": "欢迎"}}
}
},
{
"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": "取消"}}
}
},
{
"id": "ok-btn",
"component": {
"Button": {
"child": "ok-text",
"action": {"name": "ok"}
}
}
},
{
"id": "ok-text",
"component": {
"Text": {"text": {"literalString": "确定"}}
}
}
]
}
}
看起来更长了?但这种设计带来了巨大的优势:
✅ 流式生成友好:LLM可以一个组件一个组件地输出,不需要提前规划整个结构。
✅ 增量更新简单:要修改某个组件?只需发送一个新的同ID组件定义即可,无需重新生成整个树。
✅ 容错性强:即使某个组件定义有问题,也不会影响其他组件的渲染。
✅ 易于理解:每个组件都是独立的,结构清晰,便于调试和维护。
2.3 实战案例:餐厅列表的生成
让我们看一个真实的例子。假设用户问:"给我推荐5家中餐厅"。
智能体会这样生成UI:
第一步:定义UI结构
{
"surfaceUpdate": {
"surfaceId": "default",
"components": [
{
"id": "root-column",
"component": {
"Column": {
"children": {"explicitList": ["title-heading", "item-list"]}
}
}
},
{
"id": "title-heading",
"component": {
"Text": {
"usageHint": "h1",
"text": {"path": "/title"}
}
}
},
{
"id": "item-list",
"component": {
"List": {
"direction": "vertical",
"children": {
"template": {
"componentId": "item-card-template",
"dataBinding": "/items"
}
}
}
}
},
{
"id": "item-card-template",
"component": {"Card": {"child": "card-layout"}}
}
// ... 更多组件定义
]
}
}
注意这里的巧妙设计:item-list使用了模板机制 (template),它会根据/items数组中的数据自动生成多个卡片。这样,无论有5家餐厅还是50家餐厅,组件定义都是一样的。
第二步:填充数据
{
"dataModelUpdate": {
"surfaceId": "default",
"path": "/",
"contents": [
{
"key": "title",
"valueString": "为您推荐的中餐厅"
},
{
"key": "items",
"valueMap": [
{
"key": "0",
"valueMap": [
{"key": "name", "valueString": "老北京炸酱面"},
{"key": "rating", "valueNumber": 4.8},
{"key": "detail", "valueString": "正宗老北京风味"},
{"key": "imageUrl", "valueString": "https://example.com/restaurant1.jpg"}
]
},
{
"key": "1",
"valueMap": [
{"key": "name", "valueString": "川味小馆"},
{"key": "rating", "valueNumber": 4.6},
{"key": "detail", "valueString": "麻辣鲜香,回味无穷"}
]
}
// ... 更多餐厅数据
]
}
]
}
}
第三步:触发渲染
{
"beginRendering": {
"surfaceId": "default",
"root": "root-column"
}
}
这三个步骤可以分别发送,也可以合并发送。客户端会缓冲这些消息,直到收到beginRendering才开始渲染。
三、数据绑定:结构与状态的优雅分离
3.1 JSON Pointer:简单而强大的路径系统
A2UI使用JSON Pointer(RFC 6901)来实现数据绑定。这是一个非常简单但强大的标准。
语法很直观:
-
/user/name- 访问对象属性 -
/items/0- 访问数组第一个元素(从0开始) -
/items/0/price- 访问嵌套路径
例如,对于这样的数据模型:
{
"user": {
"name": "张三",
"email": "zhangsan@example.com"
},
"cart": {
"items": [
{"name": "商品A", "price": 99.9, "quantity": 2}
],
"total": 199.8
}
}
你可以这样绑定:
{
"id": "username",
"component": {
"Text": {
"text": {"path": "/user/name"}
}
}
}
当/user/name从"张三"变成"李四"时,文本会自动更新为"李四",无需重新定义组件。
3.2 响应式更新:UI自动跟随数据变化
这种设计的精妙之处在于:UI结构和应用状态完全分离。
想象一个订单状态显示:
{
"id": "order-status",
"component": {
"Text": {
"text": {"path": "/order/status"}
}
}
}
初始状态:/order/status = "处理中..." → 显示"处理中..."
智能体发送更新:
{
"dataModelUpdate": {
"surfaceId": "default",
"path": "/order",
"contents": [
{"key": "status", "valueString": "已发货"}
]
}
}
界面自动更新为"已发货",无需重新定义任何组件!
3.3 动态列表:模板的魔法
前面提到的模板机制是A2UI最强大的特性之一。
假设你有一个商品列表:
{
"id": "product-list",
"component": {
"Column": {
"children": {
"template": {
"dataBinding": "/products",
"componentId": "product-card"
}
}
}
}
}
这个定义会为/products数组中的每个元素渲染一个product-card。
更巧妙的是,在模板内部,路径是相对于当前数组元素的:
{
"id": "product-name",
"component": {
"Text": {
"text": {"path": "/name"}
}
}
}
对于/products/0,/name会解析为/products/0/name → "商品A" 对于/products/1,/name会解析为/products/1/name → "商品B"
添加或删除数组元素时,渲染器会自动添加或删除对应的UI组件。这种机制让动态列表的处理变得异常简单。
3.4 双向绑定:用户输入自动同步
对于交互式组件,A2UI支持双向绑定:
{
"id": "name-input",
"component": {
"TextField": {
"label": {"literalString": "姓名"},
"text": {"path": "/form/name"}
}
}
}
当用户在输入框中输入"Alice"时,/form/name会自动更新为"Alice"。
当用户点击提交按钮时,智能体可以通过action获取完整的表单数据:
{
"id": "submit-btn",
"component": {
"Button": {
"child": "submit-text",
"action": {
"name": "submit_form",
"context": [
{
"key": "formData",
"value": {"path": "/form"}
}
]
}
}
}
}
这样,智能体就能获取到用户填写的所有表单数据,无需手动收集。
四、实战演练:从零构建餐厅预订流程
让我们通过一个完整的实战案例,看看A2UI如何处理一个真实的业务场景。
4.1 场景设定
用户说:"我想预订明天晚上7点的晚餐,2个人。"
智能体需要:
-
展示餐厅列表供用户选择
-
生成预订表单让用户填写详细信息
-
确认预订并显示确认页面
4.2 第一步:展示餐厅列表
智能体调用get_restaurants工具获取餐厅数据,然后生成UI:
# 智能体的Python代码(简化版)
async def handle_restaurant_search(query, session_id):
# 调用工具获取餐厅数据
restaurants = await get_restaurants(cuisine="中餐", location="附近", count=5)
# 生成A2UI消息
messages = [
{
"surfaceUpdate": {
"surfaceId": "default",
"components": [
# 定义列表组件
{
"id": "restaurant-list",
"component": {
"List": {
"children": {
"template": {
"componentId": "restaurant-card",
"dataBinding": "/restaurants"
}
}
}
}
},
# 定义卡片模板
{
"id": "restaurant-card",
"component": {
"Card": {"child": "card-content"}
}
}
# ... 更多组件定义
]
}
},
{
"dataModelUpdate": {
"surfaceId": "default",
"path": "/",
"contents": [
{
"key": "restaurants",
"valueMap": [
# 填充餐厅数据
{"key": "0", "valueMap": [
{"key": "name", "valueString": restaurants[0]["name"]},
{"key": "rating", "valueNumber": restaurants[0]["rating"]}
]}
]
}
]
}
},
{"beginRendering": {"surfaceId": "default", "root": "restaurant-list"}}
]
return messages
4.3 第二步:生成预订表单
用户点击某个餐厅的"立即预订"按钮,触发book_restaurant action。智能体收到action后,生成预订表单:
[
{
"beginRendering": {
"surfaceId": "booking-form",
"root": "booking-form-column"
}
},
{
"surfaceUpdate": {
"surfaceId": "booking-form",
"components": [
{
"id": "booking-form-column",
"component": {
"Column": {
"children": {
"explicitList": [
"booking-title",
"restaurant-image",
"party-size-field",
"datetime-field",
"dietary-field",
"submit-button"
]
}
}
}
},
{
"id": "booking-title",
"component": {
"Text": {
"usageHint": "h2",
"text": {"path": "/title"}
}
}
},
{
"id": "party-size-field",
"component": {
"TextField": {
"label": {"literalString": "用餐人数"},
"text": {"path": "/partySize"},
"type": "number"
}
}
},
{
"id": "datetime-field",
"component": {
"DateTimeInput": {
"label": {"literalString": "日期和时间"},
"value": {"path": "/reservationTime"},
"enableDate": true,
"enableTime": true
}
}
},
{
"id": "submit-button",
"component": {
"Button": {
"child": "submit-text",
"action": {
"name": "submit_booking",
"context": [
{
"key": "bookingData",
"value": {"path": "/"}
}
]
}
}
}
}
]
}
},
{
"dataModelUpdate": {
"surfaceId": "booking-form",
"path": "/",
"contents": [
{"key": "title", "valueString": "预订:老北京炸酱面"},
{"key": "restaurantName", "valueString": "老北京炸酱面"},
{"key": "partySize", "valueString": "2"},
{"key": "reservationTime", "valueString": "2025-12-22T19:00:00Z"},
{"key": "dietary", "valueString": ""}
]
}
}
]
注意这里的细节:
-
智能体根据用户的原始请求预填了
partySize(2人)和reservationTime(明天晚上7点) -
用户可以修改这些值,修改会自动同步到数据模型
-
提交按钮的action会携带完整的表单数据
4.4 第三步:确认预订
用户填写完表单并点击提交,智能体收到submit_booking action和完整的表单数据。智能体处理预订逻辑后,生成确认页面:
[
{
"beginRendering": {
"surfaceId": "confirmation",
"root": "confirmation-card"
}
},
{
"surfaceUpdate": {
"surfaceId": "confirmation",
"components": [
{
"id": "confirmation-card",
"component": {"Card": {"child": "confirmation-column"}}
},
{
"id": "confirmation-column",
"component": {
"Column": {
"children": {
"explicitList": [
"confirm-title",
"confirm-image",
"divider1",
"confirm-details",
"divider2",
"confirm-text"
]
}
}
}
},
{
"id": "confirm-title",
"component": {
"Text": {
"usageHint": "h2",
"text": {"path": "/title"}
}
}
},
{
"id": "confirm-details",
"component": {
"Text": {
"text": {"path": "/bookingDetails"}
}
}
},
{
"id": "confirm-text",
"component": {
"Text": {
"usageHint": "h5",
"text": {"literalString": "期待您的光临!"}
}
}
},
{
"id": "divider1",
"component": {"Divider": {}}
}
]
}
},
{
"dataModelUpdate": {
"surfaceId": "confirmation",
"path": "/",
"contents": [
{"key": "title", "valueString": "预订成功:老北京炸酱面"},
{"key": "bookingDetails", "valueString": "2人 · 2025年12月22日 19:00"},
{"key": "imageUrl", "valueString": "https://example.com/restaurant.jpg"}
]
}
}
]
整个流程行云流水,用户体验流畅自然。
4.5 代码实现:智能体端的关键逻辑
让我们看看智能体端的核心代码(基于Google ADK):
class RestaurantAgent:
def __init__(self, base_url: str, use_ui: bool = False):
self.base_url = base_url
self.use_ui = use_ui
self._agent = self._build_agent(use_ui)
# 加载A2UI Schema用于验证
self.a2ui_schema_object = json.loads(A2UI_SCHEMA)
def _build_agent(self, use_ui: bool) -> LlmAgent:
"""构建LLM智能体"""
if use_ui:
# 构建包含UI指令、示例和Schema的完整提示词
instruction = AGENT_INSTRUCTION + get_ui_prompt(
self.base_url, RESTAURANT_UI_EXAMPLES
)
else:
instruction = get_text_prompt()
return LlmAgent(
model=LiteLlm(model="gemini/gemini-2.5-flash"),
name="restaurant_agent",
description="帮助用户查找餐厅和预订座位的智能体",
instruction=instruction,
tools=[get_restaurants] # 注册工具
)
关键点在于:
-
提示词工程:智能体的指令中包含了A2UI的使用方法、示例和Schema
-
工具集成 :智能体可以调用
get_restaurants等工具获取数据 -
Schema验证:生成的A2UI消息会经过Schema验证,确保格式正确
智能体的提示词大致是这样的:
你是一个餐厅查找助手。你的目标是帮助用户查找和预订餐厅,使用丰富的UI界面。
你必须遵循以下逻辑:
1. 查找餐厅时:
a. 调用 get_restaurants 工具
b. 根据餐厅数量选择合适的UI模板(单列列表或双列列表)
c. 生成符合A2UI Schema的JSON消息
2. 预订餐厅时:
a. 使用预订表单模板
b. 用用户提供的信息预填表单
c. 生成符合A2UI Schema的JSON消息
3. 确认预订时:
a. 使用确认页面模板
b. 显示预订详情
c. 生成符合A2UI Schema的JSON消息
以下是UI示例:
[这里会插入具体的A2UI JSON示例]
以下是A2UI Schema:
[这里会插入完整的JSON Schema]
这种方式让LLM能够理解如何生成正确的A2UI消息。
五、渐进式渲染:流式体验的秘密
5.1 为什么需要流式渲染?
传统的UI生成方式是:等待AI生成完整的响应 → 一次性渲染整个界面。
这种方式的问题是:用户需要盯着加载动画等待,体验很差。
A2UI支持渐进式渲染(Progressive Rendering):
智能体生成第一个组件 → 客户端立即渲染
智能体生成第二个组件 → 客户端追加渲染
智能体生成第三个组件 → 客户端追加渲染
...
用户可以看到界面逐步构建的过程,就像看着一幅画逐渐完成,体验更加流畅。
5.2 JSONL格式:流式传输的载体
A2UI使用JSONL(JSON Lines)格式进行流式传输。每一行是一个完整的JSON对象:
{"surfaceUpdate":{"surfaceId":"main","components":[...]}}
{"dataModelUpdate":{"surfaceId":"main","contents":[...]}}
{"beginRendering":{"surfaceId":"main","root":"root-component"}}
这种格式的优势:
-
每行都是独立的、完整的JSON对象,易于解析
-
支持流式传输,可以逐行发送和接收
-
容错性好,某一行出错不影响其他行
5.3 消息缓冲与批量渲染
客户端渲染器会智能地处理消息流:
-
消息缓冲 :收到
surfaceUpdate和dataModelUpdate时先缓存,不立即渲染 -
等待信号 :直到收到
beginRendering消息才开始渲染 -
批量更新:将16ms内的多个更新合并为一次渲染,避免频繁重绘
-
增量更新:只更新变化的组件,不重新渲染整个界面
这种机制既保证了流畅的用户体验,又优化了性能。
六、安全性设计:信任边界的艺术
6.1 组件目录:白名单机制
A2UI的安全性核心在于组件目录(Component Catalog)机制。
客户端维护一个预先审核的组件白名单:
// 客户端的组件目录
const componentCatalog = {
'Text': TextComponent,
'Button': ButtonComponent,
'TextField': TextFieldComponent,
'Card': CardComponent,
'Image': ImageComponent,
// ... 更多预定义组件
};
智能体只能使用这个目录中的组件。如果智能体尝试使用不存在的组件,渲染器会直接忽略或报错。
这就像餐厅的菜单:AI只能从菜单上点菜,无法要求厨师做菜单上没有的菜品。
6.2 自定义组件:可控的扩展性
但如果你需要特殊的组件怎么办?比如图表、地图、视频播放器?
A2UI支持自定义组件,但控制权完全在客户端:
// 客户端注册自定义组件
componentCatalog.register('GoogleMap', {
component: GoogleMapComponent,
props: {
center: { type: 'object', required: true },
zoom: { type: 'number', default: 10 },
markers: { type: 'array', default: [] }
},
// 安全策略:限制地图只能显示特定区域
validate: (props) => {
if (props.zoom > 20) throw new Error('缩放级别过高');
return true;
}
});
然后将这个自定义组件广告给智能体:
{
"customComponents": [
{
"type": "GoogleMap",
"description": "显示Google地图",
"properties": {
"center": "地图中心坐标 {lat, lng}",
"zoom": "缩放级别 (1-20)",
"markers": "标记点数组"
}
}
]
}
智能体看到这个广告后,就知道可以使用GoogleMap组件了。但关键是:
-
组件的实现完全由客户端控制
-
客户端可以添加任何安全策略(沙箱、权限检查等)
-
智能体无法绕过这些限制
6.3 无代码执行:纯数据传输
A2UI的另一个安全保障是:整个协议中没有任何可执行代码。
传输的全是JSON数据:
{
"component": {
"Button": {
"child": "button-text",
"action": {"name": "submit"}
}
}
}
这里的action只是一个字符串标识符,不是可执行的JavaScript函数。客户端收到后,会映射到预定义的处理函数:
// 客户端的action处理器
const actionHandlers = {
'submit': handleSubmit,
'cancel': handleCancel,
'book_restaurant': handleBookRestaurant
};
// 当用户点击按钮时
function onButtonClick(action) {
const handler = actionHandlers[action.name];
if (handler) {
handler(action.context);
}
}
即使智能体发送了恶意的action名称,最坏的情况也只是找不到对应的处理器,不会执行任何危险代码。
6.4 跨信任边界:远程智能体的安全通信
A2UI特别适合跨信任边界的场景。
想象这样的架构:
主智能体(你的服务器)
↓ 调用
远程智能体A(第三方旅游服务)
↓ 返回A2UI消息
主智能体
↓ 转发(可选择性修改)
客户端渲染器
在这个场景中:
-
远程智能体A是第三方服务,你不完全信任它
-
但你需要它提供的专业能力(比如旅游规划)
-
A2UI让你可以安全地使用它的UI响应
主智能体可以:
-
透传:直接转发远程智能体的A2UI消息
-
过滤:移除某些敏感组件或数据
-
修改:调整样式、添加水印等
-
验证:检查消息是否符合安全策略
这种灵活性让多智能体协作变得安全可控。
七、跨平台渲染:一次生成,到处运行
7.1 框架无关的设计哲学
A2UI最令人兴奋的特性之一是:同一份JSON可以在不同平台上渲染。
智能体生成的A2UI消息:
{
"id": "welcome-card",
"component": {
"Card": {
"child": "card-content"
}
}
}
可以渲染为:
-
Web :
<div class="card">...</div>(使用Lit或Angular) -
Flutter :
Card(child: ...)(使用GenUI SDK) -
React :
<Card>...</Card>(即将支持) -
SwiftUI :
CardView { ... }(计划中) -
Jetpack Compose :
Card { ... }(计划中)
每个平台使用自己的原生组件,保持原生的外观和性能。
7.2 现有渲染器生态
目前A2UI已经支持的渲染器:
1. Lit(Web Components)
-
框架无关,可以在任何Web项目中使用
-
轻量级,性能优秀
-
支持Shadow DOM,样式隔离
2. Angular
-
完整的Angular集成
-
支持Angular的依赖注入和生命周期
-
类型安全
3. Flutter(GenUI SDK)
-
跨平台:iOS、Android、Web、Desktop
-
使用Flutter原生组件
-
性能接近原生应用
4. React(开发中)
-
预计2026年Q1发布
-
基于Hooks的API
-
完整的TypeScript支持
7.3 构建自己的渲染器
如果你想为自己喜欢的框架构建渲染器,需要实现以下功能:
核心功能:
-
解析A2UI JSON消息
-
处理邻接表结构,构建组件树
-
实现数据绑定和响应式更新
-
处理用户交互和action
-
支持模板和动态列表
关键接口:
interface A2UIRenderer {
// 处理消息流
processMessage(message: A2UIMessage): void;
// 渲染surface
renderSurface(surfaceId: string, rootId: string): void;
// 更新数据模型
updateDataModel(surfaceId: string, path: string, value: any): void;
// 处理用户action
handleAction(action: Action): void;
// 注册自定义组件
registerComponent(type: string, component: Component): void;
}
A2UI的文档提供了详细的渲染器开发指南,帮助你快速上手。
八、实际应用场景:A2UI能做什么?
8.1 动态表单生成
场景:企业审批系统,不同类型的审批需要不同的表单。
传统做法:为每种审批类型硬编码一个表单页面,维护成本高。
A2UI做法:智能体根据审批类型动态生成表单。
用户:"我要申请出差"
智能体:生成出差申请表单(日期、目的地、预算等)
用户:"我要申请采购"
智能体:生成采购申请表单(物品、数量、供应商等)
同一个智能体,根据上下文生成不同的表单,无需预先定义所有可能的表单类型。
8.2 数据可视化
场景:用户问"显示上个月的销售数据"。
智能体可以:
-
查询数据库获取销售数据
-
判断数据适合用什么图表展示(折线图、柱状图、饼图)
-
生成包含图表组件的A2UI消息
{
"id": "sales-chart",
"component": {
"ChartComponent": {
"type": "line",
"data": {"path": "/salesData"},
"options": {
"title": "上月销售趋势",
"xAxis": "日期",
"yAxis": "销售额"
}
}
}
}
用户看到的是一个漂亮的交互式图表,而不是一堆数字。
8.3 多智能体协作
场景:旅游规划助手,需要协调多个专业智能体。
主智能体(协调者)
├─ 航班智能体(查询和预订航班)
├─ 酒店智能体(查询和预订酒店)
└─ 景点智能体(推荐景点和活动)
每个子智能体返回自己的A2UI消息,主智能体将它们组合成一个完整的旅游规划界面:
┌─────────────────────────────────┐
│ 您的北京3日游计划 │
├─────────────────────────────────┤
│ 航班信息(航班智能体生成) │
│ [航班卡片] [航班卡片] │
├─────────────────────────────────┤
│ 酒店推荐(酒店智能体生成) │
│ [酒店卡片] [酒店卡片] │
├─────────────────────────────────┤
│ 景点推荐(景点智能体生成) │
│ [景点卡片] [景点卡片] │
└─────────────────────────────────┘
每个智能体专注于自己的领域,通过A2UI协议无缝协作。
8.4 自适应工作流
场景:客户服务智能体,根据问题类型展示不同的处理流程。
用户:"我的订单还没到"
智能体:
1. 查询订单状态
2. 生成订单追踪界面(时间线组件)
3. 提供"联系快递"和"申请退款"按钮
用户:"我想退货"
智能体:
1. 检查退货政策
2. 生成退货申请表单
3. 上传照片、选择退货原因等
同一个智能体,根据用户意图展示完全不同的界面和流程。
8.5 个性化仪表板
场景:企业BI系统,每个用户需要不同的数据视图。
智能体可以根据用户角色、权限、偏好动态生成仪表板:
销售经理:销售漏斗、团队业绩、客户分布
财务总监:收入报表、成本分析、现金流
产品经理:用户增长、功能使用率、反馈统计
无需为每个角色硬编码仪表板,智能体根据上下文动态生成。
九、技术对比:A2UI vs 其他方案
9.1 vs 传统模板引擎
传统模板引擎(如Jinja、Handlebars):
<div class="card">
<h2>{{ restaurant.name }}</h2>
<p>评分: {{ restaurant.rating }}</p>
</div>
优点:简单直观 缺点:
-
需要预先定义所有模板
-
难以动态调整结构
-
不支持跨平台
A2UI:
{
"id": "restaurant-card",
"component": {
"Card": {
"child": "card-content"
}
}
}
优点:
-
LLM可以动态生成任意结构
-
跨平台支持
-
安全性更高
9.2 vs Server-Driven UI
Server-Driven UI(如Airbnb的方案):
服务器返回UI描述,客户端渲染。听起来和A2UI很像?
关键区别:
-
Server-Driven UI:通常由人工编写UI描述,适合A/B测试、动态配置
-
A2UI:专为LLM设计,支持流式生成、增量更新,更适合AI生成场景
A2UI可以看作是"AI-Driven UI"。
9.3 vs 直接生成代码
直接生成代码(如GPT生成React组件):
function RestaurantCard({ name, rating }) {
return (
<div className="card">
<h2>{name}</h2>
<p>评分: {rating}</p>
</div>
);
}
优点:灵活性极高 缺点:
-
安全风险:可能包含恶意代码
-
质量不稳定:AI生成的代码质量参差不齐
-
难以维护:生成的代码可能不符合项目规范
-
不可控:无法限制AI的行为
A2UI:
-
安全:只能使用预定义组件
-
稳定:Schema验证确保格式正确
-
可维护:声明式结构清晰
-
可控:客户端完全掌控渲染逻辑
9.4 vs 纯文本响应
纯文本响应(传统聊天机器人):
AI: 为您找到以下餐厅:
1. 老北京炸酱面 - 评分4.8 - 地址:xxx
2. 川味小馆 - 评分4.6 - 地址:xxx
3. ...
优点:简单、通用 缺点:
-
交互性差:无法点击、筛选、排序
-
信息密度低:大量文本难以快速浏览
-
用户体验差:无法提供表单、图片等丰富元素
A2UI:
-
交互性强:按钮、表单、选择器等
-
信息密度高:卡片、列表、图表等
-
用户体验好:接近原生应用的体验
十、未来展望:A2UI的发展方向
10.1 协议演进:v0.9和v1.0
A2UI目前是v0.8版本,处于公开预览阶段。
v0.9(开发中):
-
改进主题支持,让智能体能够控制UI样式
-
优化开发者体验,简化API
-
增强性能优化机制
v1.0(2026年Q4):
-
稳定性保证,向后兼容承诺
-
完整的测试套件
-
渲染器认证计划
10.2 更多渲染器
即将支持的平台:
-
React(2026 Q1):基于Hooks的API,完整TypeScript支持
-
SwiftUI(2026 Q2):iOS和macOS原生支持
-
Jetpack Compose(2026 Q2):Android原生支持
-
Vue(社区需求):Vue 3组合式API
-
ShadCN(社区需求):基于Radix UI的React组件库
目标是覆盖所有主流UI框架。
10.3 高级UI模式
未来可能支持的特性:
-
拖拽操作:让用户可以拖拽组件重新排列
-
手势和动画:更丰富的交互效果
-
3D渲染:支持Three.js等3D库
-
AR/VR界面(探索性):为元宇宙做准备
10.4 无障碍支持
计划中的无障碍特性:
-
自动生成ARIA属性
-
屏幕阅读器优化
-
键盘导航标准
-
对比度和颜色指导
让AI生成的界面也能被所有人使用。
10.5 生态系统建设
社区期待的功能:
-
组件市场:分享和复用自定义组件
-
示例库:各种场景的A2UI示例
-
评估数据集:用于测试和优化AI生成的UI质量
-
开发工具:可视化编辑器、调试工具等
10.6 与其他协议的集成
A2UI已经与多个协议和框架集成:
A2A Protocol :智能体间通信的标准协议,A2UI是其UI扩展 AG UI / CopilotKit :全栈React应用框架,原生支持A2UI GenUI SDK:Flutter的AI UI框架,基于A2UI构建
未来可能集成:
-
MCP(Model Context Protocol):模型上下文协议
-
LangGraph:多智能体编排框架
-
CrewAI:协作智能体框架
十一、快速上手:5分钟体验A2UI
11.1 运行官方Demo
想要快速体验A2UI?跟着这个步骤:
1. 克隆仓库
git clone https://github.com/google/a2ui.git
cd a2ui
2. 设置API密钥
export GEMINI_API_KEY="your_gemini_api_key_here"
(从Google AI Studio免费获取)
3. 运行Demo
cd samples/client/lit
npm install
npm run demo:all
这个命令会:
-
安装依赖
-
构建Lit渲染器
-
启动Python智能体后端
-
启动开发服务器
-
自动打开浏览器
4. 尝试对话
在浏览器中尝试这些提示:
-
"预订2人的餐桌"
-
"找附近的意大利餐厅"
-
"你们的营业时间是?"
看看智能体如何生成不同的UI界面!
11.2 查看组件库
想看看A2UI支持哪些组件?运行组件库Demo:
npm start -- gallery
这会展示所有标准组件(Card、Button、TextField、Timeline等)的实时示例和代码。
11.3 探索代码
想了解实现细节?查看这些目录:
-
智能体代码 :
samples/agent/adk/restaurant_finder/ -
客户端代码 :
samples/client/lit/ -
渲染器实现 :
renderers/lit/
每个目录都有详细的README文档。
十二、开发建议:如何用好A2UI
12.1 智能体端的最佳实践
1. 提供丰富的示例
在提示词中包含多个A2UI示例,让LLM学习正确的格式:
AGENT_INSTRUCTION = """
你是一个餐厅助手。以下是UI示例:
示例1:单列列表
[完整的A2UI JSON]
示例2:双列列表
[完整的A2UI JSON]
示例3:预订表单
[完整的A2UI JSON]
根据用户需求选择合适的示例。
"""
2. 使用Schema验证
始终验证LLM生成的A2UI消息:
import jsonschema
try:
jsonschema.validate(instance=generated_json, schema=a2ui_schema)
except jsonschema.ValidationError as e:
# 重试或返回错误
logger.error(f"A2UI validation failed: {e}")
3. 实现重试机制
LLM可能偶尔生成无效的JSON,实现重试逻辑:
max_retries = 2
for attempt in range(max_retries + 1):
response = await llm.generate(prompt)
if validate_a2ui(response):
return response
else:
prompt = f"上次响应无效,请重试:{error_message}"
4. 预计算显示值
在智能体端格式化数据,不要让客户端处理:
# 好的做法
{"key": "price", "valueString": "$19.99"}
{"key": "date", "valueString": "2025年12月22日"}
# 不好的做法
{"key": "price", "valueNumber": 19.99} # 客户端需要格式化
{"key": "date", "valueString": "2025-12-22"} # 客户端需要本地化
12.2 客户端的最佳实践
1. 合理设计组件目录
不要暴露过多组件给智能体,保持目录简洁:
// 基础组件(必需)
Text, Button, TextField, Image, Card, Row, Column, List
// 高级组件(按需添加)
DateTimeInput, Slider, CheckBox, Tabs, Modal
// 自定义组件(谨慎添加)
ChartComponent, MapComponent, VideoPlayer
2. 实现优雅降级
当遇到未知组件时,不要直接报错:
function renderComponent(component) {
const ComponentClass = componentCatalog[component.type];
if (!ComponentClass) {
// 降级为文本显示
console.warn(`Unknown component: ${component.type}`);
return <Text>不支持的组件类型</Text>;
}
return <ComponentClass {...component.props} />;
}
3. 性能优化
-
虚拟滚动:长列表使用虚拟滚动
-
懒加载:大型组件树按需加载
-
Memo化:缓存不变的组件
-
批量更新:合并16ms内的多次更新
4. 错误处理
提供友好的错误提示:
try {
renderA2UIMessage(message);
} catch (error) {
showErrorBoundary({
title: "界面渲染失败",
message: "智能体返回的界面格式有误,请重试",
action: "重新生成"
});
}
12.3 安全性检查清单
在生产环境使用A2UI前,确保:
-
\] 所有组件都经过安全审核
-
\] Action处理器验证了所有输入
-
\] 实现了速率限制,防止滥用
-
\] 测试了各种恶意输入场景
13.1 开源协议
A2UI采用Apache 2.0开源协议,这意味着:
-
✅ 商业使用:可以在商业项目中使用
-
✅ 修改:可以修改源代码
-
✅ 分发:可以分发修改后的版本
-
✅ 专利授权:获得专利使用权
-
⚠️ 需要:保留版权声明和许可证
13.2 贡献者
A2UI是Google发起的项目,但得到了广泛的社区支持:
-
Google团队:核心协议设计、Lit渲染器
-
Flutter团队:GenUI SDK(Flutter渲染器)
-
Angular团队:Angular渲染器
-
CopilotKit团队:AG UI集成、Widget Builder
-
开源社区:文档、示例、bug修复
13.3 如何参与
想为A2UI做贡献?有很多方式:
1. 使用并反馈
-
在项目中使用A2UI
-
报告bug和问题
-
分享使用经验
2. 贡献代码
-
修复bug
-
添加新功能
-
改进文档
-
编写示例
3. 构建生态
-
开发新的渲染器
-
创建自定义组件库
-
编写教程和文章
-
制作视频教程
4. 参与讨论
-
GitHub Discussions:讨论新特性
-
GitHub Issues:报告问题
-
社区会议:参与技术讨论
13.4 学习资源
官方资源:
社区资源:
-
CopilotKit的A2UI Widget Builder:在线体验A2UI
-
Flutter GenUI文档:Flutter集成指南
-
示例项目:餐厅查找、联系人查询、图表展示等
十四、总结:AI时代的UI新范式
14.1 A2UI解决了什么问题?
回到文章开头的问题:如何让AI安全地生成丰富的交互界面?
A2UI给出了一个优雅的答案:
不让AI写代码,而是让AI"说"UI。
通过声明式的JSON格式,A2UI在安全性和表达力之间找到了完美的平衡点:
-
安全性:纯数据格式,无代码执行风险
-
表达力:支持复杂的UI结构和交互
-
灵活性:LLM可以动态生成任意UI
-
可控性:客户端完全掌控渲染逻辑
14.2 A2UI的核心创新
1. 邻接表模型
-
扁平化的组件结构
-
流式生成友好
-
增量更新简单
2. 数据绑定机制
-
结构与状态分离
-
响应式更新
-
模板化的动态列表
3. 安全设计
-
组件白名单
-
无代码执行
-
跨信任边界支持
4. 跨平台能力
-
框架无关的协议
-
多平台渲染器
-
原生组件支持
14.3 适用场景
A2UI特别适合以下场景:
✅ 动态表单生成 :根据上下文生成不同的表单 ✅ 数据可视化 :智能选择合适的图表类型 ✅ 多智能体协作 :不同智能体贡献不同的UI部分 ✅ 自适应工作流 :根据用户意图展示不同的流程 ✅ 跨平台应用:一次生成,多平台渲染
不太适合:
❌ 静态页面 :如果UI结构固定,传统方式更简单 ❌ 极致性能要求 :如游戏、实时渲染等 ❌ 复杂动画:目前对动画支持有限
14.4 技术启示
A2UI的设计给我们带来了一些启示:
1. 为AI优化的设计
-
传统的嵌套结构对人类友好,但对AI不友好
-
扁平化的邻接表模型更适合LLM生成
-
设计协议时要考虑AI的特点
2. 安全与灵活的平衡
-
不是所有问题都要用代码解决
-
声明式数据可以实现很强的表达力
-
白名单机制比黑名单更安全
3. 跨平台的价值
-
协议层的抽象带来巨大的复用价值
-
同一份数据可以在多个平台渲染
-
降低了多端开发的成本
14.5 未来展望
A2UI还很年轻(v0.8),但已经展现出巨大的潜力。
随着AI能力的提升,我们可以期待:
-
更智能的UI生成:AI能够理解设计原则,生成更美观的界面
-
更丰富的交互:支持更复杂的手势、动画、3D等
-
更广泛的应用:从Web到移动,从桌面到AR/VR
-
更成熟的生态:更多渲染器、组件库、工具链
A2UI不仅仅是一个技术协议,它代表了一种新的思维方式:让AI成为UI的创作者,而不仅仅是内容的生成者。
在AI时代,界面不再是静态的、预先设计的,而是动态的、根据上下文生成的。A2UI为这个未来提供了一个坚实的基础。
结语
从ChatGPT让AI学会说话,到A2UI让AI学会"画"界面,我们正在见证AI能力的快速扩展。
A2UI的出现,让我们看到了一个可能的未来:每个用户都能拥有为自己量身定制的界面,每个应用都能根据上下文动态调整UI,每个智能体都能用最合适的方式与用户交互。
这不是科幻,而是正在发生的现实。
如果你对A2UI感兴趣,不妨:
-
访问GitHub仓库,star并关注项目
-
运行官方Demo,亲身体验AI生成的UI
-
阅读文档,了解更多技术细节
-
在自己的项目中尝试使用A2UI
-
参与社区讨论,分享你的想法和经验
让我们一起探索AI时代的UI新范式!
关于作者:本文基于对A2UI项目的深入研究和代码分析撰写,旨在帮助开发者理解这个创新协议的技术原理和应用价值。
参考资料:
-
A2UI GitHub仓库:https://github.com/google/A2UI
-
A2UI官方文档:https://google.github.io/A2UI/
-
A2A协议:https://a2a-protocol.org
-
Flutter GenUI:https://docs.flutter.dev/ai/genui
-
CopilotKit A2UI Widget Builder:https://go.copilotkit.ai/A2UI-widget-builder
