跳到主要内容

A2UI — 架构与原理

30 秒导读: A2UI(Agent-to-User Interface)是一个让 AI agent「说 UI」的开放协议。Agent 不写也不发可执行代码,而是发一段声明式 JSON,描述「我想要一个卡片、里面一个文本和一个按钮」;客户端拿到后用自己的原生组件库(Flutter/React/Angular/Lit/SwiftUI…)把它画出来。一句话:安全得像数据,表达力像代码

1. 这是什么(零基础也能懂)

一句话定义: A2UI 是一套 JSON 消息格式 + 配套渲染库,让 agent 能跨「信任边界」生成可增量更新的富交互界面,而客户端永远只渲染自己预先批准过的组件。

解决什么问题: 大模型很擅长写文本和代码,但要它「给用户一个真正能点、能填、能动的界面」就很尴尬——

  • 直接让模型生成 HTML/JS 代码 → 等于让远程 agent 在你的页面里跑任意代码,安全噩梦
  • 让模型只回文本 → 表达力太弱,做不出表单、仪表盘、选择器。

A2UI 走第三条路:模型生成的是数据(一段描述「意图」的 JSON),不是代码。你的 App 维护一份组件目录(catalog)——一份白名单,里面是 CardButtonTextField 这些你信得过、自己实现好的组件。Agent 只能从这份白名单里「点菜」,点不到的组件根本画不出来。

给谁用:

  • 远程子 agent 场景: 编排 agent 把任务派给一个远程的「订餐 agent」,后者返回一段 UI 让主聊天窗渲染。
  • 动态表单: agent 根据对话上下文临时拼一个预约表单(日期选择器 + 滑块 + 输入框)。
  • 自适应工作流: 企业 agent 按用户的问题现场生成审批面板或数据可视化。

用起来什么样: 协议的核心就是一条 JSON 消息流(JSONL,每行一条)。下面是渲染一张「用户资料卡」的最小流(来自规范 specification/v1_0/docs/a2ui_protocol.md:192-218,精简后):

{"version":"v1.0","createSurface":{"surfaceId":"profile","catalogId":"https://a2ui.org/...basic/catalog.json",
"components":[
{"id":"root","component":"Column","children":["name"]},
{"id":"name","component":"Text","text":{"path":"/name"}}
],
"dataModel":{"name":"John Doe"}}}

读法:开一个叫 profile 的「画面(surface)」;它的组件树根是 root(一个竖排 Column),里面装一个 Text;Text 的内容不是写死的,而是绑定到数据模型里的 /name;初始数据 name = "John Doe"。之后想改名字,不必重发整棵树,只要发一条 updateDataModel/name 改掉,界面自动刷新。

一句话直觉/类比: 把 A2UI 想成给 UI 用的「菜单点菜 + 模板填空」——

  • agent 不进厨房(不写渲染代码),只在菜单(catalog)上勾选要哪些「菜」(组件);
  • 每道菜的「馅料」(文本、数值)用占位符(JSON Pointer 路径)指向一张配料表(data model);
  • 厨房(客户端)按自己的手艺把菜做出来,配料随时能换而不必重新点菜。

本节不碰底层。记住三件事就够:声明式 JSON、白名单目录、数据与结构分离

2. 顶层全景(它大概怎么转)

A2UI 的核心设计是把「生成 UI」和「执行 UI」彻底解耦:agent 负责生成意图,客户端负责映射到原生实现。中间靠一条单向 JSON 消息流连接。

┌─────────────────────────────┐ ┌──────────────────────────────────────┐
│ Agent 侧 (生成) │ │ Client 侧 (渲染) │
│ │ JSON │ │
用户 │ LLM ──► Agent SDK │ 消息流 │ Renderer (web_core / Flutter / …) │
请求 ──►│ · 目录→prompt │ ──────► │ · MessageProcessor 收消息 │ ──► 原生
│ · 流式解析 LLM 输出 │ (A2A/ │ · SurfaceModel 存状态 │ 界面
│ · 校验 JSON │ AG-UI/ │ · GenericBinder 按 schema 绑数据 │
│ · 封装传输 │ MCP) │ · 框架适配器画成组件 │
│ ▲ │ │ │ action(用户点击) │
│ └────────────────┼─────────┼────────────┘ │
└─────────────────────────────┘ 回传 └──────────────────────────────────────┘

怎么读这张图:左生成、右渲染,中间是传输无关的 JSON 流;用户交互(如按钮点击)走一条独立的回传通道(action 消息)送回 agent,可能再触发新一轮 UI 更新。

部件一句话职责:

部件干什么在哪
协议消息(信封)定义 4 类服务端→客户端消息 + 客户端→服务端事件specification/v1_0/docs/a2ui_protocol.md
Catalog(目录)白名单:有哪些组件、哪些函数、各自的 JSON Schemarenderers/web_core/src/v0_9/catalog/types.ts (Catalog)
MessageProcessor渲染内核入口:分发每条消息、维护所有 surfacerenderers/web_core/src/v0_9/processing/message-processor.ts
SurfaceModel单个画面的状态:组件集合 + 数据模型 + action 派发renderers/web_core/src/v0_9/state/surface-model.ts
DataModel响应式数据存储,按 JSON Pointer 读写 + 订阅renderers/web_core/src/v0_9/state/data-model.ts
GenericBinder读组件 schema 自动决定「每个属性怎么绑」的引擎renderers/web_core/src/v0_9/rendering/generic-binder.ts
框架适配器把绑好的 props 喂给 React/Angular/Lit 视图renderers/react/src/v0_9/adapter.tsx
Agent SDK目录管理、prompt 生成、流式解析、校验agent_sdks/agent_sdk_guide.md

主线走一遍(高层,不进代码):

  1. agent 想要个表单 → SDK 把目录(组件 schema + 例子)塞进 prompt → LLM 吐出一段被 <a2ui-json> 包住的 JSON。
  2. SDK 流式解析、修常见 JSON 错误、按 schema 校验,再封进传输(A2A 的 DataPart 等)发给客户端。
  3. 客户端 MessageProcessor 逐条消费:createSurface 建画面、updateComponents 填组件、updateDataModel 填数据。
  4. 组件以扁平 ID 列表到达,客户端按 ID 引用重建出树;只要 root 一就位就能开始渲染,其余边到边补(渐进渲染)。
  5. 用户在表单里打字 → 本地数据模型立刻更新(双向绑定),但立刻发网络;点「提交」按钮才把数据随 action 送回 agent。

3. 阅读地图(建议顺序)

A2UI 有两条主线:协议(消息和数据模型怎么定义)和实现(渲染内核怎么把 JSON 变成界面)。建议这样读:

  1. 01-protocol-messages.md — 先懂协议在传什么:4 条流式消息、信封结构、v1.0 新增的双向 RPC。这是一切的地基。
  2. 02-component-and-catalog.md — 邻接表组件模型(为什么 UI 是扁平 ID 列表)+ 目录白名单(A2UI 安全模型的核心)。
  3. 03-data-binding-and-scope.md — 数据怎么绑到 UI:JSON Pointer、根作用域 vs 列表迭代作用域、双向绑定、formatString 表达式语法。
  4. 04-renderer-internals.md — 真正的代码走读:signals 响应式、DataModel 的祖先/后代通知、GenericBinder 怎么「读 schema 决定绑法」。想看实现精华看这章。
  5. 05-agent-side.md — 反方向:agent 怎么被引导生成合法 A2UI(prompt 工程、流式解析、校验闭环)。

一条贯穿全篇的诚实提醒: 仓库里规范文档(specification/v1_0/)标的是 v1.0「候选(Candidate)」,而真正发布的渲染器代码renderers/web_core/src/v0_9/(对应生产版 v0.9.1)。两者大体一致,但有可观察到的差异(如能力协商的版本键、theme vs surfaceProperties、v1.0 才有的 callFunction/actionResponse 在 v0_9 内核里尚未处理)。本系列引规范时标 spec、引实现时引 v0_9 源码,差异处会明确点出。

4. 巧妙之处(精华速览,详见各章)

  • 数据即安全边界。 UI 是数据不是代码,agent 只能引用目录里的组件名;客户端 MessageProcessor 找不到目录就直接抛错(message-processor.ts:269-272)。这把「LLM 生成任意代码」的风险从根上消除。
  • 邻接表 + root 锚点 = 天然支持流式。 组件是一张扁平的 Map<id, Component>,树靠 ID 引用隐式构建,所以消息可乱序到达;只要 root 出现就能开画,缺的孩子先占位(02 章)。
  • 「读 schema 决定怎么绑」的零样板引擎。 GenericBinder 不为每个组件写死绑定逻辑,而是爬组件的 Zod schema,识别出 DynamicString/Action/ChildList/checks 这些 A2UI 原语,自动建立订阅、生成 setXxx 双向 setter、把 action 包成可调用闭包(generic-binder.ts:54-117,04 章重点)。
  • 客户端数据模型是唯一真相源。 用户打字只改本地模型、不发网络;直到一个 action 触发,才把数据随事件回传——把「实时输入」和「网络往返」解耦(03 章双向绑定)。

5. 边界与局限(诚实)

  • 协议尚在演进。 v1.0 是候选、v0.9.1 是生产、v0.8 是遗留;README 明说「Expect changes」。
  • 规范与实现有版本错位。 见上文诚实提醒。
  • 传输不归 A2UI 管。 A2UI 只定义 JSON 消息和语义契约,可靠有序投递、消息分帧、元数据(能力协商、数据模型回传)全靠传输层(A2A/AG-UI/MCP/SSE/WebSocket)兑现(a2ui_protocol.md:81-118)。
  • agent 侧的解析靠正则块提取。 SDK 用 <a2ui-json>...</a2ui-json> 标签 + 正则抓 JSON(agent_sdk_guide.md:159-180),对 LLM 的格式纪律有依赖,故配了 PayloadFixer 兜底。

6. 横向对比(同 shelf 兄弟)

  • A2UI vs MCP(../mcp-spec): MCP 是「把数据/工具暴露给 LLM」的协议(LLM ← 工具),A2UI 是「把 UI 暴露给用户」的协议(agent → 用户界面);二者正交,A2UI 甚至可以搭在 MCP 之上当一种传输(a2ui_protocol.md:108-110)。
  • A2UI vs AG-UI / A2A: 这两者是 A2UI 的默认传输绑定——AG-UI 面向 agent↔前端、A2A 面向 agent↔agent;A2UI 负责「UI 长什么样」,它们负责「字节怎么送、能力怎么协商」。

7. 代码地图(导航索引)

主题文件关键符号
协议规范(v1.0 候选)specification/v1_0/docs/a2ui_protocol.mdcreateSurface / updateComponents / updateDataModel / deleteSurface
渲染内核入口renderers/web_core/src/v0_9/processing/message-processor.tsMessageProcessorprocessMessage
目录(白名单)renderers/web_core/src/v0_9/catalog/types.tsCatalogComponentApiinvoker
画面状态renderers/web_core/src/v0_9/state/surface-model.tsSurfaceModeldispatchAction
数据模型renderers/web_core/src/v0_9/state/data-model.tsDataModelsetnotifySignals
绑定引擎renderers/web_core/src/v0_9/rendering/generic-binder.tsGenericBinderscrapeSchemaBehavior
数据上下文/作用域renderers/web_core/src/v0_9/rendering/data-context.tsDataContextresolveSignalnested
表达式引擎renderers/web_core/src/v0_9/basic_catalog/expressions/expression_parser.tsExpressionParserparse
React 适配renderers/react/src/v0_9/adapter.tsxcreateComponentImplementation
Agent SDK 设计agent_sdks/agent_sdk_guide.mdA2uiSchemaManagerA2uiStreamParserA2uiValidator