CopilotKit 工具渲染(Tool Rendering)实战指南:在聊天流中为 Agent 工具调用渲染 React 卡片
2026/9/12 11:12:56 网站建设 项目流程

CopilotKit 工具渲染(Tool Rendering)实战指南:在聊天流中为 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

导读

当后端 Agent 在对话中调用工具(查询天气、搜索航班、获取股价、掷骰子)时,CopilotKit 允许开发者在前端把这些工具调用渲染成内嵌在聊天流中的 React 组件,并区分“工具执行中”与“执行完成”两种视觉状态。本文以仓库built-in-agent(TanStack AI 内建 Agent)示例中的 Tool Rendering 演示为对象,逐条解析其 QA 验收清单背后的实现原理与验证方法。读完本文,你将掌握useRenderTool按工具注册专属渲染器、useDefaultRenderTool注册兜底渲染器的完整用法,以及如何用 Playwright 对工具卡片做确定性回归测试。

Demo 概览与运行环境

Tool Rendering 演示位于showcase/integrations/built-in-agent/,页面路径为/demos/tool-rendering。根据 QA 文档 tool-rendering.md,运行该演示需要满足以下前置条件:

前置项说明
API Key.env.local(或环境变量)中设置OPENAI_API_KEY
依赖安装built-in-agent/包目录下执行npm install --legacy-peer-deps
启动built-in-agent/包目录下执行npm run dev
访问地址http://localhost:3000/demos/tool-rendering

其中--legacy-peer-deps表明该演示依赖树中存在 peer dependency 版本约束,需跳过严格 peer 校验;这也是仓库内多个示例包的通用安装约定。

从页面源码 page.tsx 可以看到,Demo 通过CopilotKit组件挂载运行时,并通过agent="tool-rendering"绑定到后端注册的命名 Agent:

<CopilotKit runtimeUrl="/api/copilotkit" agent="tool-rendering"> <CopilotChat agentId="tool-rendering" className="h-full rounded-2xl" /> </CopilotKit>

对应的后端路由在 route.ts 中注册为"tool-rendering": createBuiltInAgent(),由InMemoryAgentRunner在进程内运行。也就是说,这个 Demo 的 Agent 与前端同属一个 Next.js 应用,工具调用通过/api/copilotkit单路由(mode: "single-route")完成。

三层工具渲染架构:专属渲染器 + 兜底渲染器

演示 README(README.md)概括了核心机制:后端 Agent 的工具调用被渲染为聊天流中的 React 组件;前端用useRenderTool按工具名注册渲染器,接收argsresultstatus三个输入,从而让 UI 同时反映“进行中”与“已完成”两种状态。

在 page.tsx 的注释中可以看到该 Demo 的完整注册清单,这正是“三层”结构:

后端工具前端渲染器类型
get_weather<WeatherCard />专属渲染器
search_flights<FlightListCard />专属渲染器
get_stock_price<StockCard />专属渲染器
roll_d20<D20Card />专属渲染器
其他任意工具<CustomCatchallRenderer />兜底渲染器

专属渲染器:useRenderTool

useRenderTool@copilotkit/react-core/v2提供的 Hook,用于为特定工具名注册渲染函数。以天气工具为例(page.tsx):

useRenderTool( { name: "get_weather", parameters: z.object({ location: z.string(), }), render: ({ parameters, result, status }) => { const loading = status !== "complete"; const parsed = parseJsonResult<WeatherResult>(result); return ( <WeatherCard loading={loading} location={parameters?.location ?? parsed.city ?? ""} temperature={parsed.temperature} humidity={parsed.humidity} windSpeed={parsed.wind_speed} conditions={parsed.conditions} /> ); }, }, [], );

几个关键点:

  • parameters用 zod 描述入参契约(与后端inputSchema对应),既是运行时校验,也是类型推导依据;
  • render回调每次工具状态变化时都会重新执行,因此loading = status !== "complete"可以在执行期间显示加载态;
  • result在完成前通常是原始字符串,Demo 通过共享工具函数parseJsonResult(位于 parse-json-result.ts)将其安全解析为结构化对象;
  • 注册后传入的依赖数组[]与 React Hook 约定一致,需保持引用稳定。

航班搜索(page.tsx)、股价(L126-L146)、d20 掷骰(L150-L169)三个工具遵循完全相同的模式,分别渲染FlightListCardStockCardD20Card

兜底渲染器:useDefaultRenderTool

并非每个后端工具都值得定制 UI。对于未被专属渲染器认领的工具调用,useDefaultRenderTool提供了通配兜底(page.tsx):

useDefaultRenderTool( { render: ({ name, parameters, status, result }) => ( <CustomCatchallRenderer name={name} parameters={parameters} status={status as CatchallToolStatus} result={result} /> ), }, [], );

兜底渲染器 custom-catchall-renderer.tsx 是一个自包含的“通用工具卡”,展示工具名、状态徽章(streaming/running/done三态)、格式化后的参数 JSON 与结果 JSON。CatchallToolStatus类型定义在 custom-catchall-renderer.tsx,取值为"inProgress" | "executing" | "complete",其状态描述逻辑在describeStatus函数中实现。

这套“专属优先、兜底托底”的设计意味着:新增后端工具时,前端无需改动即可自动获得可读的工具卡片;需要品牌化展示时再补一个专属渲染器即可。

QA 一:页面加载检查

QA 清单的第一组断言聚焦页面初始状态:

  • 页面标题 "Tool Rendering" 可见;
  • 提示文本Try: "What's the weather in Tokyo?"可见;
  • 聊天输入框可见。

从体验设计看,这组断言对应 suggestions.ts 中通过useConfigureSuggestions配置的 5 个建议药丸(suggestion pill):

药丸标题对应消息
Weather in SFWhat's the weather in San Francisco?
Find flightsFind flights from SFO to JFK.
Stock priceWhat's the current price of AAPL?
Roll a d20Roll a 20-sided die.
Chain tools单轮内链式调用:东京天气 + SFO→东京航班 + 掷 d20

这些药丸是 QA 交互的快捷入口,同时也是 e2e 测试的确定性触发源。对应验证逻辑可参见 e2e 用例 tool-rendering.spec.ts,其中逐一对 5 个药丸标题做了可见性断言。

QA 二:快乐路径交互(Happy Path)

快乐路径是工具渲染的核心体验验证,QA 文档要求逐步检查三个场景。

场景 1:执行中状态可见

发送 "What's the weather in Tokyo?" 后,工具执行期间应出现一张内联的 "Fetching weather…" 进行中卡片。

这一行为的实现就在WeatherCard的加载态分支(weather-card.tsx):loading为真时,副标题位置渲染"Fetching weather..."文本,右侧主视觉替换为省略号占位,同时隐藏温度、湿度、风力等数据区块(weather-card.tsx)。也就是说,“进行中卡片”与“完成卡片”是同一张卡片组件在两个状态下的切换,而不是两套独立 UI。

场景 2:完成态数据正确

Agent 执行完毕后,卡片应显示:城市名、温度(°F)、天气状况文本、湿度百分比。

WeatherCard渲染的字段与后端返回一一对应(server-tools.ts):

export const getWeatherTool = toolDefinition({ name: "get_weather", description: "Get the current weather for a given location. ...", inputSchema: z.object({ location: z.string(), }), }).server(async ({ location }) => ({ city: location, temperature: 68, humidity: 55, wind_speed: 10, conditions: "Sunny", }));

后端temperaturehumiditywind_speedconditions四个字段分别映射到卡片的温度({temperature}°F)、湿度({humidity}%)、风速({windSpeed} mph)与状况文本;状况还会被conditionsEmoji映射为 emoji 图标(weather-card.tsx)。

注意这里getWeatherTool返回的是 mock 数据(固定 68°F / 55% / 10 mph / Sunny),这正是 QA 与 e2e 断言能够确定性的基础。

场景 3:多次工具调用的隔离

继续询问第二座城市(如 "And what about London?"),应出现第二张天气卡片,且不得干扰第一张。

这个“每张卡片独立”的行为源于渲染模型:每个工具调用都会以独立的 React 节点挂载到消息流中。d20 卡片是这一点的极端体现——每次掷骰都会产生一张独立卡片,e2e 用轮询断言“恰好出现 5 张 d20 卡片,且最后一张结果为 20”(tool-rendering.spec.ts)。而 "Chain tools" 药丸则验证单轮内多工具并存的场景:一次提问同时渲染出天气卡、航班卡和 d20 卡(tool-rendering.spec.ts)。

QA 三:边界用例检查

边界 1:非 weather 工具走兜底渲染器

发送触发weather之外工具的消息(如 "Tell me today's date"),应渲染通用工具卡——显示工具名、状态和 JSON 载荷——而不是自定义天气卡。

这正是useDefaultRenderTool兜底渲染器的工作。CustomCatchallRenderer携带稳定的data-testid="custom-catchall-card"data-tool-name={name}data-status={status}属性(custom-catchall-renderer.tsx),便于自动化定位。执行期间结果区显示 "waiting for tool to finish…",完成后通过parseResult尝试 JSON 解析并美化为缩进展示。

这个设计同时印证了一个关键约束:当后端新增一个没有专属渲染器的工具时,前端不会白屏或丢失信息,兜底卡会立即接管展示。

边界 2:纯对话消息不渲染工具卡

发送普通对话消息(如 "Hi there"),Agent 正常回复,且不出现任何工具卡片。

这验证的是渲染器的触发条件:只有存在真实的工具调用流时,useRenderTool/useDefaultRenderToolrender才会被调用;纯文本回复不经过工具渲染路径。这一行为保证了工具卡片不会误伤正常对话体验。

补充:状态机视角

从兜底渲染器的状态徽章可以反推工具生命周期:inProgress(streaming,流式输出中)→executing(running,工具执行中)→complete(done,完成)。专属卡片则把状态折叠为二元判断status !== "complete"。理解这一状态序列,有助于设计自己的卡片加载态与错误态。

后端工具契约:与渲染器对齐的输入输出

工具渲染之所以能“前后端对齐”,是因为工具定义集中在 server-tools.ts 中,并通过baseServerTools数组挂载到createBuiltInAgent()。除天气外,Tool Rendering 相关的还有:

  • searchFlightsTool(server-tools.ts):入参origin/destination,返回 3 条确定性 mock 航班(UA231、DL412、B6722),供FlightListCard渲染“航空公司 + 航班号 + 起降时间 + 价格”列表;
  • getStockPriceTool(L93-L109):入参ticker,返回随机价格与涨跌幅,StockCard据此显示$price与带正负号的change%(涨绿跌红,见 stock-card.tsx);
  • rollD20Tool(L136-L145):入参sides(默认 6),返回{ sides, result }D20Card在结果为 20 时显示 "critical!" 徽章并加高亮描边(d20-card.tsx)。

工具描述文本(description)还承担着提示工程职责:例如get_weather的描述提示模型“提到城市时也考虑查航班”,search_flights描述规定“未匹配到出发地时默认 SFO”。这解释了为什么 "Chain tools" 药丸能可靠地触发多工具链式调用。

e2e 自动化验证:把 QA 清单变成可回归的测试

QA 文档的每一条检查项,在 tool-rendering.spec.ts 中都有对应的 Playwright 用例,形成 6 个测试的完整闭环:

e2e 用例对应 QA 检查关键断言
页面加载与 5 个建议药丸页面加载各药丸标题可见
Weather in SF 药丸快乐路径weather-card可见,城市为 "San Francisco"、湿度 "55%"、风速含 "10"
Find flights 药丸快乐路径flights-card可见,SFO → JFK,至少 2 行flight-row
Stock price 药丸快乐路径stock-card可见,ticker 为 "AAPL"、价格 "$338.37"、涨跌 "-2.96%"
Roll a d20 药丸隔离性恰好 5 张d20-card,最后一张值为 "20",前 4 张非 20
Chain tools 药丸多工具并存同一轮内天气卡、航班卡、d20 卡全部可见

测试注脚(tool-rendering.spec.ts)说明了确定性来源:aimock fixtures 位于showcase/aimock/d5-all.json,把每个药丸提示固定到确定的工具调用序列;卡片则依赖稳定data-testid定位。测试超时分为建议加载(15s)与工具执行(60s)两档。

这说明 QA 文档并非一次性人工清单,而是与自动化回归测试一一对应的“可执行验收规范”。如果你要复现验证,只需要在满足前置条件后访问/demos/tool-rendering,依次点击药丸并按 QA 清单勾选;要自动化,则可直接复用上述 spec 的定位策略(data-testid="weather-card"weather-cityweather-humidity等)。

变体与延伸:工具渲染的进阶形态

工具渲染在本仓库内还有多个进阶变体,可作为继续深入的方向:

  • tool-rendering-default-catchall:展示框架内置的默认兜底渲染器(shadcn 风格),位于 tool-rendering-default-catchall;
  • tool-rendering-custom-catchall:仅演示自定义兜底渲染器,见 tool-rendering-custom-catchall;
  • tool-rendering-reasoning-chain:在工具渲染之上叠加可见的推理链(reasoning)展示,见 tool-rendering-reasoning-chain;
  • headless-complete的 tools 目录 提供了weather-card.tsxgeneric-tool-card.tsxchart-card.tsx等更丰富的渲染器集合,可参考其无头模式下的注册方式。

这三个变体各自带有独立的 e2e spec(tool-rendering-default-catchall.spec.tstool-rendering-custom-catchall.spec.tstool-rendering-reasoning-chain.spec.ts),验证方式与本 Demo 一脉相承。

验收核对表:一次完整的 QA 执行

最后,汇总成可直接执行的核对表(对应 tool-rendering.md 全量条目):

准备:设置OPENAI_API_KEYnpm install --legacy-peer-deps && npm run dev→ 打开http://localhost:3000/demos/tool-rendering

页面加载

  • 标题 "Tool Rendering" 可见
  • 提示文本 "What's the weather in Tokyo?" 可见
  • 聊天输入框可见

快乐路径

  • 发送天气问题后,出现 "Fetching weather…" 进行中卡片
  • 完成后卡片显示城市、温度(°F)、状况、湿度
  • 追问第二城市,第二张卡片渲染且不影响第一张

边界用例

  • 触发非weather工具时,渲染通用工具卡(工具名 + 状态 + JSON 载荷)
  • 发送纯对话消息,正常回复且无任何工具卡片

至此,你既掌握了 CopilotKit 工具渲染的前端注册机制(useRenderTool+useDefaultRenderTool),也拥有了完整的验收标准与自动化回归测试方法,可以直接迁移到自己的 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

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询