Function Calling 回放测试:工具链路别只测一次成功
Function Calling 回放测试工具链路别只测一次成功一、工具调用的问题常在第二次出现Function Calling 第一次跑通很容易。模型生成参数服务端调用工具返回结果页面展示答案。但生产环境里的麻烦通常出现在重试、并发、参数漂移和工具版本升级后。只测一次成功路径等于没有测工具链路。我做过一个内部运维 Agent上线时所有工具调用都很稳。两个月后加了两个新工具、升级了一次模型版本某个老的查询工具开始频繁失败。排查发现新模型对同一个 prompt 生成的参数结构变了——原来返回{user_id: 123}升级后变成{user_id: 123, options: {format: json}}。工具代码是强类型的多出的字段被拒绝。问题是这个工具每天被调 2000 次线上故障持续了 20 分钟才被发现因为大部分调用还是成功的只有少数用户的参数恰好触发了不同分支。回放测试的目标是把真实请求中的模型输出、工具参数、工具响应和最终答案保存下来在模型或工具升级后重新跑一遍。它不追求替代人工评测而是先发现明显回归。二、回放样本要保存关键证据sequenceDiagram participant U as 用户请求 participant M as 模型 participant T as 工具服务 participant R as 回放仓库 participant A as 断言引擎 U-M: 原始问题 M-T: function call (参数) T--M: tool result M--U: final answer M-R: 保存完整调用轨迹 Note over A,R: 模型/工具升级后 R-A: 回放历史样本 A--A: 比较工具选择/参数/答案回放样本不能只保存最终答案。至少要有原始问题、系统提示版本、模型名称、工具 schema 版本、函数名、参数、工具响应摘要和最终答案。这样回放失败时才能知道是模型没选工具还是参数不兼容。敏感字段要脱敏。工具参数里可能有用户 ID、订单号和内部资源路径。回放仓库应该保存 hash 或脱敏值并提供 mock 工具响应。测试要可复现也要守住数据边界。三、工具响应要可模拟class ToolMock: def __init__(self, fixtures: dict): self.fixtures fixtures self.call_counts {} async def call(self, name: str, args: dict) - dict: key self._build_key(name, args) self.call_counts[key] self.call_counts.get(key, 0) 1 if key not in self.fixtures: raise AssertionError( f缺少 fixture: tool{name}, args{self._mask_sensitive(args)} ) return self.fixtures[key] def _build_key(self, name: str, args: dict) - str: # 关键参数决定 fixture 匹配 return f{name}:{sorted(args.keys())} def _mask_sensitive(self, args: dict) - str: # 错误日志中脱敏 return str({k: *** if k in (user_id, api_key) else v for k, v in args.items()})回放测试不应该每次打真实外部系统。真实系统有状态变化也有成本和限流。对大多数工具使用 fixture 模拟响应更稳。真正需要验证外部接口时可以单独做少量集成测试。call_counts能发现模型多调了工具这类回归。参数匹配不要过度宽松。模型把limit从 10 改成 100可能导致成本上升把date_range改错可能导致答案不准。回放框架要能比较关键参数而不是只看函数名是否一样。assert: tool_name: search_docs required_args: [query, top_k] max_top_k: 20 forbid_extra_args: false # 允许多余参数但不报警四、回归结果要分级pass: 工具选择、参数和答案都稳定 warn: 答案不同但证据等价参数有新增但兼容 fail: 工具缺失、参数越界、答案不被证据支持回放测试不可能要求文字完全一致。大模型输出有波动关键是语义、证据和工具行为是否稳定。可以把结果分为通过、警告和失败。警告进入抽检失败阻塞发布。工具 schema 升级时要特别谨慎。新增必填字段、修改枚举值、变更返回结构都会让旧提示词失效。回放样本能快速暴露这些兼容问题比线上用户先发现要便宜得多。建议回放测试加入 CI 流水线工具升级前自动跑一遍历史样本——失败率超过 5% 阻塞上线。还要考虑回放样本的时效性。三个月前的样本可能基于已经不存在的业务场景逐步衰减后应该淘汰。给样本加权重最近的样本权重高旧样本只作为参考。这样测试信号不会因为大量过期样本而失效。五、总结Function Calling 要做回放测试保存调用轨迹模拟工具响应并按语义和参数风险分级判断结果。工具链路不是跑通一次就结束。每次模型、提示词和工具升级都应该让历史样本重新说话。