基于 ADK 构建生成 A2UI 界面的 Agent:从文本助手到流式 UI 消息的完整指南
2026/9/14 3:35:28 网站建设 项目流程

基于 ADK 构建生成 A2UI 界面的 Agent:从文本助手到流式 UI 消息的完整指南

【免费下载链接】a2ui项目地址: https://gitcode.com/GitHub_Trending/a2/a2ui

导读:本文基于 A2UI 仓库的官方开发指南 docs/public/guides/agent-development.md,完整讲解如何用 Google ADK(Agent Development Kit)构建能够生成并流式输出 A2UI 界面的 Agent。你将掌握从"纯文本 Agent"升级到"UI Agent"的完整路径:如何用A2uiSchemaManager/DirectJsonFormat注入 A2UI schema 与示例、如何设计让 LLM 遵循的 UI 规则、如何解析与校验结构化 JSON 输出,并了解仓库内置的餐厅推荐示例与底层源码实现。读完即可在 ADK 项目中复现同一套"意图理解 → 生成 A2UI JSON → 校验与流式发送 → 处理动作"的开发流程。

Agent 开发流程概览

构建一个 A2UI Agent 的核心工作流可以概括为四个环节:

  1. 理解用户意图(Understand user intent)→ 决定展示什么样的 UI;
  2. 生成 A2UI JSON(Generate A2UI JSON)→ 通过 LLM 结构化输出或提示工程生成界面描述;
  3. 校验与流式发送(Validate & stream)→ 检查 JSON 是否符合 schema,再发送给客户端渲染;
  4. 处理动作(Handle actions)→ 响应客户端回传的用户交互事件。

上图展示了这一流程的全貌:Server 端(Agent)通过 SSE 流把surfaceUpdate(组件定义)与dataModelUpdate(数据模型更新)发送给客户端,客户端缓冲、渲染组件树后,用户交互产生的userAction又通过 A2A 消息回传 Server,触发新一轮的动态更新——这正是 A2UI 消息在 Agent 与渲染器之间的完整闭环。

从零开始:用 ADK 搭建第一个简单 Agent

本指南以 ADK 为 Agent 运行框架,从一个纯文本餐厅推荐助手起步,再逐步升级为生成 A2UI 界面的 Agent。仓库中对应的完整可运行示例位于 samples/agent/adk/restaurant_finder 目录。

创建项目

首先安装并初始化 ADK 项目:

pip install google-adk adk create my_agent

TIP:如果你使用uv且正在示例目录(或任何已经依赖google-adk的项目)中工作,可以用uv run adk代替全局安装:

uv run adk create my_agent

编写一个纯文本版本的餐厅推荐 Agent

编辑my_agent/agent.py,写入一个极其简单的餐厅推荐 Agent。它通过get_restaurants工具返回 JSON 数据,当前只是以纯文本形式输出给用户:

import json from google.adk.agents.llm_agent import Agent from google.adk.tools.tool_context import ToolContext def get_restaurants(tool_context: ToolContext) -> str: """Call this tool to get a list of restaurants.""" return json.dumps([ { "name": "Xi'an Famous Foods", "detail": "Spicy and savory hand-pulled noodles.", "imageUrl": "http://localhost:10002/static/shrimpchowmein.jpeg", "rating": "★★★★☆", "infoLink": "[More Info](https://www.xianfoods.com/)", "address": "81 St Marks Pl, New York, NY 10003" }, { "name": "Han Dynasty", "detail": "Authentic Szechuan cuisine.", "imageUrl": "http://localhost:10002/static/mapotofu.jpeg", "rating": "★★★★☆", "infoLink": "[More Info](https://www.handynasty.net/)", "address": "90 3rd Ave, New York, NY 10003" }, { "name": "RedFarm", "detail": "Modern Chinese with a farm-to-table approach.", "imageUrl": "http://localhost:10002/static/beefbroccoli.jpeg", "rating": "★★★★☆", "infoLink": "[More Info](https://www.redfarmnyc.com/)", "address": "529 Hudson St, New York, NY 10014" }, ]) AGENT_INSTRUCTION=""" You are a helpful restaurant finding assistant. Your goal is to help users find and book restaurants using a rich UI. To achieve this, you MUST follow this logic: 1. **For finding restaurants:** a. You MUST call the `get_restaurants` tool. Extract the cuisine, location, and a specific number (`count`) of restaurants from the user's query (e.g., for "top 5 chinese places", count is 5). b. After receiving the data, you MUST follow the instructions precisely to generate the final a2ui UI JSON, using the appropriate UI example from the `prompt_builder.py` based on the number of restaurants.""" root_agent = Agent( model='gemini-2.5-flash', name="restaurant_agent", description="An agent that finds restaurants and helps book tables.", instruction=AGENT_INSTRUCTION, tools=[get_restaurants], )

注意get_restaurants的签名在仓库实际示例(samples/agent/adk/restaurant_finder/tools.py)中更为完整:它接收cuisinelocationcount参数,从同目录的restaurant_data.json读取数据,并根据tool_context.state中的base_url动态替换图片 URL——这样在本地/远端部署时图片都能正确加载。

运行前需要设置GOOGLE_API_KEY环境变量:

echo 'GOOGLE_API_KEY="YOUR_API_KEY"' > .env

启动 ADK 自带的 Web 界面测试:

adk web

从列表中选择my_agent,询问纽约的餐厅,你会看到 UI 中以纯文本形式返回的餐厅列表。

生成 A2UI 消息:让 LLM 输出结构化界面 JSON

要让 LLM 生成 A2UI 消息,关键是提示工程:把 A2UI schema 和你组件目录中的示例注入系统提示。SDK 提供的A2uiSchemaManager正是为此设计——它自动加载指定协议版本的 schema 与示例,保证提示词格式正确且与协议保持同步。

安装与导入

确保已安装a2ui-agent-sdk(示例项目中已包含)。在 agent 文件中导入相关类:

from a2ui.schema.constants import VERSION_0_8, VERSION_0_9 from a2ui.strategies.schema import A2uiSchemaManager from a2ui.basic_catalog.provider import BasicCatalog

关于导入路径的说明a2ui.strategies.schema是旧版命名空间。从仓库源码看,A2uiSchemaManager当前被实现为 agent_sdks/python/a2ui_agent/src/a2ui/schema/manager.py 中的一个兼容重定向类,它继承自a2ui.inference_formats.direct_json.format.DirectJsonFormat,并在导入/构造时抛出DeprecationWarning,提示改用DirectJsonFormat(agent_sdks/python/a2ui_agent/src/a2ui/inference_formats/direct_json/format.py)。新代码推荐直接导入:

from a2ui.inference_formats.direct_json import DirectJsonFormat from a2ui.schema.constants import VERSION_0_8, VERSION_0_9 from a2ui.basic_catalog.provider import BasicCatalog from a2ui.schema.common_modifiers import remove_strict_validation

定义角色与 UI 规则

# Define your agent's role ROLE_DESCRIPTION = ( "You are a helpful restaurant finding assistant. Your final output MUST be a a2ui" " UI JSON response." ) # Define rules for when to use which UI template UI_DESCRIPTION = """ - If the query is for a list of restaurants, use the restaurant data you have already received from the `get_restaurants` tool to populate the `dataModelUpdate.contents` (v0.8) or `updateDataModel.value` (v0.9+) object (e.g., for the "items" key). - If the number of restaurants is 5 or fewer, you MUST use the `SINGLE_COLUMN_LIST_EXAMPLE` template. - If the number of restaurants is more than 5, you MUST use the `TWO_COLUMN_LIST_EXAMPLE` template. - If the query is to book a restaurant (e.g., "USER_WANTS_TO_BOOK..."), you MUST use the `BOOKING_FORM_EXAMPLE` template. - If the query is a booking submission (e.g., "User submitted a booking..."), you MUST use the `CONFIRMATION_EXAMPLE` template. """

仓库中更完整的规则定义见 samples/agent/adk/restaurant_finder/prompt_builder.py,它额外强调了 v0.9 协议下updateDataModel的两个关键约束:更新列表时必须指定path: "/items"value必须是餐厅数组;使用updateDataModel时必须始终指定path,缺少 path 时该消息会被忽略

初始化 Schema 管理器并生成系统提示

# Initialize the schema manager with the Basic Catalog schema_manager = A2uiSchemaManager( version=VERSION_0_8, # Use VERSION_0_9 for newer protocol catalogs=[ BasicCatalog.get_config( version=VERSION_0_8, examples_path="examples/0.8" ) ], ) # Generate the full system prompt A2UI_AND_AGENT_INSTRUCTION = schema_manager.generate_system_prompt( role_description=ROLE_DESCRIPTION, ui_description=UI_DESCRIPTION, include_schema=True, include_examples=True, validate_examples=True, ) root_agent = Agent( model='gemini-2.5-flash', name="restaurant_agent", description="An agent that finds restaurants and helps book tables.", instruction=A2UI_AND_AGENT_INSTRUCTION, tools=[get_restaurants], )

底层实现原理:DirectJsonFormat 做了什么

从源码看,A2uiSchemaManagerDirectJsonFormat的构造过程实际完成了三件事(format.py):

  1. 按版本加载协议 schema:通过SPEC_VERSION_MAP映射表(schema/constants.py)加载server_to_client(v1.0 起为agent_to_renderer)与common_typesschema,支持0.80.90.9.11.0四个版本;
  2. 加载组件目录(Catalog)BasicCatalog.get_config()返回一个CatalogConfig,其 provider 从打包资源中加载 basic catalog schema(basic_catalog/provider.py);
  3. 关联示例路径:把examples_path(如examples/0.8)登记到 catalog 上,供提示生成时加载 few-shot 示例。

生成系统提示的最终逻辑在DirectJsonPromptGenerator.generate()(prompt_generator.py)中,组装顺序为:role_description## Workflow Description(含默认工作流规则)→## UI Description→ catalog 指令块(schema)→### Examples(示例)。其中默认工作流规则(schema/constants.py)明确要求:A2UI JSON 块必须包裹在<a2ui-json></a2ui-json>标签中;JSON 必须是一个原始 JSON 对象(通常是 A2UI 消息列表)并通过 schema 校验;且组件必须自顶向下排序——root组件必须是第一个元素、父组件先于子组件,这样流式解析器才能边到达边增量渲染 UI。

validate_examples=True会让 few-shot 示例在生成提示前就经过 schema 校验,避免把非法示例喂给 LLM;schema_modifiers=[remove_strict_validation]则用于移除 schema 中的strict校验约束,使输出容忍度更适合实际 LLM 生成场景(见 prompt_builder.py 的实际用法)。

理解输出:从纯文本到 JSON 消息列表

接入 A2UI 后,Agent 不再只输出文本,而是同时输出文本 + 一个 A2UI 消息的 JSON 列表。这个 JSON 遵循标准的 JSON Schema,定义了两类核心操作:

  • render(渲染 UI);
  • update(更新既有 UI 中的数据)。

一个 v0.9 的 A2UI 消息列表实例如下(完整文件见 samples/agent/adk/restaurant_finder/examples/0.9/single_column_list.json):

[ { "version": "v0.9", "createSurface": { "surfaceId": "default", "catalogId": "https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json", "theme": { "primaryColor": "#FF0000", "font": "Roboto" } } }, { "version": "v0.9", "updateComponents": { "surfaceId": "default", "components": [ { "id": "root", "component": "Column", "children": ["title-heading", "item-list"] }, { "id": "title-heading", "component": "Text", "variant": "h1", "text": { "path": "/title" } }, { "id": "item-list", "component": "List", "direction": "vertical", "children": { "componentId": "item-card-template", "path": "/items" } } ] } }, { "version": "v0.9", "updateDataModel": { "surfaceId": "default", "path": "/title", "value": "Found Restaurants" } }, { "version": "v0.9", "updateDataModel": { "surfaceId": "default", "path": "/items", "value": [ /* 餐厅数据数组 */ ] } } ]

可以看到,v0.8 与 v0.9+ 在数据绑定上有明显差异:v0.8 使用dataModelUpdate.contents,v0.9+ 使用updateDataModel.value,且 v0.9+ 通过path(如/items/title)精确指定数据模型更新位置——这是编写 UI 规则时最容易踩坑、也最需要写进提示词的地方。

解析与校验:发送前守住最后一道防线

因为输出是结构化 JSON,你可以在发给客户端之前先解析并校验它:

# 1. Parse the JSON # Warning: Parsing the output as JSON is a fragile implementation useful for documentation. # LLMs often put Markdown fences around JSON output, and can make other mistakes. # Rely on frameworks to parse the JSON for you. parsed_json_data = json.loads(json_string_cleaned) # 2. Validate against A2UI_SCHEMA # This ensures the LLM generated valid A2UI commands jsonschema.validate( instance=parsed_json_data, schema=self.a2ui_schema_object )

代码注释中的警告值得反复强调:手工json.loads是脆弱的——LLM 常在 JSON 外包裹 Markdown 围栏或犯其他错误,应依赖框架(如 SDK 内置解析器)来解析。对 schema 做校验可以确保客户端永远不会收到畸形的 UI 指令。

生产级示例:SDK 自带解析器 + 校验重试 + 流式输出

仓库示例 samples/agent/adk/restaurant_finder/agent.py 给出了比文档更完整的生产级实现模式:

  • 框架解析器:使用a2ui.parser.parser.parse_response()从响应中提取 A2UI JSON 块(而不是裸json.loads),该解析器位于 agent_sdks/python/a2ui_agent/src/a2ui/parser/parser.py;
  • schema 校验:调用selected_catalog.validator.validate(parsed_json_data)(对应文档中的jsonschema.validate),失败会抛出jsonschema.exceptions.ValidationError
  • 自动重试:校验失败时,构造一条包含错误信息的纠正提示("Your previous response was invalid. ...You MUST generate a valid response..."),把原始请求重新发给 LLM,默认最多重试 1 次(共 2 次尝试,见 agent.py);
  • 流式解析DirectJsonStreamParser(agent_sdks/python/a2ui_agent/src/a2ui/inference_formats/direct_json/streaming.py)配合a2ui.a2a.parts.stream_response_to_parts()将 token 流增量解析为 A2UI Part,实现边生成边渲染;
  • 会话管理:按session_id缓存解析器(上限 1000 个),保证多轮会话状态一致。

运行与验证:以 A2A 服务器方式托管

示例以 A2A 服务器方式托管 Agent(对应文档末尾的 TODO——"不通过 A2A 扩展解析、校验并发送输出" 在仓库中已有完整落地)。运行方式见 samples/agent/adk/restaurant_finder/README.md:

cd samples/agent/adk/restaurant_finder cp .env.example .env # 填入真实 API Key(勿提交 .env) uv run .

另开终端验证 Agent 是否可用并发送消息:

# 验证 AgentCard(A2A 发现) curl http://localhost:10002/.well-known/agent-card.json # 发送用户消息 curl http://localhost:10002 \ -H 'Content-Type: application/json' \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "message/send", "params": { "message": { "role": "user", "parts": [{"text": "Find me an Italian restaurant"}], "messageId": "1" } } }'

AgentCard 的capabilities.extensions中会挂载 A2UI 扩展信息(含支持的协议版本、catalog ID 列表),客户端据此发现 Agent 的 UI 能力(见 agent.py)。

安全注意事项

仓库 README(samples/agent/adk/restaurant_finder/README.md)明确警示:示例仅用于演示机制,生产环境中必须把 Agent 外部实体视为不可信输入。恶意 Agent 可能在字段中注入精心构造的数据,未经净化就拼进 LLM 提示会引发提示注入攻击;接收到的 UI 定义与数据流同样不可信,可能被用于界面仿冒钓鱼、属性值脚本注入(XSS)或构造超大布局拖垮客户端(DoS)。开发者有责任实施输入净化、内容安全策略(CSP)、严格隔离内嵌内容并安全处理凭据。

延伸阅读

  • 协议定义:各版本的server_to_client/agent_to_rendererschema 位于 specification/v0_9/json 与 specification/v1_0/json;
  • 基础组件目录:每个版本下的catalogs/basic/(如 specification/v0_9_1/catalogs/basic)定义了 LLM 可用的全部组件与字段;
  • 数据流与传输层概念:docs/public/concepts/data-flow.md 与 docs/public/concepts/transports.md;
  • 与任意 Agent 框架集成的通用说明:docs/public/guides/a2ui-with-any-agent-framework.md;
  • 客户端渲染器能力:docs/public/reference/renderers.md。

【免费下载链接】a2ui项目地址: https://gitcode.com/GitHub_Trending/a2/a2ui

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询