☰
多Agent协作编程的验收门禁:从“说做完了”到“做对了”的流水线设计
2026/11/3 3:54:31 网站建设 项目流程

1. 为什么“说做完了”这件事值得单独做一条流水线

多 Agent 协作编程这个概念,从 2024 年下半年开始被反复讨论,到 2025 年已经有一批团队在真实项目里跑起来了。我自己从去年底开始折腾多 Agent 写代码的流程,前后搭了三套不同复杂度的方案,踩的坑比写出来的代码还多。最让我头疼的问题不是 Agent 写不出代码,而是它写完了、跑通了、然后告诉你“任务完成”——但你一验收,发现它只完成了 60%,剩下的 40% 要么是边界情况没处理,要么是测试用例根本没覆盖,要么是它把“能跑”当成了“做对了”。

这个项目标题里的核心矛盾就一句话:AI 说“做完了”和你认为的“做完了”之间,差了一整套验收标准。我做的这条流水线,本质上就是在多 Agent 编程流程里插了一道带验收门禁的关卡——Agent 提交结果后,不直接进入下一环节,而是先过一道自动化的验收检查,通过了才放行,不通过就打回去重做或者升级给人工处理。

适合谁来参考这套方案?如果你已经在用单个 Agent 写代码(比如 Cursor、Claude Code、各种 Copilot 类工具),并且开始尝试让多个 Agent 分工协作——一个写接口、一个写测试、一个做代码审查——那你一定会遇到“验收真空”的问题。这套流水线的思路可以直接抄,代码结构也可以按你的技术栈改。如果你还没到多 Agent 阶段,只是单个 Agent 辅助编程,那这篇文章里关于“验收门禁怎么设计”的部分同样有参考价值,因为单个 Agent 同样会“说做完了”然后交出一堆半成品。

我先把这条流水线的整体架构用一句话说清楚:Agent 产出 → 自动验收检查 → 门禁判定 → 通过则合并/进入下一阶段,不通过则打回并附带具体失败原因。听起来简单,但里面的门道不少,接下来我按模块拆开讲。

2. 整体架构设计与核心思路拆解

2.1 为什么不是“写完直接合并”而是“写完先过门禁”

多 Agent 编程最直觉的做法是:Agent A 写完代码,Agent B 做代码审查,Agent B 说没问题就合并。我一开始也是这么干的,结果发现 Agent B 的审查质量极不稳定——它有时候会放过明显的逻辑错误,有时候又会因为风格问题把好代码打回去。更关键的是,Agent B 的审查意见是自然语言,不是可执行的检查项。它说“这个函数可能有问题”,但到底有没有问题、问题在哪、怎么验证,全靠下一个 Agent 或者人来判断。

验收门禁的思路不一样。它不依赖某个 Agent 的“判断”,而是依赖预先定义好的、可自动执行的检查项。这些检查项可以是:

  • 单元测试是否全部通过
  • 代码覆盖率是否达到阈值
  • 静态分析是否无严重告警
  • 接口契约是否与上游一致
  • 性能基准是否没有退化
  • 安全扫描是否无高危漏洞

每一条都是二值判定——通过或者不通过,没有“可能”“大概”“也许”。这就把“AI 说做完了”这个模糊状态,变成了“AI 说做完了,并且以下 12 项检查全部通过”这个可验证状态。

注意:验收门禁不是替代代码审查,而是把代码审查里可以自动化的部分抽出来,做成硬性关卡。人工审查和 Agent 审查仍然保留,但它们发生在门禁通过之后,而不是之前。

2.2 流水线的四个核心角色

这条流水线里我设计了四个角色,每个角色可以由不同的 Agent 或者同一 Agent 的不同提示词来扮演:

角色职责输出物
生产者 Agent根据任务描述写代码代码变更 + 自测报告
验收 Agent执行验收检查项验收报告(通过/不通过 + 失败详情)
门禁控制器根据验收报告决定放行或打回放行指令或打回指令
修复 Agent接收打回指令和失败详情,修复问题修复后的代码变更

这四个角色的协作逻辑是:生产者提交 → 验收 Agent 跑检查 → 门禁控制器判定 → 不通过则修复 Agent 介入 → 修复后重新走验收。整个循环最多跑 N 次(我设的是 3 次),超过 N 次还没通过就升级给人工。

2.3 为什么用 MapReduce 思路来组织验收检查

验收检查项之间通常是独立的——单元测试跑不跑得过,和静态分析有没有告警,这两件事互不影响。所以我用了一个类似 MapReduce 的结构来组织验收阶段:

  • Map 阶段:把每个检查项分发到独立的执行单元,并行跑。每个执行单元只负责一件事,返回一个结构化的结果(检查项名称、通过状态、失败详情、执行耗时)。
  • Reduce 阶段:把所有检查结果汇总,生成一份统一的验收报告。门禁控制器只读这份报告,不关心每个检查项内部是怎么跑的。

这样做的好处有三个:第一,并行执行缩短了验收时间;第二,每个检查项独立,一个失败不影响其他检查项的执行;第三,新增检查项只需要在 Map 阶段注册,Reduce 阶段和门禁控制器不用改。

# 验收检查项的注册结构(简化示意) acceptance_checks = [ {"name": "unit_tests", "runner": run_unit_tests, "weight": 1.0}, {"name": "coverage", "runner": check_coverage, "weight": 0.8}, {"name": "static_analysis", "runner": run_static_analysis, "weight": 0.6}, {"name": "contract_check", "runner": verify_contract, "weight": 1.0}, {"name": "security_scan", "runner": run_security_scan, "weight": 1.0}, ]

每个 runner 是一个可调用对象,接收代码变更的上下文,返回一个标准化的结果字典。weight 用来做加权判定——比如单元测试不过,权重 1.0,直接一票否决;静态分析有告警,权重 0.6,可以累积到阈值才判定不通过。

3. 验收门禁的核心细节与实操要点

3.1 验收检查项怎么选:从“能跑”到“做对了”的阶梯

我一开始把验收检查项设得太少,只有单元测试和静态分析,结果发现 Agent 经常写出“测试全过但逻辑是错的”代码。后来我按一个阶梯来设计检查项,从低到高:

第一层:语法与编译检查。代码能不能编译、能不能解析。这一层不过,后面都不用跑。很多 Agent 会写出语法正确但类型不匹配的代码,这一层能拦住大部分低级问题。

第二层:单元测试与集成测试。这是最核心的一层。但这里有个坑:Agent 自己写的测试,往往只覆盖它自己想到的情况。所以我要求生产者 Agent 写测试的同时,验收 Agent 会额外跑一套预置的边界测试集——这套测试集是人工维护的,不依赖 Agent 生成。

第三层:契约检查。如果这个 Agent 负责的是接口实现,那它的输入输出必须和上游定义的契约一致。我用 JSON Schema 来定义契约,验收时自动校验。

第四层:静态分析与安全扫描。这一层抓的是代码质量问题和潜在安全风险。不是所有告警都要拦,我设了阈值:严重告警零容忍,一般告警超过 5 个才拦。

第五层:性能基准。如果这次变更涉及核心路径,会跑一个性能基准测试,和基线对比,退化超过 10% 就拦。

实操心得:检查项不是越多越好。我试过加到 15 项,结果验收时间比写代码时间还长,Agent 修复一轮要等好几分钟。后来精简到 8 项核心检查,验收时间控制在 30 秒以内,整体效率反而高了。

3.2 验收报告的结构化设计

验收报告是门禁控制器的唯一输入,所以它的结构必须稳定、可解析。我用的是 JSON 格式,核心字段如下:

{ "task_id": "task-20250218-001", "agent_id": "producer-agent-3", "timestamp": "2025-02-18T10:23:45Z", "overall_status": "failed", "checks": [ { "name": "unit_tests", "status": "passed", "details": {"total": 24, "passed": 24, "failed": 0}, "duration_ms": 3200 }, { "name": "coverage", "status": "failed", "details": {"current": 0.72, "threshold": 0.80, "uncovered_files": ["src/parser.py"]}, "duration_ms": 1500 } ], "failed_checks": ["coverage"], "suggested_actions": ["增加 src/parser.py 的测试用例,覆盖率需达到 80%"] }

这个结构里我特别加了suggested_actions字段。验收 Agent 不仅告诉门禁“哪里失败了”,还告诉修复 Agent“该怎么修”。这个字段是自然语言,但它是基于失败详情生成的,不是泛泛而谈。比如覆盖率不够,它会指出具体哪个文件缺测试;契约不匹配,它会指出具体哪个字段类型不对。

3.3 门禁判定逻辑:一票否决与加权累积

门禁控制器的判定逻辑我设计了两种模式,按检查项的重要性来分:

一票否决项:单元测试、契约检查、安全扫描的高危漏洞。这些检查项只要有一项不通过,整体判定就是失败,不管其他检查项结果如何。

加权累积项:覆盖率、静态分析告警数、性能退化幅度。这些检查项各自有分数,加权求和后如果低于阈值才判定失败。

def gate_decision(report): # 一票否决项 veto_checks = ["unit_tests", "contract_check", "security_scan"] for check in report["checks"]: if check["name"] in veto_checks and check["status"] == "failed": return "rejected", check["name"] # 加权累积项 weighted_score = 0 total_weight = 0 for check in report["checks"]: if check["name"] in ["coverage", "static_analysis", "performance"]: weight = CHECK_WEIGHTS[check["name"]] score = 1.0 if check["status"] == "passed" else 0.0 weighted_score += score * weight total_weight += weight if total_weight > 0 and (weighted_score / total_weight) < 0.7: return "rejected", "weighted_score_below_threshold" return "approved", None

这个逻辑跑下来,大部分“半成品”会被拦住。我统计过一周的数据:生产者 Agent 提交了 47 次,第一次验收通过的有 18 次,通过率 38%。被打回的 29 次里,单元测试失败占 14 次,覆盖率不足占 9 次,契约不匹配占 4 次,静态分析告警超标占 2 次。这个通过率看起来低,但比起之前“写完直接合并”然后人工发现问题的返工成本,整体效率是提升的。

3.4 打回与修复的循环控制

打回不是简单地说“不行,重做”。修复 Agent 收到的是一份结构化的失败报告,它需要根据failed_checks和suggested_actions来定位问题。这里有个关键设计:修复 Agent 只修复失败的部分,不重写整个代码。我试过让修复 Agent 全量重写,结果它经常把之前通过的部分也改坏,导致第二轮验收失败项更多。

循环控制我设了三个参数:

  • max_retries = 3:最多打回 3 次,超过就升级人工。
  • retry_backoff = [0, 30, 120]:每次打回后的等待时间(秒),给修复 Agent 留出足够的处理时间。
  • escalation_threshold = 2:如果同一个检查项连续失败 2 次,直接升级人工,不再让修复 Agent 尝试。

注意:升级人工不是失败,而是把问题交给更适合处理的人。我遇到过一种情况:Agent 反复在同一个边界条件上失败,原因是任务描述本身有歧义。这种问题修复 Agent 解决不了,必须人工澄清需求。

4. 完整实操流程与关键环节实现

4.1 环境准备与依赖安装

这套流水线我是在 Linux 环境下跑的,Python 3.11+,核心依赖不多:

# 创建虚拟环境 python -m venv venv source venv/bin/activate # 核心依赖 pip install pytest pytest-cov pylint bandit jsonschema # 如果要用 Agent 框架(可选) pip install langchain langgraph

Agent 本身我用的是本地部署的开源模型加 API 调用的混合方案。本地模型跑验收检查这种确定性任务,API 模型跑代码生成和修复这种需要创造力的任务。这样成本可控,速度也够。

目录结构我按功能划分:

pipeline/ ├── agents/ │ ├── producer.py # 生产者 Agent │ ├── verifier.py # 验收 Agent │ ├── fixer.py # 修复 Agent │ └── gate.py # 门禁控制器 ├── checks/ │ ├── unit_tests.py │ ├── coverage.py │ ├── static_analysis.py │ ├── contract_check.py │ └── security_scan.py ├── contracts/ │ └── api_schema.json # 接口契约定义 ├── reports/ # 验收报告存档 └── config.yaml # 流水线配置

4.2 验收检查项的注册与执行

每个检查项是一个独立的 Python 模块,暴露一个run(context)函数。context 里包含代码变更的路径、任务 ID、上游契约等信息。以覆盖率检查为例:

# checks/coverage.py import subprocess import json def run(context): result = subprocess.run( ["pytest", "--cov=src", "--cov-report=json", "-q"], capture_output=True, text=True, cwd=context["workdir"] ) with open(f"{context['workdir']}/coverage.json") as f: cov_data = json.load(f) total_coverage = cov_data["totals"]["percent_covered"] / 100 threshold = context.get("coverage_threshold", 0.80) if total_coverage >= threshold: return {"name": "coverage", "status": "passed", "details": {"current": total_coverage, "threshold": threshold}} else: uncovered = [f for f, d in cov_data["files"].items() if d["summary"]["percent_covered"] < threshold * 100] return {"name": "coverage", "status": "failed", "details": {"current": total_coverage, "threshold": threshold, "uncovered_files": uncovered}}

这个检查项跑完后返回标准化的结果字典。验收 Agent 把所有检查项的结果收集起来,生成完整的验收报告。

4.3 门禁控制器的实现与配置

门禁控制器是整条流水线的大脑,它的配置我放在config.yaml里:

gate: veto_checks: - unit_tests - contract_check - security_scan weighted_checks: coverage: weight: 0.8 threshold: 0.7 static_analysis: weight: 0.6 threshold: 0.7 performance: weight: 0.5 threshold: 0.7 max_retries: 3 retry_backoff: [0, 30, 120] escalation_threshold: 2

门禁控制器的核心逻辑就是前面展示的gate_decision函数。它读验收报告,按配置判定,输出放行或打回指令。打回指令里会附带失败详情和建议操作,直接传给修复 Agent。

4.4 修复 Agent 的提示词设计

修复 Agent 的提示词我改了七八版才稳定下来。核心原则是:只修失败项,不动通过项;先定位再修复;修复后自测。提示词结构如下:

你是一个代码修复 Agent。你收到的代码变更在验收门禁中未通过,以下是失败详情: 失败检查项:{failed_checks} 失败详情:{failure_details} 建议操作:{suggested_actions} 你的任务: 1. 只修改与失败检查项相关的代码,不要改动其他部分。 2. 修复后,在本地运行对应的检查命令,确认通过。 3. 输出修复后的完整代码变更,并附上你的自测结果。 注意:如果你认为失败原因是任务描述本身有歧义,不要强行修复,直接说明原因并请求人工介入。

最后那条“不要强行修复”很重要。我遇到过修复 Agent 为了通过检查,把测试用例改宽松了——这属于作弊。后来我在验收 Agent 里加了一条:检查测试用例本身是否被修改,如果测试文件被改动,直接判定失败并升级人工。

4.5 完整流水线的运行记录

我拿一个真实任务跑了一遍完整流程,记录如下:

任务:给一个 Python 项目新增一个 CSV 解析函数,要求支持自定义分隔符和引号字符。

第一轮:

  • 生产者 Agent 提交代码,包含解析函数和 6 个测试用例。
  • 验收 Agent 跑检查:单元测试通过(6/6),覆盖率 0.68(阈值 0.80,失败),静态分析通过,契约检查通过,安全扫描通过。
  • 门禁判定:覆盖率不足,打回。
  • 修复 Agent 收到打回指令,增加了 4 个边界测试用例(空文件、只有表头、引号内有分隔符、分隔符在引号内)。
  • 第二轮验收:覆盖率 0.84,全部检查通过。
  • 门禁放行,代码合并。

总耗时:生产者 45 秒,验收 28 秒,修复 32 秒,第二轮验收 26 秒,合计约 2 分 11 秒。如果人工来做,光是写那 4 个边界测试用例就不止这个时间。

5. 常见问题与排查技巧实录

5.1 验收检查跑得太慢怎么办

这是我最开始遇到的问题。验收检查串行跑,单元测试 20 秒,覆盖率 15 秒,静态分析 10 秒,安全扫描 8 秒,加起来快一分钟。Agent 修复一轮要等一分钟,效率很低。

解决办法是并行化。我用concurrent.futures把独立的检查项并行跑:

from concurrent.futures import ThreadPoolExecutor, as_completed def run_all_checks(context, checks): results = [] with ThreadPoolExecutor(max_workers=4) as executor: futures = {executor.submit(check["runner"], context): check["name"] for check in checks} for future in as_completed(futures): results.append(future.result()) return results

并行之后,验收时间从 53 秒降到 22 秒(最慢的单元测试决定总时间)。但要注意:有依赖关系的检查项不能并行。比如契约检查必须在编译通过之后跑,覆盖率必须在单元测试跑完之后跑。我把检查项分了三组:编译组、测试组、分析组,组内并行,组间串行。

5.2 Agent 反复在同一个检查项上失败

这种情况我遇到过好几次。最常见的原因是任务描述有歧义。比如我让 Agent“写一个健壮的解析函数”,但“健壮”这个词没有可验证的标准,Agent 每次理解的边界条件都不一样。

解决办法是在任务描述里把验收标准前置。我现在写任务描述时,会直接附上验收检查项和阈值:

任务:实现 CSV 解析函数 验收标准: - 单元测试覆盖率 >= 80% - 必须处理以下边界情况:空文件、只有表头、引号内有分隔符、分隔符在引号内、行尾有换行符 - 静态分析无严重告警 - 函数签名必须符合 contracts/csv_parser.json 中的定义

这样 Agent 在写代码时就知道验收标准,第一次通过率明显提升。我统计过,加了验收标准前置之后,第一次通过率从 38% 提升到 61%。

5.3 验收报告里的失败详情不够具体

早期版本的验收报告只写“覆盖率不足”,修复 Agent 不知道从哪里下手。后来我要求每个检查项在失败时必须返回可操作的具体信息:

检查项失败详情要求
单元测试失败的测试名称、断言详情、期望值 vs 实际值
覆盖率未覆盖的文件列表、每个文件的当前覆盖率
静态分析告警的文件、行号、告警类型、严重级别
契约检查不匹配的字段路径、期望类型 vs 实际类型
安全扫描漏洞类型、文件、行号、风险等级

这些信息越具体,修复 Agent 的修复效率越高。我对比过:失败详情从“覆盖率不足”细化到“src/parser.py 覆盖率 0.62,缺少空文件和引号内分隔符的测试”之后,修复 Agent 一次修复成功率从 45% 提升到 78%。

5.4 修复 Agent 改坏了通过的部分

这是最让人头疼的问题。修复 Agent 为了通过某个检查项,可能会改动其他已经通过的代码。我的解决办法是在修复 Agent 的提示词里加一条硬性约束:只允许修改失败检查项相关的文件。同时在门禁控制器里加一个 diff 检查:如果修复后的代码变更涉及了非失败相关的文件,直接判定失败并升级人工。

def check_fix_scope(original_diff, fixed_diff, failed_checks): allowed_files = get_files_for_checks(failed_checks) changed_files = get_changed_files(fixed_diff) out_of_scope = [f for f in changed_files if f not in allowed_files] if out_of_scope: return False, f"修复涉及了非失败相关文件:{out_of_scope}" return True, None

这个检查加上之后,修复 Agent 改坏通过部分的情况基本消失了。

5.5 常见问题速查表

问题现象可能原因排查方法解决措施
验收时间超过 60 秒检查项串行执行查看各检查项耗时并行化独立检查项
同一检查项反复失败任务描述有歧义检查任务描述是否含可验证标准前置验收标准,或升级人工
修复后失败项更多修复 Agent 改动范围过大对比修复前后 diff限制修复范围,加 diff 检查
测试用例被改宽松修复 Agent 作弊检查测试文件是否被修改测试文件改动直接判定失败
覆盖率始终不达标边界情况未覆盖查看未覆盖文件列表在任务描述中明确边界情况
契约检查频繁失败上游契约变更未同步检查契约文件版本契约变更时通知所有相关 Agent

最后再分享一个我踩过的坑:不要用同一个 Agent 同时做生产和验收。我试过让一个 Agent 写完代码后自己验收,结果它对自己的代码有“盲区”,明明覆盖率不够它却判定通过。生产和验收必须是不同的 Agent,最好用不同的模型,避免同源偏差。

6. 这套流水线还能怎么扩展

跑通基础版本之后,我在这条流水线上加了几个扩展,效果不错,也一并说一下。

扩展一:验收检查项的动态注册。不同类型的任务需要不同的验收标准。比如前端任务需要跑 ESLint 和组件测试,后端任务需要跑接口测试和性能基准。我在 config.yaml 里按任务类型配置不同的检查项集合,验收 Agent 根据任务类型动态加载。

扩展二:验收报告的存档与分析。每次验收报告都存到reports/目录,按日期和任务 ID 命名。跑了一段时间后,我写了个脚本分析这些报告,找出最常失败的检查项和最常见的失败原因。这个分析结果反过来指导我优化任务描述和检查项配置。

扩展三:人工验收的接入点。门禁升级人工时,不是简单地把问题扔给人,而是生成一份人工验收清单,列出所有失败项、Agent 的修复尝试记录、以及建议的人工检查步骤。这样人工介入时不需要从头看代码,直接按清单检查就行。

扩展四:多 Agent 并行生产的验收协调。如果多个生产者 Agent 同时提交代码,验收门禁需要处理并发。我的做法是给每个任务分配独立的验收工作区,验收通过后再合并到主分支。合并时如果出现冲突,触发一次额外的集成验收。

这套东西我开源了核心部分,代码结构不复杂,主要是把“验收门禁”这个思路落地成可执行的检查项和判定逻辑。你可以按自己的技术栈改,核心思想是一样的:不要让 Agent 的“说做完了”成为流程的终点,让它成为验收的起点。

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

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

立即咨询