CopilotKit 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 是 CopilotKit Generative UI 能力的一种实现路径——Agent 不直接输出 UI 描述,而是调用后端工具获取结构化数据,前端再把该工具的执行结果渲染为定制 React 组件(图表、卡片等),并天然支持加载态与完成态。本文以仓库内 Langroid 集成展示区的gen-ui-tool-basedDemo 为完整案例,结合 demo 页面源码、后端 Agent 实现 与 E2E 测试,带你掌握注册渲染器、定义组件参数 Schema、配置建议提示词(Suggestion)以及前后端联调的完整套路。
一、核心机制:工具调用结果 ≠ 纯文本
在普通 Chat 中,Agent 调用后端工具后,返回值通常会被 LLM 再加工成一段文本回复。Tool-Based Generative UI 改变了这一链条:Agent 调用后端工具 → 工具返回结构化数据(JSON)→ 前端将该结果映射为一个自定义 React 组件直接渲染在对话流中。
正如 Demo 的 README 所概括的:
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.
也就是说,对话气泡里出现的不是一段"数据说明文字",而是一张真正的柱状图、饼图或业务卡片——数据可视化、富交互都由前端组件完成,LLM 只需负责"决定调用哪个工具、生成什么参数"。
useRenderTool:工具名到渲染器的映射表
README 指出,前端通过useRenderTool把每个工具名(tool name)映射到一个渲染器(renderer),渲染器会收到三样东西:
args—— Agent 调用工具时生成的参数;result—— 后端工具返回的结构化结果;status—— 工具调用的生命周期状态。
因为有status的参与,UI 才能区分"正在执行(loading)"与"已完成(complete)"两种呈现,避免用户在等待期间看到空白或过时数据。
实现说明:在当前仓库的 Langroid 集成里,该能力由
@copilotkit/react-core/v2的useComponent钩子落地(详见下文源码分析),README 中的useRenderTool是对这一机制的上层描述。两者本质一致:注册"工具/组件名 → 渲染组件 + 参数 Schema"。
二、Demo 源码拆解:一条消息如何变成一张图表
1. 页面骨架:CopilotKit+CopilotChat
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注册渲染器:每个渲染器都有一个唯一name。Agent 在对话中决定渲染某种 UI 时,就是按这个名字找到对应组件。description帮助 LLM 理解该组件"何时该用",parameters规定组件所需的入参结构。CopilotKitProvider 指定 Runtime 与 Agent:runtimeUrl="/api/copilotkit"指向 Next.js 侧的运行时路由;agent="gen-ui-tool-based"是 manifest 与运行时路由中登记的 agent 名。CopilotChat指定agentId:与 Provider 上的agent保持一致,聊天会路由到同一 Agent 后端。
2. 参数 Schema:用 Zod 约束组件入参
渲染组件与普通 React 组件最大的区别是:它的 props 由 LLM 动态生成,必须有一个机器可读、可校验的 Schema。Demo 用 Zod 定义(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)采用了完全相同的title / description / data[{label, value}]结构。这样做的好处是:
- Schema 即契约:LLM 生成参数时必须符合 Schema,前端拿到后可以直接
z.infer出类型,无需手写重复的 TS 接口; - 描述即提示:
z.string().describe(...)中的描述文本会进入 Agent 的上下文,引导 LLM 生成语义正确的字段值(例如title应填图表标题而非任意文本)。
3. 渲染组件:把结构化数据画出来
以BarChart为例,它是一个基于 recharts 的纯展示组件:ResponsiveContainer自适应宽度、CartesianGrid网格线、XAxis/YAxis坐标轴、Tooltip悬浮提示,并用一组预置色板给每根柱子着色。值得一提的两个细节:
- 空数据兜底:当
data缺失或为空数组时,组件渲染一个带"No data available"的空状态卡片,而不是抛出异常或白屏; - 增量动画:通过
useRef记录已渲染过的柱子索引,仅对"新到达"的柱子播放barSlideIn入场动画,避免数据流式更新时整张图反复闪烁。
PieChart则是一个纯 SVG 实现的环形图:按value占比计算每段弧长,用strokeDasharray与strokeDashoffset绘制圆环切片,下方配合图例展示数值与百分比(val.toLocaleString()格式化千分位)。
4. 建议提示词:引导用户快速触发图表
suggestions.ts 通过useConfigureSuggestions注册了三条建议提示词:
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"表示这些建议在任何状态下都可用。它们以可点击的提示胶囊(pill)形式展示在聊天界面,用户点一下即以message作为输入发出,从而降低"空想一句触发图表的话"的试错成本——E2E 测试里也把这三条建议的可见性作为页面加载成功的断言。
三、后端与运行时:工具从哪来、结果如何到达前端
1. 后端工具定义:ToolMessage+ AG-UI 适配
Langroid 集成中,所有工具都是langroid.agent.tool_message.ToolMessage的子类(agent.py)。与本 Demo 直接相关的核心工具是QueryDataTool:
class QueryDataTool(ToolMessage): request: str = "query_data" purpose: str = "Query the database. Always call before showing a chart or graph." query: str def handle(self) -> str: try: result = query_data_impl(self.query) return _json_dumps(result) except Exception as exc: return _tool_error( error=_ToolErrorKind.QUERY_DATA_FAILED, message=f"{exc.__class__.__name__}: {str(exc)[:200]}", )purpose字段是给 LLM 看的工具说明——"展示图表之前必须先调用本工具",这保证了 Agent 在用户要求"画个图"时不会跳过数据查询直接编造。handle()执行真实数据逻辑并返回 JSON 字符串;出错时返回统一结构的错误 JSON(_ToolErrorKind枚举限定错误码,防止字符串写错),便于外层 LLM 一致地识别可恢复的工具失败。
从源码注释可以确认这套机制的关键设计(agent.py):Langroid 没有原生的 AG-UI 适配器,集成层手工实现了 AG-UI 协议的 SSE 事件流;工具分为BACKEND_TOOLS(服务端执行)与FRONTEND_TOOLS(客户端执行)两组,并通过 import 时的金丝雀断言(_EXPECTED_FRONTEND_TOOL_NAMES)防止前后端工具名漂移。
2. 运行时路由:CopilotKit Runtime 代理到 Python Agent
前端runtimeUrl指向的 route.ts 是 Next.js 侧的运行时入口:它用HttpAgent(@ag-ui/client)把每个 agent 名代理到运行在AGENT_URL(默认http://localhost:8000)的 Python 后端,并用createCopilotRuntimeHandler以single-route模式统一处理 POST 请求。gen-ui-tool-based与其它 demo 共享同一个统一 Agent 端点/,前端仅通过 UI 组合(本 Demo 即CopilotChat+ 两个useComponent渲染器)区分行为。
GET分支则提供健康探针:返回agent_url、agent_status、OPENAI_API_KEY是否设置等诊断信息,方便在部署后快速确认后端连通性。
3. 请求-响应链路总览
将以上代码串联起来,一次"画饼图"请求的完整链路是:
- 用户在
CopilotChat中输入"Show me a pie chart of revenue by category"; - 前端通过
/api/copilotkit把消息转发给运行时,运行时经 AG-UI 协议转发给 Python 侧的 Langroid Agent; - Agent 的 LLM 依据
QueryDataTool.purpose决定先调用query_data获取结构化数据,再根据上下文调用前端注册的组件渲染工具; - 结构化结果随 AG-UI 事件流回到前端;
- 前端按工具名命中
useComponent注册的PieChart渲染器,用args填充 props,将 SVG 图表渲染进助手消息气泡中。
四、展示区定位与验证:manifest 与 E2E 测试
1. 在展示清单中的定位
manifest.yaml 将gen-ui-tool-based登记为名为"Tool-Based Generative UI"的 demo(route: /demos/gen-ui-tool-based),归入generative-ui标签,并高亮了三个关键文件:src/agents/agent.py(工具与 Agent 逻辑)、src/app/demos/gen-ui-tool-based/page.tsx(渲染器注册)与src/app/api/copilotkit/route.ts(运行时桥接)——与本 Demo 的前、中、后端三层完全对应。README 中所说 "The canonical description lives in the showcase manifest" 即指此处。
2. E2E 测试:可验证的行为契约
gen-ui-tool-based.spec.ts(Playwright)从用户视角锁定了该 Demo 的行为:
- 页面加载:聊天输入框与三条建议胶囊("Sales bar chart" / "Traffic pie chart" / "Market share")在 10~15 秒内可见;
- 饼图渲染:输入
"Show me a pie chart of revenue by category"后,首个助手消息气泡内应出现 SVG 可视化(60 秒超时); - 柱状图渲染:输入
"Show me a bar chart of monthly expenses"后同样断言 SVG 可见; - 基础对话:发送
"Hello"能收到助手回复。
其中对[data-testid="copilot-assistant-message"]下svg的断言,正是"工具结果以自定义组件而非纯文本渲染"这一核心特性的自动化验证——图表必须真的渲染在消息流里,而不是仅仅输出一段文字描述。
五、在 CopilotKit 中实践 Tool-Based Generative UI 的要点
结合本 Demo,可以提炼出一份可直接套用的实现清单:
- 前端注册渲染器:用
useComponent为每个 UI 类型注册唯一name、面向 LLM 的description、parametersSchema 与render组件; - Schema 双写合一:用 Zod 的
describe()把字段语义喂给 LLM,同时用z.infer导出 TS 类型供组件消费,避免"提示词"与"类型"两套定义失同步; - 组件做好空态与流式适配:
data为空要渲染占位卡片,增量数据更新要避免整体重渲染闪烁(Demo 中的isNew动画即为一例); - 后端工具职责单一:数据获取与 UI 渲染解耦——后端只负责"查数据并返回结构化 JSON",
purpose中写明使用前提(如"画图前先查询")引导 LLM 正确编排工具调用; - 运行时桥接:确认前端
runtimeUrl、Provider 的agent、CopilotChat的agentId与运行时路由(route.ts)中登记的 agent 名三者一致; - 建议提示词降低门槛:用
useConfigureSuggestions提供 2~3 个直击场景的示例输入,同时方便 E2E 稳定断言。
六、与其它 Generative UI 路径的区分
在 Langroid 集成的 manifest 中,Generative UI 是一个完整的演示族系(generative_ui字段与features列表),Tool-Based Generative UI 只是其中一条路径:
- Tool Rendering(
tool-rendering):对 Agent 的工具调用过程做自定义展示(如天气卡片、航班卡片); - Agentic Generative UI(
gen-ui-agent):面向长时任务,Agent 驱动pending → in_progress → completed状态机并配合状态快照事件; - Declarative Generative UI / A2UI(
a2ui-fixed-schema、declarative-gen-ui):Agent 依据前端声明的组件目录(catalog)动态生成 UI 操作指令,属于 A2UI(AG-UI)协议的 schema 驱动路线。
Tool-Based Generative UI 的独特之处在于:UI 形态由后端工具的返回结果决定,前端渲染器是"按图索骥"的静态注册表——不需要 LLM 去设计 UI 结构,而是让 LLM 专注于"选对工具、给对参数",可视化质量完全由前端组件保证。对于图表、报表、业务卡片这类"数据结构稳定、渲染要求高"的场景,这是比完全开放式 UI 生成更可控、更稳的方案。
参考文件:本 Demo 全部源码见 demo 目录(
page.tsx、bar-chart.tsx、pie-chart.tsx、suggestions.ts),后端工具与 Agent 见 agent.py,运行时桥接见 route.ts,行为契约见 E2E 测试。
【免费下载链接】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),仅供参考