CopilotKit Open Generative UI 实战:用 .NET 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
本文以 CopilotKit 仓库中的open-gen-ui演示(位于 MS Agent Framework .NET 集成 showcase 中)为主体,完整拆解 Open Generative UI 的最小可用链路:一个 .NET 后端 Agent 如何通过 AG-UI 协议流式调用前端工具generateSandboxedUi,把 LLM 即时生成的 HTML + CSS + SVG 渲染进聊天中的沙盒 iframe。读完本文,你能掌握前端 Provider 的最小配置、Runtime 侧openGenerativeUI开关与中间件的事件转换机制、内置OpenGenerativeUIActivityRenderer的沙盒渲染细节,以及自定义 design skill 引导 LLM 产出“教科书级”可视化图表的完整方法。
一、演示定位:Agent 直接“画”出可视化组件
该演示(showcase/integrations/ms-agent-dotnet/src/app/demos/open-gen-ui/README.md)展示的是:
A .NET-backed agent that streams on-the-fly HTML + CSS + SVG visualisations into a sandboxed iframe inside the chat.(一个 .NET 驱动的 Agent,把即时生成的 HTML + CSS + SVG 可视化流式渲染进聊天中的沙盒 iframe。)
交互方式是点击四个预置建议(3D 坐标轴、神经网络、快速排序、傅里叶级数),或直接输入任意教学可视化需求,Agent 每轮都会生成一个自运行、带动画的组件。
与“声明式 Generative UI”(JSON 结构映射到预定义组件)不同,Open Generative UI 让 LLM 直接编写前端代码,自由度更高。整条链路从源码看如下:
<CopilotChat>(agentId="open-gen-ui") │ 用户消息 ▼ /api/copilotkit-ogui ── openGenerativeUI: { agents: [...] } 开启 │ AG-UI HttpAgent 代理(重写 toolCallId) ▼ .NET Agent 端点 /open-gen-ui(MapAGUI) │ ChatClientAgent + 系统提示词 ▼ LLM 调用前端工具 generateSandboxedUi(流式参数) │ OpenGenerativeUIMiddleware 拦截 ▼ ACTIVITY_SNAPSHOT / ACTIVITY_DELTA(activityType = "open-generative-ui") ▼ OpenGenerativeUIActivityRenderer → Websandbox 沙盒 iframe 渲染下面按“前端 → Agent → Runtime → 中间件 → 渲染器”的顺序逐层展开。
二、前端接线:最小 Provider 配置
演示页面 page.tsx 的核心代码只有几行:
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> );三点关键说明(对应文件内注释):
- 内置活动渲染器由
CopilotKitProvider自动注册——只要 Runtime 开启了openGenerativeUI,一个普通的<CopilotChat />就够了,无需自定义 tool renderer、无需手动注册 activity renderer; runtimeUrl指向专用路由/api/copilotkit-ogui,而不是默认 runtime(原因见第四节);openGenerativeUI.designSkill用于替换默认的 shadcn 风格 design skill,注入面向教育可视化的设计约束(见第三节)。
配套的聊天组件 chat.tsx 仅两行有效逻辑:
export function Chat() { useOpenGenUISuggestions(); return <CopilotChat agentId="open-gen-ui" className="flex-1 rounded-2xl" />; }而四个预置建议定义在 suggestions.ts 中:
const minimalSuggestions = [ { title: "3D axis visualization", message: "3D axis visualization (model airplane)" }, { title: "How a neural network works", message: "How a neural network works" }, { title: "Quicksort visualization", message: "Quicksort visualization" }, { title: "Fourier: square wave from sines", message: "Fourier: square wave from sines" }, ];这些message字符串同时被用作确定性 aimock fixture 的键,保证每次点击 pill 都能产生稳定的generateSandboxedUi工具调用(见该文件顶部注释)。
三、Design Skill:把 LLM 调教成“可视化设计师”
README 指出:该页面用VISUALIZATION_DESIGN_SKILL覆盖了默认的 shadcn 风格 design skill,以把 LLM 导向教育可视化。完整提示词在 design-skill.ts 中,它作为 Agent 上下文注入generateSandboxedUi工具。其约束体系可以概括为七个方面(原文为英文提示词,此处归纳并摘录关键条目):
- 几何与渲染:优先内联 SVG(或
<canvas>),绝不允许用几十个<div>堆叠画图形;内容控制在约 600×400 区域、留 16–24px 边距,用viewBox + preserveAspectRatio保证缩放;3D 场景用 SVG 手动透视或 CSS 3D transform(transform-style: preserve-3d)。 - 动画:优先 CSS
@keyframes+ transition 而非 JSsetInterval,单周期 300–900ms,周期性概念用animation-iteration-count: infinite循环;必须用 JS 计时时用requestAnimationFrame;用animation-delay让相关元素错峰。 - 标签与图例:每条轴必须有标签(如 "Pitch (X)");每个颜色序列必须配图例色块和短说明;加入文字标注解释“观众正在看什么”;顶部一行标题 + 一行副标题。
- 语义色板(固定 hex 值,保持一致):
- 主运动/强调: indigo #6366f1 - 成功/稳定: emerald #10b981 - 警示/活跃: amber #f59e0b - 错误/对比: rose #ef4444 - 中性轴/网格: slate #64748b - 表面: 白 #ffffff / 容器底 #f8fafc / 文字 #0f172a- 排版:system-ui 字体族;标题 16–18px/600,副标题 12–13px/500,轴与图例 11–12px,标注 11–13px;数字读数用
tabular-nums。 - 输出契约(严格按顺序):
- 先发 initialHeight(可视化通常 480–560) - placeholderMessages: 2–3 行短句(如 ["Sketching the scene…", "Labelling axes…"]) - css: 完整且自包含 - html: 单一根容器(标题 + 副标题 + SVG/canvas + 图例); Chart.js / D3 等 CDN <script> 标签写在 html 内部- 无障碍:文字对比度 ≥ 4.5:1;不单独依赖颜色区分序列,需配合形状、线型或标签。
此外提示词明确了两条“不变量”:动画必须“为了教学”(每个动画元素对应概念的一个步骤),以及本最小演示没有任何宿主侧 sandbox 函数——可视化必须自运行、自循环,禁止 fetch / XHR / localStorage / cookie /Websandbox.connection.remote调用。
四、.NET 后端 Agent:OpenGenUiAgentFactory
Agent 实现在 agent/OpenGenUiAgent.cs 中。README 的核心描述是:这是一个ChatClientAgent,其系统提示词约束 LLM每轮恰好调用一次generateSandboxedUi前端工具,且不注册任何后端工具。
CreateAgent()的关键部分(对应 OpenGenUiAgent.cs):
public AIAgent CreateAgent() { var chatClient = _openAiClient.GetChatClient("gpt-4o-mini").AsIChatClient(); // No backend tools. The `generateSandboxedUi` tool is registered // on the frontend by CopilotKitProvider (when `openGenerativeUI` // is enabled on the runtime) and merged into the agent's tool // list by the AG-UI protocol as a frontend-side action. return new ChatClientAgent( chatClient, name: "OpenGenUiAgent", instructions: SystemPrompt); }值得注意的设计点:generateSandboxedUi对 .NET 后端而言是“不存在”的工具——它由前端CopilotKitProvider注册,AG-UI 协议在握手时将其合并进 Agent 的可见工具列表,作为 frontend-side action 返回前端执行。后端的职责只是“提示词工程”:系统提示词(OpenGenUiAgent.cs)复述了 design skill 的关键不变量(SVG 优先、轴标签、@keyframes优先、动画即教学、无网络访问),并规定输出顺序initialHeight → placeholderMessages → css → html,同时要求 LLM 的聊天消息保持一句话——真正的输出是渲染出来的可视化本身。
模型与凭据从配置解析:构造函数通过ApiKeyResolver.ResolveApiKey/ResolveEndpoint读取配置并创建OpenAIClient(见 OpenGenUiAgent.cs),默认模型为gpt-4o-mini。
.NET 服务侧通过MapAGUI暴露 AG-UI 端点(agent/Program.cs):
var openGenUiFactory = new OpenGenUiAgentFactory(builder.Configuration); app.MapAGUI("/open-gen-ui", openGenUiFactory.CreateAgent()); var openGenUiAdvancedFactory = new OpenGenUiAdvancedAgentFactory(builder.Configuration); app.MapAGUI("/open-gen-ui-advanced", openGenUiAdvancedFactory.CreateAgent());五、Runtime 路由:开启openGenerativeUI开关
专用路由 src/app/api/copilotkit-ogui/route.ts 是整条链路的枢纽。核心配置:
runtime: new CopilotRuntime({ agents, openGenerativeUI: { agents: ["open-gen-ui", "open-gen-ui-advanced"], }, }),配合basePath: "/api/copilotkit-ogui"与mode: "single-route",由createCopilotRuntimeHandler导出POST处理器。
该文件顶部注释解释了两个重要实现细节:
- 为什么需要独立路由:
openGenerativeUI运行时会把 probe 响应中的openGenerativeUIEnabled: true全局置位,这会导致CopilotKitProvider的setToolseffect 清掉默认 runtime 里其他演示的useFrontendTool/useComponent注册。因此把该开关隔离在专用 runtime 中,避免互相干扰。 OpenGenUiHttpAgent的 toolCallId 重写:路由对@ag-ui/client的HttpAgent做了包装(route.ts),对所有generateSandboxedUi的工具调用 ID 追加__ogui_run_{runId}后缀(常量OGUI_TOOL_CALL_ID_SUFFIX = /__ogui_run_[0-9a-f-]+$/i),并在回放历史消息时剥掉该后缀。从源码结构看,其意图是让每一轮 run 的 OGUI 活动消息 ID 全局唯一、避免跨 run 复用同一 toolCallId 造成的活动冲突。两个 agent 分别代理到 .NET 后端的/open-gen-ui与/open-gen-ui-advanced端点,AGENT_URL环境变量可覆盖默认的http://localhost:8000。
六、中间件原理:OpenGenerativeUIMiddleware把工具调用转成活动事件
README 提到“runtime 的OpenGenerativeUIMiddleware把流式的generateSandboxedUi工具调用转换为open-generative-uiactivity 事件”。实现位于 packages/runtime/src/v2/runtime/open-generative-ui-middleware.ts,核心常量:
const TOOL_NAME = "generateSandboxedUi"; const ACTIVITY_TYPE = "open-generative-ui";其工作机制(均可在该文件中验证):
- 增量 JSON 解析:每个
TOOL_CALL_START(工具名为generateSandboxedUi)时创建ArgsParser,内部用clarinet流式 JSON 解析器逐个消费TOOL_CALL_ARGS的delta,在参数(或数组元素)解析完成时立即发出事件,无需等整个 JSON 结束(ArgsParser)。 - 快照先于增量:AG-UI 活动消息要求
ACTIVITY_SNAPSHOT必须先于任何ACTIVITY_DELTA存在(客户端会丢弃没有快照的 delta),而 LLM 控制流式参数的键顺序,因此emitParamDelta内部会在需要时补发快照,快照内容为{ initialHeight, generating: true }(open-generative-ui-middleware.ts)。 - HTML 特殊流式处理:
html参数不会等完整字符串解析完,而是直接读解析器内部textNode缓冲区,把增量内容以/{"/html"}/-的 JSON Patch 数组追加方式持续发出(emitPendingHtml);字符串结束后发htmlComplete: true。 - 数组参数:
placeholderMessages与jsExpressions在onopenarray时先 patch 一个空数组,之后每个元素用add /<key>/-追加。 - 完成信号:
TOOL_CALL_END到达时发出{ op: "add", path: "/generating", value: false }的 delta,标记生成结束。 - 保序策略:
TOOL_CALL_START事件被暂存(held),直到首个活动事件发出后才随活动流一起放行;RUN_FINISHED同样被压后,保证活动事件先于结束事件到达前端(processStream)。解析出错时会重置解析器状态并继续(parser.resume()),保证流不中断。
七、渲染器原理:OpenGenerativeUIActivityRenderer与沙盒 iframe
前端渲染器实现位于 packages/react-core/src/v2/components/OpenGenerativeUIRenderer.tsx,由CopilotKitProvider针对 activityTypeopen-generative-ui自动注册。
内容契约:活动内容由 Zod schema 约束(OpenGenerativeUIContentSchema),字段为initialHeight、generating、css/cssComplete、html(字符串数组)/htmlComplete、jsFunctions/jsFunctionsComplete、jsExpressions(数组)/jsExpressionsComplete。
渲染流程的关键设计:
- 节流与即时刷新的双轨机制:外层组件用 ref 吸收父组件重渲染,默认对内容更新做 1 秒节流(
THROTTLE_MS = 1000);但shouldFlushImmediately判定若干关键状态(首个 html chunk、cssComplete、htmlComplete、generating === false、jsFunctions出现、jsExpressions增长)时同步刷新,保证关键帧不延迟。 - 预览流式渲染:在
cssComplete之前显示占位;CSS 就绪后,processPartialHtml+extractCompleteStyles处理不完整的 HTML 片段,注入预览沙盒 iframe 实时呈现“正在写出的”文档。 - 最终沙盒:HTML 完整后动态
import("@jetbrains/websandbox")创建沙盒(动态导入是为规避 websandbox 在模块顶层引用self导致的 SSR 问题),通过allowAdditionalAttributes: ""限制 iframe 属性——从源码结构看,即 iframe 仅允许脚本运行,对应 README 所述<iframe sandbox="allow-scripts">语义。CSS 通过injectCssIntoHtml注入<head>。 - 高度自适应:
initialHeight默认回退 200;生成结束后在沙盒内执行一段测量脚本,用body.scrollHeight(而非被 iframe 视口钳制的documentElement.scrollHeight)计算真实高度,经postMessage({ type: "__ck_resize", height })回传宿主并一次性生效。 - 高级变体的 JS 通道:
jsFunctions会整段注入沙盒,jsExpressions按序执行(沙盒未就绪时进 pending 队列,就绪后冲刷)。最小演示不使用这两个字段。 - 占位符渲染:同文件的
OpenGenerativeUIToolRenderer在工具执行期间轮播placeholderMessages(完成时返回 null,把舞台交给活动渲染器)。
八、进阶变体:让生成的 UI 调用宿主函数
README 末尾指向open-gen-ui-advanced演示(open-gen-ui-advanced/README.md):进阶版让生成的 UI 通过Websandbox.connection.remote.<name>(args)回调宿主页面函数(如evaluateExpression计算器求值、notifyHost通知)。其接线差异仅在前端——Provider 增加openGenerativeUI={{ sandboxFunctions: openGenUiSandboxFunctions }},每个宿主函数用 Zod schema 描述,Provider 会把描述注入 Agent 上下文让 LLM 知道有哪些 remote 可用;服务端openGenerativeUI开关配置与最小版完全相同(见 route.ts 注释)。
九、验证与延伸阅读
- 该演示配有 Playwright E2E 用例 tests/e2e/open-gen-ui.spec.ts 与 tests/e2e/open-gen-ui-advanced.spec.ts,可通过仓库的 showcase 测试体系回归“建议点击 → 沙盒组件出现”的完整链路。
- Runtime 侧中间件的行为由 packages/runtime/src/v2/runtime/tests/open-generative-ui-middleware.e2e.test.ts 覆盖;前端 Provider 的工具注册行为有 packages/react-core/src/v2/providers/tests/CopilotKitProvider.openGenerativeUIToolLoss.test.tsx 与 packages/react-core/src/v2/components/tests/OpenGenerativeUIRenderer.test.tsx 佐证。
- 前端 Provider 侧的
openGenerativeUI能力入口在 packages/react-core/src/v2/providers/CopilotKitProvider.tsx,设计 skill 的注入逻辑可在此文件中检索designSkill定位。 - 相关但不同的 Generative UI 路线(声明式组件注册、工具渲染)可对照本 showcase 中的 gen-ui-tool-based 演示说明 与 declarative-gen-ui 演示。
适用前提小结:该链路要求前端使用@copilotkit/react-core/v2的CopilotKit+CopilotChat,Runtime 使用@copilotkit/runtime/v2的CopilotRuntime并显式声明openGenerativeUI.agents白名单,后端任意能暴露 AG-UIMapAGUI端点的 Agent 框架(本演示为 Microsoft Agent Framework .NET + OpenAIgpt-4o-mini);生成物运行在仅允许脚本的沙盒 iframe 中,无同源网络与存储访问,initialHeight、placeholderMessages、css、html的输出顺序由 design skill 与系统提示词共同约束。
【免费下载链接】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),仅供参考