多Agent协作测试:从单兵到编排
2026/7/30 18:43:37 网站建设 项目流程

之前在全链路拆解那篇文章里,把一套完整的AI自动化测试Agent从头到尾捋了一遍——执行端动手、决策层动脑、知识库查经验、记忆体攒经验,四个组件串成一个闭环。那套单Agent架构跑了挺久,大部分场景够用。但随着测试场景越来越复杂,一个Agent同时干导航、操作、校验三件事,上下文越来越长,推理质量反而开始往下掉。

这篇文章聊聊从单Agent到多Agent协作的演进。怎么把一个什么都干的Agent拆成导航、操作、校验三个专职Agent,再设计一个编排层把它们串起来。不是所有场景都值得这么拆——文章最后也会说哪些情况下单Agent反而更好。


单Agent遇到了什么问题

全链路那篇文章里写的架构,跑了大半年,大部分测试场景都能覆盖。但今年开始接了一批更复杂的回归测试任务——一个测试流程动辄二三十步,跨五六个页面,中间还有各种弹窗和异常流。跑着跑着就发现单Agent架构有三个问题越来越明显。

第一个是上下文膨胀。单Agent在每一步都要同时处理三件事:看截图识别当前在哪个页面(导航)、决定下一步点什么(操作)、判断这步操作有没有达到预期(校验)。这三件事的上下文全塞在一个Agent的messages里,步骤一多,上下文就跟着膨胀。之前那篇文章里写的三层记忆架构——滑动窗口、知识库归档、语义检索——确实能缓解token爆炸,但治标不治本。根本问题在于,一个Agent的注意力是有限的,上下文里塞了太多不同类型的信息,推理质量就会被稀释。差不多到第十五步之后,Agent开始出现"忘了自己在干嘛"的情况——明明在测购物车流程,突然跳回去点首页的搜索框。

第二个是角色混乱。全链路那套架构里,Agent的角色定义写的是"你是一个移动端自动化测试专家",但实际干活的时候,这个专家要同时当导航员、操作员和裁判。一个人干三个角色,Prompt就得写得很大很全,结果就是每个角色都做得不够专注。防止自作聪明那篇文章里提过这个问题——Agent有时候会自作主张,该点的时候不点,不该点的时候乱点。后来分析发现,很多时候是因为Prompt里同时有导航指令和校验指令,Agent在两种思维模式之间来回切换,容易产生矛盾的行为。比如导航逻辑说"应该点击购物车图标",但校验逻辑同时在说"先确认当前页面没有异常弹窗",两个指令打架,Agent就懵了。

第三个是稳定性衰减。两种路径那篇文章里讨论过用例驱动和自主执行两种模式,不管哪种模式,单Agent在简单场景下表现都还行。但场景复杂度上来之后,稳定性明显下降。跑十次同样的流程,可能七次通过三次失败,失败的原因每次还不一样——有时候是导航判断错了,有时候是操作坐标偏了,有时候是校验逻辑漏了。排查问题也很头疼,因为所有逻辑都在一个Agent里,你很难定位到底是哪个环节出了问题。

这三个问题不是孤立的,而是相互关联的。上下文膨胀导致推理质量下降,推理质量下降导致角色混乱加剧,角色混乱又导致稳定性进一步衰减。形成一个负向循环。


拆Agent的逻辑

想明白这三个问题之后,方向其实挺清楚的:把一个什么都干的Agent拆成几个专职的。

但拆几个?拆两个的话,导航和操作可以合在一起,校验单独出来——但导航和操作的思维方式差异挺大,一个是在做空间规划,一个是在做精确执行,合在一起还是会有角色混乱的问题。拆五个的话又太碎了,通信开销和编排复杂度会上来,得不偿失。

最后落地的方案是拆三个:导航Agent、操作Agent、校验Agent。每个Agent只干一件事,Prompt精简、上下文干净、角色清晰。三个Agent之上再加一个编排层,负责任务分发、状态管理和异常处理。

这个拆法的好处是每个Agent的Prompt可以写得非常聚焦。导航Agent的Prompt只管"看路"——分析截图、识别页面类型、规划路径。操作Agent的Prompt只管"动手"——根据导航信息生成具体操作指令。校验Agent的Prompt只管"裁判"——对比操作前后的截图,判断是否达到预期。三个Agent各干各的,互不干扰。

共享的基础设施——知识库和记忆体——还是放在外面,三个Agent都可以访问。但每个Agent只检索和自己职责相关的经验,不会把导航的经验塞给操作Agent。


三个Agent各干什么

导航Agent

导航Agent干的事情跟人测试时候"看一眼屏幕想想下一步往哪走"差不多。它接收当前截图和任务描述,输出的是页面分析结果和路径建议,不直接输出操作指令。

具体来说,导航Agent做三件事:识别当前页面是什么页面(首页?详情页?购物车?)、找出页面上有哪些可交互元素以及它们的大致位置、规划从当前页面到目标页面的路径。这些信息以结构化JSON输出,传给操作Agent。

导航Agent不用管操作坐标的精确度,也不用管操作结果对不对。它只管"看路",看完把信息递出去就行。这样它的上下文就很干净——只有截图、任务描述和历史路径,不需要塞操作记录和校验结果进来。

操作Agent

操作Agent拿导航Agent给的路径信息,生成具体的操作指令。它干的事情跟全链路那篇文章里写的执行端对接差不多——输出归一化坐标的action指令,比如do(action="Tap", element=[450, 280])

操作Agent的Prompt可以写得很简洁,因为它不需要理解整个测试流程,只需要根据当前页面信息和目标路径,决定下一步操作是什么。它的上下文里只有:当前截图、导航Agent的输出、上一步操作的结果。不掺杂导航分析和结果校验的信息。

校验Agent

校验Agent是最后出场的。操作Agent执行完操作之后,校验Agent拿到操作前后的截图对比,判断操作有没有达到预期效果。它的输出是结构化的校验结果——通过还是失败,失败原因是什么,建议怎么处理。

把校验单独拆出来,好处比想象中大。之前单Agent架构里,操作和校验是同一个人干,容易产生"自己干自己评"的问题——Agent执行了操作之后,倾向于认为自己的操作是对的,校验标准会不自觉地放宽。拆成独立Agent之后,校验Agent不知道操作Agent"想"干什么,它只看结果对不对,判断更客观。

代码层面,三个Agent的角色定义大概长这样:

# 导航Agent — 只管看路NAVIGATOR_PROMPT=""" 你是一个页面导航专家。分析当前屏幕截图, 识别页面类型和可交互元素,规划到目标页面的路径。 你不执行操作,只输出分析结果。 输出格式:{"page_type": "...", "elements": [...], "next_step": "..."} """# 操作Agent — 只管动手OPERATOR_PROMPT=""" 你是一个操作执行专家。根据导航信息生成操作指令。 使用归一化坐标(0-999),输出action指令。 输出格式:do(action="Tap", element=[450, 280]) """# 校验Agent — 只管裁判VALIDATOR_PROMPT=""" 你是一个结果校验专家。对比操作前后的截图, 判断操作是否达到预期。你不知道操作意图,只看结果。 输出格式:{"status": "pass/fail", "reason": "...", "suggestion": "..."} """

三个Prompt都不长,每个只关注自己的领域。跟之前单Agent那种又长又全的Prompt比,推理质量和稳定性都有提升。


编排层:让Agent们配合起来

三个Agent拆好了,但谁来指挥它们?谁先跑谁后跑?谁负责把上一个Agent的输出传给下一个?出了异常谁处理?这些事情就是编排层干的。

编排层不是Agent,它是一段确定性的调度逻辑。用确定性的代码来编排,而不是再搞一个"管理Agent"来调度——这点挺重要的。之前试过用一个Agent来调度其他Agent,结果调度Agent自己也会产生推理偏差,相当于多了一层不确定性。后来改成纯代码编排,确定性逻辑做调度,AI只做执行,这样整个系统的行为更可控。

编排层的职责可以归纳为四个:任务分发、状态管理、结果汇总、异常处理。

任务分发就是根据当前状态决定调用哪个Agent。流程是固定的:先调导航Agent分析页面,拿到结果后调操作Agent生成指令,执行完之后再调校验Agent验证结果。这个顺序不需要AI来决定,写死在代码里就行。

状态管理负责维护整个任务的执行上下文——当前执行到第几步、每步的导航结果和校验结果、累计重试次数。这些状态用Python字典管理就行,不需要搞复杂的状态机。

异常处理是编排层最花心思的部分。校验Agent返回fail之后怎么办?直接重试?换个策略?还是请求人工介入?这些判断逻辑是写在编排层里的,不是让Agent自己决定。

编排层的调度逻辑大概长这样:

classOrchestrator:def__init__(self):self.navigator=Agent(role=NAVIGATOR_PROMPT)self.operator=Agent(role=OPERATOR_PROMPT)self.validator=Agent(role=VALIDATOR_PROMPT)self.max_retry=3defrun_task(self,task_desc):steps=[]retry_count=0whilenotself._task_complete(task_desc,steps):# 导航Agent看路nav=self.navigator.run(screenshot=self._capture(),task=task_desc)# 操作Agent动手action=self.operator.run(nav_info=nav,screenshot=self._capture())before_shot=self._capture()self._execute_on_device(action)# 校验Agent裁判result=self.validator.run(before=before_shot,after=self._capture())ifresult["status"]=="fail":retry_count+=1ifretry_count>=self.max_retry:self._escalate(task_desc,result)break# 重试时让导航Agent重新分析,不沿用上次路径continuesteps.append({"nav":nav,"action":action,"result":result})retry_count=0returnself._build_report(steps)

有几个细节值得说一下。重试的时候,不让操作Agent沿用上次的导航信息,而是让导航Agent重新分析——因为操作失败可能就是因为导航判断错了,重新看一遍可能发现新的路径。另外,校验Agent的Prompt里特意写了"你不知道操作意图,只看结果"——这会让校验更严格,不会因为"操作Agent想点这个按钮"就放宽判断标准。


实际跑起来什么效果

拆成多Agent之后,跑了一段时间对比数据。同样的回归测试套件,大概四十多个用例,单Agent和多Agent各跑了一周。

通过率方面,单Agent那周大概在85%上下浮动,好的时候能到90%,差的时候掉到78%。多Agent那周稳定在92%左右,波动小了不少。提升主要来自复杂场景——那些超过十五步的用例,单Agent的通过率大概只有60%出头,多Agent拉到了80%以上。简单场景(五步以内的)两者差不多,多Agent甚至偶尔还不如单Agent,因为多了编排层的调度开销。

稳定性方面改善更明显。之前单Agent跑同一个用例十次,可能七次过三次挂,失败原因还各不相同。多Agent之后,十次里大概九次能过,偶尔失败的基本都是设备或网络问题,不是Agent推理出了偏差。

排查问题的效率也高了。之前单Agent出了问题,要扒一大坨上下文日志,在导航逻辑、操作逻辑、校验逻辑里来回找。现在每个Agent的输入输出都是独立的,导航出了问题看导航Agent的日志,操作出了问题看操作Agent的日志,定位快了很多。


前面说的都是好的方面,但也得说说不好的。

成本是第一个问题。三个Agent意味着三次大模型调用,而单Agent只需要一次。token消耗大概是单Agent的三倍。如果测试量大,这笔开销不小。简单场景用多Agent有点杀鸡用牛刀——五步以内的流程,单Agent完全够用,多Agent反而增加了调度开销。

调试复杂度也上来了。单Agent出问题,看一个日志就行。多Agent出问题,要看三个Agent的日志加编排层的调度日志,有时候还要对比它们之间的输入输出是否匹配。虽然定位问题更快了,但看日志的工作量反而更大了。

还有一个不太容易注意到的问题——Agent间的信息损耗。导航Agent看到的页面信息,传递给操作Agent的时候需要做一次序列化,从视觉信息变成文本描述。这个过程中有些细节会丢失,比如导航Agent注意到某个按钮颜色不对(可能是禁用状态),但如果这个信息没有被结构化输出捕捉到,操作Agent就不知道,可能还是会去点那个按钮。这种信息损耗在单Agent架构里不存在,因为同一个人看和做,不需要传递信息。

所以我的建议是:复杂场景(超过十步、跨多个页面、有异常流)用多Agent,简单场景继续用单Agent。不用一刀切,两种架构可以共存——编排层加个判断,根据任务复杂度决定走单Agent还是多Agent路径。


从单Agent到多Agent,说到底是分工的问题。一个人什么都干,效率不一定高;拆开各干各的,配合好了效率能提上来,但配合本身也有成本。这个度得根据自己的项目情况来把握。

之前写的全链路拆解是"一套Agent长什么样",这篇算是"Agent多了之后怎么管"。后续打算再聊聊多Agent场景下的知识库和记忆体怎么设计——三个Agent共享一个知识库还是各用各的,这个问题也挺有意思。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询