A2UI 协议深度解析:基于 JSONL 的流式 UI 渲染协议设计指南(v0.8)
【免费下载链接】a2ui项目地址: https://gitcode.com/GitHub_Trending/a2/a2ui
A2UI(Agent to UI)是 a2ui 项目中面向 LLM 智能体的一整套流式 UI 传输协议:服务器(Agent)通过 JSON Lines(JSONL)流将平台无关的抽象组件树推送给客户端,客户端用原生控件渐进式渲染,并以 A2A 消息回传用户事件。本文以 specification/v0_8/docs/a2ui_protocol.md 为主体,结合仓库中的 JSON Schema、标准组件目录(Standard Catalog)与完整示例,逐层拆解协议的设计动机、四种服务端消息、Catalog 协商、数据绑定、事件回传与客户端解释器实现,帮助读者既能照着示例写出第一份可运行的 A2UI 消息流,也能理解协议底层为什么这样设计。
设计需求:每一个选择都有明确动机
A2UI 协议不是为了"流式传输 JSON"而设计的,它的每个核心决策都回溯到四个底层挑战:LLM 生成可靠性、感知性能、平台无关性与状态解耦。
1. 必须易于 Transformer 大模型生成——这是最关键的驱动因素。LLM 擅长生成结构化、声明式的数据,而不擅长编排命令序列,因此协议选择:
- 声明式简单结构:用"这是一个包含这些子节点的 Column"来描述 UI,而非"现在添加一个 Column,然后向它追加一个 Text 控件"这类命令式指令;
- 扁平组件列表(邻接表):让 LLM 一次性生成完美嵌套的 JSON 树既困难又易错,而扁平列表中每个组件只带一个字符串 ID、关系靠 ID 引用,模型可以"想到一个组件、给它一个 ID、之后用 ID 引用它",无需担心树的深度与对象嵌套;
- 无状态消息:每条 JSONL 消息都是自包含的信息单元(
componentUpdate、dataModelUpdate),流式 LLM 可以在处理请求时增量输出这些消息。
2. UI 必须渐进式渲染,让用户感觉系统很快,哪怕完整 UI 很复杂、需要较长时间生成。通过 JSONL/SSE 流式传输,客户端无需等待单个巨型 JSON 负载,收到组件即可开始解析处理,显著改善感知性能。
3. 协议必须平台无关:同一套服务端逻辑应当不加修改地在 Flutter 应用、Web 浏览器或其他平台上渲染。核心手段是客户端自定义控件目录(Widget Catalog)——协议只定义抽象组件树("我需要一个 Card,里面放一个 Row"),由客户端负责把抽象类型映射为原生控件实现(Flutter 的Card控件、HTML 带 card 样式的<div>等)。服务端只需要知道客户端支持哪些组件名。
4. 状态管理必须高效且与 UI 结构解耦:修改 UI 中的一段文字不应要求重发整个 UI 定义。将surfaceUpdate(结构)与dataModelUpdate(数据)区分为独立消息是关键:UI 结构只需发送一次,后续更新可以是只含变更数据的细粒度dataModelUpdate消息。
5. 通信架构必须健壮、可扩展:UI 更新采用单向流(SSE),客户端只需监听并响应,比管理复杂的双向通道更健壮;事件处理则通过客户端向服务端 Agent 发送一条A2A 消息完成。
协议概览:四条服务端消息与两种客户端事件
A2UI 的核心哲学是UI 结构与应用数据的严格分离,以及由Catalog承载的可扩展组件模型——可用组件集合不由协议本身固定,而是定义在独立的 Catalog 中,允许平台特定或自定义组件。
通信通过 JSON Lines(JSONL)流进行,客户端逐行解析并增量构建 UI。服务端到客户端协议定义四种消息类型:
| 消息类型 | 作用 |
|---|---|
surfaceUpdate | 提供一组组件定义,用于添加或更新特定 UI 区域(surface)中的组件 |
dataModelUpdate | 提供新数据,插入或替换某个 surface 的数据模型(每个 surface 有自己独立的数据模型) |
beginRendering | 通知客户端已有足够信息执行首次渲染,指定根组件 ID,并可选指定要使用的组件目录 |
deleteSurface | 显式地从 UI 中移除一个 surface 及其全部内容 |
客户端到服务端的用户交互通过独立的 A2A 消息处理,且必须是以下两种类型之一,从而保持主数据流的单向性:
userAction:上报组件触发的用户动作;error:上报客户端错误。
第一节:基础架构与数据流
1.1 核心哲学:解耦与契约
A2UI 的解耦体现在三个关键元素上:
- 组件树(结构):服务端提供的抽象组件树,描述 UI 结构,由
surfaceUpdate消息定义; - 数据模型(状态):服务端提供的 JSON 对象,包含填充 UI 的动态值(文本、布尔、列表),由
dataModelUpdate消息管理; - 控件注册表(Catalog):客户端定义的组件类型(如
Row、Text)到具体原生控件实现的映射。它属于客户端应用的一部分,而非协议流的一部分——服务端必须生成目标客户端注册表能理解的组件。
1.2 JSONL 流:通信的基本单位
所有 UI 描述都作为 JSON 对象流从服务端发往客户端,格式为 JSON Lines(JSONL):每一行是一个独立的、紧凑的 JSON 对象,代表一条消息。客户端可以边到达边解析处理 UI 定义的每个部分,从而实现渐进式渲染。
1.3 Surface:管理多个 UI 区域
Surface是屏幕上可以渲染 A2UI UI 的连续区域。协议引入surfaceId来唯一标识和管理这些区域,使单个 A2UI 流可以同时控制多个相互独立的 UI 区域。每个 surface 有独立的根组件、独立的组件层级,以及独立的数据模型,以避免在大量 surface 场景下发生键冲突。
典型场景如聊天应用:每条 AI 生成的回复可渲染到对话历史中的独立 surface;一个持续存在的 surface 可用于侧边栏展示相关信息。surfaceId是每条服务端到客户端消息内的属性,配合beginRendering、surfaceUpdate、dataModelUpdate、deleteSurface将变更定向到正确的区域。
1.4 数据流模型
A2UI 协议由一条描述 UI 的服务端到客户端流和发送给服务端的独立事件组成。客户端消费流、构建 UI 并渲染。通信通过 JSONL 流进行,通常基于Server-Sent Events (SSE)传输。
- 服务端流:服务端开始通过 SSE 连接发送 JSONL 流;
- 客户端缓冲:客户端接收消息并缓冲——
surfaceUpdate的组件定义按surfaceId组织存入Map<String, Component>(surface 不存在则创建);dataModelUpdate则构建或更新客户端内部 JSON 数据模型; - 渲染信号:服务端发送带
root组件 ID 的beginRendering消息,防止"不完整内容闪烁"(flash of incomplete content)。客户端缓冲组件与数据,但等待该显式信号才尝试首次渲染,确保初始视图一致; - 客户端渲染:客户端进入 ready 状态后,从
root组件出发,通过查询缓冲中的组件 ID 递归遍历组件树,解析数据绑定,并使用WidgetRegistry实例化原生控件; - 用户交互与事件处理:用户与渲染控件交互(如点击按钮),客户端从组件的
action.context解析数据绑定,构造userActionJSON 负载,通过 A2A 消息发给服务端; - 动态更新:服务端处理
userAction后,若 UI 需要变化,则在原始 SSE 流上发送新的surfaceUpdate与dataModelUpdate消息;客户端更新组件缓冲与数据模型,UI 重渲染。服务端也可发送deleteSurface移除 UI 区域。
上述流程在协议文档中以 Mermaid sequenceDiagram 呈现(见 a2ui_protocol.md),核心闭环是:SSE 推送 → 客户端缓冲 →beginRendering触发构建 → 用户事件经 A2A 回传 → 服务端增量更新。
1.5 完整流示例:渲染一张用户资料卡
下面是一份完整的最小化 JSONL 流,渲染一张用户资料卡(原文示例,可对照仓库 08_user-profile.json 的完整版):
{"surfaceUpdate": {"components": [{"id": "root", "component": {"Column": {"children": {"explicitList": ["profile_card"]}}}}]}} {"surfaceUpdate": {"components": [{"id": "profile_card", "component": {"Card": {"child": "card_content"}}}]}} {"surfaceUpdate": {"components": [{"id": "card_content", "component": {"Column": {"children": {"explicitList": ["header_row", "bio_text"]}}}}]}} {"surfaceUpdate": {"components": [{"id": "header_row", "component": {"Row": {"alignment": "center", "children": {"explicitList": ["avatar", "name_column"]}}}}]}} {"surfaceUpdate": {"components": [{"id": "avatar", "component": {"Image": {"url": {"literalString": "https://www.example.com/profile.jpg"}}}}]}} {"surfaceUpdate": {"components": [{"id": "name_column", "component": {"Column": {"alignment": "start", "children": {"explicitList": ["name_text", "handle_text"]}}}}]}} {"surfaceUpdate": {"components": [{"id": "name_text", "component": {"Text": {"usageHint": "h3", "text": {"literalString": "A2A Fan"}}}}]}} {"surfaceUpdate": {"components": [{"id": "handle_text", "component": {"Text": {"text": {"literalString": "@a2a_fan"}}}}]}} {"surfaceUpdate": {"components": [{"id": "bio_text", "component": {"Text": {"text": {"literalString": "Building beautiful apps from a single codebase."}}}}]}} {"dataModelUpdate": {"contents": {}}} {"beginRendering": {"root": "root"}}注意示例中surfaceUpdate未显式携带surfaceId——协议要求消息可定向到 surface,实际落地时(如仓库示例)每条消息都会带"surfaceId": "gallery-user-profile"这样的标识。
第二节:组件模型与 Catalog 协商
A2UI 的组件模型为灵活性而设计,将协议本身与组件集合分离。每个版本协议关联一个Standard Catalog,v0.8 的标识符为https://a2ui.org/specification/v0_8/standard_catalog_definition.json(对应仓库文件 standard_catalog_definition.json)。
Catalog ID 是简单的字符串标识符,虽然可以是任意值,但惯例是使用自己拥有域内的 URI,以简化调试、避免混淆和命名冲突。此外,任何可能破坏 Agent 与渲染器兼容性的目录变更,必须分配新的catalogId,保证清晰的版本管理,防止 Agent 有变更而客户端没有(反之亦然)时出现意外行为。
Catalog 协商流程让客户端与服务端就某个 UI surface 使用哪个目录达成一致,支持标准目录、自定义目录、甚至动态定义的目录。
第一步:服务端(Agent)通告能力
服务端在 A2A 协议的 Agent Card 中通告其能力,对 A2UI 而言包括支持的目录以及是否能处理客户端内联(inline)定义的目录:
supportedCatalogIds(字符串数组,可选):Agent 已知支持的所有预定义目录 ID 列表;acceptsInlineCatalogs(布尔,可选):若为true,服务端可处理客户端发送的inlineCatalogs,默认为false。
示例服务端 Agent Card 片段:
{ "name": "Restaurant Finder", "capabilities": { "extensions": [ { "uri": "https://a2ui.org/a2a-extension/a2ui/v0.8", "params": { "supportedCatalogIds": [ "https://a2ui.org/specification/v0_8/standard_catalog_definition.json", "https://my-company.com/a2ui/v0.8/my_custom_catalog.json" ], "acceptsInlineCatalogs": true } } ] } }注意:这不是严格契约,仅作为帮助编排器(orchestrator)与客户端识别具备匹配 UI 能力的 Agent 的信号。运行时,编排 Agent 可能动态地把任务委托给支持额外目录的子 Agent,因此客户端应把通告的supportedCatalogIds视为 Agent 或其子 Agent 真实支持目录的子集。
第二步:客户端声明支持的目录
在发送给服务端的每条消息中,客户端都要在 A2AMessage的metadata字段里携带a2uiClientCapabilities对象,告知 Agent 服务端客户端能渲染的所有目录:
supportedCatalogIds(字符串数组,必填):客户端支持的所有预定义目录 ID 列表。若支持标准目录,客户端必须显式包含标准目录 ID。这些目录的内容预期编译进 Agent 服务端,而非运行时下载,以防止恶意内容动态注入 prompt,并保证结果可预测;inlineCatalogs(对象数组,可选):完整的 Catalog Definition Document 数组,允许客户端提供自定义、临时(on-the-fly)的目录,通常用于本地开发工作流——在客户端一处更新目录更快。仅当服务端通告acceptsInlineCatalogs: true时才能提供。
示例带客户端能力的 A2A 消息:
{ "metadata": { "a2uiClientCapabilities": { "supportedCatalogIds": [ "https://a2ui.org/specification/v0_8/standard_catalog_definition.json", "https://my-company.com/a2ui_catalogs/custom-reporting-catalog-1.2" ], "inlineCatalogs": [ { "catalogId": "https://my-company.com/inline_catalogs/temp-signature-pad-catalog", "components": { "SignaturePad": { "type": "object", "properties": {"penColor": {"type": "string"}} } }, "styles": {} } ] } }, "message": { "prompt": { "text": "Find me a good restaurant" } } }第三步:服务端选择目录并渲染
服务端收到客户端能力后,为特定 UI surface 选择一个目录,并在beginRendering消息中用catalogId字段指定:
catalogId(字符串,可选):所选目录的标识符,必须是客户端supportedCatalogIds之一,或客户端inlineCatalogs中某个目录的catalogId。
若省略catalogId,客户端必须默认使用协议版本对应的标准目录(https://a2ui.org/specification/v0_8/standard_catalog_definition.json)。
示例beginRendering消息:
{ "beginRendering": { "surfaceId": "unique-surface-1", "catalogId": "https://my-company.com/inline_catalogs/temp-signature-pad-catalog", "root": "root-component-id" } }每个 surface 可以使用不同的目录,这在多 Agent 系统中提供很高的灵活性——不同 Agent 可能支持不同目录。
面向开发者的 Schema 解析
构建 Agent 时,建议使用已解析(resolved)的 Schema,即包含你目标特定组件目录的 schema(例如把server_to_client.json与你的自定义目录定义组合起来)。这能为 LLM 提供所有可用组件、属性及目录专属样式的严格定义,使 UI 生成更可靠。通用的server_to_client.json是抽象线协议,resolved schema 才是具体的生成工具。
基于标准server_to_client_schema与custom_catalog_definition对象做替换,可使用类似如下 JSON 操作逻辑(对应仓库文件 server_to_client.json):
component_properties = custom_catalog_definition["components"] style_properties = custom_catalog_definition["styles"] resolved_schema = copy.deepcopy(server_to_client_schema) resolved_schema["properties"]["surfaceUpdate"]["properties"]["components"]["items"]["properties"]["component"]["properties"] = component_properties resolved_schema["properties"]["beginRendering"]["properties"]["styles"]["properties"] = style_properties仓库已提供替换好标准目录组件的 resolved schema 示例:server_to_client_with_standard_catalog.json。此外 catalog_description_schema.json 是定义 Catalog 的元 Schema:一个目录由components对象与styles对象构成,每个键是组件/样式名,值是其属性的 JSON Schema,catalogId、components、styles三者必填——这是自定义组件集合法的基础。
2.2surfaceUpdate消息
这是定义 UI 结构的主要消息,包含surfaceId与components数组:
{ "surfaceUpdate": { "surfaceId": "main_content_area", "components": [ { "id": "unique-component-id", "component": { "Text": { "text": { "literalString": "Hello, World!" } } } }, { "id": "another-component-id", "component": { ... } } ] } }components(必填):扁平的组件实例列表。schema 中组件项还支持可选的weight数值属性——组件作为 Row/Column 直接子节点时的相对权重,对应 CSS 的flex-grow属性(仅当组件是 Row 或 Column 的直接后代时才可设置)。
2.3 组件对象(Component Object)
components数组中的每个对象结构如下:
id(必填):标识该组件实例的唯一字符串,用于父子引用;component(必填):定义组件类型与属性的对象。
2.4component通用对象
在线上,该对象是通用的,其结构不由 A2UI 核心协议定义,而是由激活的Catalog校验。它是一个包装对象,必须恰好包含一个键,键是目录中的组件类型名字符串(如"Text"、"Row"),值是目录定义的该组件属性对象。
Text 组件示例:
"component": { "Text": { "text": { "literalString": "This is text" } } }Button 组件示例:
"component": { "Button": { "label": { "literalString": "Click Me" }, "action": { "name": "submit_form" } } }完整的可用组件类型及其属性集合由Catalog Schema定义,而非核心协议 schema。以 v0.8 标准目录(standard_catalog_definition.json)为例,它定义了 20 个组件:Text、Image、Icon、Video、AudioPlayer、Row、Column、List、Card、Tabs、Divider、Modal、Button、CheckBox、TextField、DateTimeInput、MultipleChoice、Slider等,以及font、primaryColor(十六进制色值,pattern^#[0-9a-fA-F]{6}$)两个全局样式。其中Icon.name的literalString枚举了 50 余个内置图标名(accountCircle、search、send、settings等),Text.usageHint枚举h1–h5、caption、body,Image.usageHint枚举icon、avatar、smallFeature、mediumFeature、largeFeature、header——这些细节正是 resolved schema 能约束 LLM 输出合法消息的关键。
第三节:UI 组合
3.1 邻接表模型
A2UI 协议把 UI 定义为扁平组件列表,树结构通过 ID 引用隐式构建,即邻接表(adjacency list)模型。
容器组件(如Row、Column、List、Card)的属性引用其子组件的id。客户端负责把所有组件存入映射(如Map<String, Component>),渲染时重建树结构。该模型允许服务端以任意顺序发送组件定义,只要在发送beginRendering时所有必要组件都已就绪即可。
协议文档用 Mermaid flowchart 展示了该过程(见 a2ui_protocol.md):一条surfaceUpdate携带root、title、button、button_text四个扁平组件,客户端解析后存入缓冲 Map,beginRendering触发从缓冲构建渲染树。
3.2 容器子节点:explicitList与template
容器组件(Row、Column、List)通过children对象定义子节点,该对象必须且只能包含explicitList或template之一:
explicitList:组件 ID 字符串数组,用于静态、已知的子节点;template:对象,用于从数据绑定的列表渲染动态子节点列表。
Schema 定义(minProperties: 1、maxProperties: 1强制二选一):
{ "type": "object", "description": "Defines the children of a container component. Must contain exactly one of `explicitList` or `template`.", "properties": { "explicitList": { "type": "array", "description": "An ordered list of component IDs that are direct children.", "items": { "type": "string", "description": "The ID of a child component." } }, "template": { "type": "object", "description": "Defines a template for rendering dynamic lists of children.", "properties": { "dataBinding": {"$ref": "#/definitions/DataPath"}, "componentId": { "type": "string", "description": "The ID of the component to use as a template for each item in the>{ "dataModelUpdate": { "surfaceId": "main_content_area", "path": "user", "contents": [ {"key": "name", "valueString": "Bob"}, {"key": "isVerified", "valueBoolean": true}, { "key": "address", "valueMap": [ {"key": "street", "valueString": "123 Main St"}, {"key": "city", "valueString": "Anytown"} ] } ] } }4.2 数据绑定(BoundValue对象)
组件通过绑定连接数据模型。任何可数据绑定的属性(如 Text 组件的text)都接受BoundValue对象,它定义字面值、数据路径,或二者兼有作为初始化简写。
目录 schema 中绑定的text属性定义如下:
{ "type": "object", "description": "A value that can be either a literal string or bound to the data model.", "properties": { "literalString": { "type": "string", "description": "A static string value." }, "path": {"$ref": "#/definitions/DataPath"} }, "minProperties": 1, "additionalProperties": false }组件也可绑定数字(literalNumber)、布尔(literalBoolean)或数组(literalArray)。具体行为取决于提供的属性:
仅字面值:只提供
literal*值(如literalString)时,值为静态、直接显示:"text": { "literalString": "Hello" }仅路径:只提供
path时,值为动态,渲染时从数据模型解析:"text": { "path": "/user/name" }路径 + 字面值(初始化简写):同时提供
path与literal*值时,作为数据模型初始化的简写。客户端必须:- 用提供的
literal*值更新指定path处的数据模型(隐式dataModelUpdate); - 将该组件属性绑定到该
path用于渲染与后续更新。
这让服务端在一个步骤内既设置默认值又完成绑定:
// 将 '/user/name' 处数据模型初始化为 "Guest" 并绑定到它 "text": { "path": "/user/name", "literalString": "Guest" }- 用提供的
客户端的解释器负责在渲染前解析数据模型中的路径。A2UI 协议支持直接 1:1 绑定,不包含转换器(如格式化器、条件表达式);任何数据转换都必须由服务端在发送dataModelUpdate前完成。
仓库中的完整示例可以直观印证绑定用法:08_user-profile.json 中头像Image.url绑定/avatar、昵称Text.text绑定/name,随后一条dataModelUpdate用 8 个valueString条目填充avatar、name、username、bio、followers、following、posts、followText等键,最后beginRendering指定root触发渲染——结构、数据、渲染信号三段式非常清晰。其余 30 个 basic 目录示例(examples/ 下的01_flight-status、02_email-compose、12_chat-message等)覆盖了航旅、邮件、聊天、电商、多媒体等各类场景,可作为参考素材。
第五节:事件处理
虽然服务端到客户端的 UI 定义是单向流(如 SSE),用户交互通过 A2A 消息回传给服务端。
5.1 客户端事件消息
客户端发送单个 JSON 对象作为包装器,必须恰好包含userAction或error两个键之一(schema 中oneOf保证,见 client_to_server.json)。
5.2userAction消息
当用户与定义了 action 的组件交互时发送,是用户驱动事件的主要机制,结构如下:
name(字符串,必填):动作名,直接取自组件的action.name属性(如"submit_form");surfaceId(字符串,必填):事件发起处的 surface 的id;sourceComponentId(字符串,必填):触发事件的组件id(如"my_button");timestamp(字符串,必填):事件发生的 ISO 8601 时间戳(如"2025-09-19T17:01:00Z");context(对象,必填):JSON 对象,包含组件action.context中的所有键值对,并已将所有BoundValue针对数据模型解析。
解析action.context的过程与数据绑定一致:客户端遍历context数组,解析所有字面值或数据绑定值,构建context对象。
5.3error消息
这是提供给服务端的反馈机制,当客户端遇到错误(如 UI 渲染或数据绑定出错)时发送。对象内容灵活,可包含任何相关错误信息。
5.4 事件流示例(userAction)
- 组件定义(来自
surfaceUpdate):
{ "surfaceUpdate": { "surfaceId": "main_content_area", "components": [ { "id": "submit_btn_text", "component": { "Text": { "text": {"literalString": "Submit"} } } }, { "id": "submit_btn", "component": { "Button": { "child": "submit_btn_text", "action": { "name": "submit_form", "context": [ { "key": "userInput", "value": {"path": "/form/textField"} }, {"key": "formId", "value": {"literalString": "f-123"}} ] } } } } ] } }- 数据模型(来自
dataModelUpdate):
{ "dataModelUpdate": { "surfaceId": "main_content_area", "path": "form", "contents": [{"key": "textField", "valueString": "User input text"}] } }- 用户动作:用户点击
submit_btn按钮; - 客户端解析:客户端解析
action.context; - 客户端到服务端请求:客户端向
https://api.example.com/handle_event发送POST请求,请求体如下:
{ "userAction": { "name": "submit_form", "surfaceId": "main_content_area", "sourceComponentId": "submit_btn", "timestamp": "2025-09-19T17:05:00Z", "context": { "userInput": "User input text", "formId": "f-123" } } }- 服务端响应:服务端处理该事件;若 UI 需要随之变化,则在独立的 SSE 流上发送新的
surfaceUpdate或dataModelUpdate消息。
值得注意:context中userInput的值来自数据绑定路径/form/textField,在点击时被解析为 "User input text"(用户实际输入),而formId是字面值 "f-123"——这正是绑定解析与字面值混用的典型场景。标准目录的Button组件 schema(见 standard_catalog_definition.json)中,action.context的value支持path、literalString、literalNumber、literalBoolean四种来源。
第六节:客户端实现
一个健壮的 A2UI 客户端解释器应由以下关键组件构成:
| 组件 | 职责 |
|---|---|
| JSONL Parser | 逐行读取流,把每行解码为独立 JSON 对象 |
| Message Dispatcher | 用机制(如switch语句)识别消息类型(beginRendering、surfaceUpdate等)并路由到正确的处理器 |
| Component Buffer | Map<String, Component>,按id存储所有组件实例,由surfaceUpdate消息填充 |
| Data Model Store | Map<String, dynamic>(或类似结构)持有应用状态,由dataModelUpdate消息构建与修改 |
| Interpreter State | 状态机,跟踪客户端是否准备好渲染(如_isReadyToRender布尔,由beginRendering置为true) |
| Widget Registry | 开发者提供的映射(如Map<String, WidgetBuilder>),把组件类型字符串(Row、Text)关联到构建原生控件的函数 |
| Binding Resolver | 工具函数,接收BoundValue(如{ "path": "/user/name" })并针对 Data Model Store 解析 |
| Surface Manager | 基于surfaceId创建、更新、删除 UI surface 的逻辑 |
| Event Handler | 暴露给WidgetRegistry的函数,构造并发送客户端事件消息(如userAction)到配置的 REST API 端点 |
从仓库的渲染器实现可以印证这套客户端架构。以 react/src/v0_8 为例,其渲染器从root递归构建 React 组件树,并在每次收到surfaceUpdate/dataModelUpdate后触发重新渲染;angular/src/v0_8 同样实现了按surfaceId分区的组件缓冲与数据模型。仓库还提供了一份最小目录minimal_catalog.json,仅含Text、Row、Column、Button、TextField五个基础组件,是标准目录的严格子集(符合它的消息必然也符合标准目录),专为测试新渲染器实现设计——先覆盖布局算法、组件嵌套、数据绑定与事件处理的基础,再扩展到完整标准目录。
第七节:完整 Schema 总览
协议在 specification/v0_8/json 目录下提供全部正式 JSON Schema:
- server_to_client.json:面向服务端到客户端消息的核心、目录无关 schema,定义四种消息类型(
beginRendering、surfaceUpdate、dataModelUpdate、deleteSurface)及结构。其中surfaceUpdate的component对象是通用的(additionalProperties: true),允许任何组件定义传入。每条消息必须恰好包含四个 action 属性之一; - server_to_client_with_standard_catalog.json:已解析、对 LLM 友好的版本,把通用
component对象替换为包含标准目录全部组件的严格oneOf定义,是让 LLM 无歧义生成合法 A2UI 消息的完整强类型 schema; - client_to_server.json:客户端到服务端事件消息 schema,包含
userAction与error,通过oneOf与minProperties/maxProperties: 1保证包装器恰好包含其一; - catalog_description_schema.json:定义组件目录结构的元 schema(
catalogId+components+styles); - a2ui_client_capabilities_schema.json:客户端能力声明的正式 schema。
在 a2ui_extension_specification.md 中,A2UI 被定义为 A2A 协议的扩展:扩展 URI 为https://a2ui.org/a2a-extension/a2ui/v0.8(唯一接受值);A2UI 消息编码为 A2ADataPart,mimeType为application/json+a2ui;客户端通过传输层机制激活扩展(JSON-RPC/HTTP 用X-A2A-Extensions头,gRPC 用同名 metadata 值)。客户端侧每个Message的metadata.a2uiClientCapabilities声明支持的目录,服务端侧 Agent Card 的AgentCapabilities.extensions中通告supportedCatalogIds与acceptsInlineCatalogs——两条规范共同构成完整的协商闭环。
结语:从协议到实现
A2UI v0.8 协议的全部设计可以浓缩为一句话:用 LLM 最容易生成的形式(扁平、声明式、无状态的 JSONL 消息)承载平台无关的 UI 描述,用目录机制解耦协议与组件集合,用 surface 与数据模型分离保证状态管理与多区域渲染的灵活,用单向 SSE 流加 A2A 事件回传保持架构健壮。掌握本指南后,你可以依据 server_to_client_with_standard_catalog.json 生成符合规范的流,对照 catalogs/basic/examples 的 30 个场景示例快速起步,并借助 minimal_catalog.json 轻量验证自己的渲染器——从一条surfaceUpdate开始,逐步构建出完整的流式 UI 渲染链路。
【免费下载链接】a2ui项目地址: https://gitcode.com/GitHub_Trending/a2/a2ui
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考