在 CopilotKit 中实现开放生成式 UI(Open-Ended Generative UI):以 CrewAI Conversational Flows 集成为例
【免费下载链接】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
开放生成式 UI(Open-Ended Generative UI)是 CopilotKit 生成式 UI 能力中自由度最高的一种形态:Agent 不再局限于预定义的工具渲染器,而是直接以 HTML + CSS 编写界面,再由前端把这段"代理编写的网页"挂载进沙箱 iframe 中运行。本文以仓库中 CrewAI Conversational Flows 集成的 QA 文档 open-gen-ui.md 为骨架,结合演示源码、运行时路由与 Playwright 端到端测试,完整梳理"最小化开放生成式 UI"的启用方式、运行链路、安全模型与自动化验证手段。读完本文,你将能够在自己的 CopilotKit 应用中用几行配置复刻这一能力,并理解其底层原理。
从 QA 检查清单看核心验收标准
该 QA 文档是全仓库自动化验证体系的组成部分,它以检查清单的形式规定了开放生成式 UI 最小演示必须通过的验收标准:
- 导航到
/demos/open-gen-ui页面; - 点击"3D axis visualization (model airplane)"建议(suggestion pill);
- 验证 Agent 流式产出
open-generative-uiactivity,并且沙箱 iframe 渲染出可视化内容; - 验证沙箱 UI自动运行动画,无需任何用户交互。
四条标准恰好对应开放生成式 UI 的四个关键特征:零预定义工具的聊天入口、Agent 生成的 HTML/CSS 走 activity 通道回传、沙箱 iframe 渲染、以及自运行(self-running)可视化。仓库中同时存在"最小化(minimal)"与"进阶(advanced)"两个 demo 目录,本文主线聚焦 QA 文档对应的 minimal 版本,最后补充 advanced 版本的能力延伸。
运行链路:从建议 pill 到沙箱 iframe
结合演示源码 page.tsx、运行时路由 route.ts 和测试注释,整条链路可以归纳为:
- 用户点击建议 pill(或输入提示词),
CopilotChat把消息发给 Agent; - Agent 端流式返回一个
generateSandboxedUi工具调用,内含 LLM 自主编写的css、html、initialHeight与placeholderMessages; - 运行时中间件(Runtime Middleware)监听该工具调用,把它转换为
open-generative-uiactivity 事件; - 前端内置的
OpenGenerativeUIActivityRenderer接收该事件,将 HTML + CSS 组合进srcdoc,挂载到<iframe sandbox="allow-scripts">中; - 由于该活动渲染器由
CopilotKitProvider自动注册,页面无需注册任何自定义工具渲染器。
正如 page.tsx 注释所述:"No custom sandbox functions, no custom tools — just chat",这是该形态与声明式生成式 UI(依赖前端预先注册的工具渲染器)最本质的区别:界面结构由 Agent 当场编写,前端只负责安全地执行它。
最小化前端:一个 Provider 配置就够
/demos/open-gen-ui的页面代码极为精简,完整的前端配置如下:
// src/app/demos/open-gen-ui/page.tsx(节选) "use client"; import React from "react"; import { CopilotKit } from "@copilotkit/react-core/v2"; import { VISUALIZATION_DESIGN_SKILL } from "./design-skill"; import { Chat } from "./chat"; export default function OpenGenUiDemo() { return ( <CopilotKit runtimeUrl="/api/copilotkit-ogui" agent="open-gen-ui" openGenerativeUI={{ designSkill: VISUALIZATION_DESIGN_SKILL }} > <div className="flex justify-center items-center h-screen w-full"> <div className="h-full w-full max-w-4xl flex flex-col p-3"> <Chat /> </div> </div> </CopilotKit> ); }三个关键配置项:
runtimeUrl="/api/copilotkit-ogui":指向一个专用运行时路由。该 demo 刻意把开放生成式 UI 与默认运行时隔离,原因见下一节。agent="open-gen-ui":指定要连接的 Agent 标识,必须与运行时路由中注册的 Agent 名称一致。openGenerativeUI={{ designSkill: VISUALIZATION_DESIGN_SKILL }}:向 LLM 注入"视觉设计技能"提示词。把openGenerativeUI传给CopilotKit的同时,也激活了内置的OpenGenerativeUIActivityRenderer。
聊天本体更简单,chat.tsx 只做两件事:调用useOpenGenUISuggestions()注入建议 pill,然后渲染一个普通的CopilotChat:
import { CopilotChat } from "@copilotkit/react-core/v2"; import { useOpenGenUISuggestions } from "./suggestions"; export function Chat() { useOpenGenUISuggestions(); return <CopilotChat agentId="open-gen-ui" className="flex-1 rounded-2xl" />; }专用运行时路由:开放生成式 UI 的隔离策略
前端指向的/api/copilotkit-ogui是一个独立于默认运行时的路由。源码 route.ts 给出了明确的隔离理由:
因为
openGenerativeUI运行时标志会在探测(probe)响应上全局置位openGenerativeUIEnabled: true,否则会影响默认运行时上每个 demo 各自的工具注册。
也就是说,开放生成式 UI 是一个运行时级全局开关,打开后会对所有经由该路由的 Agent 生效;为了不干扰同仓库其他 demo 的工具注册,仓库将其隔离到独立路由。核心配置如下:
// src/app/api/copilotkit-ogui/route.ts(节选) const AGENT_URL = process.env.AGENT_URL || "http://localhost:8000"; function createAgent() { return new HttpAgent({ url: `${AGENT_URL}/conversational_flows/frontend-tools`, }); } const agents: Record<string, AbstractAgent> = { "open-gen-ui": createAgent(), "open-gen-ui-advanced": createAgent(), }; export const POST = async (req: NextRequest) => { const copilotHandler = createCopilotRuntimeHandler({ runtime: new CopilotRuntime({ agents, openGenerativeUI: { agents: ["open-gen-ui", "open-gen-ui-advanced"], }, }), basePath: "/api/copilotkit-ogui", mode: "single-route", }); return await copilotHandler(req); };值得注意的细节:
- Agent 通过
HttpAgent(来自@ag-ui/client)桥接到后端的 CrewAI 服务端点/conversational_flows/frontend-tools,默认地址http://localhost:8000,可用环境变量AGENT_URL覆盖; openGenerativeUI: { agents: [...] }明确声明哪些 Agent 启用开放生成式 UI,这是运行时中间件把generateSandboxedUi流转换为open-generative-uiactivity 的触发条件;mode: "single-route"表示该路由采用单路由处理模式。
建议 pill:可预测的入口设计
chat.tsx 调用的useOpenGenUISuggestions()定义在 suggestions.ts,提供了四个 QA 文档中点名的基础建议:
| 标题(title) | 消息(message) |
|---|---|
| 3D axis visualization | 3D axis visualization (model airplane) |
| How a neural network works | How a neural network works |
| Quicksort visualization | Quicksort visualization |
| Fourier: square wave from sines | Fourier: square wave from sines |
建议的实现通过useConfigureSuggestions注入,available: "always"表示建议常驻。源码注释揭示了一个工程细节:message字符串同时充当 aimock 夹具(fixture)的确定性匹配键。在自动化测试中,夹具数据d5-all.json会先于通用兜底夹具加载,因此"first-match-wins"的顺序能让每个 pill 点击命中稳定的generateSandboxedUi工具调用,而不会落入对"hi"的通用兜底回复。这意味着pill 文案的修改必须同步维护夹具键,否则测试会退化。
designSkill:用提示词约束 Agent 的"网页设计品味"
QA 文档强调的可视化必须"自动动画",其保障机制之一是 design-skill.ts 中定义的VISUALIZATION_DESIGN_SKILL。这段提示词替换了默认的 shadcn 风格设计技能,作为generateSandboxedUi工具的 Agent 上下文注入,对输出提出了硬性约束,其中与本 QA 场景强相关的要点包括:
- 渲染方式:几何内容优先使用内联 SVG 或
<canvas>,禁止用大量<div>拼形状;建议 600×400 内容区、16-24px 边距,用viewBox+preserveAspectRatio保证缩放; - 动画方式:优先 CSS
@keyframes+transition而非 JSsetInterval;循环动画用animation-iteration-count: infinite;必须用 JS 时使用requestAnimationFrame;用animation-delay错开相关元素; - 教学性:每个坐标轴要有标签、每个色系要有图例、要有文字标注(如 "Input layer"、"Forward pass")、顶部要有标题与副标题;
- 调色板:规定了语义化配色(主强调色 indigo
#6366f1、成功色 emerald#10b981、警示色 amber#f59e0b、错误色 rose#ef4444、中性 slate#64748b等); - 自运行约束:本 minimal 场景没有宿主侧沙箱函数,明确禁止
fetch、XHR、localStorage、cookie以及Websandbox.connection.remote调用,场景必须自行循环或自动推进——这正是 QA 第 4 条"自动动画、无需交互"的提示词层保证; - 输出契约:按顺序产出
initialHeight(典型 480-560)、2-3 行placeholderMessages、完整的css与html; - 可访问性:文本对比度 ≥ 4.5:1,不单靠颜色区分序列。
从代码结构可以推断,这套"设计技能"机制是开放生成式 UI 控制输出质量的主要杠杆:不写死界面,但通过提示词约束界面的结构与风格。
沙箱渲染与安全模型
QA 第 3 条要求验证"沙箱 iframe 渲染出可视化",其安全实现细节在测试注释中交代得最清楚:
- 渲染器把 Agent 编写的 HTML + CSS 组合进
srcdoc属性(测试优先断言srcdoc非空,src作为回退); - iframe 只授予
sandbox="allow-scripts",不授予allow-same-origin。由于沙箱 iframe 处于 null origin,宿主页面无法通过contentFrame()直接窥探 iframe 内部 DOM——测试因此只断言"宿主成功填入了 iframe",而不检查内部 HTML 的具体正确性; - 这样既允许 Agent 产出的脚本在 iframe 内运行(实现自运行动画),又切断了脚本触达宿主页面 DOM 与存储的路径,构成了"不可信内容在可信边界内执行"的隔离模型。
自动化验证:Playwright 端到端测试
QA 文档的验收标准由 open-gen-ui.spec.ts 自动化落实。测试文件开头的注释明确标注了对应的 QA 参考、演示源码与 aimock 夹具路径,形成"QA 文档 → 演示源码 → 夹具 → e2e 测试"的完整可追溯链。
测试覆盖两个层次:
1. 页面加载与 pill 可见性——断言聊天输入框可见,并逐一断言四个建议 pill 可见(pill 标题与suggestions.ts逐字对齐):
const expected = [ "3D axis visualization", "How a neural network works", "Quicksort visualization", "Fourier: square wave from sines", ];2. 点击 pill → iframe 挂载——测试辅助函数assertPillRendersIframe点击 pill 后,等待iframe[sandbox*="allow-scripts"]出现,再用轮询断言srcdoc(首选)或src存在且非空。四个 pill(Fourier、3D axis、Neural network、Quicksort)各有一条独立用例,整套用例超时上限为 120 秒。
这里还有两个工程细节值得借鉴:
- 断言粒度刻意保守:"Renders something" 只意味着宿主成功填充了 iframe,内部 HTML 是否正确不属于本测试范围——因为 Agent 生成的 HTML 每次运行都可能不同,无法做 DOM 级快照断言;
- aimock 优先级:夹具
d5-all.json在feature-parity.json之前加载,其 first-match-wins 顺序会优先命中与 pillmessage精确匹配的高优先级条目,从而稳定产出generateSandboxedUi工具调用。
能力延伸:sandboxFunctions 让沙箱 UI 调用宿主能力
QA 文档针对的是 minimal 形态(无宿主侧函数),但同目录的兄弟文档与源码展示了同一条管线的进阶能力,可作为理解完整能力边界的参照。进阶 demo open-gen-ui-advanced/page.tsx 通过向CopilotKit传入openGenerativeUI={{ sandboxFunctions: [...] }},把宿主函数"桥接"进 Agent 生成的 iframe。
以 sandbox-functions.ts 中的evaluateExpression为例,宿主函数遵循"名称 + 描述 + Zod 参数 Schema + handler"的结构:
{ name: "evaluateExpression", description: "Safely evaluate a basic arithmetic expression on the host page and return the numeric result.", parameters: z.object({ expression: z.string().describe("An arithmetic expression, e.g. '12 * (3 + 4.5)'"), }), handler: async ({ expression }: { expression: string }) => { if (!/^[\d+\-*/().\s]+$/.test(expression)) { return { ok: false, error: "Unsupported characters in expression." }; } // ... return { ok: true, value }; }, }这些函数的名称、描述与 Zod 派生 JSON Schema 会注入 Agent 上下文,使其在编写 HTML/JS 时知道有哪些可用的"桥";生成的 iframe 内脚本通过Websandbox.connection.remote.<name>(args)调用,处理器运行在宿主页面,返回值回传给 iframe 内的调用方。进阶 e2e 测试 open-gen-ui-advanced.spec.ts 甚至驱动 iframe 内按钮,验证宿主日志(带[open-gen-ui/advanced]前缀)与 iframe 输出元素,形成完整的"iframe → 宿主 → iframe"往返验证。
实践要点与注意事项
综合 QA 文档、源码与测试,在 CopilotKit 中落地开放生成式 UI 时有几个值得注意的工程要点:
- 运行时开关是全局的:
openGenerativeUI会在探测响应上全局置位,与多个 demo 共享一个运行时路由时,应像本仓库一样为开放生成式 UI 单独开辟路由; - 前端与运行时必须同时开启:前端
CopilotKit传openGenerativeUI(激活内置活动渲染器),运行时CopilotRuntime配openGenerativeUI.agents(激活流转换中间件),两者缺一不可; - Agent 标识要一致:前端
agent/CopilotChat的agentId与运行时路由中注册的 Agent 名称(及openGenerativeUI.agents列表)必须对齐; - 设计技能是输出质量的抓手:需要特定风格(教育可视化、数据看板、计算器等)时,通过
openGenerativeUI.designSkill覆盖默认设计技能,用提示词约束结构、动画、配色与自运行行为; - 自动化验证的边界:由于 Agent 输出不可枚举,e2e 断言应聚焦"iframes 挂载 + 非空 srcdoc"这类管线级结果;需要确定性输入时,可借助 aimock 夹具让建议文案命中稳定的工具调用。
QA 文档四行检查清单背后,是"前端最小配置 + 运行时全局标志 + 提示词约束输出 + 沙箱隔离执行 + e2e 自动化验收"一整套工程闭环。参考本仓库的 minimal 与 advanced 两个 demo 及其测试,可以在自己的 CopilotKit 应用中快速复现并安全地扩展这一能力。
【免费下载链接】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),仅供参考