Agent多轮交互批量回归测试:从理念到工程落地
一、为什么Agent测试不能照搬LLM的套路
1.1 单轮测试的“天花板”
过去两年,大模型应用测试的主流做法是构造Prompt-Response黄金集,跑一遍模型,算个准确率。这套方法在ChatBot、文本生成、知识问答等场景下足够好用——输入输出一一对应,评判标准相对客观。
但Agent不是ChatBot。一个真实的Agent会经历:
- 接收模糊目标(如“帮我规划五一去成都的行程”)
- 任务拆解(查机票、订酒店、排景点、算预算)
- 工具调用(搜索、API请求、数据库查询)
- 多轮修正(用户中途改变要求,Agent动态调整)
- 最终交付(结构化行程单、预订确认信息)
这个过程中,正确性不能仅用最终回复来衡量。Agent可能在最后一轮给出了完美答案,但中间调用错了工具、漏掉了关键参数、或者推理链路存在严重风险——这些“隐藏缺陷”在单轮测试中完全暴露不出来。
1.2 多轮回归测试要解决什么问题
回归测试的本质是**“变更后的质量守护”。每次Agent升级(模型换版本、Prompt调优、工具链更新、记忆策略调整),我们都要确认原本能跑通的复杂场景依然能跑通**。
多轮回归测试的核心价值就是:
- 防止能力退化:新增功能不能破坏旧有核心流程。
- 量化质量趋势:通过通过率、失败类型分布,直观评估每次迭代的效果。
- 精准定位问题:失败时能精确到第几轮、哪个工具调用、哪条断言,而不是只知道“出错了”。
- 支撑版本对比:A版本和B版本在同一个场景集合上的表现差异,一目了然。
二、五大核心挑战(工程视角)
在设计这套系统时,我们遇到了五个真正让人头疼的问题。
2.1 状态爆炸与上下文依赖
多轮对话的每一轮都不是孤立的。第5轮的Agent决策,依赖于第1~4轮的全部历史——包括用户说了什么、Agent回复了什么、调用了哪些工具、工具返回了什么、Agent记住了什么。
这意味着测试用例必须携带完整的上下文,不能只存单轮输入输出。在存储层面,我们需要把整条轨迹作为一等公民来管理,且支持版本化、追溯和重放。
2.2 工具调用的非确定性
同一个意图,Agent可能在不同运行中选择不同的工具。比如“查天气”,这次调用了get_weather,下次可能调用了weather_api_query——两者都是“正确”的。
这要求我们的断言机制从“精确匹配”走向“语义等价”。我们不再简单断言“必须调用工具X”,而是断言“调用了能够完成目标Y的某个工具”或“工具调用的效果符合预期”。
2.3 环境隔离与可复现性
Agent重度依赖外部服务——搜索引擎、地图API、支付网关等。如果回归测试直接调用真实环境,那么:
- 外部数据变化会导致测试用例失效(比如“推荐餐厅”的推荐结果每天都在变);
- 网络波动、服务限流会导致假性失败;
- 有些操作(如真实下单)不能在生产环境执行。
因此必须引入隔离的测试环境,关键外部依赖用Mock或Sandbox替代,确保每次执行时的“世界状态”一致。但Mock过度又会失去真实性,这个平衡需要持续调整。
2.4 断言的主观性与模糊性
Agent的“正确”很多时候是主观的。例如:
- 推理路径不同但结果合理,算不算对?
- 回复风格变化但信息准确,算不算退化?
- 工具调用顺序与预期有差异,但最终效果一样,算不算错?
这要求我们建立多层级断言体系:
- 硬断言(工具是否调用、参数是否匹配、关键词是否存在)——自动且严格。
- 软断言(语义是否合理、逻辑是否自洽)——引入LLM-as-Judge进行辅助评估。
- 人工兜底——对不确定结果标记为“待复核”,交由人工判断。
2.5 批量执行的效率瓶颈
假设我们有800个场景,平均每个场景6轮,每轮2次LLM调用和3次工具调用——串行执行可能要几个小时甚至一天。我们需要:
- 并发调度:用异步+限流控制并发数,最大化吞吐量。
- 智能重试:对偶发失败(超时、网络抖动)自动重试,避免误报。
- 增量回归:只运行受本次变更影响的场景子集,而非全量重跑。
三、系统架构设计(核心模块)
整体架构分为四层:场景管理层、调度执行层、断言校验层、数据存储层。
3.1 场景定义:把“用户故事”变成“可执行代码”
每个测试场景本质上是一个多轮剧本,包含:
- 用户消息序列(每一轮说什么)
- 每轮对应的期望(工具调用、参数约束、回复检查、语义标准)
- 全局前置条件(模拟用户登录状态、预置记忆数据等)
- 标签和优先级(用于筛选和调度)
设计要点:
- 场景采用JSON/YAML格式存储,支持版本管理和可视化编辑。
- 期望支持“精确”和“宽松”两种模式,允许配置容差。
- 每个场景关联需求ID和缺陷ID,方便追溯。
精简代码示意(仅展示结构,不展开全部字段):
@dataclassclassTestScenario:id:strname:struser_messages:List[str]# 每轮用户输入turn_expectations:List[dict]# 每轮期望(工具、参数、回复等)setup:dict# 前置环境配置tags:List[str]# 用于批量筛选3.2 执行引擎:驱动Agent跑完完整轨迹
执行引擎的核心职责是:按顺序将用户消息喂给Agent,记录每一轮的完整输出,包括思考链、工具调用、工具结果、最终回复。
关键设计决策:
- Agent抽象层:屏蔽不同Agent实现(ReAct、Plan-and-Execute、多Agent协作)的差异,统一提供
chat(messages, tools)接口。 - 工具Mock机制:每个场景可独立配置Mock规则,不影响其他场景。
- 超时与重试:每轮单独设置超时(如30秒),超时后可配置重试。
- 轨迹全量记录:每轮保存请求、响应、工具调用栈、时间戳,用于后续审计和调试。
精简代码(只显示核心循环):
asyncdefexecute_scenario(scenario):history=[]results=[]foridx,msginenumerate(scenario.user_messages):# 构建上下文messages=history+[{"role":"user","content":msg}]# 调用Agent(带超时)response=awaitagent.chat(messages,tool_mocks=scenario.tool_mocks)# 记录本轮所有信息turn_record={"turn":idx,"user":msg,"response":response["content"],"tool_calls":response["tool_calls"],"duration":response["duration"]}results.append(turn_record)# 更新历史history.append({"role":"user","content":msg})history.append({"role":"assistant","content":response["content"]})returnresults3.3 断言引擎:多维度、可插拔的校验体系
断言引擎是回归测试的“裁判”。我们把它设计为可组合的断言管道,每个断言独立运行,互不干扰。
断言类型:
- 工具调用断言:是否调用了指定工具、是否禁止调用某工具、调用顺序是否正确。
- 参数匹配断言:支持精确匹配、正则匹配、范围匹配(如价格在300~500之间)。
- 回复内容断言:关键词包含/不包含、正则匹配。
- 语义断言(LLM-as-Judge):用强模型评估回复是否满足某个标准(如“是否清晰列出了所有预订信息”)。
- 自定义断言:允许用户编写Python函数进行任意检查(如校验JSON Schema、验证计算结果)。
设计原则:
- 每个断言独立失败,不影响其他断言执行,便于收集所有问题。
- 断言分级:Critical(阻断,场景即失败)、Warning(记录但不阻断)、Info(仅统计)。
- 语义断言默认使用双模型校验(如GPT-4和Claude同时评估,结果一致才通过),降低误判率。
精简代码示例(只展示接口):
classAssertionEngine:asyncdefassert_turn(self,turn_record,expectations)->List[Failure]:failures=[]# 检查工具调用failures+=self.check_tool_calls(turn_record,expectations)# 检查参数failures+=self.check_args(turn_record,expectations)# 检查回复failures+=self.check_response(turn_record,expectations)# 语义检查(调用LLM Judge)ifexpectations.semantic_criteria:failures+=awaitself.semantic_check(turn_record,expectations)returnfailures3.4 批量调度器:高效并发与资源控制
调度器负责管理数百个场景的并发执行,核心指标是吞吐量和稳定性。
实现策略:
- 使用异步任务池(如
asyncio.Semaphore)控制最大并发数(通常设为4~8,取决于LLM服务的限流)。 - 场景按优先级和预估执行时间排序,高优先级先跑。
- 支持标签过滤,例如只运行
smoke标签的快速场景,用于开发自测。 - 执行结果实时汇总,支持流式输出进度。
失败重试机制:
- 对超时、网络错误等非确定性失败,自动重试(最多2次)。
- 重试前重置Agent状态,避免历史污染。
- 重试仍失败的场景,记录详细堆栈,供人工排查。
精简代码(仅核心逻辑):
asyncdefrun_batch(scenarios,max_workers=4):sem=asyncio.Semaphore(max_workers)asyncdefrun_one(scenario):asyncwithsem:returnawaitexecute_and_assert(scenario)tasks=[run_one(s)forsinscenarios]results=awaitasyncio.gather(*tasks,return_exceptions=True)returnaggregate(results)3.5 LLM-as-Judge:让模型评价模型
语义断言是Assertion Engine中最复杂的一环。我们实现了一个独立的Judge服务,它接收待评估的回复和评判标准,输出通过/不通过及理由。
关键设计:
- 评判标准采用结构化提示词,包含示例和边界情况说明。
- 结果不仅包含
passed布尔值,还包含confidence(置信度)、reason(详细理由)、suggestions(改进建议)。 - 为降低偏差,我们采用多Judge投票(如GPT-4 + Claude + DeepSeek),多数表决。
- 缓存常见评判结果,减少重复调用开销。
四、落地实践中的“坑”与经验
我们在实际部署这套系统时,踩了不少坑,总结下来有几点值得分享。
4.1 Mock数据的“保鲜”问题
一开始我们为所有工具配置了静态Mock返回,结果两个月后,Agent的业务逻辑变了,Mock数据却还是老版本,导致很多场景莫名其妙地失败。
解法:将Mock数据与场景版本绑定,同时建立Mock数据版本管理机制。当底层数据模型变更时,自动触发相关场景的Mock数据迁移或标记为“需更新”。
4.2 语义断言的误判与校准
LLM-as-Judge刚开始经常误判——有时过于严格(把合理的换表述判为不通过),有时过于宽松(明显遗漏信息却判为通过)。
解法:
- 构造校准集:人工标注100个典型回复,定期用Judge模型评测,计算准确率和一致性,若低于阈值则调整提示词或切换Judge模型。
- 引入置信度阈值:只有置信度>0.8的判定才自动采纳,0.5~0.8之间标记为待人工复核。
4.3 并发执行时的资源竞争
多个场景并发时,共享的LLM服务、数据库连接、文件系统都可能成为瓶颈,甚至触发限流导致大面积超时。
解法:
- 使用令牌桶控制对LLM的请求速率。
- 每个Worker使用独立的数据库连接池。
- 对文件写入操作加锁,或改为异步队列写入。
4.4 增量回归——如何“只跑该跑的”
全量回归耗时太长,尤其当场景库增长到数千个时。我们需要精准识别本次变更影响的范围。
解法:建立依赖图谱——每个场景记录它用到了哪些工具、哪些Prompt模板、哪些模型版本。当某工具或模板变更时,自动计算受影响场景列表,只运行这些场景。其余场景沿用上次的测试结果,但标记为“未执行,沿用快照”。
五、数据治理与版本对比
回归测试产生的数据量巨大,但如果不加以治理,它们只是一堆日志。
我们建设了评测数据底层,涵盖:
- 用例库:场景的完整定义、版本历史、变更记录。
- 执行记录:每次运行的详细轨迹、断言结果、性能指标。
- 版本快照:每个模型版本或Agent版本对应的全量测试结果。
- 趋势分析:通过率、各类失败占比、平均耗时随版本变化的曲线。
实际应用:
- 版本发布前,自动生成质量对比报告,列出新版本相比上一版本的“新增失败”和“修复成功”。
- 当发现退化时,一键下钻到具体场景和轮次,快速定位根因。
- 支持A/B实验:同时运行两个Agent版本,输出对比数据辅助决策。
六、未来演进方向
这套系统目前已经跑通了核心流程,但我们认为还有几个方向值得深入:
1. 自适应测试选择:基于历史数据预测哪些场景最容易失败,优先执行它们,提高缺陷发现效率。
2. 自动修复断言:当断言因非本质变化(如回复措辞调整)失败时,系统可建议更新断言规则,减轻维护负担。
3. 与CI/CD深度融合:将回归测试作为GitHub Actions或GitLab CI的门禁任务,合并请求触发自动回归,结果评论到PR中。
4. 可视化诊断:提供交互式的轨迹浏览器,支持按时间线回放Agent的思考、调用和结果,让调试变得直观。
七、总结
Agent多轮交互批量回归测试,本质上是将复杂的用户交互流程资产化、自动化、可度量化的过程。它不仅是测试工具,更是质量体系的一部分。
- 从理念上,它要求我们转变测试思维:从“验证输出”到“验证过程”。
- 从架构上,它需要兼顾灵活性与性能,支持插件化的断言和可配置的执行环境。
- 从工程上,它需要解决状态管理、非确定性、环境隔离、效率优化等一系列实际问题。
我们已经用这套体系在内部支撑了多个Agent产品的迭代,每次发布前跑一遍回归,通过率的变化直接反映了版本质量。更重要的是,当测试失败时,我们能精确到“第3轮调用了错误的工具”,开发人员可以在几分钟内定位问题,而不是花半天复现。
Agent的复杂度只会越来越高,测试基建也必须同步演进。这套多轮回归测试框架,正是我们迈向可信任Agent的第一步。
