CopilotKit 声明式生成式 UI 实战:基于 CrewAI Conversational Flows 的 A2UI 动态 Schema 目录开发
2026/9/13 3:01:10 网站建设 项目流程

CopilotKit 声明式生成式 UI 实战:基于 CrewAI Conversational Flows 的 A2UI 动态 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 仓库中showcase/integrations/crewai-conversational-flows的声明式生成式 UI(Declarative Generative UI,即 A2UI Dynamic Schema)Demo 为研究对象,完整讲解"前端注册自定义组件目录 → 运行时注入render_a2ui工具 → 后端 Flow 强制调用 → 运行时二次 LLM 渲染为 UI 操作流"的完整链路,并附上该 Demo 的 QA 验收清单与 Playwright 端到端测试依据。读完本文,你将掌握如何用 Zod Schema + React Renderer 构建自己的 A2UI 组件目录、如何用 AG-UI 协议打通 CrewAI Flow 与 CopilotKit Runtime,以及如何用data-testid与 DOM 指纹对动态渲染的 UI 进行自动化验收。

关联文档与源码证据位于:QA 验收清单、Demo 页面源码、专用 Runtime 路由、后端 CrewAI Flow、E2E 测试。

一、什么是声明式生成式 UI:A2UI Dynamic Schema 模式

在传统 Agent 应用中,前端组件与后端 Agent 之间通常靠硬编码的协议字段耦合:后端返回什么结构,前端就渲染什么结构。CopilotKit 的 A2UI(Agent-to-UI)协议则采用"声明式"思路——Agent 只负责输出"要渲染什么组件、传什么属性",真正"怎么画"由前端注册的 React 组件目录决定。

本 Demo 所在的crewai-conversational-flows集成示例,演示的是A2UI 动态 Schema(BYOC,Bring Your Own Components)模式,其核心特征是:

  • 前端在运行期通过a2ui={{ catalog: myCatalog }}把一份自定义组件目录注册给 CopilotKit Provider;
  • 组件目录由平台无关的 Zod 定义definitions.ts)与React 实现renderers.tsx)两部分组成;
  • 运行时会把目录中的 Schema 序列化进 Agent 的上下文,让大模型知道"可以画哪些组件、每个组件接受什么属性";
  • 后端 CrewAI Flow 强制调用运行时注入的render_a2ui工具,运行时拦截该工具调用并执行一次二次 LLM 渲染,把流式工具调用转换成前端可消费的操作流(operations)。

该 Demo 的完整工程上下文记录在页面源码 page.tsx 的注释中:它定义了一套带品牌风格的 React 组件与 Zod Schema,通过createCatalog(..., { includeBasicCatalog: true })生成目录并导出myCatalog,再交给 Provider 消费;专用运行时路由负责注入render_a2ui;后端 Flow 强制使用该工具,运行时中间件负责把流式工具调用变为前端绘制的 UI 操作。

二、QA 验收清单逐条解读:八个检查点

仓库中的 QA 文档 declarative-gen-ui.md 是本文最核心的验收骨架,它列出了八条人工验收步骤,全部针对/demos/declarative-gen-ui页面。下面逐条解读其验证意图,并给出对应源码依据。

1. 页面与根节点渲染

访问/demos/declarative-gen-ui,验证页面根节点data-testid="declarative-gen-ui-root"是否渲染。

该页面由 page.tsx 实现,是典型的"use client"组件:用<CopilotKit runtimeUrl="/api/copilotkit-declarative-gen-ui" agent="declarative-gen-ui" a2ui={{ catalog: myCatalog }}>包裹<Chat />。根节点的data-testid用于端到端测试的稳定定位,与 E2E 用例中page.goto("/demos/declarative-gen-ui")的入口一致。

2. 预置建议 Pill

验证 composer 渲染出四个预置的建议 Pill。

四个 Pill 定义在 suggestions.ts 中,通过useConfigureSuggestions注册,且available: "always"表示始终可用:

  • Show a KPI dashboard→ 消息Show me my sales dashboard for this quarter.
  • Team performance→ 消息How are our sales reps performing against quota?
  • Anything at risk?→ 消息Are any accounts or pipeline deals at risk this quarter?
  • Top account details→ 消息Pull up the details on our biggest account.

需要注意:文件注释明确指出,Pill 的提示文案是自然语言业务问题,图表类型的选择权不在前端而在 Agent 的 system prompt 中(即 declarative_gen_ui.py 里的DECLARATIVE_GEN_UI_BACKSTORY)。每个 Pill 对应一个不同的目录组件,供 D5 探针与 E2E 测试分别断言。同时,E2E 测试 里断言的是"KPI dashboard / Pie chart — sales by region / Bar chart — quarterly revenue / Status report"四个文案,与 QA 清单保持同步,说明QA 文档、E2E 测试、Pill 定义三者必须联动更新

3. 点击"Show a KPI dashboard"Pill

点击该 Pill,验证 Agent 调用render_a2ui,并出现一个组合式 KPI 仪表盘。

这是整个模式的"黄金路径"。其背后的强制机制见后端 Flow:

# src/agents/declarative_gen_ui.py if "render_a2ui" not in action_names: raise RuntimeError( "CopilotKit did not inject the required render_a2ui tool. " "Check a2ui.injectA2UITool on the declarative GenUI runtime." )

Flow 先检查运行时是否注入了render_a2ui工具,随后在acompletion中显式传tool_choice={"type": "function", "function": {"name": "render_a2ui"}}并关闭parallel_tool_calls,确保每一轮对话都强制走 A2UI 渲染。注释中还解释了为什么不直接用ChatWithCrewFlow:它会把 crew 本身作为一个工具暴露给模型,导致真实模型"跑一遍 crew 然后返回纯文本"而不是挂载请求的 UI 表面。

KPI 仪表盘的组合规则写在 sales-context.ts 的COMPOSITION_RULES中:整体快照应输出一个Column(gap 16),第一个子节点是 4 个 Metric 瓦片的Row,随后是 PieChart(按地区营收)与 BarChart(Jan–Jun 每月营收)并列的Row,且不得用 StatusBadge、DataTable 或 InfoRow,仪表盘外不得再包一层 Card(图表自带卡片样式)。

4. 点击"Pie chart — sales by region"Pill

验证渲染出带品牌色与可读图例的 PieChart。

PieChart 的定义(Schema)位于 definitions.ts:props 为titledescriptiondata: { label: string; value: number }[]。其 React 实现位于 renderers.tsx 的DonutChart:这是一个用<circle>+stroke-dasharray手工绘制的环形图,按比例把圆周切成多个弧形切片,切片颜色取自本地 ShadCN 风格CHART_COLORS调色板(zinc 系 7 色),图例行展示色块、名称、数值与百分比。

E2E 测试对这种"无 testid 的视觉指纹"做了强断言:背景圆 + 切片圆合计svg circle数量 ≥ 3,且存在形如45%的百分比图例文本;由于二次 LLM 渲染是多步的,冷启动时渲染可能耗时 30–60 秒,因此渲染断言统一使用 60–90 秒的预算。

5. 点击"Bar chart — quarterly revenue"Pill

验证渲染出带四个带标签柱体的 BarChart。

BarChart 基于 Recharts 构建:ResponsiveContainer内嵌RechartsBarChart,带CartesianGrid(虚线、禁用纵向网格线)、X/Y 轴(去掉轴线与刻度线,颜色跟随--border/--muted-foreground主题变量)、自定义 Tooltip 样式,以及maxBarSize={48}的圆角柱体。源码中还有两处值得注意的细节:

  • 动画指纹:柱体用自定义AnimatedBarshape 包裹,仅对"新出现的柱体"应用barSlideIn关键帧动画(0.5s、cubic-bezier(0.16,1,0.3,1)),关键帧通过内联<style>局部注入,不污染globals.css
  • 回归防护(E2E 测试 中明确标注的 #4734 回归):旧版部署曾出现"A2UI render error: Cannot create component root without a type"循环报错,原因是二次 LLM 的render_a2ui工具调用在防御性校验丢弃畸形组件之前就被 A2UI 中间件拦截。修复方式是重命名绕开拦截,测试中显式断言该类报错文案与Catalog not found的计数均为 0,同时断言同一时刻只有一个ResponsiveContainer,防止循环渲染堆叠多个图表。

6. 点击"Status report"Pill

验证渲染出带 StatusBadge 子节点的 Card(API / database / workers)。

对应组件是Card+StatusBadge的组合。Card的 renderer 输出data-testid="declarative-card",支持title、可选subtitle与单一child插槽;StatusBadge输出data-testid="declarative-status-badge"variant枚举为success | warning | error | info(默认info)。风险类问题的组合规则同样来自COMPOSITION_RULES:先是一行 3 个 Metric 瓦片(ARR 风险 $615k、风险账户 3 个、最大敞口 Northwind $340k),再按账户逐个渲染紧凑 Card,Card 内用StatusBadge(high severity 用error,否则warning)加一行 Text 说明原因与建议动作。

7. 自由输入兜底

输入自由文本(如"Show me a pie chart of traffic sources")并发送,验证 Agent 跳出建议流程后仍能输出 PieChart。

这验证的是提示词兜底能力:当用户不点 Pill、直接自由提问时,Agent 依然遵循DECLARATIVE_GEN_UI_BACKSTORY中的组件选择规则——"part-of-whole"类问题选 PieChart、"trend/comparison"类问题选 BarChart,且"永远不要反问用户要哪种图表",由 Agent 自行决定并输出完整组件树。

三、从源码看 A2UI 目录的三层结构

声明式 UI 的核心资产是"组件目录(Catalog)",本 Demo 把它拆成三个文件、三个职责:

1. 定义层:definitions.ts(平台无关的 Zod Schema)

每个组件声明四件事:组件名、用途描述(写给 LLM 看的)、props 的 Zod Schema、以及约束说明。本 Demo 注册了 7 个自定义组件:

组件名props 要点用途
Cardtitle、可选subtitle、可选child插槽带标题的容器,组合相关内容
StatusBadgetextvariant: success/warning/error/info状态色块(healthy/degraded/down 等)
Metriclabelvalue、可选trend: up/down/neutralKPI 关键指标,如 "Revenue • $12.4k • up"
InfoRowlabelvalue紧凑的"标签: 值"事实行
PrimaryButtonlabel、可选action(派发回 Agent)主 CTA 按钮
PieCharttitledescriptiondata: {label, value}[]环形图,用于整体占比分析
BarCharttitledescriptiondata: {label, value}[]柱状图,用于跨类别比较
DataTablecolumns: {key, label}[]rows: Record<string, string\|number>[]排名/明细表格(E2E 中 Team performance 场景)

定义层有一处非常"实战"的注释(definitions.ts):DataTable本应通过z.object(...).refine(...)强制"行键必须是columns[].key的子集",但宿主目录包的CatalogComponentDefinition类型要求props: ZodObject(运行时检查.shape),refine返回的ZodEffects会同时破坏satisfies CatalogDefinitions类型断言与运行期.shape访问,因此只能把约束写进描述文本让 LLM 遵守,硬性校验留给渲染管线(渲染层对未知行键渲染空单元格)。

2. 实现层:renderers.tsx(React 组件)

myRenderers: CatalogRenderers<MyDefinitions>把定义与实现一一对应,并大量使用 ShadCN 风格本地原语(CardBadgeButton)与主题 CSS 变量(--card--border--muted-foreground)。实现层还承担了布局职责:例如Metricflex-1 min-w-[120px]保证一行 3 个指标在 600px 卡片列中均分约 200px 宽,PieChart/BarChartflex-1 min-w-0保证同一 Row 内多图均分宽度,InfoRowborder-b last:border-b-0避免最后一行悬挂分隔线。

3. 组装层:catalog.tscreateCatalog

// src/app/demos/declarative-gen-ui/a2ui/catalog.ts export const myCatalog = createCatalog(myDefinitions, myRenderers, { catalogId: "declarative-gen-ui-catalog", includeBasicCatalog: true, });

createCatalog把定义 × 实现组装成 Provider 可消费的目录;includeBasicCatalog: true会合并 CopilotKit 内置 A2UI 原语(Column、Row、Text、Image、Card、Button、List、Tabs 等),让 Agent 可以自由混排自定义组件与基础组件。catalogId则与运行时路由中的defaultCatalogId严格对应(见下节)。

四、专用 Runtime 路由:render_a2ui注入与目录绑定

Demo 使用了一个独立于主路由的专用 Next.js Route Handler:route.ts。它的关键配置有三处:

const runtime = new CopilotRuntime({ agents, a2ui: { defaultCatalogId: "declarative-gen-ui-catalog", }, });
  • Agent 绑定HttpAgent指向AGENT_URL/conversational_flows/declarative-gen-ui(默认http://localhost:8000,可由环境变量覆盖),即agent_server.py挂载的专用 FastAPI 端点,保证该 Demo 运行在属于自己的 Flow 上;
  • injectA2UITool默认为 true:由运行时把render_a2ui前端工具注入给后端 Flow,Flow 再强制使用(见第二节);
  • defaultCatalogId必须钉死:注释解释了这是踩坑后的补丁——遵循工具使用指南的模型会省略catalogId,此时中间件会回退到未注册的规范基础目录,导致 "Catalog not found" 渲染错误。把默认目录 ID 钉为页面实际注册的declarative-gen-ui-catalog,即可保证二次 LLM 渲染使用正确的组件集。

请求经由createCopilotRuntimeHandler({ runtime, basePath, mode: "single-route" })处理,异常统一以{ error, stack }JSON 形式返回 500。

五、后端 CrewAI Flow:状态、系统提示与强制渲染

后端由 declarative_gen_ui.py 提供,使用 CrewAI 的Flow原语(而非带 crew 的ChatWithCrewFlow)。核心包括:

  1. 状态定义DeclarativeGenUIState(CopilotKitState)保留 AG-UI CrewAI 端点准备好的上下文字段——context: list[dict]与别名ag-uiag_ui字典;
  2. 系统提示组装_system_prompt):把state.context中的条目逐个拼成"{description}:\n{value}"段落;若ag_ui中携带a2ui_schema,再追加 "A2UI catalog schema and tool usage guide" 段落。这印证了目录 Schema 是在请求期被运行时序列化进copilotkit.context的;
  3. 强制渲染:检查render_a2ui存在后,用acompletion(model="openai/gpt-5.4", ..., tools=actions, tool_choice={... render_a2ui}, parallel_tool_calls=False, stream=True)流式调用,再把首个 choice 的消息追加回state.messages

此外,DECLARATIVE_GEN_UI_BACKSTORY还承担"数据接地"职责:Agent 被设定为虚构 B2B 服装公司 Vantage Threads 的销售分析师,所有数字必须来自 App Context 中的销售数据集,不得虚构;每次回答必须调用render_a2ui绘制可视化表面,聊天回复只保留一句话。

六、上下文接地:前端如何把数据与组合规则交给 Agent

前端通过 sales-context.ts 的useSalesAnalystContext()注册两条 Agent 上下文:

  • 销售数据集:Q2 营收 $4.2M(环比 +12%)、按地区营收(NA $1.9M / EMEA $1.3M / APAC $720k / LATAM $280k)、各月营收、销售代表配额达成率、3 个风险账户明细、最大客户 Meridian Apparel Group 画像及其产品线营收等;
  • 仪表盘组合规则:五条"按问题形状选组件"的规则(快照→KPI 仪表盘、团队表现→表格、风险→状态徽章、单账户→信息行、部分占比→饼图、趋势比较→柱状图),并强调"组合要大方,仪表盘要像真正的分析产品而不是单个组件"。

useAgentContext注册的上下文会同时到达主 Agent(App Context)与二次 A2UI 规划 LLM——运行时把前端上下文条目序列化进其系统指令,从而保证"Flow 画什么"与"前端画得出什么"基于同一份数据与规则,这也是 QA 中"每个数字都必须与数据集一致"的前提。

七、端到端验证:从 QA 清单到 Playwright 自动断言

E2E 测试 是 QA 清单的自动化镜像,二者通过文件头注释显式互链(QA reference: qa/declarative-gen-ui.md)。其设计要点:

  • 入口beforeEach跳转/demos/declarative-gen-ui;首屏断言仅验证输入框可见且没有任何.recharts-responsive-container(首帧不渲染 A2UI 表面);
  • Pill 文案断言:用data-testid="copilot-suggestion"逐个匹配四个 Pill 的原文标题;
  • 渲染指纹断言:PieChart 看svg circle数量与\d+%图例文本;BarChart 看.recharts-responsive-container.recharts-bar-rectangle数量;KPI 看[data-testid="declarative-metric"]≥ 3;Status report 看[data-testid="declarative-status-badge"]≥ 1;
  • 时间预算:因二次 LLM 渲染冷启动可达 30–60 秒,用例test.setTimeout(120_000),渲染断言普遍用 60–90 秒轮询;
  • 回归护栏:断言无 "Cannot create component ... without a type" 与 "Catalog not found" 报错,且同一时刻图表容器 ≤ 1,防止循环渲染堆叠。

测试注释还记录了历史教训:W8-7 曾因 aimock fixtures 在单次响应里同时返回 content 与 toolCalls,导致前端在 A2UI 工具调用渲染前就关闭了 assistant 回合,KPI 与 StatusReport 用例在 Railway 上偶发跳过;拆分开 fixtures(提交 2436adba6)后四个 Pill 全部稳定通过。这提醒我们:QA 清单、E2E 用例与 aimock 录制数据必须视为同一套验收体系

八、给开发者的实操要点总结

  1. 按"定义—实现—组装"三文件组织目录:Zod Schema 写给模型看、React 实现写给人看、createCatalog负责合并内置基础目录;
  2. 在定义描述里写清楚约束:受宿主类型限制无法用.refine强校验时,把规则写进description让 LLM 遵守,并在渲染层做容错(空单元格、String(row[col.key] ?? ""));
  3. tool_choice强制 A2UI:Flow 层显式tool_choice指向render_a2ui并关闭并行工具调用,避免模型"跑完 crew 返回纯文本";
  4. defaultCatalogId与页面注册的catalogId必须一致:否则二次 LLM 回退到未注册目录会报 "Catalog not found";
  5. 上下文是双通道:数据与组合规则通过useAgentContext注册,会同时进入主 Agent 与二次渲染 LLM,保证数据接地与组合一致;
  6. 验收三件套联动:QA 清单、Playwright 断言、Pill 文案三者必须同步更新;对纯视觉组件,用 DOM 指纹(donut SVG 圆、Recharts 类名、testid、百分比图例)做稳定断言,并预留 60–90 秒渲染预算。

九、相关文档与代码索引

  • QA 验收清单:showcase/integrations/crewai-conversational-flows/qa/declarative-gen-ui.md
  • Demo 页面与聊天组件:page.tsx、chat.tsx
  • 目录三件套:definitions.ts、renderers.tsx、catalog.ts
  • 建议与上下文:suggestions.ts、sales-context.ts
  • 专用 Runtime:route.ts
  • 后端 CrewAI Flow:declarative_gen_ui.py
  • 端到端测试:tests/e2e/declarative-gen-ui.spec.ts

【免费下载链接】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),仅供参考

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

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

立即咨询