CopilotKit Tool-Based Generative UI:用 useComponent 将 Agent 工具调用渲染为自定义 React 组件
【免费下载链接】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 仓库内置示例gen-ui-tool-based(Tool-Based Generative UI)展开,讲解“受控生成式 UI”的核心模式:Agent 调用一个返回结构化数据的工具,前端不再以纯文本展示结果,而是通过useComponent把该工具调用映射为自定义 React 组件(如柱状图、饼图),并根据args(参数)、result(结果)与status(状态)呈现加载态与完成态。读完本文,你将掌握useComponent的注册机制、Zod 参数 Schema 的写法、渲染器生命周期、后端 Agent 的系统提示词配置,以及对应的端到端测试验证方式。
一、什么是 Tool-Based Generative UI
在传统聊天界面中,Agent 调用工具后,无论返回多复杂的数据,前端通常只能把结果以文本形式输出。Tool-Based Generative UI 改变了这一范式:
Agent 调用一个(返回结构化数据的)工具,前端将该工具结果渲染为自定义 React 组件,而不是纯文本。
这是 CopilotKit 中“受控生成式 UI(controlled generative UI)”的一种实现——UI 形态不是由 Agent 自由组合,而是由开发者预先注册好的组件集合决定,Agent 负责“选哪个组件、填什么数据”。这种模式适合图表、卡片、表单等数据形态相对固定的场景,既保留了 Agent 的自主决策能力,又保证界面风格与交互体验完全可控。
在仓库的示例清单 showcase/integrations/built-in-agent/manifest.yaml 中,该示例的正式定义为:
- 名称:
Generative UI: useComponent - 描述:Agent 使用工具来触发 UI 生成("Agent uses tools to trigger UI generation")
- 路由:
/demos/gen-ui-tool-based - 标记:
controlled-generative-ui
示例自身源码位于 showcase/integrations/built-in-agent/src/app/demos/gen-ui-tool-based/ 目录,由 4 个文件组成:page.tsx(聊天页面与渲染器注册)、bar-chart.tsx(柱状图组件)、pie-chart.tsx(饼图组件)、suggestions.ts(建议提示词)。
二、核心 API:useComponent 注册渲染器
在 page.tsx 中,整个演示的核心只有两处useComponent调用——把工具名映射到渲染组件:
"use client"; import { CopilotChat, CopilotKit, useComponent } from "@copilotkit/react-core/v2"; import { BarChart, barChartPropsSchema } from "./bar-chart"; import { PieChart, pieChartPropsSchema } from "./pie-chart"; import { useSuggestions } from "./suggestions"; function Chat() { useComponent({ name: "render_bar_chart", description: "Display a bar chart with labeled numeric values.", parameters: barChartPropsSchema, render: BarChart, }); useComponent({ name: "render_pie_chart", description: "Display a pie chart with labeled numeric values.", parameters: pieChartPropsSchema, render: PieChart, }); useSuggestions(); return ( <div className="flex justify-center items-center h-screen w-full"> <div className="h-full w-full max-w-4xl"> <CopilotChat agentId="gen-ui-tool-based" className="h-full rounded-2xl" /> </div> </div> ); } export default function ControlledGenUiDemo() { return ( <CopilotKit runtimeUrl="/api/copilotkit" agent="gen-ui-tool-based"> <Chat /> </CopilotKit> ); }2.1 配置项说明
| 配置项 | 类型 | 作用 |
|---|---|---|
name | string | 工具名,Agent 在对话中会以该名称发起工具调用,同时作为渲染器匹配的键 |
description | string | 给模型的工具描述,帮助 Agent 判断何时调用该组件 |
parameters | Standard Schema V1(如 Zod) | 工具入参的结构化 Schema,同时用于推导渲染组件的 props 类型 |
render | ComponentType | 实际渲染的 React 组件,组件接收 Schema 推断出的 props |
agentId | string(可选) | 限定该渲染器只作用于指定 Agent |
followUp | boolean(可选) | 是否允许渲染后的组件继续触发后续对话 |
2.2 useComponent 的底层实现
从 packages/react-core/src/v2/hooks/use-component.tsx 的源码可以看到,useComponent是useFrontendTool的便捷封装,它自动拼接出一段面向模型的工具描述前缀:
const prefix = `Use this tool to display the "${config.name}" component in the chat. This tool renders a visual UI component for the user.`; const fullDescription = config.description ? `${prefix}\n\n${config.description}` : prefix;随后把name、description、parameters转发给useFrontendTool,渲染时把工具参数透传给注册的组件:
render: ({ args }: { args: unknown }) => { const Component = config.render; return <Component {...(args as InferRenderProps<TSchema>)} />; },也就是说,渲染器注册之后会同时做两件事:一是把工具声明注册进 Agent 可用的工具集(模型看到的是工具名 + 描述 + 参数 Schema),二是在聊天流中挂载同名的渲染器(渲染器会处理历史消息中的工具调用渲染)。这也是 Tool-Based Generative UI 中“Agent 调工具、前端渲染组件”的契约来源。
三、用 Zod 定义组件参数,让模型按 Schema 填数据
为了让 Agent 生成的结构化数据能安全地进入 React 组件,示例使用 Zod 为每个图表组件定义了参数 Schema,并借助z.infer获得类型安全的 props。bar-chart.tsx 与 pie-chart.tsx 中的 Schema 完全一致:
import { z } from "zod"; export const barChartPropsSchema = z.object({ title: z.string().describe("Chart title"), description: z.string().describe("Brief description or subtitle"), data: z.array( z.object({ label: z.string(), value: z.number(), }), ), }); export type BarChartProps = z.infer<typeof barChartPropsSchema>;Schema 的要点:
title、description是图表标题与副标题,describe()中的提示文本会随工具描述一起交给模型,让模型生成更贴合语义的字段;data是{ label, value }数组,label为分类名,value为数值;- 通过
z.infer导出BarChartProps类型,useComponent会把该类型推断为render组件的 props,实现端到端类型安全。
3.1 渲染组件的实现要点
BarChart组件基于 recharts 实现,并针对“流式/增量到达的数据”做了动画处理:它用useRef(new Set<number>())记录已经渲染过的数据下标,只有新到达的 bar 才播放barSlideIn入场动画,避免每次数据更新都整体重播:
const seen = useRef(new Set<number>()); const isNew = (i: number) => { if (seen.current.has(i)) return false; seen.current.add(i); return true; };PieChart则用纯 SVG 绘制环形图:先计算circumference(圆周长),再为每个数据项计算strokeDasharray={arc gap}与strokeDashoffset实现扇区切分,并在图下方输出每个分类的数值与百分比。两个组件都处理了data为空的情况,渲染“No data available”的兜底卡片——这在模型尚未产出数据时保证界面不崩溃。
四、渲染器的生命周期:args、result 与 status
README 中明确指出:渲染器会收到args、result和status,因此 UI 可以分别展示加载中与完成状态。这一契约在 packages/react-core/src/v2/hooks/use-render-tool.tsx 中以“判别联合”类型完整定义:
export interface RenderToolInProgressProps<S extends StandardSchemaV1> { name: string; toolCallId: string; parameters: Partial<InferSchemaOutput<S>>; status: "inProgress"; result: undefined; } export interface RenderToolExecutingProps<S extends StandardSchemaV1> { name: string; toolCallId: string; parameters: InferSchemaOutput<S>; status: "executing"; result: undefined; } export interface RenderToolCompleteProps<S extends StandardSchemaV1> { name: string; toolCallId: string; parameters: InferSchemaOutput<S>; status: "complete"; result: string; }三个阶段与渲染器的对应关系为:
inProgress:工具调用已开始、参数可能尚不完整(parameters为部分值),此时可渲染占位/加载 UI;executing:参数完整(parameters为完整 Schema 输出),工具正在执行,可展示“正在处理”的状态;complete:工具执行完成,result携带返回结果,渲染器此时展示最终组件。
在 use-render-tool.tsx 的注册逻辑中,status分支被显式处理,确保判别联合类型在args重新暴露为parameters后仍然相关;而 use-frontend-tool.tsx 进一步说明了注册时的去重策略:同名工具重复注册时会先console.warn提示并用最新注册覆盖,卸载时移除工具声明但刻意保留渲染器,保证历史聊天中的工具调用仍然可以正常渲染。
在gen-ui-tool-based演示里,图表组件直接消费parameters(title、description、data),因此加载态由 CopilotChat 的默认工具调用气泡兜底,完成态即渲染出的图表卡片。
五、后端协同:命名 Agent 注册与系统提示词
前端声明了工具,后端则需要一个知道“何时调用这些工具”的 Agent。演示的后端在 showcase/integrations/built-in-agent/src/app/api/copilotkit/route.ts 中以命名 Agent 的方式注册:
"gen-ui-tool-based": createBuiltInAgent({ systemPrompt: GEN_UI_TOOL_BASED_PROMPT, }),该 Agent 使用进程内运行的InMemoryAgentRunner(无独立 Agent 服务),并附带针对本演示的专用系统提示词GEN_UI_TOOL_BASED_PROMPT,定义在 showcase/integrations/built-in-agent/src/lib/factory/demo-prompts.ts:
You are a data visualization assistant. When the user asks for a chart, call `render_bar_chart` or `render_pie_chart` with a concise title, short description, and a `data` array of `{label, value}` items. Pick bar for comparisons over a small set of categories; pick pie for composition / share-of-whole. If the user names a chart subject but does NOT supply concrete numbers (e.g. "show me a pie chart of website traffic by source"), do NOT ask them for data. Invent plausible illustrative sample values yourself, call the appropriate `render_*` tool immediately, and briefly note in the follow-up that the values are illustrative samples. Always render the chart on the first turn -- never reply with a clarifying question asking for the data. Every `value` MUST be a non-zero number. Never emit placeholder zeros. Keep chat responses brief -- let the chart do the talking.这段提示词值得细读,它把“模型行为规范”写进了 Agent 配置:
- 工具选择策略:比较多个分类时用柱状图,表示构成/占比时用饼图;
- 数据补全策略:用户只给主题不给数据时,不允许反问,直接自造合理的示例数据并调用工具(这一条直接规避了模型“反问用户要数据”的常见失败模式);
- 数据质量红线:所有
value必须非零,禁止用占位零值(源码注释记录了该提示词的来历——模型曾回答“我用了占位值”并绘制出全零图表); - 回复风格:聊天回复保持简短,让图表本身说话。
5.1 前后端工具如何汇合
Agent 侧的工具面在 showcase/integrations/built-in-agent/src/lib/factory/tanstack-factory.ts 中组装:服务端工具(stateTools、baseServerTools、subagentTools)与来自 AG-UI 协议的前端工具(useComponent、useRenderTool、useFrontendTool注册的)会被合并后一起声明给模型。关键逻辑是按名称去重:凡是服务端已注册的工具名,前端同名注册不会重复声明;前端工具则以“仅声明、由前端执行”的方式参与,模型发起调用后由聊天流事件驱动前端渲染器。这正是 Tool-Based Generative UI 能在“模型自主决定 + 前端可控渲染”之间取得平衡的底层机制。
六、建议提示词:引导用户发起图表请求
为了让演示开箱即用,suggestions.ts 通过useConfigureSuggestions配置了三枚常驻建议按钮:
"use client"; import { useConfigureSuggestions } from "@copilotkit/react-core/v2"; export function useSuggestions() { useConfigureSuggestions({ suggestions: [ { title: "Sales bar chart", message: "Show me a bar chart of quarterly sales for Q1, Q2, Q3, Q4." }, { title: "Traffic pie chart", message: "Show me a pie chart of website traffic by source." }, { title: "Market share", message: "Show a pie chart of smartphone market share by brand." }, ], available: "always", }); }available: "always"表示建议始终可用(而非仅在空对话时展示)。点击建议按钮即自动发送对应的message,让用户无需动脑即可触发图表生成。其中“Traffic pie chart”与“Market share”刻意不给具体数字,用于演示系统提示词中“自动补全示例数据”的行为。
七、端到端验证与本地运行
7.1 端到端测试
该演示配有 Playwright 端到端测试 showcase/integrations/built-in-agent/tests/e2e/gen-ui-tool-based.spec.ts,覆盖了四条关键断言:
- 页面加载后聊天输入框与三枚建议按钮(
data-testid="copilot-suggestion")可见; - 发送 “Show me a pie chart of revenue by category” 后,助手消息(
data-testid="copilot-assistant-message")内出现 SVG 可视化; - 发送 “Show me a bar chart of monthly expenses” 后同样出现 SVG 可视化;
- 普通文本消息能正常获得助手回复。
这些用例既验证了渲染器注册生效,也验证了普通对话能力不受影响,是复现与回归该功能的可靠基准。
7.2 本地运行方式
built-in-agent示例支持通过 CLI 一键初始化(见 manifest.yaml 的cli-start入口):
npx copilotkit@latest init --framework built-in-agent初始化后启动 Next.js 开发服务,访问/demos/gen-ui-tool-based即可体验:在对话中输入“Show me a bar chart of quarterly sales…”,Agent 会调用render_bar_chart,前端随即渲染出带标题、副标题与七色配色的柱状图卡片。整个 Agent 运行在 Next.js 路由处理器内(/api/copilotkit),无需单独启动 Agent 服务。
八、与其他 Generative UI 模式的边界
同仓库的 manifest.yaml 将本示例标记为controlled-generative-ui,与相邻模式形成清晰对比:
- Tool Rendering(工具渲染):同样是为“具名后端工具”挂自定义渲染器(如
WeatherCard),强调“把某个后端工具的结果画成卡片”;而useComponent更偏向“Agent 用工具触发 UI 生成”,组件注册在前端、模型按 Schema 填参。 - Declarative UI / A2UI(声明式 UI):Agent 直接输出 UI 树(如
Card、StatusBadge),前端按目录(catalog)映射渲染,属于“模型自由编排”;而本演示的组件集合固定、参数由 Schema 约束,属于“模型受限选择 + 数据注入”。 - gen-ui-agent(Agent State 驱动):通过共享状态流驱动 UI 更新;本演示则是工具调用结果驱动一次性渲染。
选择哪种模式,取决于你的 UI 形态是“少量固定组件(选图表)”,还是“模型动态拼装界面(自由画布)”。Tool-Based Generative UI 的定位,正是前者:把结构化工具结果安全、类型化地变成第一方 React 组件。
参考资料(仓库内)
- 演示源码:showcase/integrations/built-in-agent/src/app/demos/gen-ui-tool-based/
- 示例清单条目:showcase/integrations/built-in-agent/manifest.yaml
- 后端 Agent 注册:showcase/integrations/built-in-agent/src/app/api/copilotkit/route.ts
- 系统提示词:showcase/integrations/built-in-agent/src/lib/factory/demo-prompts.ts
- 工具合并逻辑:showcase/integrations/built-in-agent/src/lib/factory/tanstack-factory.ts
- 渲染器状态类型:packages/react-core/src/v2/hooks/use-render-tool.tsx
- useComponent 实现:packages/react-core/src/v2/hooks/use-component.tsx
- useFrontendTool 实现:packages/react-core/src/v2/hooks/use-frontend-tool.tsx
- 端到端测试:showcase/integrations/built-in-agent/tests/e2e/gen-ui-tool-based.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),仅供参考