ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

CopilotKit A2UI Fixed-Schema 模式实战:前端持有组件树、后端流式数据模型的航班卡片演示

2026/9/13 17:32:32 拓冰建站 浏览量
CopilotKit A2UI Fixed-Schema 模式实战:前端持有组件树、后端流式数据模型的航班卡片演示 CopilotKit A2UI Fixed-Schema 模式实战前端持有组件树、后端流式数据模型的航班卡片演示【免费下载链接】CopilotKitThe Frontend Stack for Agents Generative UI. React, Angular, Mobile, Slack, and more. Makers of the AG-UI Protocol项目地址: https://gitcode.com/GitHub_Trending/co/CopilotKit导读本文围绕 CopilotKit 仓库中 CrewAICrews集成示例的 A2UI Fixed-Schema 演示讲解一种前端预置组件树Schema、后端只流式传输数据模型的声明式生成式 UI 模式。通过完整阅读后端 Agent 实现、前端 Catalog 三件套、固定 Schema JSON 与端到端测试你将掌握 A2UI Fixed-Schema 的前后端职责划分、a2ui_operations容器协议、Zod 定义与 React 渲染器的接线方式以及如何用一条条可验证的 QA 清单验收该演示的完整交互链路。一、什么是 A2UI Fixed-Schema 模式A2UIAgent UI是 CopilotKit 提出的生成式 UI 协议Agent 不直接返回 HTML/React 代码而是通过结构化操作operations在前端声明创建界面、更新组件、填充数据。A2UI 有两种主流风味Open Generative UIOpen 模式组件树schema由 Agent 在运行时生成并随每次渲染下发前端只负责按名字解析组件Fixed-Schema固定 Schema 模式组件树在开发期就作为 JSON 预置在前端Agent 只把数据写入数据模型data model前端把数据绑定到已声明的组件树上。本次演示是后者的典型实现。演示页面路由为/demos/a2ui-fixed-schema其职责边界在 前端 page.tsx 与 后端 a2ui_fixed.py 的注释中写得很清楚组件树住在前端Agent 只把航班数据流进数据模型。这样做的收益是前端 UI 结构可控、可独立走查Agent 不需要关心布局细节只需要稳定产出结构化字段起降机场、航司、价格。二、演示架构总览前端与后端各管什么整个演示是一条完整的链路QA 清单中的每个验收点都能在源码中找到对应实现层级位置职责前端页面page.tsx挂载CopilotKit注入固定 Catalog渲染聊天区前端 Cataloga2ui/catalog.ts用createCatalog把定义与渲染器绑成copilotkit://flight-fixed-catalog前端定义a2ui/definitions.ts用 Zod 声明每个组件的 props 契约前端渲染器a2ui/renderers.tsxReact 实现Card、Title、Airport、Arrow、AirlineBadge、PriceTag、Button固定 Schemaflight_schema.json12 节点的航班卡片组件树Card Column Title/Row/...后端 Agenta2ui_fixed.pyCrewAI Agent 调用display_flight工具返回a2ui_operations容器运行时copilotkit-a2ui-fixed-schema/route.ts专用CopilotRuntime关闭injectA2UITool代理到 FastAPI Agent验收脚本a2ui-fixed-schema.spec.tsPlaywright 端到端断言把 QA 清单转成可执行测试QA 清单第一项Navigate to/demos/a2ui-fixed-schema对应的是浏览器直访该路由而 e2e 测试里则通过page.goto(/demos/a2ui-fixed-schema)完成同一件事。三、后端预置 Schema 与display_flight工具后端核心在 a2ui_fixed.py。它复刻了langgraph-python示例中的同名 Agent体现了 Fixed-Schema 模式的三个关键设计。3.1 Schema 在模块加载时读入内存with (_SCHEMAS_DIR / flight_schema.json).open() as _fp: _FLIGHT_SCHEMA json.load(_fp)Schema 以 JSON 形式独立存放便于脱离 Python 代码评审并在模块加载时一次性读入避免首个请求付出 JSON 解析的 I/O 成本。后端持有整棵组件树的静态描述但它不负责动态拼装 UI 结构——这是与前者的根本区别。3.2DisplayFlightTool返回a2ui_operations容器工具输入用 Pydantic 严格约束了四个字段origin、destination、airline、price其中origin/destination要求 3 字母机场码如SFOprice是形如$289的字符串。工具执行时构造三个v0.9版本的操作ops [ {version: v0.9, createSurface: {surfaceId: SURFACE_ID, catalogId: CATALOG_ID}}, {version: v0.9, updateComponents: {surfaceId: SURFACE_ID, components: _FLIGHT_SCHEMA}}, {version: v0.9, updateDataModel: {surfaceId: SURFACE_ID, path: /, value: { origin: origin, destination: destination, airline: airline, price: price, }}}, ] return json.dumps({a2ui_operations: ops})三个操作合起来就是 Fixed-Schema 的完整语义建 Surface → 下发组件树 → 把 LLM 抽出的字段写入数据模型根路径。运行时的 A2UI 中间件在工具结果中检测到a2ui_operations容器后会把它序列化为 AG-UI 线上的render_a2ui工具结果转发给前端渲染器。3.3 会话生命周期由 Flow 接管为了让 A2UI 收到工具结果事件演示用A2UIFixedFlow(Flow[CopilotKitState])显式接管后端工具生命周期它把display_flight的 JSON schema 并入self.state.copilotkit.actions最多迭代 3 轮完成LLM 调用 → 检测 tool_calls → 执行工具 → 追加 tool 消息 → 发送工具结果的循环并通过copilotkit_emit_tool_result(tool_call_id, content)把渲染结果流给前端。Crew 本体A2UIFixedSchema.crew()以懒加载缓存方式暴露给 FastAPI 端点。四、前端 Catalog 三件套定义、渲染器与接线Fixed-Schema 的前端不需要等待 Agent 下发组件树它自己就持有这棵树。三个文件各司其职。4.1 definitions.tsZod 契约与DynString绑定每个组件条目声明组件名 Zod props schema。这里有一个必须掌握的约定凡是要绑定数据模型路径的字段schema 里写成{ path: /origin }其 Zod 类型必须是字符串与路径对象的联合类型const DynString z.union([z.string(), z.object({ path: z.string() })]);注释里明确警告如果偷懒用普通z.string()渲染期未解析的{ path }对象会直达渲染器React 会抛出 error #31object with keys {path}。A2UI 的GenericBinder正是靠这个联合类型识别动态字段并在渲染时把路径解析成数据模型里的实际值。Button的定义还演示了 action 的声明方式——action字段必须是{ event: { name, context } }与null的联合GenericBinder据此把事件解析为可调用函数。4.2 renderers.tsxReact 实现渲染器用 Tailwind 实现 ShadCN 风格neutral 色板、rounded-xl、细边框其中Card带data-testida2ui-fixed-cardTitle展示 Itinerary 小标题与 Flight Details 主标题Airport用等宽大字号展示机场码Arrow用Separator SVG 箭头表现航程PriceTag突出总价。渲染器里的s()辅助函数统一收窄DynString类型因为绑定器解析后渲染器只会看到普通字符串。4.3 catalog.ts把两者绑成 Catalogexport const catalog createCatalog(definitions, renderers, { catalogId: CATALOG_ID, includeBasicCatalog: true, });CATALOG_ID copilotkit://flight-fixed-catalog与后端a2ui_fixed.py里的CATALOG_ID常量严格对齐。includeBasicCatalog: true把 CopilotKit 内置组件Card、Column、Row、Text、Button、Divider 等并入本 Catalog使固定 Schema 可以自由混用自定义组件与内置组件同名自定义条目按后写覆盖规则按comp.name去重替换内置实现例如本演示就用自己的Card/Button覆盖了内置版本。前端页面把 Catalog 注入 CopilotKitCopilotKit runtimeUrl/api/copilotkit-a2ui-fixed-schema agenta2ui-fixed-schema a2ui{{ catalog: catalog }} Chat / /CopilotKit五、固定 Schema JSON12 节点组件树逐层拆解flight_schema.json 预置了 12 个节点结构如下Card (root) └── Column (content) ├── Title Flight Details ├── Row (route): [Airport(/origin) → Arrow → Airport(/destination)] ├── Row (meta): [AirlineBadge(/airline), PriceTag(/price)] └── Button (bookButton, actionbook_flight) ── Text Book flight可以观察到三种字段形态它们是理解 Fixed-Schema 的关键字面量字段如Title.text Flight Details、Text.text Book flight写死在 JSON 中不依赖数据模型路径绑定字段如Airport.code { path: /origin }、AirlineBadge.name { path: /airline }、PriceTag.amount { path: /price }渲染时从数据模型对应路径取值事件字段bookButton.action.event携带name: book_flight和一组绑定到数据模型路径的contextorigin/destination/airline/price事件触发时可携带完整上下文。e2e 测试注释特别说明由于标题与按钮文本是字面量常量而非路径绑定即使数据模型缺失也不会泄漏{ path }对象因此它们可以安全地作为渲染成功的断言锚点。六、运行时接线关闭自动注入由后端自己发渲染route.ts 为该演示提供专用CopilotRuntime关键配置是const runtime new CopilotRuntime({ agents, a2ui: { injectA2UITool: false }, });injectA2UITool: false的语义很重要因为后端 Crew 自带display_flight工具、会自行发射引用预置 Schema 的a2ui_operations容器所以运行时不再向模型注入额外的 A2UI 工具但 A2UI 中间件仍然运行负责把后端帧序列化给前端。Agent 通过HttpAgent指向 FastAPI 的/a2ui-fixed-schema端点默认http://localhost:8000可用AGENT_URL环境变量覆盖。七、QA 验收清单逐条对照附源码依据qa/a2ui-fixed-schema.md 是人工走查清单a2ui-fixed-schema.spec.ts 则是同一组验收标准的自动化版本。逐条对照如下1. 导航到/demos/a2ui-fixed-schema验证页面根节点渲染e2e 对应page.goto(/demos/a2ui-fixed-schema)后断言输入框可见。测试还额外断言Flight Details字样在渲染前必须出现 0 次——如果提前出现说明有陈旧渲染或 Schema 泄漏。2. 点击 Find SFO → JFK 建议 pill建议由 suggestions.ts 通过useConfigureSuggestions注册available: always。点按后发送消息Find me a flight from SFO to JFK on United for $289.机场码与价格显式出现在提示词中确保次级 LLM 大概率逐字绑定到 origin/destination。3. 验证航班卡片渲染出 SFO → JFK、United Airlines、$289e2e 以 Flight Details字面量作为整棵 12 节点树渲染成功的信号再分别断言SFO、JFK可见并断言Book flight按钮存在。由于标题与按钮文本是 schema 里的常量它们不依赖数据模型是稳健的断言锚点。4. 点击 Book flight 按钮切换为 Booked ✓本地状态无后端往返这里需要注意当前仓库的实际状态QA 清单把按钮描述为带本地状态的 ActionButton但 renderers.tsx 与 e2e 注释都明确说明本演示是纯展示型——Button 只渲染标签点击处理器处于未接线状态要等 Python SDK 在a2ui.render上暴露action_handlers参数后才会生效。因此第 4 条在人工 QA 时属于预期行为与当前实现存在差异的观察点自动化测试目前仅验证按钮渲染而未验证点击切换。后端DisplayFlightTool仍会保留book_flight事件以便未来接线时无需改动 Schema。5. 自由提问flight from LAX to BOS on Delta for $320后卡片随新数据模型更新数据驱动正是 Fixed-Schema 的核心价值组件树不变updateDataModel覆盖/origin、/destination、/airline、/price后同一棵 Card 树就重渲染为新行程。e2e 测试对这条流程的回归保护在于断言整个往返后Flight Details 与 Book flight 都恰好只出现 1 次——若 LLM 把不透明的a2ui.render(...)返回值误判为失败而循环调用display_flight就会出现重复卡片这正是历史上在 Railway 环境出现过的回归#4734。八、实战要点与已知注意点Catalog ID 必须前后端对齐后端createSurface.catalogIdcopilotkit://flight-fixed-catalog与前端createCatalog({ catalogId })不一致会导致前端报 Catalog not founde2e 测试专门断言该错误横幅不出现。路径绑定字段的类型必须是联合类型DynString z.union([z.string(), z.object({ path: z.string() })])是GenericBinder识别动态字段的前提写成纯z.string()会导致渲染期崩溃。防止重复渲染回归System prompt 与工具 docstring 需要明示卡片已渲染不要再次调用否则 LLM 可能对不透明的渲染返回值产生困惑而循环调用工具e2e 通过断言卡片数量恰好为 1 兜底。时间预算e2e 注释提示在 Railway 上display_flight偶尔会在次级 LLM 阶段停顿因此渲染预算设为 60s整条用例超时 120s首卡断言放宽到 90s。人工 QA 冷启动时应预留类似等待。本地运行前置需要同时启动 Next.js 前端与agent_server.py暴露的 FastAPI Agent默认 8000 端口并通过AGENT_URL指向后端只起前端无法看到 Agent 侧的a2ui_operations流。总结A2UI Fixed-Schema 模式把界面长什么样前端 JSON Schema与数据是什么Agent 流式写入数据模型彻底解耦后端只负责用display_flight这样的工具吐出a2ui_operations容器前端用 Catalogdefinitions renderers把预置组件树渲染成真实的 React UI。通过对照本文的源码路径你可以把这份 QA 清单当作学习 A2UI 协议的最小可运行教材逐步迁移到自己的 CrewAI 或 LangGraph Agent 上。【免费下载链接】CopilotKitThe Frontend Stack for Agents Generative UI. React, Angular, Mobile, Slack, and more. Makers of the AG-UI Protocol项目地址: https://gitcode.com/GitHub_Trending/co/CopilotKit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考