Google 最近开源了一个叫 A2UI 的项目,解决了一个很实际的问题:AI 智能体如何安全地生成丰富的用户界面?
简单说,以前智能体只能返回文本,用户得一来一回地聊天才能完成任务。现在有了 A2UI,智能体可以直接生成表单、按钮、日期选择器这些交互组件,用户点几下就搞定。
从固定界面到动态生成的转变
传统的智能体交互基本就是文字聊天——用户问,AI 答,来回好几轮才能完成一个简单任务。比如订餐:
用户:"我想订个桌子"
AI:"什么时间?"
用户:"明天晚上"
AI:"几点?"
用户:"7点"
AI:"几个人?"
...
而且很多应用还在用固定的界面设计,不管用户需求如何变化,界面都是一成不变的。这种"一刀切"的做法越来越跟不上用户的个性化需求。
真正的趋势是 AI 根据具体场景动态生成交互界面 。现在的 AI 能力已经远超简单的问答,它们能理解复杂的业务逻辑,处理多步骤的工作流,甚至协调多个子任务。但如果还局限在文本交互上,就像让一个建筑师只能用语言描述设计图,而不能画出来给你看。
场景驱动的界面生成正在成为刚需:
- 个性化体验 :根据用户历史行为、偏好设置动态调整界面元素
- 上下文感知 :同一个功能在不同场景下展现不同的界面复杂度
- 任务导向 :界面结构完全服务于当前任务,去掉无关元素
- 实时适配 :随着对话进展,界面可以动态增减字段和选项
同样是订餐,如果用户说"给我家老人订个桌子",AI 可能会生成一个更简化的表单,突出无障碍选项;如果是商务聚餐,可能会增加预算范围、菜系偏好等字段。
在此之前,已经有过一些业内已经有一些尝试,让AI能够自动的生成UI。
两个产品窥见生成AI时代下的UI发展趋势,真正的AI Native产品或将很快出现
A2UI 正是加速这一趋势的关键协议。它让智能体能够"看图说话",可以根据对话上下文、用户偏好、任务复杂度,实时生成最合适的交互界面。
核心思路
A2UI 的协议设计考虑很周到。它不是让智能体直接生成 HTML 或 JavaScript 代码(那样会有安全风险),而是生成一种声明式的 JSON 格式,描述界面应该长什么样。客户端收到后,用自己的原生组件来渲染。
比如智能体想生成一个订餐表单,它会发送这样的消息:
{
"surfaceUpdate": {
"surfaceId": "booking",
"components": [
{"id": "title", "component": {"Text": {"text": {"literalString": "预订餐桌"}, "usageHint": "h1"}}},
{"id": "datetime", "component": {"DateTimeInput": {"value": {"path": "/booking/date"}, "enableDate": true}}},
{"id": "submit-btn", "component": {"Button": {"action": {"name": "confirm\_booking"}}}}
]
}
}
客户端看到这个消息,就用自己的 Angular、Flutter 或 React 组件来渲染对应的标题、日期选择器和按钮。同时,它采用了一种对 LLM 很友好的"邻接表"模型来描述界面结构,智能体可以增量式地构建界面,不需要一次性生成完美的 JSON。目前支持 Web(Lit、Angular)和 Flutter 渲染器,计划加入 React、SwiftUI、Jetpack Compose 等更多平台。传输层兼容 A2A 协议、AG-UI 等。
三个关键优势
安全性 :智能体只能请求客户端预先定义好的组件类型,不能执行任意代码。这避免了传统方案中 iframe 嵌入第三方代码的安全隐患。
原生体验 :生成的界面完全融入应用的设计系统,不像 iframe 那样显得突兀。用户看不出这是智能体生成的,还是开发者写的。
跨平台兼容 :同一个 JSON 消息可以在 Web、移动端、桌面端渲染,因为每个平台用自己的原生组件实现。
为什么现在需要动态界面
现在的 AI 能力已经远超简单的问答。它们能理解复杂的业务逻辑,处理多步骤的工作流,甚至协调多个子任务。但如果还局限在文本交互上,就像让一个建筑师只能用语言描述设计图,而不能画出来给你看。
场景驱动的界面生成 正在成为刚需:
- 个性化体验 :根据用户历史行为、偏好设置动态调整界面元素
- 上下文感知 :同一个功能在不同场景下展现不同的界面复杂度
- 任务导向 :界面结构完全服务于当前任务,去掉无关元素
- 实时适配 :随着对话进展,界面可以动态增减字段和选项
这种变化不是技术炫技,而是用户体验的自然演进。就像从命令行到图形界面,从静态网页到动态应用,智能体界面也在从固定模式向生成式模式转变。
智能体 UI 生态中的定位
智能体 UI 这个领域发展很快,各种工具都在解决不同层面的问题。A2UI 不是要替代现有框架,而是专门解决跨平台、可互操作的生成式 UI 响应 这个特定问题。
与 MCP Apps 的差异
MCP (Model Context Protocol) 最近推出了 MCP Apps,把 UI 当作一种资源来处理。智能体可以返回一个 ui:// URI,客户端获取后在沙盒 iframe 中渲染预构建的 HTML 内容。
A2UI 的不同 :A2UI 采用"原生优先"的方法。不是获取一个不透明的 HTML 包来显示,而是发送原生组件的蓝图。这让 UI 能够完美继承宿主应用的样式和无障碍功能。在多智能体系统中,编排智能体可以轻松理解子智能体发送的轻量级 A2UI 消息内容,实现更流畅的智能体间协作。
简单说:
- MCP Apps :智能体发送完整的 HTML 页面,客户端在 iframe 中显示
- A2UI :智能体发送组件描述,客户端用自己的原生组件渲染
与 AG-UI 的关系
AG-UI 是智能体与用户交互的协议,处理状态同步、聊天历史、输入处理等"管道"工作。
继MCP、A2A协议之后,又一个Agent协议AG-UI来了
A2UI 可以很好的与AG-UI协同,如果你用 AG-UI 构建宿主应用,它可以使用 A2UI 作为数据格式来渲染来自宿主智能体和第三方智能体的响应。这给你两全其美的效果:一个丰富、有状态的宿主应用,同时能安全渲染来自它不控制的外部智能体的内容。
智能体 UI 生态正在快速发展,不同的协议和框架各有专长:MCP 处理工具和数据连接,AG-UI 处理智能体交互,A2UI 专注于跨平台的原生 UI 渲染。它们不是竞争关系,而是互补的,共同构建了一个完整的智能体应用开发栈。
实际开发体验
从 CopilotKit 团队提供的技术教程来看,A2UI 的开发体验相当友好。让我们通过一个完整的餐厅查找智能体例子来看看具体怎么用。
快速开始
首先克隆启动模板:
git clone https://github.com/copilotkit/with-a2a-a2ui.git
echo "GEMINI\_API\_KEY=your\_api\_key\_here" > .env
pnpm install
pnpm dev
后端:定义 A2UI 组件
智能体需要在提示中包含 A2UI 的 JSON Schema 和组件模板。比如餐厅列表的模板:
RESTAURANT\_UI\_EXAMPLES = """
{
"surfaceUpdate": {
"surfaceId": "default",
"components": [
{"id": "root-column", "component": {"Column": {"children": {"explicitList": ["title-heading", "item-list"]}}}},
{"id": "title-heading", "component": {"Text": {"usageHint": "h1", "text": {"literalString": "Top Restaurants"}}}},
{"id": "item-list", "component": {"List": {"direction": "vertical", "children": {"template": {"componentId": "item-card-template", "dataBinding": "/items"}}}}},
{"id": "item-card-template", "component": {"Card": {"child": "card-layout"}}},
{"id": "template-book-button", "component": {"Button": {"child": "book-now-text", "primary": true, "action": {"name": "book\_restaurant", "context": [{"key": "restaurantName", "value": {"path": "name"}}]}}}}
]
}
}
"""
def get\_ui\_prompt(base\_url: str, examples: str) -> str:
return f"""
You are a helpful restaurant finding assistant. Your final output MUST be a A2UI UI JSON response.
To generate the response, you MUST follow these rules:
1. Your response MUST be in two parts, separated by the delimiter: `---a2ui\_JSON---`.
2. The first part is your conversational text response.
3. The second part is a single, raw JSON object that is a list of A2UI messages.
{examples}
"""
智能体配置
配置智能体处理 A2UI 响应和用户交互:
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)
def \_build\_agent(self, use\_ui: bool) -> LlmAgent:
if use\_ui:
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="An agent that finds restaurants and helps book tables.",
instruction=instruction,
tools=[get\_restaurants],
)
处理用户交互
当用户点击按钮时,A2UI 会自动将 UI 事件转换为智能体能理解的查询:
# 在 AgentExecutor 中处理 UI 事件
if ui\_event\_part:
action = ui\_event\_part.get("actionName")
ctx = ui\_event\_part.get("context", {})
if action == "book\_restaurant":
restaurant\_name = ctx.get("restaurantName", "Unknown Restaurant")
address = ctx.get("address", "Address not provided")
# 转换为智能体理解的查询
query = f"USER\_WANTS\_TO\_BOOK: {restaurant\_name}, Address: {address}"
elif action == "submit\_booking":
# 处理预订表单提交
restaurant\_name = ctx.get("restaurantName", "Unknown Restaurant")
party\_size = ctx.get("partySize", "Unknown Size")
reservation\_time = ctx.get("reservationTime", "Unknown Time")
query = f"User submitted a booking for {restaurant\_name} for {party\_size} people at {reservation\_time}"
前端:集成渲染器
前端集成非常简单,使用 CopilotKit 的 A2UI 渲染器:
// 创建 A2UI 消息渲染器
import { createA2UIMessageRenderer } from "@copilotkitnext/a2ui-renderer";
const A2UIMessageRenderer = createA2UIMessageRenderer({ theme });
// 配置 CopilotKit 提供者
<CopilotKitProvider
runtimeUrl="/api/copilotkit"
renderActivityMessages={[A2UIMessageRenderer]}
>
<CopilotChat style={{ flex: 1, minHeight: "100%" }} />
</CopilotKitProvider>
API 路由配置
连接前后端的 API 路由:
import { CopilotRuntime, createCopilotEndpoint, InMemoryAgentRunner } from"@copilotkitnext/runtime";
import { A2AAgent } from"@ag-ui/a2a";
import { A2AClient } from"@a2a-js/sdk/client";
// 创建 A2A 客户端连接到智能体服务器
const a2aClient = new A2AClient("http://localhost:10002");
const agent = new A2AAgent({ a2aClient, debug: true });
// 创建 CopilotKit 运行时
const runtime = new CopilotRuntime({
agents: { default: agent },
runner: new InMemoryAgentRunner(),
});
// 创建 HTTP 端点
const app = createCopilotEndpoint({
runtime,
basePath: "/api/copilotkit",
});
exportconst GET = handle(app);
exportconst POST = handle(app);
使用 A2UI Composer
Google 还提供了一个在线工具 A2UI Composer,让你可以用自然语言描述想要的界面,自动生成对应的 A2UI JSON 规范:
- 访问 https://a2ui-editor.ag-ui.com/
- 描述你想要的组件,比如"创建一个餐厅卡片,包含图片、名称、评分和预订按钮"
- 点击创建,系统会生成完整的 A2UI JSON
- 复制生成的 JSON 到你的智能体提示中
这大大降低了学习和使用门槛。
运行效果
运行后,用户问"找一些纽约的中餐厅",智能体会:
- 调用
get\_restaurants工具获取数据 - 根据餐厅数量选择合适的模板(≤5个用单列,>5个用双列)
- 生成包含餐厅卡片、图片、评分、预订按钮的界面
- 用户点击"Book Now"按钮时,自动跳转到预订表单
- 提交预订后显示确认界面
整个过程中,界面都是智能体根据上下文动态生成的,不是预先写好的静态页面。
以下是一个来自Yasin Ertan更复杂的例子。
应用现状
A2UI 已经在 Google 内部大规模使用。Google Opal 让数十万人用自然语言构建 AI 小程序,Gemini Enterprise 用它为企业生成定制界面。Flutter GenUI SDK 和 CopilotKit 也都集成了 A2UI 支持。
使用方式很灵活:前端开发者可以集成渲染器(支持 Angular、Flutter,React 即将推出),智能体开发者可以用 Python 或 Node.js 框架生成 A2UI 响应,平台构建者可以通过现有框架快速接入。
典型场景包括动态表单生成、专业工具界面和数据可视化。比如景观设计师智能体根据用户上传的照片,生成定制化的设计需求表单。
小结
图文聊天已经成了AI与人类交流的范式,而A2UI将颠覆交互体验,面向不同场景不同人产生不同的交互形式,将个性化体验再往前推进一步。我们正在从"适应机器的交互方式"转向"机器适应人的交互需求"。以前是用户学习如何使用固定的界面,现在是界面学习如何适应用户的场景。A2UI 提供了一个标准化的方案,让智能体能够"说 UI",同时保持安全性和原生体验。从 Google 内部的大规模应用到合作伙伴的积极集成,都说明这个协议正在成为智能体 UI 领域的重要标准。
