欢迎光临
我们一直在努力

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

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

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

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

    智能体需要:

  • 展示餐厅列表供用户选择

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

  • 确认预订并显示确认页面

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

    关键点在于:

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

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

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

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

  • 消息缓冲:收到surfaceUpdate和dataModelUpdate时先缓存,不立即渲染

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

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

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

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

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

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

    主智能体可以:

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

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

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

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

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

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

    7.1 框架无关的设计哲学

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

    智能体生成的A2UI消息:

    {
      "id": "welcome-card",
      "component": {
        "Card": {
          "child": "card-content"
        }
      }
    }

    可以渲染为:

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

    • Flutter:Card(child: …)(使用GenUI SDK)

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

    • SwiftUI:CardView { … }(计划中)

    • Jetpack Compose:Card { … }(计划中)

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

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

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

    核心功能:

  • 解析A2UI JSON消息

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

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

  • 处理用户交互和action

  • 支持模板和动态列表

  • 关键接口:

    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 数据可视化

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

    智能体可以:

  • 查询数据库获取销售数据

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

  • 生成包含图表组件的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处理器验证了所有输入

    • [ ] 数据绑定路径进行了权限检查

    • [ ] 实现了速率限制,防止滥用

    • [ ] 记录了所有A2UI消息用于审计

    • [ ] 测试了各种恶意输入场景

    十三、社区与生态

    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 学习资源

    官方资源:

    • GitHub仓库

    • 官方文档

    • 快速入门指南

    • API参考

    社区资源:

    • CopilotKit的A2UI Widget Builder:在线体验A2UI

    • Flutter GenUI文档:Flutter集成指南

    • 示例项目:餐厅查找、联系人查询、图表展示等

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

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

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

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

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

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

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


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

    参考资料:

    • A2UI GitHub仓库:https://github.com/google/A2UI

    • A2UI官方文档:https://google.github.io/A2UI/

    • A2A协议:https://a2a-protocol.org

    • Flutter GenUI:https://docs.flutter.dev/ai/genui

    • CopilotKit A2UI Widget Builder:https://go.copilotkit.ai/A2UI-widget-builder

    更多AIGC文章

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

    更多VibeCoding文章

    赞(0)
    未经允许不得转载:171主机测评 » 当AI学会“画“界面:A2UI如何让智能体拥有UI表达能力
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址