ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

多Agent协作让AI代码审查召回率翻倍的实践

多Agent协作让AI代码审查召回率翻倍的实践

前阵子我拿一个历史遗留的支付网关项目做了个实验:11.6万行Java代码,让Claude以单Agent方式去查Bug,用尽各种提示词优化,单次最高只找到27个问题。后来我换了个思路,让Claude"自己组队"——架构审查Agent、并发检查Agent、边界条件Agent、资源管理Agent各查各的,最后由一个裁决Agent汇总合并,结果一口气找出66个问题,召回率直接翻倍还多。

这个数字之差不是模型变聪明了,而是Agent协作机制带来的质变。这篇文章我就把整个实验的来龙去脉、设计思路、提示词模板、上下文管理技巧和踩过的坑完整拆开,给做AI工程化或者想在团队里落地AI代码审查的朋友一个可以照抄的参考。

1. 为什么单Agent查Bug总是"差一口气"

1.1 不是模型不够强,是"上下文嘴太小"

先算一笔账。11.6万行代码,按平均每行20个token粗估,全量就是230万token以上。市面上任何商用大模型的上下文窗口都装不下这个量级,所以单Agent方案实际上必然要分片。

分片之后问题就来了:一个Bug往往不是单文件的问题,而是跨文件、跨模块的调用关系导致的。比如一个支付金额校验的缺陷,可能在订单服务里写了错误的数值比较,在支付服务里又没有二次校验,在数据库层还有精度丢失问题。单Agent每次只能看到其中一个文件,它的判断依据天然是残缺的,等于让一个人戴着望远镜找针,看到哪里都像,又哪里都拿不准。

我试过各种缓解手段:把相关文件拼进同一窗口、先用RAG检索再喂给模型、让模型自己决定要看哪些文件。结果都不理想。主要原因在于,模型一旦看不到完整调用链,就倾向于靠"猜测"补全逻辑,猜对了皆大欢喜,猜错了它就报一个幻觉Bug出来,你还得花时间验证真伪。

1.2 注意力漂移:查着查着就"走神"了

单Agent查Bug另一个隐蔽问题是注意力漂移。模型处理超长上下文时,注意力分布是不均匀的,原则上开头和结尾的内容权重更高,中段内容容易被"忽略"。这就导致一个典型现象:同一个Agent在同一个项目上,如果我把代码顺序打乱,它报出的Bug集合差异非常大,有的问题在这个顺序下能发现,换个顺序就漏了。

更麻烦的是任务一致性难以维持。让一个Agent连续审查十几个文件,查完第5个文件之后,它可能已经忘了自己是在"找Bug",开始无意识地复述代码逻辑、解释代码意图,甚至自己开始写改进建议。我对比过输出内容,早期文件的审查结果里还有具体的行号和缺陷描述,到后期文件就变成泛泛而谈的"建议优化"了,根本没有定位到具体问题。

1.3 单一角色必然存在思维盲区

代码审查本来就不是单一维度的事,它至少包含这些视角:

审查维度关注点典型缺陷
业务逻辑条件判断、状态流转、异常分支金额校验缺失、状态机死循环
并发安全锁、原子性、线程安全重复提交、死锁、数据竞争
边界条件null、空集合、极端值NPE、越界、除零
资源管理连接、文件句柄、内存连接泄漏、未关闭流
安全合规注入、越权、敏感数据SQL注入、权限绕过

单Agent在给定一个宽泛的"找Bug"任务时,默认会偏向业务逻辑类问题,因为它最容易从代码表面看出来。并发问题和资源泄漏问题需要结合执行时序和运行场景去推导,模型在单一窗口下很难形成这种全局判断。我观察到单Agent报出的27个问题里,业务逻辑类占了将近七成,并发和资源类加起来不到五个,这明显是角色单一导致的盲区。

2. 多Agent组队查Bug的整体设计:让模型学会"开评审会"

2.1 角色分工:按缺陷类型分配岗位

意识到单Agent的天花板之后,我参考了真实团队做代码评审的方式:不是一个人从头看到尾,而是不同背景的人各看各的角度,前端同事关注交互边界,后端同事关注接口一致性,DBA关注SQL效率。于是我把这套逻辑搬到了Agent设计上。

实验里我设置了五个角色,每个角色只负责一个维度。架构审查Agent负责跨模块调用链的正确性,业务逻辑Agent负责状态机和业务规则,并发检查Agent专门看多线程场景,资源管理Agent盯连接和内存,安全审查Agent查注入和越权。为了让每个Agent的注意力足够聚焦,我会在提示词里明确写清楚"你只需要关注某某类问题,其他问题不要报告",后面会说具体模板。

角色数量不是越多越好。我试过从2个加到5个,召回率确实在提升,但到6个以上就开始出现大量重复报告和幻觉问题。因为角色之间关注维度会有交叉,比如安全审查和边界条件审查在"非法输入校验"上高度重叠,这个分寸需要自己拿捏。

2.2 协作流程:分头扫描、两两交叉、统一裁决

多Agent不是简单地把任务丢给多个Agent各跑一遍,它需要一套明确的流程。我用的流程分三个阶段。

第一个阶段是分头扫描。每个Agent拿到同一份代码库的索引和划分好的代码切片,独立执行审查任务,产出结构化的问题列表。这个阶段刻意不让Agent之间互相通信,避免早期结论相互干扰。

第二个阶段是交叉复核。把第一阶段每个Agent的问题列表,随机分配给其他Agent进行复核。复核Agent要判断:这个问题是否真实存在、严重程度如何、有没有更精确的复现路径。这一步能过滤掉相当一部分幻觉Bug,因为不同角色从不同视角看同一段代码,很容易发现"这个所谓Bug其实是设计如此"的误报。

第三个阶段是统一裁决。由一个裁决Agent接收所有复核通过的问题,去除重复项,按模块和严重度排序,产出一份最终报告。裁决Agent的提示词里有一条很关键:要求它把每个问题映射到具体文件、函数和行号,并且标注置信度。这为后续人工验证节省了大量时间。

2.3 为什么"组队"比"单Agent多跑几遍"更有效

有人肯定会问:单Agent多跑几遍,把每次的结果合并起来,不也能提高召回率吗?我最初也是这么想的,但实测下来效果很有限。原因在于:同一个模型在相同提示词下,即使随机性带来一些差异,它的思维模式依然是同质的。你可以把单Agent多跑几遍理解为同一个人反复用同一种思路看同一份代码,发现的问题类型基本还是那一类。

多Agent的价值在于把不同的"思维框架"固化到了提示词里,而且每个Agent的任务约束让它的注意力始终钉在特定维度上。并发检查Agent不会去关心业务逻辑是否合理,它就只盯锁、原子性和共享变量,反而更容易发现那些藏在执行时序里的问题。认知多样性才是召回率提升的根本原因,这一点在实验数据里体现得非常明显。

3. 核心实现:提示词、上下文与任务切分

3.1 一套可以直接抄的角色提示词

提示词是这次实验里最核心的部分。我的经验是:角色提示词不能只写一句"你是并发审查专家",那不够,必须把审查范围、输出格式、禁止事项全部写明。下面是我用的并发检查Agent提示词模板,其他角色照葫芦画瓢就行。

你是一名资深的Java并发编程审查专家。你的任务是对给定的代码片段进行并发安全审查。 只关注以下问题: 1. 共享变量的可见性和原子性 2. 锁的粒度和死锁风险 3. 线程池使用是否正确 4. 集合类的线程安全问题 5. 竞态条件和重复提交问题 不要报告其他类型的缺陷,例如业务逻辑错误或性能优化建议。 输出格式为JSON数组,每个元素包含:filePath(文件路径)、lineNumber(行号)、severity(high/medium/low)、problemType(问题类型)、description(缺陷描述)、suggestion(修复建议)。 注意:只报告你有把握的真实问题,不确定的请标记confidence为low。

这段提示词里最关键的是最后两句。一句是把输出格式钉死为JSON数组,这样后续流水线可以直接解析;另一句是"只报告有把握的真实问题并标记置信度",这能显著压制幻觉Bug。我做过对比,加了这句之后幻觉问题数量大概下降了40%。

3.2 上下文管理的三条铁律

多Agent方案比单Agent更吃上下文管理,因为每个Agent都需要尽可能多的相关信息,但窗口又是有限的。我总结出三条铁律,供大家参考。

铁律一:不同角色给不同的代码切片。并发检查Agent需要看的是多线程相关的类、共享状态、锁使用,而业务逻辑Agent需要看的是订单状态机、支付回调这些核心流程。如果统一给所有Agent一份相同的代码切片,等于浪费了角色的差异性。我在实验里会给每个角色单独生成一份代码清单,这份清单可以借助简单的静态分析工具生成,也可以直接让调度Agent根据项目结构自行决定。

铁律二:用"代码地图"代替原始全量代码。什么叫代码地图?就是给Agent一份结构化的项目索引,包含包结构、类名、关键方法签名、类之间的调用关系,而不是把11.6万行源码一股脑丢给它。代码地图消耗的token远小于原始代码,但它给Agent提供了"导航能力",使它能精准定位到需要深入看源码的文件,再针对性地拉取。这个策略让我的上下文使用量减少了约60%,同时召回率没有明显下降。

铁律三:输出结构化结果,禁止自由发挥。自由文本的问题在于:Agent会写很多分析过程,真正的缺陷反而被淹没在一大段流畅的叙述里。强制JSON输出后,每个Agent的产出就是一行行明确的问题记录,后端的去重和汇总流程处理起来非常方便。我见过很多团队做Agent应用时不约束输出格式,结果下游流程被迫用正则去抓关键信息,效率和准确率都很差。

3.3 任务切分的两种粒度对比

给Agent分配任务时有两种切分粒度。按模块切分是纵向的,比如把支付网关拆成订单模块、支付模块、对账模块、通知模块,每个Agent负责一个模块的所有方面。按缺陷类型切分是横向的,每个Agent只看自己负责的缺陷类型穿透所有模块。

纵向切分的优点是局部上下文完整,Agent容易理解一个模块的内部逻辑,适合模块内部的状态管理类Bug。横向切分的优点是能发现跨模块的共性问题,比如"所有数据库操作都没有关闭连接"这种规律性缺陷,纵向切分下每个Agent只会看到一个模块不具备全局视野。

我的实践是推荐混合切分:先按模块纵向切分,让架构审查Agent、业务逻辑Agent各管几个模块;再按类型横向切分,让并发检查Agent、资源管理Agent穿透所有模块。这样既保住了局部上下文,又覆盖了全局规律性问题。混合切分在实验里的召回率比单一切分方式高出大约20%。

4. 实测效果:66个Bug到底从哪来

4.1 数字背后的含义:召回率从27到66

我把单Agent方案最好的一次结果(27个Bug)和多Agent方案的结果(66个Bug)做了对比,按问题类型拆分之后差距非常直观。

缺陷类型单Agent最佳多Agent协作提升倍数
业务逻辑13171.3倍
并发安全2115.5倍
边界条件4123倍
资源管理3155倍
安全合规263倍
其他351.7倍

多出的39个Bug,大头在并发安全和资源管理这两类,这正是单Agent最容易忽视的领域。原因也好理解:这两类缺陷没法从代码表面直接看出来,需要沿着调用链去模拟运行场景,单Agent在上下文不足的情况下根本没有余力做这种推演。而多Agent里的并发检查Agent和资源管理Agent可以反复咀嚼同一段代码,专门从运行时序的角度去"找茬",效果自然不一样。

4.2 成本与耗时:多Agent值得吗

提到多Agent方案,很多人第一反应是"贵"。这次实验的成本数据可以说明一些问题。单Agent方案因为要反复尝试才能覆盖更多维度,我跑了六轮,总计消耗约80万token。多Agent方案因为每个Agent只聚焦单一维度,输出更精简,五个角色加一个裁决Agent总计消耗约120万token,算下来是单Agent方案的1.5倍左右。

从召回率看,多Agent方案找到了66个问题,是单Agent最好一轮的2.4倍。这意味着换算成"找到单个Bug的平均成本",多Agent方案的实际成本反而更低。更关键的是,代码审查里漏掉一个生产环境的Bug,修复成本可能是审查成本的几十倍,从这个角度看多Agent方案的经济账非常划算。

当然这不是说单Agent方案一无是处。如果项目只有几千行代码,单Agent完全够用;如果只是临时想快速看一遍代码质量,单Agent也可以。但只要是中大型项目、历史遗留代码、或者你希望"一次就把问题找全",多Agent方案的优势是不可替代的。

4.3 适合与不适合的场景

多Agent查Bug不是万能的,我实验下来总结出几个适用边界。

最适合的场景是历史遗留系统。这类代码通常没有完善的测试用例,也没有清晰的架构文档,单靠人肉审查效率极低。多Agent能快速铺开所有模块,而且因为它不依赖文档,完全靠代码本身找问题,反而能把人容易忽略的隐藏问题翻出来。

其次是多人交接项目。新接手的开发者对代码不熟,不知道该重点关注哪些模块。多Agent一次扫描能产出一份完整的问题地图,帮助新人快速建立对项目健康状况的整体认知。

不太适合的场景也很明确:几百行的小脚本、一次性代码、或者对时延要求极高的实时交互场景。小脚本直接扔给单Agent或者干脆人肉看就行,组队反而大材小用;时延敏感的线上场景,多Agent一轮跑下来分钟级起,根本等不起。

5. 复现这套方案:从零搭一个多Agent查Bug流水线

5.1 环境准备

复现这套方案不需要重型的框架,我用的就是一个Python脚本加模型API直接编排。建议环境:Python 3.10以上、模型API访问权限、一份待审查的代码库(建议先拿一个3万行以上的中大型项目试手,小项目看不出差距)。

如果你完全没有基础,建议先别上框架,用最简单的requests循环调API把流程跑通,再考虑引入现成的Agent编排框架。很多团队一上来就上重框架,结果被抽象概念绕晕了,连最基本的角色提示词都没调好。

5.2 最小可跑的编排脚本

下面这个脚本是我实验后精简的最小版本,只保留了核心逻辑:初始化角色列表、调用模型API、收集结果、裁决合并。你可以直接替换API接口适配自己的模型供应商。

import json import openai # 初始化客户端,按需替换 client = openai.OpenAI( api_key="your-api-key", base_url="your-base-url" ) def run_agent(system_prompt, code_snippet): """调用模型API,返回JSON格式的Bug列表""" response = client.chat.completions.create( model="claude-3.5-sonnet", messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": f"请审查以下代码:\n{code_snippet}"} ], temperature=0.2, response_format={"type": "json_object"} ) content = response.choices[0].message.content return json.loads(content) roles = [ { "name": "架构审查Agent", "prompt": "你是一名架构审查专家,关注跨模块调用链的正确性..." }, { "name": "并发检查Agent", "prompt": "你是一名Java并发审查专家,只关注线程安全..." }, { "name": "资源管理Agent", "prompt": "你是一名资源管理审查专家,只关注连接泄漏、未关闭流..." } ] def chunk_code(code_files, max_tokens=3000): """按token上限切分代码,返回切片列表""" chunks = [] current = [] current_tokens = 0 for file_path, content in code_files.items(): tokens = len(content.split()) if current_tokens + tokens > max_tokens: chunks.append("\n\n".join(current)) current = [] current_tokens = 0 current.append(f"### {file_path}\n{content}") current_tokens += tokens if current: chunks.append("\n\n".join(current)) return chunks def run_pipeline(code_files): """执行完整的多Agent审查流水线""" chunks = chunk_code(code_files) all_issues = [] for chunk in chunks: for role in roles: result = run_agent(role["prompt"], chunk) all_issues.extend(result.get("issues", [])) # 裁决Agent去重合并 judge_prompt = "你是最终裁决者,请合并以下问题列表,去重并输出最终报告..." final_result = run_agent(judge_prompt, json.dumps(all_issues)) return final_result if __name__ == "__main__": code_files = load_project("path/to/project") report = run_pipeline(code_files) print(json.dumps(report, indent=2, ensure_ascii=False))

这个脚本里有两个容易被忽略但很关键的细节。一个是chunk_code函数里的max_tokens参数,我把它设为3000,这个值不能太高也不能太低。太高了Agent的注意力会分散,太低了代码上下文不完整没法判断调用关系。另一个是temperature=0.2,做审查任务温度一定要低,温度高了模型会放飞自我,产生大量幻觉Bug,这点在调参部分还会细说。

5.3 参数调优记录

我在实验里记录了几组关键参数的影响,这里分享出来给大家参考。

角色数量:从2个加到5个,召回率提升明显;从5个加到6个以上,召回率不再增长,重复和幻觉问题开始变多。最优角色数大概是4到6个,取决于代码库规模。

温度:0.2到0.3是审查任务的甜区。0.5以上幻觉Bug数量飙升,我见过一次温度调到0.8,一个Agent报出来的问题里有一半是它想象出来的,验证成本极高。0.1以下输出太保守,很多真实的低风险问题会被它忽略。

切片大小:3000到5000token的切片效果最好。少于1000token,Agent看不到完整逻辑,总在猜测;超过8000token,注意力漂移现象明显加重。如果你的代码干行很长,建议先用AST或其他工具抽取关键信息,再做切片。

上下文相关文件的拼装:我让Agent在审查时对每个文件都输出一个"相关文件列表",这些列表汇总起来后,下一轮审查时把高频相关文件预拼入切片。这个策略有点类似RAG的优化思路,实测能有效改善跨文件Bug的漏报率。

6. 常见问题与避坑实录

6.1 问题速查表

实验过程中遇到不少问题,这里整理成速查表,方便大家直接对照排查。

问题现象可能原因排查思路与解决
多个Agent报同一个Bug角色维度和视角有重叠在提示词里明确每个角色的独占维度,或靠裁决Agent去重
幻觉Bug太多温度过高、提示词没有约束置信度温度降到0.2以下,提示词强调"只报告有把握的问题"
Agent开始输出分析文字而不是问题列表没有强制输出格式使用response_format强制JSON输出,解析失败直接重试
跨文件Bug漏报切片把相关文件拆散了让Agent返回"相关文件列表",下轮预拼相关文件
审查结果前后不一致上下文长度超过注意力舒适区缩小切片尺寸,或先给Agent项目索引让其自主选文件
裁决Agent失真输入问题列表太长,汇总时丢失细节让裁决Agent按模块分批处理,再汇总最终报告
成本增长失控角色多、切片多、调用次数呈指数增长限制每个角色只审查指定模块清单,控制切片数量

6.2 "别忘了给Agent一个验证环节"

这是我在过程中最有价值的发现之一。最初版本的多Agent流程是"分头审查-汇总报告"两步走,结果汇总出来的66个问题里有将近三分之一是误报。比如有个Agent报"用户登录时密码没有加密传输",实际代码里是在前端用HTTPS做了加密,后端压根不需要处理,这就是典型的基于局部代码的错误判断。

加了交叉复核环节之后,误报率大幅下降。具体做法是让两个不同的角色互相复核对方产出的问题列表。并发检查Agent的问题让资源管理Agent去复核,业务逻辑Agent的问题让安全审查Agent去复核。因为两个Agent的思维框架完全不同,一个误报很难同时骗过两套逻辑。实测下来,经过交叉复核保留下来的问题,人工验证通过率从约70%提升到接近90%。

6.3 几个实测心得

最后分享几个比较零碎但很实用的心得。第一个是给Agent一个"预热"机会。在正式审查之前,先让每个Agent读一遍代码目录结构,输出一份它理解的模块清单,再让它去审查代码。这个小步骤能显著降低Agent"迷路"的概率,相当于先让它认路再出发。我对比过,加了预热步骤之后,输出的问题里"找不到对应代码位置"的无效项明显减少。

第二个心得是警惕"看起来完美的报告"。多Agent方案产出的报告往往结构整齐、描述专业,很容易让人产生信任感。但模型终究是在做概率预测,哪怕经过交叉复核,仍然存在误报和漏报。我的建议是:重要问题一定要人工复核,至少看一眼出问题的那段代码再决定是否改造。AI审查的定位是"帮你把需要看的地方圈出来",而不是替代你所有判断。

第三个心得是和持续集成结合。我现在把多Agent查Bug流水线接到了代码合并前的检查环节,每次提交代码后自动跑一遍,产出的问题列表直接同步到缺陷跟踪系统。这套流程运行了一段时间之后,团队里很多人已经从"抗拒AI审查"变成了"主动等审查结果",因为很多低级错误在提交之前就被拦下来了,省去了来回沟通的成本。这个例子大概能说明,多Agent协作的价值不仅体现在一次性扫描里,日常开发流程里同样能发挥稳定作用。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进