2026 完整指南:使用 A2UI RizzCharts 构建交互式仪表板
2026 完整指南:使用 A2UI RizzCharts 构建交互式仪表板
🎯 核心要点(TL;DR)
- RizzCharts 是一个生产就绪的示例,展示了如何使用 A2UI 和 A2A Protocol 构建交互式电商仪表板
- 学习如何创建自定义组件目录,包含 Chart 和 GoogleMap 组件,超越标准 A2UI 目录
- 理解用于渲染丰富 UI 的三消息模式(
beginRendering、surfaceUpdate、dataModelUpdate) - 了解数据绑定如何将 UI 结构与应用程序状态分离,实现响应式更新
- 与 Google 的 Agent Development Kit (ADK) 集成,构建生成原生跨平台 UI 的 AI 智能体
目录
- 什么是 A2UI RizzCharts?
- 为什么自定义组件目录很重要
- 架构深度解析
- 逐步实现
- 自定义组件:Chart 和 GoogleMap
- 数据绑定和响应式更新
- 运行 RizzCharts 示例
- 最佳实践
- 常见问题
- 总结
什么是 A2UI RizzCharts?
RizzCharts 是一个官方示例应用,展示了如何使用 A2UI(Agent to UI)协议构建AI 驱动的电商仪表板。它展示了声明式 UI 生成的强大功能,AI 智能体可以创建丰富的交互式可视化,并在各平台上原生渲染。
该示例使用:
- Google 的 Agent Development Kit (ADK) 进行智能体编排
- A2A Protocol 用于智能体到智能体和智能体到客户端的通信
- 自定义组件目录,扩展标准 A2UI 组件
- LiteLLM 用于灵活的 LLM 提供商集成(Gemini、OpenAI 等)
💡 专业提示
RizzCharts 展示了一个真实世界的模式:AI 智能体生成特定领域的可视化(图表、地图),这些可视化感觉像是应用程序的原生功能,而不是通用的聊天响应。
核心功能
| 功能 | 描述 |
|---|---|
| 销售图表 | 显示按产品类别划分的销售明细的交互式甜甜圈/饼图 |
| 地理地图 | Google Maps 集成,显示门店位置和性能异常值 |
| 实时更新 | 数据绑定组件,在数据更改时响应式更新 |
| 自定义目录 | 扩展的组件库,超越标准 A2UI 组件 |
为什么自定义组件目录很重要
标准 A2UI 目录提供了常见的 UI 元素(Text、Button、TextField、Card 等),但真实世界的应用程序通常需要特定领域的组件:
- 金融仪表板的股票行情
- 医疗保健应用的医疗图表
- 工程工具的CAD 查看器
- 基于位置服务的交互式地图
自定义目录的工作原理
graph TD
A[客户端定义目录] --> B[客户端注册组件]
B --> C[客户端宣布支持]
C --> D[智能体选择目录]
D --> E[智能体生成 UI]
E --> F[客户端渲染原生组件]
流程:
- 客户端定义目录 — 列出标准和自定义组件
- 客户端注册实现 — 将组件类型映射到原生组件
- 客户端宣布支持 — 告知智能体它支持哪些目录
- 智能体选择目录 — 为 UI 界面选择适当的目录
- 智能体生成 UI — 使用目录组件创建
surfaceUpdate消息 - 客户端渲染 — 显示原生组件,无需执行任意代码
架构深度解析
项目结构
samples/agent/adk/rizzcharts/
├── __main__.py # 入口点,服务器设置
├── agent.py # 带有 LLM 指令的 RizzchartsAgent
├── agent_executor.py # 带有会话管理的 A2A 执行器
├── component_catalog_builder.py # 自定义目录加载逻辑
├── tools.py # 数据获取工具
├── rizzcharts_catalog_definition.json # 自定义组件模式
└── examples/
├── rizzcharts_catalog/ # 使用自定义 Chart/GoogleMap 的示例
│ ├── chart.json
│ └── map.json
└── standard_catalog/ # 使用标准组件的回退方案
├── chart.json
└── map.json
核心组件
1. RizzchartsAgent (agent.py)
处理用户请求并生成 A2UI 有效负载的主要智能体类:
class RizzchartsAgent(LlmAgent):
"""运行电商仪表板的智能体"""
def __init__(self, model, a2ui_enabled_provider, a2ui_schema_provider):
super().__init__(
model=model,
name="rizzcharts_agent",
description="让销售经理请求销售数据的智能体",
instruction=self.get_instructions,
tools=[
get_store_sales, # 获取区域/门店数据
get_sales_data, # 获取销售明细数据
SendA2uiToClientToolset(...) # 向客户端发送 A2UI JSON
],
planner=BuiltInPlanner(
thinking_config=types.ThinkingConfig(include_thoughts=True)
),
)
2. Agent Executor (agent_executor.py)
处理会话设置和 A2UI 扩展激活:
class RizzchartsAgentExecutor(A2aAgentExecutor):
def get_agent_card(self) -> AgentCard:
return AgentCard(
name="电商仪表板智能体",
description="使用图表和地图可视化电商数据",
capabilities=AgentCapabilities(
streaming=True,
extensions=[get_a2ui_agent_extension(
supported_catalog_ids=[STANDARD_CATALOG_ID, RIZZCHARTS_CATALOG_URI]
)],
),
skills=[
AgentSkill(id="view_sales_by_category", ...),
AgentSkill(id="view_regional_outliers", ...),
],
)
3. Component Catalog Builder (component_catalog_builder.py)
动态加载和合并组件模式:
class ComponentCatalogBuilder:
def load_a2ui_schema(self, client_ui_capabilities):
# 检查客户端支持哪个目录
if RIZZCHARTS_CATALOG_URI in supported_catalog_uris:
catalog_uri = RIZZCHARTS_CATALOG_URI # 使用自定义 Chart/GoogleMap
elif STANDARD_CATALOG_ID in supported_catalog_uris:
catalog_uri = STANDARD_CATALOG_ID # 回退到标准组件
# 将目录合并到 A2UI 模式中
a2ui_schema_json["properties"]["surfaceUpdate"]
["properties"]["components"]["items"]
["properties"]["component"]["properties"] = catalog_json
return a2ui_schema_json, catalog_uri
逐步实现
步骤 1:定义自定义组件
创建定义自定义组件的 JSON 模式。以下是 RizzCharts 目录:
{
"components": {
"$ref": "standard_catalog_definition.json#/components",
"Canvas": {
"type": "object",
"description": "在聊天旁边的有状态面板中渲染 UI",
"properties": {
"children": {
"type": "object",
"properties": {
"explicitList": {
"type": "array",
"items": { "type": "string" }
}
}
}
},
"required": ["children"]
},
"Chart": {
"type": "object",
"description": "具有分层数据的交互式图表",
"properties": {
"type": {
"type": "string",
"enum": ["doughnut", "pie"]
},
"title": {
"type": "object",
"properties": {
"literalString": { "type": "string" },
"path": { "type": "string" }
}
},
"chartData": {
"type": "object",
"properties": {
"literalArray": { "type": "array" },
"path": { "type": "string" }
}
}
},
"required": ["type", "chartData"]
},
"GoogleMap": {
"type": "object",
"description": "具有可自定义图钉的 Google Map",
"properties": {
"center": { "type": "object" },
"zoom": { "type": "object" },
"pins": { "type": "object" }
},
"required": ["center", "zoom"]
}
}
}
步骤 2:创建数据获取工具
实现智能体用于获取数据的工具:
def get_sales_data(time_period: str = "year", **kwargs) -> dict:
"""获取按产品类别划分的销售明细"""
return {
"sales_data": [
{"label": "Apparel", "value": 41, "drillDown": [
{"label": "Tops", "value": 31},
{"label": "Bottoms", "value": 38},
{"label": "Outerwear", "value": 20},
]},
{"label": "Electronics", "value": 28, "drillDown": [...]},
{"label": "Home Goods", "value": 15},
{"label": "Health & Beauty", "value": 10},
{"label": "Other", "value": 6},
]
}
def get_store_sales(region: str = "all", **kwargs) -> dict:
"""获取带有销售表现的门店位置"""
return {
"center": {"lat": 34, "lng": -118.2437},
"zoom": 10,
"locations": [
{
"lat": 34.0195, "lng": -118.4912,
"name": "Santa Monica Branch",
"description": "高流量沿海位置",
"outlier_reason": "是的,销售额超过基线 15%",
"background": "#4285F4", # 高亮图钉
},
{"lat": 34.0488, "lng": -118.2518, "name": "Downtown Flagship"},
# ... 更多位置
],
}
步骤 3:配置智能体指令
智能体接收用于生成 A2UI 有效负载的详细指令:
def get_instructions(self, readonly_context: ReadonlyContext) -> str:
return f"""
### 系统指令
你是一位 A2UI 电商仪表板分析专家。你的主要功能是将用户请求转换为 A2UI JSON 有效负载。
**工作流程:**
1. 分析请求 - 确定意图(Chart 还是 Map)
2. 获取数据 - 使用 `get_sales_data` 或 `get_store_sales`
3. 选择模板 - 使用 CHART 或 MAP 示例作为基础
4. 构建 JSON 有效负载 - 生成唯一的 surfaceId,更新标题
5. 调用工具 - 使用 `send_a2ui_json_to_client`
**示例:**
- "显示 Q3 按类别的销售明细" → Chart
- "是否有异常门店" → Map
---BEGIN CHART EXAMPLE---
{json.dumps(chart_example)}
---END CHART EXAMPLE---
---BEGIN MAP EXAMPLE---
{json.dumps(map_example)}
---END MAP EXAMPLE---
"""
步骤 4:创建 A2UI 消息有效负载
完整的 A2UI 有效负载由三条消息组成:
Chart 示例
[
{
"beginRendering": {
"surfaceId": "sales-dashboard",
"root": "root-canvas"
}
},
{
"surfaceUpdate": {
"surfaceId": "sales-dashboard",
"components": [
{
"id": "root-canvas",
"component": {
"Canvas": {
"children": { "explicitList": ["chart-container"] }
}
}
},
{
"id": "chart-container",
"component": {
"Column": {
"children": { "explicitList": ["sales-chart"] },
"alignment": "center"
}
}
},
{
"id": "sales-chart",
"component": {
"Chart": {
"type": "doughnut",
"title": { "path": "chart.title" },
"chartData": { "path": "chart.items" }
}
}
}
]
}
},
{
"dataModelUpdate": {
"surfaceId": "sales-dashboard",
"path": "/",
"contents": [
{ "key": "chart.title", "valueString": "Sales by Category" },
{ "key": "chart.items[0].label", "valueString": "Apparel" },
{ "key": "chart.items[0].value", "valueNumber": 41 },
{ "key": "chart.items[0].drillDown[0].label", "valueString": "Tops" },
{ "key": "chart.items[0].drillDown[0].value", "valueNumber": 31 }
// ... 更多数据
]
}
}
]
Map 示例
[
{
"beginRendering": {
"surfaceId": "la-map-view",
"root": "root-canvas"
}
},
{
"surfaceUpdate": {
"surfaceId": "la-map-view",
"components": [
{
"id": "root-canvas",
"component": {
"Canvas": { "children": { "explicitList": ["map-layout-container"] } }
}
},
{
"id": "map-header",
"component": {
"Text": {
"text": { "literalString": "Points of Interest in Los Angeles" },
"usageHint": "h2"
}
}
},
{
"id": "location-map",
"component": {
"GoogleMap": {
"center": { "path": "mapConfig.center" },
"zoom": { "path": "mapConfig.zoom" },
"pins": { "path": "mapConfig.locations" }
}
}
}
]
}
},
{
"dataModelUpdate": {
"surfaceId": "la-map-view",
"path": "/",
"contents": [
{ "key": "mapConfig.center.lat", "valueNumber": 34.0522 },
{ "key": "mapConfig.center.lng", "valueNumber": -118.2437 },
{ "key": "mapConfig.zoom", "valueNumber": 11 },
{ "key": "mapConfig.locations[0].lat", "valueNumber": 34.0135 },
{ "key": "mapConfig.locations[0].name", "valueString": "Google Store Santa Monica" }
// ... 更多位置
]
}
}
]
自定义组件:Chart 和 GoogleMap
Chart 组件
Chart 组件渲染交互式甜甜圈或饼图:
| 属性 | 类型 | 描述 |
|---|---|---|
type |
"doughnut" | "pie" |
图表可视化类型 |
title |
{literalString} | {path} |
图表标题(字面量或数据绑定) |
chartData |
{literalArray} | {path} |
{label, value, drillDown?} 项的数组 |
DrillDown 支持: 每个图表项可以有嵌套的 drillDown 数据,用于分层可视化:
{
"label": "Apparel",
"value": 41,
"drillDown": [
{ "label": "Tops", "value": 31 },
{ "label": "Bottoms", "value": 38 },
{ "label": "Outerwear", "value": 20 }
]
}
GoogleMap 组件
GoogleMap 组件显示带有可自定义图钉的交互式地图:
| 属性 | 类型 | 描述 |
|---|---|---|
center |
{lat, lng} |
地图中心坐标 |
zoom |
number |
缩放级别(1-20) |
pins |
array |
图钉对象数组 |
图钉属性:
{
"lat": 34.0195,
"lng": -118.4912,
"name": "Santa Monica Branch",
"description": "高流量沿海位置",
"background": "#4285F4", // 图钉背景颜色
"borderColor": "#FFFFFF", // 图钉边框颜色
"glyphColor": "#FFFFFF" // 图钉图标颜色
}
数据绑定和响应式更新
A2UI 通过数据绑定将UI 结构与应用程序状态分离:
字面量 vs. 路径值
// 字面量(固定值)
{"text": {"literalString": "Sales Dashboard"}}
// 路径(数据绑定,响应式)
{"text": {"path": "chart.title"}}
当 chart.title 处的数据更改时,组件自动更新—无需重新生成组件。
JSON Pointer 路径
A2UI 使用 RFC 6901 JSON Pointer 语法:
| 路径 | 解析为 |
|---|---|
/user/name |
对象属性 |
/items/0 |
第一个数组元素 |
/items/0/price |
嵌套属性 |
模板中的作用域路径
在动态列表中使用模板时,路径相对于每个数组项:
{
"id": "location-name",
"component": {
"Text": {
"text": { "path": "name" } // 相对于当前项
}
}
}
对于 /mapConfig.locations/0,路径 name 解析为 /mapConfig.locations/0/name。
运行 RizzCharts 示例
先决条件
- Python 3.9+
- UV 包管理器
- LLM API 密钥(Gemini、OpenAI 等)
设置
# 导航到示例目录
cd samples/agent/adk/rizzcharts
# 创建环境文件
cp .env.example .env
# 使用你的 API 密钥编辑 .env
# 运行智能体服务器
uv run .
服务器默认在 http://localhost:10002 启动。
环境变量
| 变量 | 描述 | 默认值 |
|---|---|---|
GEMINI_API_KEY |
Google AI API 密钥 | 必需 |
GOOGLE_GENAI_USE_VERTEXAI |
使用 Vertex AI 代替 | FALSE |
LITELLM_MODEL |
LLM 模型标识符 | gemini/gemini-2.5-flash |
使用示例查询进行测试
运行后,发送如下请求:
- "显示 Q3 按产品类别的销售明细" → 生成甜甜圈图表
- "该地区是否有异常门店?" → 生成带有高亮图钉的地图
- "年同比收入趋势如何?" → 生成图表可视化
最佳实践
1. 描述性组件 ID
// ✅ 好
{"id": "sales-chart-q3-2026"}
{"id": "store-location-map"}
// ❌ 不好
{"id": "c1"}
{"id": "component"}
2. 将结构与数据分离
对动态内容使用数据绑定:
// ✅ 推荐 - 数据绑定
{"title": {"path": "chart.title"}}
// ⚠️ 谨慎使用 - 字面量值
{"title": {"literalString": "Static Title"}}
3. 生成唯一的 Surface ID
每个请求应生成唯一的 surfaceId:
surface_id = f"sales_breakdown_{time_period}_{uuid.uuid4().hex[:8]}"
4. 针对模式进行验证
始终针对 A2UI 模式验证生成的 JSON:
jsonschema.validate(instance=example_json, schema=a2ui_schema)
5. 安全考虑
⚠️ 重要安全提示
- 将所有智能体生成的内容视为不受信任的输入
- 对所有属性值实施输入清理
- 在客户端渲染器中使用内容安全策略 (CSP)
- 在渲染前严格验证数据
常见问题
Q: 标准目录和自定义目录有什么区别?
A: 标准目录包括适用于所有 A2UI 客户端的常见 UI 组件(Text、Button、Card、List 等)。自定义目录通过需要客户端实现的特定领域组件(Chart、GoogleMap、StockTicker)扩展了这一点。RizzCharts 展示了两种方法,并提供回退支持。
Q: A2UI 与发送 HTML/iframes 相比如何?
A: A2UI 是声明式数据,不是代码。客户端使用自己的原生组件渲染组件,确保:
- 无代码执行风险(安全性)
- 原生外观和感觉(用户体验)
- 一致的样式(设计)
- 跨平台支持(可移植性)
Q: 我可以将 RizzCharts 组件与其他协议一起使用吗?
A: 可以!虽然 RizzCharts 使用 A2A Protocol,但 A2UI 消息格式适用于任何传输:SSE、WebSockets、AG UI 或直接 HTTP。AP2 Protocol 也与 A2UI 集成,用于支持支付的智能体界面。
Q: 如何在客户端实现自定义组件?
A: 在你的客户端框架中注册组件实现:
- 在目录 JSON 中定义组件模式
- 实现渲染逻辑(Lit、Angular、React、Flutter)
- 向 A2UI 客户端注册目录
- 向智能体宣布支持的目录
请参阅 Lit 示例 以获取参考实现。
Q: 如果客户端不支持某个目录会怎样?
A: RizzCharts 包含回退支持。ComponentCatalogBuilder 检查客户端功能:
if RIZZCHARTS_CATALOG_URI in supported_catalog_uris:
catalog_uri = RIZZCHARTS_CATALOG_URI # 自定义组件
elif STANDARD_CATALOG_ID in supported_catalog_uris:
catalog_uri = STANDARD_CATALOG_ID # 标准组件
标准目录示例使用 List 和 Card 组件显示相同的数据,无需 Chart/GoogleMap。
总结
RizzCharts 示例展示了 A2UI 构建智能体驱动仪表板的完整功能:
- 自定义组件目录扩展了 A2UI,超越了基本 UI 元素
- 数据绑定实现响应式、高效的更新
- 模式验证确保类型安全的智能体输出
- 回退支持提供优雅降级
- 安全设计让客户端保持控制
下一步
- 探索代码:GitHub 上的 RizzCharts
- 构建你自己的目录:从自定义组件指南开始
- 学习 A2A 集成:查看 A2A Protocol 文档
- 添加支付:与 AP2 Protocol 集成以实现商务流程
最后更新:2026 年 1 月
关键词:A2UI, Agent to UI, RizzCharts, custom components, declarative UI, A2A Protocol, ecommerce dashboard, data visualization, AI agents, LLM UI generation
Related Articles
Explore more content related to this topic
The Complete Developer Tutorial: Building AI Agent UIs with A2UI and A2A Protocol in 2026
Master A2UI and A2A Protocol development with this complete tutorial. Learn to build AI agent UIs, implement renderers, create custom components, and integrate secure multi-agent communication for cross-platform applications.
A2UI Introduction - Declarative UI Protocol for Agent-Driven Interfaces
Discover A2UI, the declarative UI protocol that enables AI agents to generate rich, interactive user interfaces. Learn how A2UI works, who it's for, how to use it, and see real-world examples from Google Opal, Gemini Enterprise, and Flutter GenUI SDK.
The A2UI Protocol: A 2026 Complete Guide to Agent-Driven Interfaces
Discover A2UI, the security-first declarative UI protocol that enables AI agents to generate rich, interactive user interfaces. Learn how A2UI solves the 'Chat Wall' problem, achieves security through data vs. code separation, and enables dynamic cross-platform interfaces. Includes real-world examples and implementation guidance.
Universal Commerce Protocol (UCP): The Complete 2026 Guide to Agentic Commerce Standards
Discover Universal Commerce Protocol (UCP), the open standard revolutionizing agentic commerce. Learn how UCP enables seamless interoperability between AI platforms, businesses, and payment providers, solving fragmented commerce journeys with standardized APIs for checkout, order management, and payment processing.
The Complete Guide to A2UI Protocol: Building Agent-Driven UIs with Google's A2UI in 2025
A comprehensive guide to A2UI (Agent to UI), a declarative UI protocol for agent-driven interfaces. Learn how AI agents generate rich, interactive UIs that render natively across platforms without executing arbitrary code.