第13章 A2UI 协议与 Flutter GenUI 包迎来重要更新
A2UI 与 Flutter GenUI 包的新进展
探索 A2UI 和 Flutter GenUI 包的更新,用于构建由 Agent 动态生成的交互式界面。
作者:Andrew Brogdon 2026年5月14日 · 阅读时间约 7 分钟
在 RSS 中订阅
在 X 上分享 在 Bluesky 上分享 在 LinkedIn 上分享
生成式 UI(GenUI)是一种用户体验模式,其中 Agent 不仅生成内容,还决定内容的展示方式和交互形式。对于 Flutter 开发者而言,实现 GenUI 意味着使用 A2UI——一种开放协议,用于定义 Agent 和客户端(或“渲染器”)在用户界面结构与状态上如何协作。为了充分利用这一技术,Flutter 团队开发了 genui 包。它利用 A2UI 连接 Agent,向其提供一组可用 Widget 清单,并将这些 Widget 呈现给用户。
genui 包和 A2UI 协议最近都有重要更新!
genui 的最新版本引入了一些架构层面的变化。随着 A2UI 协议 v0.9 的引入,该更新使 genui 从“结构化输出优先”(即通过结构化输出 API 流式传输 A2UI 消息)转向“提示词优先”(即 Agent 在响应中以文本形式包含 JSON 代码块)。同时,架构进行了解耦,使开发者能更直接地控制应用程序与大型语言模型(LLM)的交互方式。
如果你需要将应用从 genui 包 v0.7.0 迁移到 v0.9.0,本指南涵盖了所需步骤,包括清理依赖项以及配置新的聊天循环。
架构解耦
在以前的版本中,GenUI 依赖于一系列基于 ContentGenerator 的类。这些类隐藏了提示词构建、LLM 网络调用和响应解析的细节。
package:genui 的最新版本移除了 ContentGenerator。框架现在拆分为不同层级:
- 引擎(SurfaceController):管理 UI 的状态和渲染。 - 传输层(A2uiTransportAdapter):在 Agent 和渲染器之间流式传输消息。 - 门面(Conversation):提供用于管理聊天状态的高级 API。
这种解耦意味着你可以控制聊天历史、重试逻辑和错误处理。你也可以按自己的方式建立与 LLM 的连接。框架不再用 ContentGenerator 包装你的 Agent,因此你可以自由选择模型和服务提供商,调整生成设置,添加自定义函数等,而无需通过框架 API 完成这些操作。
由于 ContentGenerator 已被移除,那些针对特定服务提供商的包装包也不再需要。如果你拉取该包的最新版本,会发现 genui_dartantic、genui_google_generative_ai 和 genui_firebase_ai 等名称已不再出现在依赖树中。
这是迁移过程中最重大的代码变更。你不再将 ContentGenerator 传递给 SurfaceController,而是由你的应用负责建立与 Agent 的连接,并通过 TransportAdapter 在双方之间传递消息。
旧方式:
```dart // 创建一个封装 Agent 交互的 ContentGenerator。 final generator = FirebaseAiContentGenerator( catalog: CoreCatalogItems.asCatalog(), systemInstruction: 'You are a helpful assistant.', );
// 创建一个将生成器与 GenUiManager(负责管理表面、更新等)关联的对话对象。 final conversation = GenUiConversation( genUiManager: GenUiManager(catalog: catalog), contentGenerator: generator, ); ```
新方式:
```dart final catalog = BasicCatalogItems.asCatalog();
// 创建 SurfaceController 以管理生成表面的状态。 final surfaceController = SurfaceController(catalogs: [catalog]);
// 创建一个传输适配器,它将 `genui` 库的消息路由到 Agent, // 然后通过 `addChunk` 将响应传回适配器。 late final adapter = A2uiTransportAdapter( onSend: (ChatMessage msg) async { // 使用字符串缓冲区准备要发送给 Agent 的数据。 final buffer = StringBuffer();
// 遍历 `genui` 包创建的消息,并将其作为数据追加到缓冲区。 for (final part in msg.parts) { if (part.isUiInteractionPart) { buffer.write(part.asUiInteractionPart!.interaction); } else if (part is genui.TextPart) { buffer.write(part.text); } }
// 向 Agent 发送内容生成请求,包含来自 `genui` 的字符串化消息。 final response = await myAgentClient.sendRequest(buffer.toString());
// 接收到 Agent 响应后,使用 `addChunk` 将其添加到 `genui` 包 // 的输入流中,以便解析 A2UI 消息。 adapter.addChunk(response); }, ); ```
看到这两个示例,你可能会想:“等等,API 改进不应该意味着我写更少的代码吗?”没错,以前这段连接 Agent 的“接线”代码包含在 genui 包中,隐藏在 ContentGenerator 类里。但新方式有一些具体优势:
- 无需 ContentGenerator,你可以按喜好配置 Agent,在内存中管理其生命周期,而不用担心等待包更新来支持新的 ContentGenerator。你还可以使用几乎任何 AI 来源。 - 你不再需要向 genui API“注入” Agent 连接。它们松散耦合,仅通过数据交换信息。 - 测试更简单。genui 直接接受数据,这些数据可以来自真实 Agent、模拟 Agent 或硬编码测试。 - 如果你仍希望将连接封装在类中,当然可以。实际上,genui 的多个示例都采用了这种做法。
转向提示词优先
在以前版本中,genui 包高度依赖 LLM 提供商的严格 API 级约束(如“JSON Mode”或复杂的函数调用定义)来强制模型输出有效的 UI 结构。架构通过特定 API 参数带外传递给 LLM,LLM 实际上被锁定在刚性结构中。
虽然这种方式引导模型生成可预测、格式规范的 JSON,但深度嵌套的架构有时会让模型困惑,或与其自然的文本生成倾向冲突。此外,依赖结构化输出限制了 Widget 清单的整体规模和复杂度。由于约束完全位于网络层而非易于读取和修改的纯文本中,调试也变得更困难。
“提示词优先”方法将真实来源回归到 LLM 擅长的领域:系统提示词。不再完全依赖严格的 API 开关,而是将 A2UI 相关的 UI 架构和指令直接作为纯文本注入 LLM 的系统提示词中。LLM 读取这些详细说明如何为客户端构建消息的指令。
这种方法有几个优势。首先,现代 LLM 高度优化以遵循详细的系统提示词和示例。在提示词中提供架构与它们的“思维方式”一致。此外,由于 UI 架构现在是提示词中的纯文本,你可以灵活调整以适应应用需求。
这意味着将正确的提示词放入 Agent 上下文窗口的责任现在落在你的应用身上。幸运的是,genui 包提供了一个新工具来帮助你为应用构建正确的系统提示词:PromptBuilder。给定一个 Widget 清单和你希望提供的额外指令,PromptBuilder 会创建一个包含 LLM 正确格式化 A2UI 消息所需的架构定义和规则的系统提示词。
```dart final promptBuilder = PromptBuilder.chat( catalog: catalog, systemPromptFragments: ['You are a helpful assistant.'], ); ```
设置完成后,你的应用可以通过 promptBuilder.systemPrompt 获取提示词的字符串版本,然后将其传递给 LLM。
协议与架构调整
如果你的代码手动构建 A2UI JSON 或依赖特定的负载结构,请注意 A2UI v0.9 更新带来的以下破坏性变更:
- 表面创建:beginRendering 重命名为 createSurface。 - 扁平化组件定义:组件不再使用嵌套键(如 {"Text": {"text": "Hello"}}),而是使用扁平鉴别器:{"component": "Text", "text": "Hello"}。 - 数据绑定:绑定简化了,使用 { "path": "/path/to/var" } 进行路径解析。
属性重命名:
- distribution => justify - alignment => align - usageHint => variant - text(在 TextField 中) => value - userAction => action
其他名称也变了!
除了一些小的调整外,几乎所有核心类的 GenUi 前缀都已移除:
- GenUiConversation => Conversation - GenUiController => SurfaceController - GenUiSurface => Surface - GenUiHost => SurfaceHost - GenUiContext => SurfaceContext - GenUiTransport => Transport - GenUiFallback => FallbackWidget
此外,CoreCatalogItems 重命名为 BasicCatalogItems,以明确其作为基础实现而非严格要求的角色。
最后,为了与标准 LLM 函数调用术语保持一致,GenUiFunctionDeclaration 和对“工具”的引用已重命名为 ClientFunction。
新的 genai_primitives 包
genui 包不再附带自己的消息类型。相反,GenUI 团队创建了新的 genai_primitives 包,其中包含 GenAI 应用常用功能所需的基础类型。该包包括 ChatMessage、MessagePart 和 ToolDefinition 等类型。
这些新类型贯穿 genui 包 API 使用,并足够灵活,可嵌入你正在开发的其他 GenAI 应用或包中。
还有更多!
除上述内容外,A2UI 和 genui 包的最新版还带来了一些新功能,例如:
- 自定义函数,可用于在客户端验证数据 - 新的模块化架构 - 改进的错误处理
有关这些功能的更多信息,请查看 v0.9 发布公告博客文章,并访问 a2ui.org。
总结
genui 的最新更新引入了一个更符合惯用、更灵活且更健壮的架构。如果你尚未开始使用 GenUI,现在是再好不过的时候!前往我们的入门代码实验室,在大约 90 分钟内获得实践经验并创建一个可用的应用。
更多来自 Flutter 的内容
宣布 Genkit Dart 1.0:使用 Dart 和 Flutter 构建生产级 Agent 应用
宣布 Genkit Dart 稳定 1.0 版本发布。这是一个用于使用 Dart 和 Flutter 构建 AI 驱动功能和 Agent 工作流的开源框架。
Chris Gill 2026年10月8日 · 阅读时间约 6 分钟
Material 和 Cupertino 解耦已到来
将 Material 和 Cupertino 从 Flutter 核心中移出如何加速修复和新功能开发
Craig Labenz 2026年9月9日 · 阅读时间约 4 分钟