A2UI React Shell 实战:用 React 渲染 A2A 协议流式传输的 Agent UI(Restaurant Finder 示例全解)
【免费下载链接】a2ui项目地址: https://gitcode.com/GitHub_Trending/a2/a2ui
本指南以 samples/client/react/shell/README.md 为核心,完整讲解如何在 A2UI 仓库中搭建一个 React Shell:它作为前端外壳,通过 Agent-to-Agent(A2A)协议与 Python 编写的 Restaurant Finder Agent 通信,将 Agent 流式返回的 A2UI surfaces 实时渲染为可交互的 Web 界面。读完本文,你将掌握从零启动示例、理解「Vite 中间件代理 → A2A 客户端 → SSE 流解析 → MessageProcessor 渲染」整条链路,并能读懂 shell 的配置项、Mock 模式与安全边界。
一、示例概览:React 前端 + Python Agent 的分工
该示例由一个 React 外壳与一个 Python Agent 组成,二者通过 A2A 协议解耦:
- React Shell(samples/client/react/shell):纯前端壳,负责输入查询、把用户动作(action)转发给 Agent、并把 Agent 返回的 A2UI 消息解析渲染成界面。它只认识 A2UI 消息协议,不关心 Agent 内部用的是什么 LLM 或框架。
- Restaurant Finder Agent(samples/agent/adk/restaurant_finder):基于 Google ADK 编写的「餐厅搜索与订座」Agent,作为 A2A 服务器监听
localhost:10002,通过 A2A 扩展头声明自己支持 A2UI(X-A2A-Extensions: https://a2ui.org/a2a-extension/a2ui/v0.9,见 middleware/a2a.ts),以application/a2ui+json类型的消息把 UI 定义推送给前端。
从仓库结构看,shell 的依赖充分体现了这一分层:package.json 中同时引用了@a2a-js/sdk(A2A 协议客户端)、@a2ui/react(React 渲染器)、@a2ui/web_core(A2UI 消息处理核心)与@a2ui/markdown-it(Markdown 渲染),React 19 + Vite 8 作为运行时底座。
二、前置条件(Prerequisites)
原文档明确列出三项前置,结合仓库可细化为:
- Node.js:用于
yarn与 Vite 构建(仓库根目录使用 Yarn 工作区管理多包)。 - uv:Python 包管理器,用于启动 Agent(Agent 依赖通过 uv workspace 解析)。
- Python:版本要求以 Agent 的 pyproject.toml 中
requires-python = ">=3.10"为准。 - LLM API Key:Agent 需要 Gemini API Key 才能执行真实查询(见下文第四步的环境变量配置)。
三、运行步骤:4 步启动完整示例
1. 安装并构建依赖
在仓库根目录执行:
yarn install yarn build:allyarn install会按 Yarn 工作区解析并安装所有子包(React 渲染器、Web Core、Markdown 渲染器等);yarn build:all负责把@a2ui/react等 workspace 包先构建出来,供 shell 引用。shell 自身的构建脚本由 package.json 的wireit.build定义,其dependencies字段显式声明了「必须先构建renderers/react」这一包间依赖关系。
2. 启动 Agent
另开一个终端:
cd samples/agent/adk/restaurant_finder cp .env.example .env # 然后编辑 .env,填入 GEMINI_API_KEY(不要把 .env 提交进仓库) uv run .- .env.example 中只有两个可选变量:
GEMINI_API_KEY(必填)与GOOGLE_GENAI_USE_VERTEXAI=TRUE(可选,改用 Vertex AI 后端)。 - Agent 默认监听
localhost:10002。可通过 Agent 侧 README 提供的命令自检:curl http://localhost:10002/.well-known/agent-card.json应返回 AgentCard;也可直接发送 JSON-RPC 形式的message/send请求验证消息通路。
3. 启动 React 开发服务器
再开一个终端:
cd samples/client/react/shell yarn devVite 配置 表明:开发服务器固定占用端口5003(strictPort: true),并通过a2aPlugin()把/a2a路径的请求代理到运行在localhost:10002的 Agent 上。
4. 打开界面
浏览器访问 http://localhost:5003(或点击终端打印的链接),即可在输入框输入如「Top 5 Chinese restaurants in New York」之类的查询(这是 restaurant.ts 中的默认 placeholder),看到 Agent 流式返回的餐厅列表、订座表单与确认页。
四、链路剖析一:Vite 中间件如何桥接 A2A
/a2a代理是整套架构的中枢,实现在 middleware/a2a.ts:
- 客户端复用:首次请求时通过
A2AClient.fromCardUrl('http://localhost:10002/.well-known/agent-card.json')拉取 AgentCard 并建立 A2A 客户端,此后复用单例;fetchWithCustomHeader为每个请求注入X-A2A-Extensions头,声明本次会话启用了 A2UI v0.9 扩展。 - 请求体判定与转发:中间件解析 POST 到
/a2a的原始请求体——若为合法 JSON,则视为 UI 事件(用户点击按钮产生的 action),包装成kind: 'data'、mimeType: 'application/a2ui+json'的 Part;否则视为纯文本查询,包装成kind: 'text'的 Part。二者都生成随机messageId、role: 'user'的标准 A2A 消息。 - 流式响应(默认开启):
ENABLE_STREAMING环境变量不为'false'时启用client.sendMessageStream,把 Agent 的status-update与message两类事件中的 parts 逐块写成text/event-stream(data: {...}\n\n格式)转发给浏览器。 - 降级与非流式兜底:若关闭流式,则改用
sendMessage一次性拿到完整 Task 结果,以 JSON 返回;出错时按「响应头是否已发送」分别返回{error}或流式错误帧。 - 防护细节:请求体上限
MAX_PAYLOAD_SIZE = 1024 * 1024(1 MB),超限直接返回 413 并销毁请求,防止异常 Shell 耗尽开发服务器内存;同时客户端断开时(res.destroyed)会停止继续向 Agent 拉取数据。
五、链路剖析二:浏览器端的 SSE 流解析(client.ts)
src/client.ts 中的A2UIClient负责把中间件转发来的流解析回 A2UI 消息:
- 按事件流边界切分:用
\r?\n\r?\n切分 SSE 帧,把最后一个不完整块留存在 buffer 中,等待下个数据块拼接。 - Part 级解析:每帧是一个 Part 数组,逐项判断——
kind: 'error'直接抛出错误;kind: 'data'则取出其中的 A2UI 消息。 - 关键的去重逻辑:A2A 的 status-update 事件携带累积式的 parts,
createSurface会在每个 chunk 里重复出现,若不去重会导致 MessageProcessor 抛出「Surface already exists」异常。因此seenSurfaceIds集合会记录已转发过的 surfaceId,重复的 createSurface 直接跳过。 - 非流式兜底:当响应 Content-Type 不是
text/event-stream时走response.json()分支,同样按 Part 数组提取 A2UI 消息。
六、链路剖析三:React 渲染层(App.tsx 与 MessageProcessor)
src/App.tsx 完成了从消息到界面的最后一公里:
- MessageProcessor 装配:用
new MessageProcessor([basicCatalog], action => ...)创建处理器——basicCatalog是 v0.9 基础组件目录,回调中把用户的 action 包装成{version: 'v0.9', action}客户端消息再发回 Agent,实现「按钮点击 → 下一轮 UI」的闭环(见 src/App.tsx)。 - Surface 生命周期管理:订阅
onSurfaceCreated/onSurfaceDeleted,把 surfaces 同步进 React state;sendAndProcess在发起新一轮请求前会清空旧 surfaces。 - 流式增量渲染:
client.send的onChunk回调里,每个 chunk 先交给processor.processMessages更新底层 SurfaceModel,再追加进消息列表;A2uiSurface组件通过useSyncExternalStore订阅各自的 surface,因此无需手动触发重渲染。 - UI 状态机:无消息时显示搜索表单(含 hero 背景与标题);请求中显示转动的 loading 文案;出错时展示错误条;有 surface 时在
<section className="surfaces">中逐个渲染<A2uiSurface>。 - 深浅色模式:跟随系统
prefers-color-scheme初始化,也可点右上角按钮手动切换。
Mock 模式:不启动 Agent 也能跑通 UI
在 URL 后加?mock=true即进入纯前端演示模式(见 src/App.tsx):getMockResponse根据 action 名称返回模拟消息——book_restaurant生成订座表单、submit_booking生成确认页、默认返回餐厅列表(数据与 mock/restaurantMessages.ts 中的餐厅样例一致),并模拟 800ms 网络延迟。页面左上角会显示Mock Mode徽标。这是快速体验 A2UI 渲染能力、或为前端调试提供稳定数据源的便捷方式。
七、Shell 配置项详解(AppConfig)
Shell 是「通用外壳 + 应用配置」的模式:只需实现 src/configs/types.ts 中的AppConfig接口,即可复用同一外壳承载不同应用(当前注册了restaurant,见 src/configs/index.ts)。完整配置项如下:
| 配置项 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
key | string | 是 | 应用唯一标识(如'restaurant') |
title | string | 是 | 页面标题,同时写入document.title |
placeholder | string | 是 | 输入框占位提示文本 |
background | string | 否 | 页面背景 CSS(可通过--background自定义变量覆盖) |
heroImage | string | 否 | 浅色模式 hero 图片路径 |
heroImageDark | string | 否 | 深色模式 hero 图片路径,缺省回退到heroImage |
loadingText | string \| string[] | 否 | 请求中的加载文案;传数组则每 2 秒轮换一次 |
serverUrl | string | 否 | Agent 服务器地址(如http://localhost:10002) |
theme | Theme | 否 | 主题覆盖(类型来自@a2ui/react) |
实际示例见 src/configs/restaurant.ts:title: 'Restaurant Finder'、四段轮换的loadingText,以及一组用light-dark()适配明暗两套配色、由四组径向渐变叠加线性渐变组成的背景。启动时 URL 的app参数决定加载哪个配置(默认restaurant)。
八、安全注意事项(务必阅读)
原文档对安全边界有明确且强制的说明,此处完整继承并强调:
该示例代码仅用于演示 A2UI 与 A2A 协议机制。生产环境中,必须把任何不受你直接控制的 Agent 视为潜在不可信实体:
- 把 Agent 传来的所有运营数据当作不可信输入——包括其 AgentCard、消息、artifacts 与任务状态。恶意 Agent 可以在字段(如
name、skills.description)中植入精心构造的数据,若未经净化直接拼接进 LLM 提示词,可能引入提示注入(prompt injection)攻击。 - 收到的 UI 定义与数据流同样不可信:恶意 Agent 可能伪装合法界面实施钓鱼,通过属性值注入恶意脚本(XSS),或生成极端复杂的布局拖垮客户端性能(DoS)。如果应用支持 iframe、web view 等可选内嵌内容,还需额外防范跳转到恶意外部站点。
- 开发者责任:未正确校验数据、未严格沙箱化渲染内容都可能引入严重漏洞。开发者必须落实输入净化(input sanitization)、Content Security Policy(CSP)、对内嵌内容的严格隔离以及安全的凭据管理。
九、延伸阅读
- Agent 侧完整说明与 A2A 消息自检命令:samples/agent/adk/restaurant_finder/README.md
- A2UI v0.9 协议规范:specification/v0_9/docs/a2ui_protocol.md、specification/v0_9/docs/a2ui_extension_specification.md
- React 渲染器源码与测试:renderers/react/src/v0_9、renderers/react/tests/v0_9
- A2UI 与 MCP 应用的集成指南:docs/public/guides/a2ui-in-mcp-apps.md
- 其他语言/框架的 Shell 实现可对照:samples/client/angular、samples/client/lit
【免费下载链接】a2ui项目地址: https://gitcode.com/GitHub_Trending/a2/a2ui
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考