 的声明式生成式 UI 原理与 QA 验收指南)
CopilotKit A2UI 固定 Schema 模式实战基于 LangGraph (Python) 的声明式生成式 UI 原理与 QA 验收指南【免费下载链接】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 仓库中showcase/integrations/langgraph-python的「A2UI — Fixed Schema」演示为对象系统讲解声明式生成式 UIDeclarative Generative UI中固定 Schema 模式的核心机制组件树Schema由 JSON 预先编排Agent 运行时只向数据模型Data Model流入数据前端目录Catalog将 Schema 中的组件名解析为真实 React 实现。读完本文你将掌握该模式从前端目录接线、后端a2ui.render操作流、数据绑定到端到端验收的完整链路并可直接复用仓库中配套的 QA 验收清单与 Playwright 回归测试。A2UI 固定 Schema 模式Schema 一次数据流式注入A2UIAgentic UI是 CopilotKit 推动的 Agent 驱动界面协议实现。在「固定 SchemaFixed Schema」这一形态下UI 的组件树不是由 LLM 在运行时即兴生成而是预先以 JSON 文件编写、在启动时加载Agent 只负责把结构化数据写入数据模型。后端代理文件 a2ui_fixed.py 的模块注释将其概括为Fixed-schema A2UI: the component tree (schema) is authored ahead of time as JSON and loaded at startup viaa2ui.load_schema(...). The agent only streamsdatainto the data model at runtime.这一设计带来三个关键收益Schema 与代码解耦组件树是独立 JSON可由设计/前端人员单独审查与演进Python 后端只负责读取渲染确定性卡片长什么样、由哪些子组件构成在 Schema 中固定LLM 无法“画”错 UI只能填错数据低成本刷新换一条航线查询时Schema 无需重新下发只要更新数据模型卡片即可原地重渲染——即 QA 文档强调的 “schema once, data streams”。端到端架构拆解四个组成部分固定 Schema 演示横跨后端、运行时路由与前端三层共四个核心文件组成部分仓库路径职责LangGraph Agenta2ui_fixed.py定义a2ui_fixed图Graph暴露display_flight工具返回a2ui_operations容器专属 Runtime 路由route.ts将 Agent 名a2ui-fixed-schema绑定到 LangGraph 部署并关闭运行时 A2UI 工具注入前端 Demo 页page.tsx挂载CopilotKitProvider指定runtimeUrl、agent与固定catalog前端 A2UI 目录catalog.ts definitions.ts renderers.tsx将 Schema 中的组件名解析为 Zod 定义与 React 渲染实现一次完整请求的调用链路如下用户在CopilotChat发送消息请求经runtimeUrl/api/copilotkit-a2ui-fixed-schema进入专属路由路由内的LangGraphAgentgraphId: a2ui_fixed把消息转发给 LangGraph 部署上的 AgentAgent 判定需要展示航班卡片时调用display_flight工具工具内通过a2ui.render(...)构造a2ui_operations容器创建 Surface、下发 Schema、注入数据模型运行时 A2UI 中间件在工具结果中检测到该容器将操作流转发至前端前端目录根据catalogId找到固定目录GenericBinder把{ path: /origin }这类绑定解析为数据模型中的实际字符串renderers.tsx中注册的 React 组件完成组装。值得注意的一个细节本演示为固定 Schema 场景单独开了一条路由其注释route.ts说明原因——后端 Agent 自己拥有display_flight工具并自行发出a2ui_operations容器因此路由配置a2ui: { injectA2UITool: false }避免运行时再向 Agent 的既有工具集之上注入一个render_a2ui工具。前置条件与部署配置QA 文档列出了启动验收前必须满足的环境条件演示已部署并可通过仪表盘主机的/demos/a2ui-fixed-schema访问Agent 后端健康Railway 环境已设置OPENAI_API_KEYLANGGRAPH_DEPLOYMENT_URL指向暴露了a2ui_fixed图的 LangGraph 部署且注册的 Agent 名为a2ui-fixed-schema见 route.ts。从 route.ts 源码可见LANGGRAPH_DEPLOYMENT_URL缺省回退到http://localhost:8123LANGSMITH_API_KEY缺省为空字符串——这意味着本地开发时可用默认地址直连本机 LangGraph 服务云端部署时通过环境变量覆盖即可。QA 文档特别提示演示源码中不含任何data-testid属性因此所有检查依赖逐字的可见文本、DOM 结构与 flight_schema.json 中的 JSON Schema 本身。从当前源码看renderers.tsx 中的Card覆盖组件已额外携带data-testida2ui-fixed-card可作为自动化脚本的补充锚点。基础功能验证页面加载、消息通路与建议胶囊页面渲染与消息通路导航到/demos/a2ui-fixed-schema页面应在 3 秒内渲染完成且只有一个居中显示的CopilotChat面板源码中 page.tsx 使用max-w-4xl即 896px 上限与居中布局chat.tsx 中CopilotChat带classNameh-full rounded-2xl——这与 QA 文档“max-width ~896px、rounded-2xl、full-height”的描述完全对应。打开 DevTools → Network发送一条消息应命中/api/copilotkit-a2ui-fixed-schema端点且请求中的 Agent 名为a2ui-fixed-schema对应 page.tsx 的runtimeUrl与agent属性。建议胶囊Suggestion Pill页面应显示唯一一条建议胶囊标题逐字为“Find SFO → JFK”。其消息体在 suggestions.ts 中定义Find me a flight from SFO to JFK on United for $289.并配置available: always使其常驻可用。纯文本响应发送 “Hello”Agent 应在 10 秒内返回一条纯文本回复且不渲染航班卡片——因为该输入不触发display_flight工具。Schema 接线验证固定目录与 includeBasicCatalogQA 文档的第 2 部分首先验证 Schema 与目录的接线DevTools → Network 中第一次成功的display_flight调用之后响应流应包含一个a2ui_operations容器其catalogId为copilotkit://flight-fixed-catalog。该值在 a2ui_fixed.pyCATALOG_ID与 catalog.ts 两侧保持严格一致。同一容器应携带完整的FLIGHT_SCHEMA组件树——共12 个节点。12 节点组件树的逐节点含义flight_schema.json 定义了一棵从根到叶的完整树全部节点如下节点 ID组件关键属性说明rootCardchild: content外层卡片容器contentColumnchildren: [title, route, meta, bookButton]纵向排布四个子行titleTitletext: Flight Details字面量卡片标题非数据绑定routeRowjustify: spaceBetween,align: center航路行fromAirportcode: { path: /origin }出发机场绑定数据模型arrowArrow无属性箭头分隔符toAirportcode: { path: /destination }到达机场绑定数据模型metaRowjustify: spaceBetween,align: center元信息行airlineAirlineBadgename: { path: /airline }航司徽章绑定数据模型pricePriceTagamount: { path: /price }价格标签绑定数据模型bookButtonButtonvariant: primary,child: bookButtonLabel,action预订按钮纯展示bookButtonLabelTexttext: Book flight字面量按钮文案值得注意的是bookButton节点上的action结构它声明了一个名为book_flight的事件并通过context把四个数据路径/origin、/destination、/airline、/price一并携带。这为未来的“点击后 Schema 切换”预留了完整语义详见后文。includeBasicCatalog 的合并机制catalog.ts 通过createCatalog(definitions, renderers, { catalogId, includeBasicCatalog: true })创建固定目录。includeBasicCatalog: true会把 CopilotKit 内置的基础组件Card、Column、Row、Text、Button、Divider等合并进目录使 Schema 可以自由混用自定义组件与基础组件。definitions.ts 的注释补充了合并规则目录按comp.name去重、后者覆盖前者last-write-wins——因此本演示中同名自定义Card、Button会覆盖基础目录中的内联样式实现而Column、Row、Text等则沿用内置版本。search-flights 提示display_flight 工具与数据模型绑定后端工具定义display_flight 是一个标准 LangChaintool其参数由FlightTypedDictorigin、destination、airline、price四个字符串字段约束。注释明确指出LangGraph 会把这个 TypedDict 序列化为工具的 JSON Schema因此严格限定工具入参形状就是在引导 LLM 产出契合前端组件 props 的数据。工具内部通过a2ui.render构造操作流return a2ui.render( operations[ a2ui.create_surface(SURFACE_ID, catalog_idCATALOG_ID), a2ui.update_components(SURFACE_ID, FLIGHT_SCHEMA), a2ui.update_data_model( SURFACE_ID, { origin: origin, destination: destination, airline: airline, price: price, }, ), ], )三条操作各司其职create_surface创建名为flight-fixed-schemaSURFACE_ID的渲染面并指向固定目录update_components一次性下发完整的 12 节点 Schemaupdate_data_model把工具参数写入数据模型。工具返回的 JSON 就是渲染器消费的表面描述符surface descriptor而非状态码——这一语义在工具 docstring 中被反复强调用于防止 LLM 误判而重复调用。数据绑定如何落到 DOMSchema 中{ path: /origin }这类对象是数据模型绑定。前端 A2UI 的GenericBinder会在渲染前把路径解析为数据模型中的实际字符串。QA 文档要求验证四条数据绑定全部正确落到 DOM 且为纯字符串originSFO、destinationJFK、airlineUnited、price$289同时要求不得出现字面量{path}泄漏也不得出现 React 错误 #31“objects are not valid as a React child, found: object with keys {path}”。卡片视觉验收规格QA 文档对点击 “Find SFO → JFK” 后的卡片给出了详细视觉规格20 秒内完成渲染在会话内就地组装外层Card内含一个Column子节点顺序为标题行 → 航路行 → 元信息行 → 预订按钮Title渲染字面文本 “Flight Details”1.15rem / 600 字重颜色#010507航路行Airport显示 “SFO” →Arrow箭头→颜色#AFAFB7→Airport显示 “JFK”两侧机场均为等宽字体、1.5rem、600 字重、0.05em 字距元信息行左侧AirlineBadge“UNITED”大写药丸徽章淡紫#BEC2FF边框0.08em 字距右侧PriceTag“$289”等宽字体、颜色#189370、1.1rem / 600 字重Button全宽渲染文案 “Book flight”黑色#010507背景、白色文字、12px 圆角。补充说明QA 文档给出的是验收用的视觉指纹规格当前 renderers.tsx 中的实现采用了 ShadCN 风格的中性色 Tailwind 工具类neutral-900文本、neutral-200分隔线等其文件头注释也说明了这一点。做视觉断言时建议以Schema JSON 中的字面量文本“Flight Details”“Book flight”与 12 节点树结构为稳定锚点视觉颜色以部署版本实际渲染为准。自定义渲染器与 DynString 类型定义DynString动态字符串联合类型definitions.ts 中定义了整个演示最关键的防回归类型const DynString z.union([z.string(), z.object({ path: z.string() })]);凡是可能被绑定为数据模型路径的字段Title.text、Airport.code、AirlineBadge.name、PriceTag.amount其 Zod 类型都必须声明为z.string()与z.object({ path: z.string() })的联合。原因在于GenericBinder正是依赖这个联合类型把字段识别为动态字段并在渲染期解析路径如果误用纯z.string()原始{ path }对象会原封不动到达渲染器React 随即抛出错误 #31。QA 文档因此将#31 出现一次即视为回归。渲染器覆写renderers.tsx 注册了Card、Title、Airport、Arrow、AirlineBadge、PriceTag、Button七个自定义渲染器并遵循 TypeScript 强制约束渲染器映射的键与 props 形状必须与 definitions 完全一致。其中两个细节值得关注共享的s(v)帮助函数DynStringprops 在类型上为string | { path }但绑定在渲染前已解析因此渲染器只会拿到字符串s把非字符串值收敛为空串把这一类型收窄集中到一处Card覆写的作用基础目录的Card使用内联样式覆写后换成 ShadCN 风格的外层容器w-full max-w-md p-5在不改动 Schema JSON 的前提下让演示保持统一的 Tailwind 视觉。Book Flight 按钮纯展示与待接入的动作句柄QA 文档明确“Book flight” 按钮按 Schema 声明的文案渲染且可点击但点击是 no-op——Agent 不会被调用、不发生 Schema 切换、按钮也不会进入 “Booked” 状态。原因是 Schema 中虽然声明了完整的action事件名book_flight 四个数据路径的context但“点击后基于动作切换 Schema”schema-swap-on-action需要 Python SDK 在a2ui.render上暴露action_handlers参数后才可接入这一点在 a2ui_fixed.py 的注释中有明确说明。definitions.ts 中对Button.action的联合类型{ event: { name, context } } | null则是让GenericBinder把该字段识别为 ACTION可调用函数的关键。Follow-up 提示数据模型原地刷新QA 文档的最后一个功能验证最有说服力发送“Find me a flight from LAX to ORD on Delta for $412.”20 秒内卡片应原地更新为originLAX、destinationORD、airlineDELTA、price$412——Schema 仍是同一棵 12 节点树后端只是再次调用了a2ui.render中的update_data_model。这直接印证了固定 Schema 模式的核心价值Schema 只下发一次后续只有数据在流动。前端无需重新解析组件树GenericBinder用新数据模型重新解析路径绑定即可完成重渲染。错误处理与边界场景QA 文档的第 3 部分聚焦三类边界情况空消息发送空消息应是 no-op——既不出现用户气泡也不产生助手响应非航班问题发送 “What is the capital of France?”Agent 应纯文本回答不调用display_flight响应中既无航班卡片也不含a2ui_operations容器控制台错误扫描完整走一遍上述所有流程后DevTools → Console 中不应有任何未捕获错误尤其不能出现 React 错误 #31。结合源码看第二类边界由后端双重约束保障Agent 的系统提示a2ui_fixed.py明确“只有在被问及航班时才调用display_flight”而工具的 docstring 与系统提示共同强调“卡片已渲染不要为同一行程再次调用”并约定工具返回后“回复一句简短确认语并停止”。预期结果与 E2E 回归防护QA 文档汇总的预期结果如下聊天 3 秒内加载完成纯文本响应 10 秒内返回搜索提示后 20 秒内渲染航班卡片每次搜索提示display_flight恰好调用一次结果中包含带catalogId: copilotkit://flight-fixed-catalog的a2ui_operations容器及完整 12 节点航班 SchemaCard、Title、Airport、Arrow、AirlineBadge、PriceTag、Button七个自定义渲染器在每次搜索流程中至少渲染一次点击 “Book flight” 为 no-op纯展示按钮无 UI 布局破坏、无{path}泄漏到 DOM、无未捕获控制台错误。仓库中配套的 Playwright 测试 a2ui-fixed-schema.spec.ts 将上述验收固化为自动化断言其中包含几处值得借鉴的工程实践预算管理测试整体 120 秒超时由于 Railway 上display_flight偶发卡在次级 LLM 阶段渲染预算放宽到 90 秒QA 文档写 60 秒渲染预算E2E 进一步放宽回归防护 #4734历史上部署在 Railway 的 Agent 曾因 LLM 无法识别a2ui.render(...)的不透明 JSON 返回值是成功信号而无限循环调用display_flight修复方式是收紧 docstring 与系统提示明确“卡片已渲染、勿重复调用”测试断言全文恰有一个“Flight Details” 与一个 “Book flight” 按钮来防循环复现渲染错误护栏断言页面不存在 “Catalog not found” 与 “Cannot create component ... without a type” 两类 A2UI 渲染错误横幅无陈旧渲染页面初始加载时 “Flight Details” 出现次数必须为 0防止 Schema 泄漏或陈旧渲染。小结固定 Schema 模式把「界面定义」与「数据流」彻底分层flight_schema.json作为唯一的组件树事实源后端display_flight只负责产出a2ui_operations操作流前端catalog.ts/definitions.ts/renderers.tsx负责把组件名解析成受类型约束的 React 实现。QA 文档与其对应的 E2E 测试共同构成了一套可复用的验收模板——从 Schema 接线、数据绑定、视觉指纹、交互惰性到错误边界每一条检查点都能在仓库源码中找到精确对应。如果你正在为 LangGraph CopilotKit 设计可预测、可测试的生成式 UI这个演示是理解「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创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考