CopilotKit 与 AG2 集成实战:用 Tool-Based Generative UI 让 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
Tool-Based Generative UI(工具驱动的生成式 UI)是 CopilotKit 生成式 UI 体系中的核心模式之一:Agent 在对话过程中调用后端工具返回结构化数据,前端不把结果展示成普通文本,而是将工具结果交给自定义 React 组件渲染。本文以 AG2 集成 showcase 中的gen-ui-tool-based演示为骨架,从组件注册、Schema 定义、后端工具契约到端到端验证,完整讲解如何在 CopilotKit 中把「工具结果」变成「可交互的图表 UI」。
这个 Demo 在解决什么问题
在传统 Chat UI 里,Agent 调用工具后用户看到的是大段 JSON 或纯文本。而 Tool-Based Generative UI 的目标是:Agent 只负责返回结构化数据,前端负责决定这些数据长什么样。这也正是 CopilotKit 官方文档中对该模式的定位——"The agent calls a backend tool that returns structured data; the frontend renders that tool result as a custom React component instead of plain text"(见 README)。
在这个 demo 中:
- Agent(AG2 的 ConversableAgent)通过
query_data等后端工具返回销售、流量等 JSON 结构化数据; - 前端通过
useComponent将工具名(如render_bar_chart、render_pie_chart)映射到 BarChart / PieChart 两个 React 组件; - 用户发出"Show me a bar chart of quarterly sales"这类自然语言请求后,Agent 调用工具,聊天流里直接渲染出带标题、图例、动画的图表卡片。
该 demo 在集成清单 manifest.yaml 中被登记为gen-ui-tool-based,路由为/demos/gen-ui-tool-based,属于generative-ui(生成式 UI)能力分类,与gen-ui-agent(Agent 驱动的长任务生成 UI)、tool-rendering(工具结果渲染)等并列。
前端:用 useComponent 注册「工具名 → 组件」映射
Demo 的入口页面 page.tsx 是整个模式的核心,全部逻辑不过几十行:
"use client"; import React from "react"; 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> ); }useComponent 的底层原理
useComponent并不是一个独立的新机制,而是对useFrontendTool的便捷封装。在源码 packages/react-core/src/v2/hooks/use-component.tsx#L59-L92 中可以看到它的实现方式:
- 自动为工具拼接面向模型(model-facing)的描述前缀:
Use this tool to display the "<name>" component in the chat. This tool renders a visual UI component for the user.,开发者传入的description会追加在该前缀之后; - 将
parameters(任何符合 Standard Schema V1 规范的类型库,例如 zod)透传给底层 frontend tool,模型据此知道该工具需要哪些参数; - 渲染时把工具调用的
args展开后作为 props 传给注册的 React 组件; - 还支持
agentId(限定只对某个 Agent 生效)与followUp(是否允许模型在渲染后继续追问),以及第二个参数deps(与useEffect相同的依赖刷新语义)。
也就是说,useComponent把「注册一个带 Schema 的渲染组件」这件事压缩成了一次声明,底层依然是 CopilotKit 的 frontend tool 体系在驱动。
Schema 与组件解耦
图表组件的 Schema 定义在组件文件里,与页面解耦。柱状图 bar-chart.tsx:
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>;饼图 pie-chart.tsx 使用完全相同的 schema 形状。这意味着:
- Schema 即契约:zod schema 一方面驱动模型生成参数(
describe注释会被当作对模型的提示),另一方面通过z.infer推导出 TypeScript 类型,让render组件的 props 在编译期就类型安全; - 数据形状统一:
{ label, value }的结构对模型友好,柱状图与饼图可以共用同一份数据。
图表组件的实现细节
BarChart 基于 Recharts 实现,值得注意的几个工程细节(bar-chart.tsx):
- 空数据降级:当
data为空时渲染一张占位卡片("No data available"),避免图表库在空数组上抛错; - 增量动画:通过
useRef(new Set<number>())记录已经渲染过的柱子 index,只有新到达的数据柱才触发barSlideIn关键帧动画(translateY(40px) → 0),避免流式更新时整图反复重播动画; - 品牌配色:固定 7 色板(
#BEC2FF、#85ECCE、#FFAC4D等),按 index 取模循环上色。
PieChart 则是手写的 SVG 环形图(pie-chart.tsx),用strokeDasharray/strokeDashoffset计算每个扇区的弧长与间隔,底部附带图例列表,显示每个类目的数值和百分比。组件自带完整的降级 UI,并且通过Number(item.value) || 0做了数据容错。
后端:Agent 工具如何返回结构化数据
前端注册好render_bar_chart/render_pie_chart后,Agent 端需要有一个能被模型调用的工具来产生数据。AG2 侧的核心 agent 定义在 src/agents/agent.py,其中query_data就是图表数据入口:
async def query_data( query: Annotated[str, "Natural language query for financial data"], ) -> str: """Query financial database for chart data.""" # Return a JSON string (not a list): autogen serializes non-str returns # with str(), producing a Python repr (single quotes) that the frontend's # parseJsonResult/JSON.parse cannot parse. Same pattern as get_weather. return json.dumps(query_data_impl(query))源码注释里透露了一个非常关键的跨端契约细节(agent.py):工具必须返回 JSON 字符串(json.dumps),而不能直接返回 Python list 或 dict。原因在于 autogen/AG2 对非字符串返回值会用str()序列化,生成带单引号的 Python repr(如"{'label': 'Q1', 'value': 100}"),前端JSON.parse无法解析。这个坑同样适用于manage_sales_todos、get_sales_todos、schedule_meeting、search_flights等其余工具。
整个 agent 的装配方式如下(agent.py):
- 使用 AG2 的
ConversableAgent承载全部工具; - 通过
AGUIStream(agent)以 AG-UI 协议把 Agent 暴露给 CopilotKit runtime; - 前端通过
<CopilotKit runtimeUrl="/api/copilotkit" agent="gen-ui-tool-based">连接到该 runtime,聊天请求经由/api/copilotkit路由(见 route.ts)转发给 agent。
提示词引导:让 Agent 主动产出图表
为了让用户开箱即用地看到图表效果,demo 还用useConfigureSuggestions配置了三条建议提示词(suggestions.ts):
"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"表示建议始终可见(而非仅在输入框聚焦或空态时出现)。这些建议文案同时在端到端测试中被断言,是验证 demo 行为的一部分。
端到端验证:测试如何证明「工具结果渲染为 UI」
Playwright 测试 tests/e2e/gen-ui-tool-based.spec.ts 把该模式的可验收行为固化成了断言:
- 页面加载即出现三条建议 pill:
Sales bar chart、Traffic pie chart、Market share必须通过[data-testid="copilot-suggestion"]可见; - 饼图请求:输入
Show me a pie chart of revenue by category后,在[data-testid="copilot-assistant-message"]内应出现一个可见的svg(PieChart 的渲染结果),超时 60 秒; - 柱状图请求:输入
Show me a bar chart of monthly expenses,同样断言 assistant 消息内出现svg; - 基础对话:普通消息能收到 assistant 回复。
测试的断言方式(在 assistant 消息里查找svg)精准对应了本文模式的定义:图表不是聊天窗口外部的静态页面,而是作为一条 assistant 消息内容被动态渲染进对话流的。这说明useComponent渲染的组件会作为消息的一部分参与对话历史与流式输出。
运行与查看方式
该 demo 属于 AG2 集成 showcase。查看它的推荐路径:
- 阅读 showcase 根目录 README 了解 AG2 集成的整体结构与启动方式;
- 本地运行时启动 Next.js 应用与 AG2 agent 后端(
agent通过AGUIStream暴露),访问/demos/gen-ui-tool-based; - 后端健康检查可通过
/api/health(见 QA 文档 qa/gen-ui-tool-based.md),QA 清单还给出了加载时间预期(sidebar 3 秒内、agent 10 秒内回复)等可观察指标; - 运行 Playwright e2e 测试验证核心行为。
模式小结:Tool-Based Generative UI 的关键要点
- 前后端各司其职:后端工具只返回 JSON 结构化数据;前端用
useComponent声明「工具名 → 渲染组件 → 参数 Schema」三元组,把展示逻辑完全留在客户端。 - Schema 即双端契约:zod 等 Standard Schema V1 兼容库既指导模型生成参数,又为 React 组件提供类型推导,一处定义两端受益。
- 注意序列化边界:AG2 工具返回值务必
json.dumps成 JSON 字符串,避免 Python repr 破坏前端解析(agent.py 中的注释是该坑的一手记录)。 - 渲染是消息流的一部分:组件以 assistant 消息形态内联渲染(e2e 测试对
copilot-assistant-message内svg的断言即是证明),因此流式更新、加载与完成状态天然融入对话体验。
如果需要在项目里复刻该模式,只需三步:后端提供一个返回 JSON 字符串的工具;前端用 zod 定义参数 Schema 并用useComponent注册渲染组件;在<CopilotKit>内挂载<CopilotChat>即可。全部源码均可在此仓库的 showcase/integrations/ag2/src/app/demos/gen-ui-tool-based/ 目录下查阅。
【免费下载链接】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),仅供参考