首页
/ AG2 工具渲染(Tool Rendering)实战指南:从 useRenderTool 到 E2E 质量验证

AG2 工具渲染(Tool Rendering)实战指南:从 useRenderTool 到 E2E 质量验证

2026-09-10 13:38:59作者:曹令琨Iris

导读

本文以仓库中 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 中高亮的关键文件覆盖了完整链路:

示例应用将同一主题做成了"三阶段递进"(见 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/copilotkitroute.ts 处理:它通过 @ag-ui/clientHttpAgent 把请求转发到独立运行的 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_chattool-rendering-default-catchall 等名称共享同一个 agent.py 中定义的 ConversableAgent——也就是说,这一系列前端差异巨大的 demo 背后是同一份后端逻辑,差异全部发生在前端渲染层。

2.2 健康检查前提

QA 文档的前置条件强调两点:

  1. Demo 已部署且可访问;
  2. 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}
        />
      );
    },
  },
  [],
);

要点拆解:

  • nameparameters:声明该渲染器负责的工具名与参数 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_datamanage_sales_todosschedule_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 → sunrain/storm → raincloud → cloudsnow → 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 FranciscoWeather in New YorkWeather in Tokyo

这些建议按钮由 useSuggestions()(demo 页面中的 hooks,按钮 DOM 使用 data-testid="copilot-suggestion")驱动。需要说明的是:当前仓库的 E2E 测试所固化的建议集合是另一套五颗 pill——Weather in SFFind flightsStock priceRoll a d20Chain toolstool-rendering.spec.ts#L26-L39),覆盖了天气、航班、股票、掷骰子与多工具链式调用五条路径。手工验收时可灵活处理:只要建议按钮可见、点击后能填充输入框或直接发送消息,即视为通过;若需要严格对齐 QA 文档,可将建议文案调整为目标城市(如 SF / New York / Tokyo)。

七、错误处理:空消息与无控制台报错

QA 文档的第三大块验收聚焦健壮性:

  1. 发送空消息应被优雅处理(不崩溃、不产生无效请求);
  2. 正常使用过程中无 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-cardflights-cardd20-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 错误、无布局破坏、空消息被优雅处理。

十、延伸阅读

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.16 K
2.78 K
kernelkernel
deepin linux kernel
C
34
18
docsdocs
暂无描述
Markdown
904
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
932
1.86 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
862
1.36 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.95 K
1.03 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.38 K
1.47 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
535
606
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
549
398
leetcodeleetcode
🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解
Markdown
77
23