A2UI GitHub:github.com/google/A2UI
A2UI文档:google.github.io/A2UI/
快速开始:google.github.io/A2UI/quicks…
一、核心定位:让智能体 "会说 UI"
A2UI (Agent-to-User Interface) 是一个新兴的协议(主要由 Google 等在近期提出并开源),它的出现是为了解决 AI Agent 仅靠“聊天框”无法高效完成复杂任务的痛点。
简单来说,A2UI 让 AI 不再只会“说”话,而是能根据当前需要,即时“画”出一个软件界面给你用。
以下是 A2UI 真正“与众不同”的几个核心原因:
-
彻底颠覆了 UI 的“控制权” (Inversion of Control)
-
传统开发模式: UI 是由设计师和程序员预先画好、写死的。界面长什么样、点哪里跳转,都是提前设定好的。用户必须去适应软件的流程。
-
A2UI 模式: UI 是由 AI Agent(智能体) 现场决定的。
- Agent 判断你现在需要预订机票,它就给你生成一个“日期选择器”和“航班列表”。
- Agent 判断你现在需要审批报销,它就给你生成一个“包含金额和同意按钮的卡片”。
- 不同之处: 界面变成了“流动的”和“按需的”,界面是为了服务当下的意图而生,用完即走。
-
传输的是“蓝图”而非“代码” (Declarative vs. Executable)
这是 A2UI 与早期“让 AI 写网页”最大的区别。
-
普通 GenUI (生成式UI): AI 往往直接生成 HTML/JavaScript/CSS 代码。
- 风险: 不安全(容易被注入恶意代码),且风格很难统一,容易崩坏。
-
A2UI 的做法: AI 发送的是一份 JSON 格式的“蓝图” (数据)。
- 例如:
{ "type": "date-picker", "label": "选择出发日期" }。 - 客户端(你的 App 或浏览器)收到这个指令后,用原生的组件把它画出来。
- 不同之处: 极其安全(因为不运行 AI 写的代码),且视觉完美统一(因为最终渲染的是 App 自带的组件,看起来就像原生功能一样,不会有“拼凑感”)。
- 例如:
-
一次生成,到处运行 (Cross-Platform Native)
因为 A2UI 传输的是抽象的 JSON 指令,它不绑定具体的编程语言。
- 同一个 A2UI 指令发送给 Web 端,会被渲染成 React/Vue 组件。
- 发送给 iPhone,会被渲染成 SwiftUI 组件。
- 发送给 Android,会被渲染成 Kotlin/Compose 组件。
- 不同之处: 这打破了 Web 视图(WebView/iFrame)的局限性,让 AI 生成的界面拥有原生的性能和体验。
-
突破了“聊天墙” (Breaking the Chat Wall)
目前的 AI(如 ChatGPT)主要通过多轮对话来收集信息:
Agent: 请问你要去哪? User: 北京。 Agent: 哪天出发? User: 明天。 Agent: 几点? ...
A2UI 的不同之处: 它能瞬间把这 5 轮对话折叠成一个交互步骤。Agent 会直接丢给你一个包含所有必要字段的表单,你填完点“提交”,一步到位。这让 AI 从“陪聊”变成了真正的“生产力工具”。
-
总结
A2UI 的独特之处在于它建立了一种标准语言,让 AI 能够安全、原生、跨平台地指挥界面生成。它标志着软件开发从 "人设计界面 -> 人操作" 向 "人表达意图 -> AI 设计界面 -> 人确认" 的架构级转变。
二、四大核心设计理念
A2UI 的突破性源于其底层的设计哲学,四大核心原则支撑起全场景适配能力:
- 安全 优先(Security First) :规避 LLM 生成任意代码的安全风险,A2UI 采用声明式数据格式,客户端仅加载预批准的可信组件(例如:卡片、按钮、输入框等),智能体只能请求渲染该组件目录内的元素,从源头筑牢安全防线。
- LLM 友好且支持增量更新:UI 以带 ID 引用的扁平组件列表形式呈现,便于 LLM 逐步生成,支持渐进式渲染与响应式体验。随着对话推进,智能体可根据新请求高效修改 UI,无需整体重构。
- 框架无关且可移植:将 UI 结构与实现彻底分离,智能体仅发送组件树描述与数据模型,客户端负责映射到原生控件,无论是 Web 组件、Flutter Widget,还是 React 组件、SwiftUI 视图,同一份 A2UI JSON 可在多框架客户端上正常渲染。
- 高度灵活性:提供开放注册表模式,支持开发者将服务端类型映射到自定义客户端实现,涵盖原生移动控件、React 组件乃至遗留内容的安全 iframe 容器。开发者可通过 "Smart Wrapper" 接入现有 UI 组件,同时掌控安全沙箱策略与 "信任阶梯",灵活度完全由开发者掌控。
三、端到端架构流程
A2UI 通过 "生成 - 传输 - 解析 - 渲染" 的全流程解耦设计,确保交互的流畅性与稳定性,具体流程如下:
服务端流传输:服务端通过服务器发送事件(SSE)传输 JSONL 流;
客户端缓冲:客户端解析消息,存储组件定义(surfaceUpdate)并构建数据模型(dataModelUpdate);
渲染信号触发:服务端发送 "beginRendering" 信号,避免不完整内容闪现;
客户端渲染:客户端从根节点递归遍历组件树,解析数据绑定,从组件注册表中实例化原生控件;
用户交互:用户执行点击等操作,客户端构建 "userAction" 负载;
事件处理:通过独立的 A2A 消息将 "userAction" 发送至服务端;
动态更新:服务端处理事件后,通过原始 SSE 流发送新的 surfaceUpdate 或 dataModelUpdate 消息,触发 UI 重建。
四、本地部署体验A2UI
-
本地启动QuickStart
把官方代码工程本地跑起来,测试一下demo的效果如何
git clone https://github.com/google/a2ui.git
cd a2ui
export GEMINI_API_KEY="your_gemini_api_key_here"
cd samples/client/lit
npm install
npm run demo:all
# npm 启动demo的详细命令
"demo:all": "npm install && npm run build:renderer && concurrently -k -n "SHELL,REST,CONT1" -c "magenta,blue,green" "npm run serve:shell" "npm run serve:agent:restaurant" "npm run serve:agent:contact_lookup"",
"demo:restaurant": "npm install && npm run build:renderer && concurrently -k -n "SHELL,REST" -c "magenta,blue" "npm run serve:shell" "npm run serve:agent:restaurant"",
"demo:contact_lookup": "npm install && npm run build:renderer && concurrently -k -n "SHELL,REST" -c "magenta,blue" "npm run serve:shell" "npm run serve:agent:contact_lookup""
package.json中完整的scripts脚本
"scripts": {
"serve:agent:restaurant": "cd ../../agent/adk/restaurant_finder && uv run . --port=10003",
"serve:agent:contact_lookup": "cd ../../agent/adk/contact_lookup && uv run . --port=10004",
"serve:agent:rizzcharts": "cd ../../agent/adk/rizzcharts && uv run . --port=10005",
"serve:agent:orchestrator": "cd ../../agent/adk/orchestrator && uv run . --port=10002 --subagent_urls=http://localhost:10003 --subagent_urls=http://localhost:10004 --subagent_urls=http://localhost:10005",
"serve:agent:restaurant_agent": "cd ../../agent/adk/restaurant_finder && uv run . --port=10002",
"serve:agent:contact_lookup_agent": "cd ../../agent/adk/contact_lookup && uv run . --port=10002",
"serve:agent:rizzcharts_agent": "cd ../../agent/adk/rizzcharts && uv run . --port=10002",
"serve:shell": "cd shell && npm run dev",
"build:renderer": "cd ../../../renderers/lit && npm install && npm run build",
"demo:all": "npm install && npm run build:renderer && concurrently -k -n "SHELL,REST,CONT1" -c "magenta,blue,green" "npm run serve:shell" "npm run serve:agent:restaurant" "npm run serve:agent:contact_lookup" "npm run serve:agent:rizzcharts" "npm run serve:agent:orchestrator"",
"demo:restaurant": "npm install && npm run build:renderer && concurrently -k -n "SHELL,REST" -c "magenta,blue" "npm run serve:shell" "npm run serve:agent:restaurant_agent"",
"demo:contact_lookup": "npm install && npm run build:renderer && concurrently -k -n "SHELL,REST" -c "magenta,blue" "npm run serve:shell" "npm run serve:agent:contact_lookup_agent"",
"demo:rizzcharts": "npm install && npm run build:renderer && concurrently -k -n "SHELL,REST" -c "magenta,blue" "npm run serve:shell" "npm run serve:agent:rizzcharts_agent""
}
2. ### 启动时遇到的问题
在本地跑官方代码工程时遇到的问题
npm run build breaks in windows OS
window上with open(file_path) as f:修改为with open(file_path, "r", encoding="utf-8") as f:
geimi限速,需要绑定信用卡,提高限速门槛
-
演示官方示例Restaurants Finder
想象你是一个查找餐厅并预订的智能体,用户对你说:"帮我订明天晚上7点的餐厅,2个人。"
作为一个传统的对话式智能体,你只能这样回复:
然后用户得一条一条地回答你的问题,像在玩"二十个问题"游戏。这种体验让人抓狂,也让你这个智能体显得很笨拙。
你明明知道应该展示一个表单,让用户一次性填写所有信息。你也知道应该先展示餐厅列表,让用户选择。但你做不到——因为你只会"说话",不会"画界面"。
这就是传统 智能体 的困境:能力强大,但表达受限。
A2UI的出现,彻底改变了这一切。它给了智能体一种全新的表达方式:用界面说话。
官方查找餐厅agent的演示过程,输入Top 5 Chinese restaurants in New York,直接返回5家中式餐厅的食物图片、描述、评分,并附带预定按钮,通过点击预定按钮,再次返回预定卡片,提示填写预定人数、时间和注意事项。
测试一下稳定性,修改一下提问Top 10 Chinese restaurants in New York,发现返回的UI里面都没有图片等信息了,全部是空占位符,说明当前的A2UI工程包括大模型直接生成A2UI JSON,并非那么稳定可靠。
-
通过ADK开发框架,创建一个my_agent
下面通过google的agent development kit框架实现查找纽约最好的中餐厅agent
cd .\samples\agent\adk
uv sync
uv pip install google-adk
uv pip show google-adk
adk crate my_agent
编辑agent.py文件,使用生成文本的提示词
import json
from google.adk.agents.llm_agent import Agent
from google.adk.tools.tool_context import ToolContext
def get_restaurants(tool_context: ToolContext) -> str:
"""Call this tool to get a list of restaurants."""
return json.dumps([
{
"name": "Xi'an Famous Foods",
"detail": "Spicy and savory hand-pulled noodles.",
"imageUrl": "http://localhost:10002/static/shrimpchowmein.jpeg",
"rating": "★★★★☆",
"infoLink": "[More Info](https://www.xianfoods.com/)",
"address": "81 St Marks Pl, New York, NY 10003"
},
{
"name": "Han Dynasty",
"detail": "Authentic Szechuan cuisine.",
"imageUrl": "http://localhost:10002/static/mapotofu.jpeg",
"rating": "★★★★☆",
"infoLink": "[More Info](https://www.handynasty.net/)",
"address": "90 3rd Ave, New York, NY 10003"
},
{
"name": "RedFarm",
"detail": "Modern Chinese with a farm-to-table approach.",
"imageUrl": "http://localhost:10002/static/beefbroccoli.jpeg",
"rating": "★★★★☆",
"infoLink": "[More Info](https://www.redfarmnyc.com/)",
"address": "529 Hudson St, New York, NY 10014"
},
])
AGENT_INSTRUCTION="""
You are a helpful restaurant finding assistant. Your goal is to help users find and book restaurants using a rich UI.
To achieve this, you MUST follow this logic:
1. **For finding restaurants:**
a. You MUST call the `get_restaurants` tool. Extract the cuisine, location, and a specific number (`count`) of restaurants from the user's query (e.g., for "top 5 chinese places", count is 5).
b. After receiving the data, you MUST follow the instructions precisely to generate the final a2ui UI JSON, using the appropriate UI example from the `prompt_builder.py` based on the number of restaurants."""
root_agent = Agent(
model='gemini-2.5-flash',
name="restaurant_agent",
description="An agent that finds restaurants and helps book tables.",
instruction=AGENT_INSTRUCTION,
tools=[get_restaurants],
)
上面的代码使用的prompt还是返回文本prompt,adk web启动之后选择my_agent,询问Top 5 Chinese restaurants in New York,返回的就是get_restaurants函数返回的json数据
之前返回的原始的json数据,修改提示词模板返回a2ui jsonl数据格式
cp samples/agent/adk/contact_lookup/a2ui_schema.py my_agent/,这时候prompt为返回UI(A2UI)Json的数据。
# Eventually you can copy & paste some UI examples here, for few-shot in context learning
RESTAURANT_UI_EXAMPLES = """
"""
# Construct the full prompt with UI instructions, examples, and schema
A2UI_AND_AGENT_INSTRUCTION = AGENT_INSTRUCTION + f"""
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 which is a list of A2UI messages.
4. The JSON part MUST validate against the A2UI JSON SCHEMA provided below.
--- UI TEMPLATE RULES ---
- If the query is for a list of restaurants, use the restaurant data you have already received from the `get_restaurants` tool to populate the `dataModelUpdate.contents` array (e.g., as a `valueMap` for the "items" key).
- If the number of restaurants is 5 or fewer, you MUST use the `SINGLE_COLUMN_LIST_EXAMPLE` template.
- If the number of restaurants is more than 5, you MUST use the `TWO_COLUMN_LIST_EXAMPLE` template.
- If the query is to book a restaurant (e.g., "USER_WANTS_TO_BOOK..."), you MUST use the `BOOKING_FORM_EXAMPLE` template.
- If the query is a booking submission (e.g., "User submitted a booking..."), you MUST use the `CONFIRMATION_EXAMPLE` template.
{RESTAURANT_UI_EXAMPLES}
---BEGIN A2UI JSON SCHEMA---
{A2UI_SCHEMA}
---END A2UI JSON SCHEMA---
"""
root_agent = Agent(
model='gemini-2.5-flash',
name="restaurant_agent",
description="An agent that finds restaurants and helps book tables.",
instruction=A2UI_AND_AGENT_INSTRUCTION,
tools=[get_restaurants], //function calling
)
-
分析A2UI数据模型:邻接表模型,为LLM优化
A2UI的设计充分考虑了LLM的特点。传统的嵌套结构对你来说很难:
// 传统嵌套结构 - 对LLM不友好
{
"type": "Column",
"children": [
{"type": "Text", "text": "标题"},
{
"type": "Row",
"children": [
{"type": "Button", "text": "取消"},
{"type": "Button", "text": "确定"}
]
}
]
}
问题:
- 必须一次性生成完美的嵌套
- 括号匹配容易出错
- 难以流式生成
A2UI使用扁平的邻接表模型:
{
"components": [
{"id": "root", "component": {"Column": {"children": {"explicitList": ["title", "buttons"]}}}},
{"id": "title", "component": {"Text": {"text": {"literalString": "标题"}}}},
{"id": "buttons", "component": {"Row": {"children": {"explicitList": ["cancel", "ok"]}}}},
{"id": "cancel", "component": {"Button": {"child": "cancel-text"}}},
{"id": "cancel-text", "component": {"Text": {"text": {"literalString": "取消"}}}},
{"id": "ok", "component": {"Button": {"child": "ok-text"}}},
{"id": "ok-text", "component": {"Text": {"text": {"literalString": "确定"}}}}
]
}
优势:
- ✅ 可以逐个生成组件
- ✅ 每个组件都是独立的JSON对象
- ✅ 支持流式输出
- ✅ 容错性更好
五、A2UI + A2A + Multi-Agent
修改samples/client/lit/package.json npm启动的命令,前端工程通过npm run启动,后端agent通过uv run .启动。
orchestrator是总代理,restaurant,contact_lookup,rizzcharts是三个子代理,三个子代理通过a2a协议注册到orchestrator总代理上,单个子代理基于google adk agent开发框架实现。总代理orchestrator启动端口是10002,三个子代理启动端口分别是10003、10004、10005,访问a2a agent card说明http://localhost:10002/.well-known/agent-card.json 可以查看全部代理agent skills and description。
"scripts": {
"serve:agent:restaurant": "cd ../../agent/adk/restaurant_finder && uv run . --port=10003",
"serve:agent:contact_lookup": "cd ../../agent/adk/contact_lookup && uv run . --port=10004",
"serve:agent:rizzcharts": "cd ../../agent/adk/rizzcharts && uv run . --port=10005",
"serve:agent:orchestrator": "cd ../../agent/adk/orchestrator && uv run . --port=10002 --subagent_urls=http://localhost:10003 --subagent_urls=http://localhost:10004 --subagent_urls=http://localhost:10005",
"serve:agent:restaurant_agent": "cd ../../agent/adk/restaurant_finder && uv run . --port=10002",
"serve:agent:contact_lookup_agent": "cd ../../agent/adk/contact_lookup && uv run . --port=10002",
"serve:agent:rizzcharts_agent": "cd ../../agent/adk/rizzcharts && uv run . --port=10002",
"serve:shell": "cd shell && npm run dev",
"build:renderer": "cd ../../../renderers/lit && npm install && npm run build",
"demo:all": "npm install && npm run build:renderer && concurrently -k -n "SHELL,REST,CONT1" -c "magenta,blue,green" "npm run serve:shell" "npm run serve:agent:restaurant" "npm run serve:agent:contact_lookup" "npm run serve:agent:rizzcharts" "npm run serve:agent:orchestrator"",
"demo:restaurant": "npm install && npm run build:renderer && concurrently -k -n "SHELL,REST" -c "magenta,blue" "npm run serve:shell" "npm run serve:agent:restaurant_agent"",
"demo:contact_lookup": "npm install && npm run build:renderer && concurrently -k -n "SHELL,REST" -c "magenta,blue" "npm run serve:shell" "npm run serve:agent:contact_lookup_agent"",
"demo:rizzcharts": "npm install && npm run build:renderer && concurrently -k -n "SHELL,REST" -c "magenta,blue" "npm run serve:shell" "npm run serve:agent:rizzcharts_agent""
},
根据demo:all启动命令,该项目的架构可以绘制如下:
-
核心模块:
- Shell:前端用户界面,作为主入口,运行在浏览器中。
- Agents:多个子代理(Sub-Agents),分别处理不同的功能模块。
-
子代理(Sub-Agents) :
- Restaurant Agent:处理餐厅查找和预订功能。
- Contact Lookup Agent:处理联系人查询功能。
- Rizzcharts Agent:处理图表相关功能。
- Orchestrator Agent:负责协调多个子代理的调用,充当中间层。
-
通信方式:
- Orchestrator通过HTTP调用各子代理的服务。
- Shell通过API与Orchestrator交互,动态渲染UI。
-
运行 端口:
- Shell:运行在前端开发服务器。
- 各Agent:运行在不同的端口(如10003、10004等)。
以下是架构图的描述:
+-------------------+ +-------------------+
| Browser |<----->| Shell |
+-------------------+ +-------------------+
|
v
+-------------------+
| Orchestrator |
+-------------------+
|
+-----------------------+-----------------------+
| | |
v v v
+-------------------+ +-------------------+ +-------------------+
| Restaurant Agent | | Contact Lookup | | Rizzcharts |
| (Port 10003) | | (Port 10004) | | (Port 10005) |
+-------------------+ +-------------------+ +-------------------+
测试用例:
Top 5 Chinese restaurants in New York.
Find me the top 10 chinese restaurants in the New York.
Who is Alex Jordan?
What's the sales breakdown for last month?
interesting. were there any outlier stores.
show me a map of store performance.
测试提问之后并没有生成期望的UI交互图,应该是官方案例有bug
六、思考如何落地,参考下面文章
A2UI:但 Google 把它写成协议后,模型和交互的最后一公里被彻底补全