AG2 工具渲染(Tool Rendering)实战指南:从 useRenderTool 到 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
导读
本文以仓库中 tool-rendering.md 质量验证清单为骨架,完整拆解 CopilotKit 仓库中 AG2 集成示例的"工具渲染"(Tool Rendering)能力:前端如何通过useRenderTool把后端 Agent 的工具调用实时渲染为品牌化 React 卡片,后端如何通过 AG-UI 协议被代理接入,以及如何用 Playwright E2E 测试把整条链路固化下来。读完本文,你将掌握 per-tool 渲染器 + 通配 catch-all 的完整注册方式、前后端数据契约的关键细节,以及一套可复制的 QA 验收方法。
一、功能定位:AG2 集成示例中的 Tool Rendering
showcase/integrations/ag2是 CopilotKit 与 AG2 中,"Tool Rendering" 被定义为:
Backend agent tools rendered as UI components(后端 Agent 工具渲染为 UI 组件)
即:后端 Agent 在执行工具时,前端聊天记录(chat transcript)中不再是干巴巴的 JSON,而是渲染为带有品牌设计的 React 组件。manifest 中高亮的关键文件覆盖了完整链路:
- agent.py — AG2 后端 Agent 与工具定义;
- tools/get_weather.py、tools/query_data.py、tools/search_flights.py、tools/schedule_meeting.py — 具体工具实现;
- page.tsx — 前端渲染器注册与页面;
- route.ts — CopilotKit 运行时与 AG-UI 协议代理。
示例应用将同一主题做成了"三阶段递进"(见 manifest 中三个独立 demo):tool-rendering(每个核心工具都有专属渲染器 + 通配兜底)、tool-rendering-default-catchall(前端零自定义渲染器,完全依赖 CopilotKit 内置默认 UI)、tool-rendering-custom-catchall(用useDefaultRenderTool注册一个统一品牌化兜底卡片)。本 QA 文档针对的是最完整的tool-rendering变体。
二、架构概览:前端、运行时与 AG2 后端如何连接
2.1 AG-UI 协议代理
前端 demo 页面挂载时指定了运行时地址与 Agent 名称:
<CopilotKit runtimeUrl="/api/copilotkit" agent="tool-rendering">/api/copilotkit由 route.ts 处理:它通过@ag-ui/client的HttpAgent把请求转发到独立运行的 FastAPI 后端(默认http://localhost:8000,可通过AGENT_URL环境变量覆盖),并使用 AG-UI 协议通信。
const AGENT_URL = process.env.AGENT_URL || "http://localhost:8000"; function createAgent(path = "/") { return new HttpAgent({ url: `${AGENT_URL}${path}` }); }route.ts 把tool-rendering注册在sharedAgentNames列表中(route.ts#L35-L56),与agentic_chat、tool-rendering-default-catchall等名称共享同一个agent.py中定义的ConversableAgent——也就是说,这一系列前端差异巨大的 demo 背后是同一份后端逻辑,差异全部发生在前端渲染层。
2.2 健康检查前提
QA 文档的前置条件强调两点:
- Demo 已部署且可访问;
- Agent 后端健康(检查
/api/health)。
后端侧的真实健康探针在 entrypoint.sh:一个 watchdog 每 30 秒curl一次http://127.0.0.1:8000/health,连续 3 次失败即判定异常。因此前端验收前先确认该端点返回正常,是保证测试结果可解释的第一步。
三、前端渲染机制:useRenderTool 与 useDefaultRenderTool
3.1 per-tool 渲染器注册
demo 页面 为每个"有趣"的后端工具注册了专属渲染器,映射关系如下:
| 后端工具 | 专属渲染器 | 说明 |
|---|---|---|
get_weather | <WeatherCard /> | 天气卡片 |
search_flights | <FlightListCard /> | 航班列表卡片 |
get_stock_price | <StockCard /> | 股票行情卡片 |
roll_d20 | <D20Card /> | 掷骰子卡片 |
| 其他一切工具 | <CustomCatchallRenderer /> | 通配兜底 |
useRenderTool的典型注册方式(以天气工具为例):
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} /> ); }, }, [], );要点拆解:
name与parameters:声明该渲染器负责的工具名与参数 Schema(使用 zod 校验);render回调:接收{ parameters, result, status }三个入参,其中result通常是后端返回的 JSON 字符串,需要先用parseJsonResult(位于 parse-json-result.ts)解析为对象;status:驱动加载态。源码中status !== "complete"视为 loading,据此切换卡片内容。
3.2 通配兜底渲染器
任何未被具名渲染器认领的工具调用,都会落到通过useDefaultRenderTool注册的 CustomCatchallRenderer:
useDefaultRenderTool( { render: ({ name, parameters, status, result }) => ( <CustomCatchallRenderer name={name} parameters={parameters} status={status as CatchallToolStatus} result={result} /> ), }, [], );兜底卡片展示工具名、状态徽章、格式化后的参数与结果 JSON。从源码看,CatchallToolStatus有三种状态(custom-catchall-renderer.tsx#L15):
| 状态值 | 徽章文案 | 含义 |
|---|---|---|
inProgress | streaming | 流式执行中 |
executing | running | 正在执行 |
complete | done | 已完成(此时才渲染结果 JSON) |
这正好与 QA 文档"验证加载态→验证渲染结果"的验收顺序一一对应。
四、天气卡片:从后端工具到前端组件的完整数据契约
4.1 后端:为什么必须返回 JSON 字符串
QA 文档要求验证天气卡片的多个数据字段(城市名、温度、湿度、风速、体感温度、天气状况)。这些字段的后端来源是 agent.py 中的get_weather工具:
async def get_weather( location: Annotated[str, "City name to get weather for"], ) -> str: """Get current weather for a location.""" result = get_weather_impl(location) return json.dumps( { "city": result["city"], "temperature": result["temperature"], "feels_like": result["feels_like"], "humidity": result["humidity"], "wind_speed": result["wind_speed"], "conditions": result["conditions"], } )源码注释明确记录了一个重要契约:工具必须返回 JSON 字符串而不是 dict——因为 autogen 对非字符串返回值会调用str()序列化,产生带单引号的 Python repr,前端JSON.parse无法解析,天气卡片就会渲染出--占位符。这是"工具渲染能否正常出数据"的关键细节,也是 QA 验证"所有数据字段已填充"背后的底层原因。query_data、manage_sales_todos、schedule_meeting等工具采用了同样的模式。
4.2 前端:WeatherCard 组件与 testid
WeatherCard 是加载态与完成态的复合组件:
- 加载态:显示城市名、"Fetching weather..." 文案与
...占位符; - 完成态:渲染温度(华氏)、湿度百分比、风速(mph)、天气状况与对应 emoji。
组件上打了一组稳定的data-testid,供 QA 手工验收与 E2E 自动断言共用:
| data-testid | 含义 |
|---|---|
weather-card | 天气卡片容器 |
weather-city | 城市名 |
weather-humidity | 湿度(如 55%) |
weather-wind | 风速(如 10 mph) |
天气图标由conditionsEmoji函数根据状况文本关键字映射(weather-card.tsx#L79-L87):sun/clear → sun,rain/storm → rain,cloud → cloud,snow → snow。
4.3 关于加载文案与主题色的说明
QA 文档预期加载态显示 "Retrieving weather..." 并带 spinner,且卡片背景色随天气状况变化(晴#667eea、雨#4A5568、多云#718096、雪#63B3ED)。对照当前源码:加载文案实际为 "Fetching weather..."(天气 emoji 位置为...),背景色为固定值#EDEDF5,未按状况动态换色。也就是说,QA 文档描述的是该变体的预期规格,而当前实现细节以源码为准——在做手工验收时,建议以实际渲染的文案与配色作为基准,同时把"加载态到完成态的切换是否清晰、信息是否齐全"作为核心判定标准,而不是逐字比对文案。
五、多城市天气查询:多卡片并存
QA 文档专门有一节验证"连续询问第二个城市后,第二张卡片应正常渲染且不破坏第一张"。这与前端的实现方式直接相关:每次工具调用都会挂载一张独立的卡片。这个设计在 d20-card.tsx 的注释中写得很明确:
Each tool call mounts its own card so e2e tests can count them.(每次工具调用挂载自己的卡片,以便 E2E 测试计数。)
因此验收要点是:
- 先问 San Francisco,再问第二个城市;
- 断言页面上出现两张
weather-card,且各自weather-city显示正确的城市名; - 第一张卡片的内容不被第二张覆盖。
E2E 侧同样验证了这一行为:d20的测试用cards.count()精确断言"恰好 5 张卡片、最后一张结果是 20"(见 tool-rendering.spec.ts#L113-L143),说明"每次调用独立成卡"是可计数、可断言的关键设计。
六、建议按钮(Suggestion Pills)
QA 文档要求验证三个天气建议按钮可见并可点击填入输入框:Weather in San Francisco、Weather in New York、Weather in Tokyo。
这些建议按钮由useSuggestions()(demo 页面中的 hooks,按钮 DOM 使用data-testid="copilot-suggestion")驱动。需要说明的是:当前仓库的 E2E 测试所固化的建议集合是另一套五颗 pill——Weather in SF、Find flights、Stock price、Roll a d20、Chain tools(tool-rendering.spec.ts#L26-L39),覆盖了天气、航班、股票、掷骰子与多工具链式调用五条路径。手工验收时可灵活处理:只要建议按钮可见、点击后能填充输入框或直接发送消息,即视为通过;若需要严格对齐 QA 文档,可将建议文案调整为目标城市(如 SF / New York / Tokyo)。
七、错误处理:空消息与无控制台报错
QA 文档的第三大块验收聚焦健壮性:
- 发送空消息应被优雅处理(不崩溃、不产生无效请求);
- 正常使用过程中无 console 错误。
这条检查背后的意义在于:工具渲染链路横跨前端渲染器、CopilotRuntime 代理、AG2 后端三层,任何一层的异常都可能在浏览器控制台暴露。建议在 DevTools Console 保持开启的状态下完成 3.1—3.3 的全部步骤,把"无报错"作为贯穿始终的观察项。
八、从手工 QA 到 Playwright E2E:把验收固化为自动化
QA 文档的每一项手工检查,几乎都能在 tool-rendering.spec.ts 中找到对应的自动化断言。该测试文件头部注明其与 QA 文档的对应关系(QA reference: qa/tool-rendering.md),并把 5 颗建议 pill 映射为 6 个测试用例:
| 测试用例 | 对应 QA 步骤 | 关键断言(testid + 确定性 fixture 值) |
|---|---|---|
| 页面加载与 5 颗建议 pill | 建议按钮检查 | copilot-suggestion× 5 可见 |
| Weather in SF | 天气卡片渲染 | weather-card可见;weather-city含 "San Francisco";weather-humidity含 "55%";weather-wind含 "10" |
| Find flights | 多卡片渲染 | flights-card可见;flight-origin含 "SFO";flight-destination含 "JFK";flight-row≥ 2 行 |
| Stock price | 股票卡片渲染 | stock-card可见;stock-ticker= "AAPL";stock-price含 "$338.37";stock-change含 "-2.96%" |
| Roll a d20 | 多卡片渲染 | 恰好 5 张d20-card;最后一张d20-value= "20",前四张非 20 |
| Chain tools | 多工具链式调用 | 一轮对话中同时出现weather-card、flights-card、d20-card |
测试中使用的确定性数据来自 Aimock fixture(showcase/aimock/d5-all.json),每条 pill 提示词都被固定映射到确定的工具调用序列——这正是"渲染结果可重复断言"的前提。测试还设置了两个超时阈值:建议按钮等待 15 秒、工具调用等待 60 秒(tool-rendering.spec.ts#L15-L16),与 QA 文档"聊天 3 秒内加载、Agent 10 秒内响应"的预期共同构成性能验收基线。
九、验收标准总结(Expected Results)
综合 QA 文档的最终判定标准,一个"通过"的工具渲染验收应同时满足:
- 性能:聊天界面 3 秒内加载完成;Agent 10 秒内给出响应;
- 功能:天气卡片渲染出全部数据字段(城市、摄氏/华氏温度、湿度、风速、体感温度、状况图标);
- 视觉一致性:天气图标与状况文本匹配(sun/rain/cloud/snow);
- 健壮性:无 UI 错误、无布局破坏、空消息被优雅处理。
十、延伸阅读
- 后端 Agent 与工具定义:agent.py
- 前端渲染器注册:page.tsx
- 天气卡片组件:weather-card.tsx
- 通配兜底渲染器:custom-catchall-renderer.tsx
- E2E 自动化验收:tool-rendering.spec.ts
- 运行时代理与 Agent 注册:route.ts
- Demo 目录与路由清单:manifest.yaml
【免费下载链接】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),仅供参考