当AI学会“画“界面:A2UI如何让智能体拥有UI表达能力

如果说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来说是个噩梦:

  1. 必须一次性生成完美的嵌套结构:LLM必须在开始输出之前就规划好整个树形结构,这对流式生成非常不友好。

  2. 难以增量更新:如果要修改深层嵌套的某个组件,必须重新生成整个父节点,甚至整个树。

  3. 容易出错:括号匹配、缩进层级,任何一个小错误都会导致整个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个人。"

智能体需要:

  1. 展示餐厅列表供用户选择

  2. 生成预订表单让用户填写详细信息

  3. 确认预订并显示确认页面

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]  # 注册工具
        )

关键点在于:

  1. 提示词工程:智能体的指令中包含了A2UI的使用方法、示例和Schema

  2. 工具集成 :智能体可以调用get_restaurants等工具获取数据

  3. 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 消息缓冲与批量渲染

客户端渲染器会智能地处理消息流:

  1. 消息缓冲 :收到surfaceUpdatedataModelUpdate时先缓存,不立即渲染

  2. 等待信号 :直到收到beginRendering消息才开始渲染

  3. 批量更新:将16ms内的多个更新合并为一次渲染,避免频繁重绘

  4. 增量更新:只更新变化的组件,不重新渲染整个界面

这种机制既保证了流畅的用户体验,又优化了性能。

六、安全性设计:信任边界的艺术

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响应

主智能体可以:

  1. 透传:直接转发远程智能体的A2UI消息

  2. 过滤:移除某些敏感组件或数据

  3. 修改:调整样式、添加水印等

  4. 验证:检查消息是否符合安全策略

这种灵活性让多智能体协作变得安全可控。

七、跨平台渲染:一次生成,到处运行

7.1 框架无关的设计哲学

A2UI最令人兴奋的特性之一是:同一份JSON可以在不同平台上渲染

智能体生成的A2UI消息:

复制代码
{
  "id": "welcome-card",
  "component": {
    "Card": {
      "child": "card-content"
    }
  }
}

可以渲染为:

  • Web<div class="card">...</div>(使用Lit或Angular)

  • FlutterCard(child: ...)(使用GenUI SDK)

  • React<Card>...</Card>(即将支持)

  • SwiftUICardView { ... }(计划中)

  • Jetpack ComposeCard { ... }(计划中)

每个平台使用自己的原生组件,保持原生的外观和性能。

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 构建自己的渲染器

如果你想为自己喜欢的框架构建渲染器,需要实现以下功能:

核心功能:

  1. 解析A2UI JSON消息

  2. 处理邻接表结构,构建组件树

  3. 实现数据绑定和响应式更新

  4. 处理用户交互和action

  5. 支持模板和动态列表

关键接口:

复制代码
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 数据可视化

场景:用户问"显示上个月的销售数据"。

智能体可以:

  1. 查询数据库获取销售数据

  2. 判断数据适合用什么图表展示(折线图、柱状图、饼图)

  3. 生成包含图表组件的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 学习资源

官方资源

社区资源

十四、总结: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感兴趣,不妨:

  1. 访问GitHub仓库,star并关注项目

  2. 运行官方Demo,亲身体验AI生成的UI

  3. 阅读文档,了解更多技术细节

  4. 在自己的项目中尝试使用A2UI

  5. 参与社区讨论,分享你的想法和经验

让我们一起探索AI时代的UI新范式!


关于作者:本文基于对A2UI项目的深入研究和代码分析撰写,旨在帮助开发者理解这个创新协议的技术原理和应用价值。

参考资料

更多AIGC文章

RAG技术全解:从原理到实战的简明指南

更多VibeCoding文章

相关推荐
九狼4 分钟前
Flutter URL Scheme 跨平台跳转
人工智能·flutter·github
ZFSS12 分钟前
Kimi Chat Completion API 申请及使用
前端·人工智能
天翼云开发者社区1 小时前
春节复工福利就位!天翼云息壤2500万Tokens免费送,全品类大模型一键畅玩!
人工智能·算力服务·息壤
知识浅谈2 小时前
教你如何用 Gemini 将课本图片一键转为精美 PPT
人工智能
Ray Liang2 小时前
被低估的量化版模型,小身材也能干大事
人工智能·ai·ai助手·mindx
shengjk13 小时前
NanoClaw 深度剖析:一个"AI 原生"架构的个人助手是如何运转的?
人工智能
西门老铁5 小时前
🦞OpenClaw 让 MacMini 脱销了,而我拿出了6年陈的安卓机
人工智能
恋猫de小郭6 小时前
AI 可以让 WIFI 实现监控室内人体位置和姿态,无需摄像头?
前端·人工智能·ai编程
是一碗螺丝粉6 小时前
5分钟上手LangChain.js:用DeepSeek给你的App加上AI能力
前端·人工智能·langchain