当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文章

相关推荐
m0_571186602 小时前
第二十八周周报
人工智能
狮子也疯狂2 小时前
【天翼AI-星辰智能体平台】| 基于Excel表实现智能问数助手智能体开发实战
人工智能·oracle·excel
小陈又菜2 小时前
【计算机网络】网络层知识体系全解:从基础概念到路由协议
服务器·人工智能·计算机网络·机器学习·智能路由器
PNP Robotics2 小时前
聚焦具身智能,PNP机器人展出力反馈遥操作,VR动作捕捉等方案,获得中国科研贡献奖
大数据·人工智能·python·学习·机器人
小霖家的混江龙2 小时前
数学不好也能懂:解读 AI 经典论文《Attention is All You Need》与大模型生成原理
人工智能·llm·aigc
njsgcs2 小时前
ai控制鼠标生成刀路系统2 环境搭建 尝试
人工智能
喜欢吃豆2 小时前
大语言模型(LLM)全栈技术深度综述:理论、系统与工程实践
人工智能·语言模型·自然语言处理·大模型
渡我白衣2 小时前
计算机组成原理(8):各种码的作用详解
c++·人工智能·深度学习·神经网络·其他·机器学习
黑客思维者2 小时前
机器学习016:监督学习【分类算法】(支持向量机)-- “分类大师”入门指南
人工智能·学习·机器学习·支持向量机·分类·回归·监督学习