AG2 工具渲染(Tool Rendering)实战指南:从 useRenderTool 到 E2E 质量验证
导读
本文以仓库中 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(开源多 Agent 框架)深度集成的完整示例应用。在 manifest.yaml 中,"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
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust4.21 K637- DDeepSeek-V4.1-FlashDeepSeek-V4.1-Flash 是一个多模态混合专家(MoE)模型,拥有 5520 亿骨干参数,并支持最多一百万 token 的上下文长度。该模型原生支持图像和文本输入,并以自回归方式生成文本Python270
cherry-studio🍒 Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端TypeScript2 K146
hello-agents📚 《从零开始构建智能体》——从零开始的智能体原理与实践教程Python46066
new-apiAI模型聚合管理中转分发系统,一个应用管理您的所有AI模型,支持将多种大模型转为统一格式调用,支持OpenAI、Claude、Gemini等格式,可供个人或者企业内部管理与分发渠道使用。🍥 A Unified AI Model Management & Distribution System. Aggregate all your LLMs into one app and access them via an OpenAI-compatible API, with native support for Claude (Messages) and Gemini formats.Go20143
JeecgBoot🔥企业级低代码平台集成了AI应用平台,帮助企业快速实现低代码开发和构建AI应用!前后端分离架构 SpringBoot,SpringCloud、Mybatis,Ant Design4、 Vue3.0、TS+vite!强大的代码生成器让前后端代码一键生成,无需写任何代码! 引领AI低代码开发模式: AI生成->OnlineCoding-> 代码生成-> 手工MERGE,显著的提高效率,又不失灵活~Java34051