编码智能体写代码已经不算新鲜事,真正麻烦的是怎么确认它写出来的东西能可靠落地。单模型自测经常出现一种情况:生成者自己认为没问题,审查者也顺着同一套思路走,结果一眼就能看出的边界条件没处理。跨模型评审(cross-model peer review for coding agents)就是想解决这个盲区:让一个模型负责生成,再让另一个不同厂商、不同训练分布的模型来扮演审查者,像代码评审一样对生成结果挑问题。
这个思路和人类团队里的代码评审很像:写代码的人容易惯性思维,让另一个人来看,往往能发现漏掉的分支、错误假设和不合理的接口设计。放在 coding agents 里,难点不是“多调一个模型”,而是怎么设计审查协议、怎么让两个模型互相配合而不是互相吹捧、怎么判断评审结果是真的有效还是表面热闹。
我最近把这条链路在本地搭了一遍,从单条任务到批量任务都跑通了。这篇就按实际落地顺序拆开讲:先看流程设计,再看最小实现,然后聊参数和判断标准,最后给常见问题和排查顺序。如果你正准备用多个模型做代码生成质检,这篇文章可以帮你少走一些弯路。
1. 跨模型评审不是“多问一个模型”,而是给代码加一道独立质检
1.1 单模型生成代码的常见盲区
编码智能体在自动写代码时,最大的问题不是代码写不出来,而是写得“看起来对”。模型是根据概率生成下一个 token 的,它天然倾向于输出在语义上连贯、在格式上正常的代码。问题恰恰出在这里:一段代码语法正确、缩进规范、变量命名清晰,不一定代表逻辑正确。模型对自身输出的评估往往带有很强的偏见,让它自己检查自己,容易忽略那些超出当前上下文窗口的边界条件。
我遇到过一个很典型的例子:让一个智能体生成处理 CSV 上传的 Python 函数,它能把文件读取、数据清洗、异常捕获都写出来,但漏掉了文件为空的情况。让同一个模型重新检查一遍,它给出的结论是“函数逻辑完整”。这不是模型笨,而是生成和审查共用同一个偏好空间,模型会倾向于认可自己生成的模式。
另外还有一种更隐蔽的盲区。编码智能体在迭代修复时,经常出现“改一个 bug 引入另一个 bug”。原因是它修改逻辑时只会围绕当前报错点做局部修补,缺少一个从外部视角看完整系统的心态。如果审查者还是同一个模型,这个局部性会被放大。
跨模型评审的核心价值,就是切断这种“自我确认”链路。
1.2 为什么要跨模型,而不是同一个模型自查
不同模型的训练数据、对齐方式、代码风格偏好都有差异。让一个和生成者完全无关的模型来做审查,相当于给编码智能体配了一个独立的质检员。它不知道前面的推理链,没有“这段代码是我写出来的”这种隐含偏好,更容易从原始任务描述和代码文本本身出发找问题。
跨模型的另一个好处是错误分布不同。同一模型容易出现同源错误,比如对某种边缘情况有共同的忽视倾向。不同模型在同一任务上犯错的位置可能不一样,让模型 B 审查模型 A 的代码,正好能覆盖一部分模型 A 的盲区。
不过这里要泼一盆冷水:跨模型评审不是“两个模型差异越大越好”。如果审查模型的理解能力明显弱于生成模型,它给出的意见可能全是表面问题,甚至误报。真正有效的组合是:审查模型必须与生成模型能力相当或更强,并且审查协议要足够严格。也就是说,跨模型解决的是盲区问题,不解决能力问题。
这部分的要点是:不要把它理解成“再多问一个模型要个答案”,而是一套有角色分工、有评审标准的流程。下一步就是把这个流程具体化。
2. 先把评审流程设计清楚,再写代码
很多人一上来就写两个 API 调用,生成完直接扔给另一个模型说“你看看”,结果评审意见毫无进展。原因就是流程没有设计好。跨模型评审至少要区分清楚三个角色和一套闭环协议。
2.1 角色定义:生成者、审查者、仲裁者
我把一个完整的跨模型评审流程拆成三个角色:
- 生成者:通常是承担编码任务的主智能体。它接收任务描述、仓库上下文、相关文件内容,输出候选代码或补丁。
- 审查者:独立的评审模型。它只看任务描述和生成者输出的代码,不看生成者的推理过程,按既定维度检查代码。
- 仲裁者:当生成者和审查者意见不一致,或者多轮修改后仍然无法收敛时,需要一个最终决策者。仲裁者可以是人,也可以是第三个模型。多数实际场景里,我更倾向让人来做仲裁,尤其是在评审意见涉及业务语义时。
这三个角色不是固定的,同一个模型可以在不同批次里担任不同角色,但在同一条评审链路里,生成者和审查者必须严格区分。如果你用同一个模型、同一个提示词体系同时担任两个角色,跨模型的价值就损失了大半。
角色的输入输出也要明确。生成者输入是任务描述和上下文,输出是代码。审查者输入是任务描述、代码和评审标准,输出是结构化评审报告。仲裁者看到的是任务描述、二轮以上的评审意见和修改记录,输出是“采纳、驳回或继续修改”的决策。
2.2 评审协议:单轮、多轮还是带修改反馈
评审协议决定了信息怎么流动。可以根据任务风险程度选择不同协议。
单轮评审是最简单的形式:生成者输出代码,审查者输出评审报告。适合低风险、内部工具、一次性脚本这类场景。它的优点是快、成本低;缺点是发现问题后没有闭环,需要人工再去改。
多轮评审把闭环补上:生成者收到审查意见后修改代码,审查者再检查修改后的版本。这里的关键是修改意见要足够具体。如果审查者只说“这段代码不够健壮”,生成者不知道把它改成什么。如果审查者说“第 14 行读取文件时没有判断文件是否为空,建议在进入循环前增加空文件检查”,修改轮次才有意义。
带修改反馈的协议可以继续加一层约束:审查者在每次评审时,除了提问题,还要明确写出“这次的修改相比上一版是否变好”。这能帮助判断流程是否收敛。如果连续两轮意见都在换方向,说明任务描述本身有问题,或者生成模型的理解与审查模型严重不一致,这时候不要再继续加轮次,直接人工介入。
我在本地搭的链路里,默认采用“最多两轮修改”。第一轮如果审查意见是“通过”,就直接收结果。第一轮“不通过”且修改意见具体,生成者改一轮,审查者再审。第二轮仍然不通过,则把案例标记为待人工仲裁。这个默认值不是最优解,但适合大多数代码生成场景。
3. 最小落地实现:从调用接口到提示词模板
3.1 环境准备
我先说环境。跨模型评审不需要一个庞大的平台,最小配置是:两个不同厂商模型的 API 访问权限、一个能运行 Python 的本地环境、一个用于存放任务和结果的目录。
我建议把 API Key 放在环境变量或者本地配置文件中,不要写死在代码里。代码仓库一旦提交,密钥就容易泄露。目录结构可以这样设计:
review_pipeline/ tasks/ # 原始任务描述 generated/ # 生成者输出的代码 reviews/ # 审查者输出的评审报告 logs/ # 每轮请求日志依赖方面,如果你用的是各种模型厂商提供的官方 SDK,直接安装对应 SDK 就行。如果用的是统一的接口封装,需要确认它支持哪些模型、响应格式是否一致。这里不需要装重型框架,一个普通 Python 环境就够。
还有一点容易被忽略:不同模型 API 对上下文的计算方式不同,有的把输入输出都计入 token,有的还按特殊字符多算。正式跑之前,先用短文本把两个 API 各调通一次,确认返回结构。这一步能避免后续批量跑的时候因为响应解析不对而报错。
3.2 生成代码和发起评审的核心流程
最小流程就是这样:先调用生成模型,拿到代码;再把任务描述和代码拼成审查请求,调用审查模型,拿到结构化评审意见。下面是一个通用示例,只展示流程框架:
def call_llm(system_prompt, user_message, model, temperature=0.2): # 这里替换成实际模型 SDK 调用 return client.chat_create( model=model, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_message} ], temperature=temperature ) def generate_code(task_desc, model): system = "你是一个编码智能体。请根据任务描述输出完整可运行的代码。" response = call_llm(system, task_desc, model, temperature=0.4) return response def review_code(task_desc, code, reviewer_model): system = "你是一个严格的独立代码审查者。请按评审模板输出 JSON。" user_message = f"任务描述:\n{task_desc}\n\n待审查代码:\n{code}" response = call_llm(system, user_message, reviewer_model, temperature=0.0) return response生成阶段我一般把温度设在 0.3 到 0.6 之间,让代码有一点多样性,又不会太散。审查阶段温度设为 0 或接近 0,保证评审输出稳定。如果你的代码任务对稳定性要求很高,生成阶段也可以把温度调低,代价是可能每次都生成同一段有问题的代码。
3.3 审查者的提示词模板示例
审查者的提示词是这条链路里最重要的部分。不要只告诉它“你是一个代码评审专家”,要告诉它从哪些维度看、输出什么格式、如何判断严重程度。
我常用这样一个 system prompt 模板:
你是一个独立代码审查者。以下是编码智能体根据任务描述生成的代码。 请从以下维度检查: 1. 正确性:是否存在空值、越界、死循环、并发问题、异常未捕获; 2. 边界:输入为空、超大、特殊字符、重复提交等情况是否处理; 3. 可读性:命名、注释、函数拆分是否清晰; 4. 安全性:是否存在明显的注入、越权、敏感信息硬编码等问题; 5. 可维护性:是否引入不必要的复杂度。 输出要求: 只输出 JSON,不要输出解释。 格式: { "问题列表": [{"位置": "行号或函数名", "问题描述": "...", "严重程度": "high|medium|low"}], "修改建议": "给编码智能体的具体修改意见", "结论": "通过" 或 "不通过" }为什么要求 JSON 输出?因为评审报告要作为下一轮修改的输入,也要用于统计、聚合和人工复盘。如果用自然语言输出,解析成本高,而且模型每轮说法都不一样,不好比较。JSON 虽然不能保证百分百合法,但配合错误重试和校验,比纯文本可靠很多。
第一次跑这种方式时,不必把审查维度写得太满。先保留正确性、边界、可读性三项,跑几条任务看看效果,再逐步加“安全性”“可维护性”。维度越多,模型越容易把精力分散,导致每个维度都审得很浅。
3.4 拿到评审结果后如何回传修改
如果评审结论是“不通过”,要把评审报告拼回生成模型的下一轮请求。这里有个常见错误:只把“修改建议”传给生成者,把原始任务描述丢了。生成者一旦只看到修改建议,很容易在修改过程中偏离原需求。我在实际跑的时候,第二轮的 user message 会这样拼:
原始任务描述: {task_desc} 你上一轮生成的代码: {previous_code} 审查者的评审报告: {review_report} 请根据评审报告修改代码。保留符合原始任务描述的部分,只修改被指出的问题。不要新增与任务无关的功能。加最后一句话很关键。编码智能体在拿到“修改建议”后,有时会顺手加一些额外功能,导致代码量膨胀,可读性下降。给它一个明确边界,能减少这类情况。
4. 核心参数怎么调,评审结果怎么看
4.1 评审轮数不是越多越好
先看一个很多人容易踩的坑:评审轮数越多,结果不一定越好。第一轮评审往往能发现最明显的逻辑问题,第二轮修改后评审,能处理掉改进型问题。到了第三轮,修改收益通常会明显下降,而每一轮都会消耗输入和输出 token,还会引入新问题。
我建议的默认策略是:第一轮如果审查者给出“通过”,直接收尾;如果“不通过”,最多让生成者改两轮。第三轮仍然“不通过”,就交给人工仲裁。判断是否继续加轮次,不是看“这轮意见是否为空”,而是看“前后两轮意见是否收敛”。如果第二轮和第一轮的修改方向相同,说明生成者在逐步接近;如果方向完全变了,就说明流程已经发散,继续跑下去只会浪费成本。
4.2 温度、采样参数和上下文长度
生成者和审查者的采样参数要分开设置。生成者可以使用相对高一点的温度,保留一定多样性;审查者使用低温度,追求稳定。如果你用 top_p,建议先维持 0.9 或 1.0,等整体链路跑通后再微调。这里不要一上来就调很多参数,参数之间会互相影响,很难定位问题。
上下文长度也要盯住。任务描述、代码、历史评审报告、修改记录都会占用上下文窗口。当代码很长时,审查模型可能会忽略中间部分,只评审开头和结尾。我的做法是在送入审查者之前,先按函数或文件切片;如果文件太大,就分多次评审,而不是一次性塞进去。
4.3 判断评审有效的三个信号
评审是不是有效,不能只看“有没有输出问题列表”。我一般看三个信号。
第一,问题是否具体可复现。有效意见会指出具体位置或函数,比如“list_dir 方法在路径不存在时会抛异常”。空洞意见只会说“建议增强健壮性”。
第二,修改建议是否保留了原有功能。审查者如果建议把整个结构重构,虽然表面很专业,但风险很高。有效建议应该是在原有逻辑上做局部修正。
第三,严重程度标注是否和人类判断一致。如果审查者把无关紧要的命名问题标成 high,却对明显的空指针不报,说明评审维度或上下文出了问题,需要调整提示词。
4.4 成本视角:一次评审到底消耗多少 token
跨模型评审不是免费的。每轮评审的 token 消耗大概是:任务描述 + 生成代码 + 评审输出 + 后续修改请求的完整历史。批量任务里这个成本会快速累积。
我建议在日志里记录每次调用的 token 使用量和耗时,跑完一个批次后可以算一个平均值。如果你的业务对成本敏感,可以考虑让能力较强的模型做生成、能力稍弱的模型做评审,或者反过来。但要注意,评审模型的判断力不能太弱,否则省下的 token 成本可能变成人工返工成本。
5. 把单条任务扩展成批量审核管线
5.1 批量任务前先跑一条完整链路
批量任务和单条任务完全是两个难度。单条跑通了,不代表批量能稳定运行。我一般会先用三到五条任务把整条链路完整跑一遍,确认每个环节的数据格式都是对的,然后再扩大到几十条甚至几百条。
批量任务里最容易出问题的不是模型能力,而是数据流。任务描述读取失败、代码文件编码不对、评审报告 JSON 解析失败、输出目录不存在,这些看似小的问题在批量场景里会被放大。所以第一步必须是先跑通链路,再看速度。
5.2 队列、日志、输出目录
搭批量管线时,我建议做一个简单的任务队列。每个任务有一个唯一 ID,任务状态分为 pending、running、reviewing、passed、failed、manual。队列可以是用 Python 写的简单循环,也可以接 Redis 或数据库,关键是要能记录状态和重试。
日志同样重要。每次调用都要记录:任务 ID、调用模型、输入摘要、输出摘要、token 用量、耗时、状态码。没有日志,批量跑完后如果发现某些任务失败,根本不知道是在哪一步失败的。
输出目录要按任务 ID 组织,不要把所有文件扔在一个目录里。比如:
outputs/ task_001/ generated.py review_round_1.json review_round_2.json final.py这样每个任务的历史都完整保留,方便复盘。
5.3 失败重试和结果聚合
调用模型 API 时,网络超时、限流、返回格式异常都可能会发生。重试策略要事先定好:哪些错误可以重试,哪些错误不能重试。网络超时和限流可以重试,通常等待一段时间后再试;模型输出 JSON 不合法也可以重试,重写 prompt 或调整温度;但输入数据格式错误不能盲目重试,要先修数据。
重试次数建议控制在 2 到 3 次。超过次数就把任务标记为 manual,留给人来检查。批量跑完后,把所有任务的评审结论按任务 ID 聚合成一张汇总表,包含是否通过、严重问题数量、修改轮数、token 消耗,这样一眼就能看出这批代码的质量分布。
5.4 接口化部署的注意点
如果要把这条评审链路做成一个内部服务,我建议直接用异步任务接口,而不是同步接口。代码生成和评审通常需要几十秒甚至几分钟,同步接口会让调用方一直等待,还容易因为超时导致重复提交。异步接口配合任务状态查询会更稳。
并发也要控制。不要一上来就并行几十个请求。先用低并发测一下不同 API 的限流边界,再逐步上调。接口服务还需要做鉴权和限流,避免公司内部共享服务被某个任务打满。日志里记得脱敏,不要让代码内容或业务敏感信息直接暴露给不需要看到的人。
6. 常见失败现象和排查顺序
6.1 现象一:审查者没有发现问题,代码却有明显 Bug
这种情况很常见。先不要急着换更强的评审模型,而要看审查者拿到的输入是什么。如果任务描述和代码之间被截断,或者上下文被大量无关内容占满,它很可能没看到真正出问题的部分。先检查提示词里代码是否完整,再检查上下文长度。
另一个常见原因是审查标准太宽。比如只让它“检查代码是否正确”,它就会倾向于说“没问题”。把审查维度明确写出来,限定“必须列出空值、越界、异常未捕获等问题”,情况会好很多。
6.2 现象二:评审意见空洞,像在重复生成者的想法
空洞评审的典型表现是“建议增加异常处理”“建议提升代码可读性”这类正确的废话。问题通常在于审查者没有被要求输出具体位置和修改建议。解决办法是强制结构化输出,要求它给出“位置:行号或函数名”“问题描述:具体场景”“修改建议:可以直接执行的改动”。
还有一个更隐蔽的原因:审查者可能能力不够,或者风格偏向“鼓励型”。这时候换一个风格更严格的模型,或者在同模型上增加“不要客气,只指出必须修改的地方”的语气,通常能改善。
6.3 现象三:多次修改后代码变得更差
如果第一轮评审意见已经具体,但生成者改了两轮之后反而更差,先看是不是上下文里塞了太多历史评审报告。生成模型面对大量互相冲突的历史消息时,会迷失在信息里。我一般只传“上一版代码”和“最近一轮评审报告”,不把全部历史都塞进去。
如果这样还变差,说明这个生成模型对修改指令的理解能力不够强。换一个更擅长指令遵循的模型,并把修改要求拆成更小的步骤,比如一次只修三个问题。
6.4 排查顺序:从输入到参数再到版本
我遇到问题时一般按这个顺序排查:
- 复现:用同一条任务和同一个参数再跑一次,看问题是稳定的还是随机的。
- 看输入:任务描述是否完整、代码是否被截断、文件编码是否正常。
- 看参数:温度是否过高、上下文窗口是否太小、超时时间是否不够。
- 看提示词:审查者是否被“这是优秀代码”这样的话术影响。
- 看环境:依赖版本、API 返回格式、网络稳定性。
- 最后才考虑换模型或改流程。
这个顺序的核心逻辑是:先用最便宜的方式排除最简单的原因,再往模型能力上靠。大部分问题其实出在输入和参数上,而不是模型不行。
7. 什么场景适合跨模型评审,什么场景不建议
7.1 适合:库代码生成、测试用例生成、算法题、配置脚本
跨模型评审最适用的场景是那些“正确性可以客观判断”的代码生成任务。比如生成一个纯函数、写一个数据处理脚本、生成一批单元测试用例。这些任务有明确的输入输出关系,审查模型能够根据任务描述独立判断代码是否符合要求。
测试用例生成是特别合适的场景。审查者可以观察测试用例是否覆盖了边界条件、是否真的会命中目标分支,而不是只看代码是否漂亮。算法题也适合,因为评判标准相对集中。如果你的代码生成任务里带有清晰的接口定义和验收条件,跨模型评审能发挥比较大的作用。
7.2 不适合:需要强领域知识、代码量大、实时交互
如果任务需要很强的领域知识,比如某个内部系统的业务规则、某个特殊框架的私有 API,审查模型可能不了解这些知识,给出的意见会偏向泛泛而谈。这时候可以把领域知识写进系统提示词,或者先做一次知识补充检索再评审。
代码量非常大的场景也不适合直接全量评审。把大仓库整体丢给模型,上下文装不下,模型会忽略大量细节。更适合的做法是先提取改动文件、函数差异,再做小范围评审。实时交互场景同样不建议用完整跨模型评审,因为延迟比较高,更适合用一次快速自查。
7.3 更稳妥的实践建议
如果你刚开始尝试跨模型评审,我的建议是先把它当作文档化质检工具,而不是自动合并代码的开关。让系统生成评审报告,由人做最终判断。跑一段时间,积累足够的通过率、失败率和误报率数据后,再利用这些数据决定要不要让部分低风险任务走自动合并。
我建议每批任务跑完后都做一次复盘,看有多少任务是审查者发现了问题但生成者没改对,有多少任务是审查者误报。这两个比例能帮你判断系统瓶颈是出在评审侧还是生成侧。
最终你会发现,真正决定跨模型评审价值的不是模型数量,而是评审协议、输入质量和结果判断标准。模型只是执行者,流程才是核心。