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):# 构建上下文messageshistory[{role:user,content:msg}]# 调用Agent带超时responseawaitagent.chat(messages,tool_mocksscenario.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[]# 检查工具调用failuresself.check_tool_calls(turn_record,expectations)# 检查参数failuresself.check_args(turn_record,expectations)# 检查回复failuresself.check_response(turn_record,expectations)# 语义检查调用LLM Judgeifexpectations.semantic_criteria:failuresawaitself.semantic_check(turn_record,expectations)returnfailures3.4 批量调度器高效并发与资源控制调度器负责管理数百个场景的并发执行核心指标是吞吐量和稳定性。实现策略使用异步任务池如asyncio.Semaphore控制最大并发数通常设为4~8取决于LLM服务的限流。场景按优先级和预估执行时间排序高优先级先跑。支持标签过滤例如只运行smoke标签的快速场景用于开发自测。执行结果实时汇总支持流式输出进度。失败重试机制对超时、网络错误等非确定性失败自动重试最多2次。重试前重置Agent状态避免历史污染。重试仍失败的场景记录详细堆栈供人工排查。精简代码仅核心逻辑asyncdefrun_batch(scenarios,max_workers4):semasyncio.Semaphore(max_workers)asyncdefrun_one(scenario):asyncwithsem:returnawaitexecute_and_assert(scenario)tasks[run_one(s)forsinscenarios]resultsawaitasyncio.gather(*tasks,return_exceptionsTrue)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的第一步。