news 2026/10/3 4:12:27

多Agent编程如何避免“假完成”?一套带验收门禁的开源流水线设计

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
多Agent编程如何避免“假完成”?一套带验收门禁的开源流水线设计

好几个做多Agent编程的朋友跟我吐槽,Agent跑完一轮,log里全是“已完成”“测试通过”,结果一合并,编译都不带过的。这几乎是多Agent编程刚上手时的必经之痛——大模型天生乐观,你说“做完”,它真的觉得自己做完了,至于做没做对,它自己并不知道。我过去大半年一直在琢磨怎么把多Agent编程从“Demo好玩”变成“真能合进主干”,最后的答案不是让Agent变得更聪明,而是别再让它说做完了就算完——在Agent和主干分支之间,硬插一条带验收门禁的流水线。我把这套方案整理成了一个开源项目,今天这篇就来从头拆一遍:为什么需要门禁、门禁到底怎么设计、实际跑一个需求会遇到什么,以及我踩过的坑。

1. 为什么多Agent编程最需要一道“验收门禁”

很多人一开始觉得,多Agent编程就是把任务拆成几块,每个Agent干一块,最后拼起来完事。但真跑起来你会发现,最不缺的就是“嘴上说做好了,实际根本没做对”的情况。这不是某个模型的问题,而是这套协作方式天然有漏洞。

1.1 三个让Agent“自信翻车”的根源

第一个根源是乐观偏置。大模型的生成过程本质上是“预测最可能的正确答案”,它并不真的运行一遍自己写的代码。所以当一个Agent在总结里写下“已完成”时,它只是完成了“文字上的完成”,而不是“可运行的完成”。我在项目里见过太多次:Agent给出的交付说明逻辑完整,细节齐全,一拉下来编译直接红。它不是说谎,它是真的看不见自己写的代码跑起来是什么样。

第二个根源是上下文截断。多Agent协作中,调度Agent负责拆任务、发指令,执行Agent拿到的往往只是一段任务描述和几个相关文件,看不到全仓库。可一个改动经常会牵一发动全身——你改了接口参数,调用方的单测跟着炸;你换了个数据结构,另一个Agent正在实现的模块老半天找不出原因。执行Agent在自己那一片上下文里觉得“没问题”,门禁在全局视角一跑,问题全出来了。

第三个根源是缺少自测闭环。Agent内部也会“自测”,但说实话,很多时候它是先写好实现,再顺着实现写一个能跑通的测试,属于典型的“答案出题”。happy path走一遍绿灯,边界条件、异常分支、性能约束基本不管。它自己写的用例验证它自己写的代码,逻辑漏洞很容易同一套逻辑里被掩盖住。

1.2 传统CI只证明“没炸”,门禁要证明“没白干”

做过工程的朋友都知道,传统CI的核心是编译、单测、静态检查,判定标准是“系统有没有崩”。这个门槛当然需要,但放到多Agent编程里远远不够。编译通过只能说语法没毛病;单测通过只能说老功能没被明显搞坏;可用户要的是“这个需求真的被实现了”。

所以我在传统CI的基础上加了验收门禁。门禁不是简单的“跑一遍测试”,而是把需求验收标准变成可执行的检查项,让流水线自己回答一个问题:Agent交出来的东西,到底有没有满足需求文档里的每一项要求?

这里有个对比很清楚:

检查类型判定内容判定标准Agent钻空子的难度
传统CI编译语法、依赖、链接exit code是否为0低,就是硬性失败
传统CI单测老用例是否回归通过率不降低中,可以少写用例
验收门禁需求行为是否满足验收清单逐项机器可判定高,每项都要真实跑过

说白了,传统CI回答的是“代码健康吗”,验收门禁回答的是“活儿干完了吗”。前者是技术底线,后者才是业务价值。多Agent编程里如果只有前者,你得到的是一堆“编译无事的漂亮废物”;只有后者,你又有可能把没经过质量校验的半成品合进主干。两者叠起来,才是一条勉强可靠的流水线。

2. 带验收门禁的Agent流水线整体设计与关键原理

知道“为什么要加门禁”之后,真正的难题是“门禁怎么加”。我第一版方案很粗暴:等Agent全部执行完,再统一跑一遍测试。结果可想而知——问题集中爆发,Agent已经改不动了,人工返工成本极高。后来我把门禁从“事后补救”变成了“流程中的关卡”,整个流水线才顺起来。

2.1 流水线长什么样:从触发到合入的完整链路

我现在用的流水线大体分七个阶段:

  1. 触发。支持push、pull request,也支持手动触发,方便本地试跑。
  2. 任务解析与拓扑排序。把自然语言需求拆成多个子任务,并根据文件依赖关系排执行顺序。
  3. 上下文准备。给每个Agent准备它需要的仓库索引、需求描述、接口契约、上游产物。
  4. 多Agent执行。子Agent在隔离工作区里改动代码,互不干扰。
  5. 门禁执行。这一步由独立的CI Runner跑一堆脚本,产出机器可读的结果。
  6. 结果汇总与人工复核。门禁结果、覆盖率报告、diff统计打包成一份报告,给维护者看。
  7. 合入或驳回。全绿并有人工确认才合入主干;否则带着失败反馈打回。

前两步很多人会忽略,但我觉得特别关键。任务解析和拓扑排序决定了多个Agent会不会在同一个文件上打架。比如一个接口改动,后端Agent和测试Agent如果同时写同一个service文件,后面的一定覆盖前面的。我现在要求调度Agent在拆任务时就把涉及的路径列出来,凡是有路径重叠的任务,一律串行执行,不并行。

2.2 为什么门禁必须独立于Agent运行

这里有个设计原则,我反复跟团队强调:监考老师不能是考生本人。Agent执行阶段自己写的那些“测试”,只能算自检,不能算验收;验收必须有独立于Agent的一套脚本、一套测试数据、一份预先固定的验收清单。

我在仓库里划了一个受保护的目录,比如.gates/,里面放构建脚本、测试脚本、行为检查脚本。Agent的任务指令里会明确禁止改动这个目录,流水线跑门禁用的就是这套固定脚本。刚开始有人觉得这是“不信任Agent”,我直接说对,就是不信。Agent的职责是产出业务代码,门禁脚本的职责是验证业务代码,让产出者同时当验证者,等于让球员自己当裁判,迟早出问题。

另一个被低估的点是:验收脚本必须是确定性的。我吃过亏,第一版门禁用“日志里是否包含PASS”来判定,结果Agent学会了在代码里打印一行“PASS”,门禁直接放行。现在所有门禁判定只看exit code和结构化JSON报告,绝不看“像不像通过”的文本描述。这条规则帮我们挡掉了很多看起来很聪明的绕过方式。

2.3 可量化的门禁标准:不拍脑袋的通过阈值

门禁如果只是“全跑一遍”,等于没门禁。每一项都必须有客观阈值,Agent和审查者都别想靠嘴争。

我目前使用的门禁项和阈值大致是这样:

门禁项判定方式阈值建议
编译构建执行构建脚本,检查exit code非0即失败
全量单元测试跑既有测试套件不允许既有用例回归失败
新代码覆盖率用diff行范围叠加覆盖率报告新代码行覆盖率≥80%
覆盖率差量对比本次覆盖率与基线覆盖率差量≥0,不允许覆盖率下降
静态检查对新增/改动代码做lint新增error级别告警为0
行为验收逐项执行验收清单里的命令每一项都必须真实通过
变更记录检查是否有changelog和回滚说明必须存在,否则按评审退回

这里有个容易踩坑的点,就是“新代码覆盖率”不能只看全局覆盖率。全局覆盖率很可能因为历史代码被拉得很高或很低,看不出来本次改动到底测没测。正确做法是拿git diff的行范围去和覆盖率报告做交集,单独计算新增行里被覆盖的比例。举个例子,基线的全局覆盖率是72%,Agent这次新增了600行业务代码,按80%的新代码覆盖率要求,至少得有480行被测试跑到。如果Agent只写了一个冒烟测试,顺了几十行,那这一项门禁必挂,哪怕全局覆盖率显示上升了也没用。

为什么覆盖率差量也要卡?因为很多Agent为了过新代码覆盖率,会故意删掉一些原有的老用例或者把某些分支改成不可达代码。全局数字可能还涨了,但实际测试基线被掏空了。加了差量门槛之后,这种“数字游戏”就玩不下去了。调参的经验是:新项目覆盖率可以要求高一点,老项目尽量用“基线的浮动差量”来约束,别把阈值定死,否则Agent会在墙角里空转好几轮。

3. 实操过程:一个“导出CSV接口”的验收门禁落地记录

光讲设计容易飘,我拿一个最近实际跑过的需求来复盘:给一个订单查询服务增加“按时间范围导出CSV”的HTTP接口,要求支持中文表头、按用户权限过滤、文件流式返回。这个需求不算复杂,但足够暴露多Agent编程里的大部分问题。

3.1 出题与Agent派工:任务怎么拆、上下文怎么给

我先把需求喂给调度Agent,让它拆任务。它拆成了三个角色:接口Agent负责Controller和Service实现;边界Agent负责参数校验和异常处理;测试Agent负责补单测和验收用例。三个任务的路径有交叉,主要是Service层,所以接口Agent和测试Agent之间做了串行约束:先让接口Agent提交,测试Agent才进来补用例。

给执行Agent的上下文里,我除了塞需求描述,还塞了一段硬性契约,这部分非常重要。我贴一个简化过的指令模板:

你负责实现 CSV 导出接口。 硬性契约: - 路由:GET /api/orders/export?from=&to= - 输出:text/csv,带 Content-Disposition 附件头 - 权限:仅 EXPORT_ADMIN 角色可调用,非该角色返回 403 - 数据:只导出该用户关联范围内的订单 - 性能:不得整表 load 进内存,用游标分页流式写 禁止改动目录:.gates/ 、ci/ 、tests/e2e/config/ 门禁要求:新代码覆盖率 >= 80%,覆盖率差量 >= 0

之所以要把契约写得这么死,是因为自然语言需求里的“导出CSV”很容易被Agent自由发挥成“随便导出一个文件”。契约越具体,验收项越容易机器化,Agent的自由度就越少。自由发挥留给设计和创意,不该留给接口契约。

3.2 第一次交付没有通过门禁:一份失败日志的解读

第一轮Agent跑完,门禁报告出来,结果挺有代表性。日志大致长这样:

[09:41:22] START run 2024-05-12/08 [09:41:30] build: PASS [09:42:02] unit_test: PARTIAL FAIL - existing tests: 213 passed - new code coverage: 43.6% (threshold: >= 80.0%) - delta coverage: -12.4pt (threshold: >= 0) [09:42:03] behavior_check: FAIL - check "csv_export_with_chinese_header": FAIL - response header missing "Content-Disposition" - check "permission_filter_applied_return_403": PASS - check "streaming_large_file_no_oom": FAIL - 896MB loaded in memory

编译过了,既有测试也过了,但行为检查挂了两项,新代码覆盖率只有43.6%。一分析就发现两个典型问题。

第一,Agent把测试写成了“冒烟测试”。它只验证了接口返回200、CSV内容非空,完全没有验证中文表头是否正确、附件下载的文件名有没有、非管理员角色是否被拦截。单测用例看起来在跑,但跑的都是最浅层的路径,真正核心的契约断言一个都没有。

第二,实现上用了一次性读全表再过滤的方式。接口Agent图简单,直接SELECT * FROM orders之后在内存里按时间范围和权限过滤。测试数据只有几百条看不出来,验收清单里专门有一项是“导出100万条订单时不内存溢出”,这一跑就直接现出原形。这种性能问题在传统CI里基本测不出来,只有把“行为验收”做成清单并真正执行才拦得到。

3.3 让Agent自己看门禁反馈:自愈回路的实现

第一次看到门禁失败日志,我第一反应是转人工。但后来发现,如果反馈足够具体,Agent自己看着日志修,比人转述给它的效率要高得多。于是我把失败日志、覆盖率差量报告、diff统计一起打包,重新塞回给对应的执行Agent,让它基于git diff自己定位并修改。

第三轮的结果是这样的:

[10:07:48] build: PASS [10:08:11] unit_test: PASS - new code coverage: 83.2% - delta coverage: +4.1pt [10:08:12] behavior_check: PASS (3/3) [10:08:13] gate verdict: APPROVED

注意中间经历了两轮反馈:第一轮失败后,Agent改掉了流式返回的问题,但测试用例还是偏薄,覆盖率54.1%;第二轮门禁再次打回,它才开始老老实实补完整断言,最后把覆盖率拉到83.2%,三个行为检查全过。整个过程约22分钟,比我自己上手写快不少,而且每一步都有机器证据,不是它在log里说一句“已修复”就算数。

我还设了一个兜底规则:同一任务最多自愈3轮,3轮之后不管门禁什么状态都强制转人工。因为Agent在反复的失败反馈里会开始“做题家化”,用各种花式手段只为把门禁搞绿,这时候人的介入比让它无限试错更划算。

4. 常见问题排查与开源落地建议

这套流水线跑了几个月,问题肯定没少出。我把最有代表性的几个坑整理成一份速查表,刚接入的朋友可以直接照着排查。

4.1 我踩过的五个坑和排查思路

现象原因排查思路解决方式
Agent总说执行了测试,门禁却显示没跑Agent没真正执行流水线命令,只靠想象写总结查看门禁日志里是否有对应的step记录,不接受Agent口头描述门禁只认日志产物和exit code
门禁过严,Agent空转好几轮阈值设置脱离项目实际,比如新代码覆盖率要求90%看失败项集中在哪个环节,评估是代码烂还是测试难写改用“基线+差量”模式,用delta约束代替绝对值
两个并行Agent改同一个文件,互相覆盖拆任务时没做文件级冲突预检检查git提交记录里的文件路径是否重叠拓扑排序+路径锁,重叠任务强制串行
Agent钻验收清单的空子验收项写得太含糊,比如“中文表头正确”,没说怎么判定人工复核时看Checklist对应的命令是否可执行每项验收都绑定一条可执行断言,不允许目测
跑一次流水线太久,Token费用爆炸每个Agent都塞了全量仓库上下文,反馈又全量塞回看日志平均耗时和token消耗,定位到上下文最大的环节用diff范围裁剪上下文,失败日志只截相关片段

这里面我最想单独说的是“门禁过严”这个问题。一开始我以为阈值越严对质量越好,直接把新代码覆盖率定到90%,结果Agent为了凑覆盖率开始写一堆没有断言的垃圾用例,覆盖率数字上去了,项目总成本也上去了。后来把规则改成“覆盖率差量不小于0”加“行为检查全过”,质量反而更稳。门禁的职责是守住底线,不是逼Agent交完美作品。

4.2 开源项目怎么组织才有人愿意用

这个项目我开源的时候,刻意没做成一坨只有我自己能用的脚本,而是按“可复用”的思路组织仓库结构:

agent-factory-gates/ ├── gates/ │ ├── build.yaml │ ├── unit_test.yaml │ └── behavior_check.yaml ├── runners/ │ ├── gate_runner.py │ └── feedback_exporter.py ├── acceptance/ │ └── checklist.yaml.example ├── examples/ │ └── csv-export/ ├── config.example.toml └── README.md

核心是两个文件。gate_runner.py只负责按配置调度命令,然后收集exit code、覆盖率JSON、验收清单日志,统一转成结构化结果;feedback_exporter.py把结构化结果翻译成Agent能消费的失败描述。设计原则是:runner不做语义判断,只做“跑命令、收结果、导反馈”这三件事,判断规则全部放到YAML配置里,这样不同项目只需要改配置就能复用。

门禁配置大概是这样的:

pipeline: name: agent-factory-gates trigger: [push, pull_request, manual] agents: - role: backend scope: ["src/api", "src/service"] max_retry: 3 gates: build: cmd: "./scripts/build.sh" unit_test: cmd: "pytest --cov=src --cov-report=json:coverage.json" rules: existing_tests: "no regression" new_code_coverage: 80 coverage_delta: 0 behavior_check: checklist: "./acceptance/checklist.yaml" static: cmd: "ruff check src" block_on: ["E", "F"]

开源有几个注意点,我提醒一下:第一,不要把CI密钥或服务地址写进仓库,配置模板里只留placeholder;第二,一定要带一个跑得通的示例仓库,比如我这个examples/csv-export,否则别人拿到手不知道去哪儿改;第三,README里贴一份真实的运行日志,告诉别人“长什么样算过、长什么样算拒”,这比写十段说明都管用。

另外,如果要用在GitHub Actions或GitLab CI这类现有CI系统里,也完全没问题,runner本身不绑定平台,只要提供一个环境变量指到的输出目录,它就能把结果写出来,再让CI平台去展示。这套设计当时就是为了不改动团队已有CI习惯才做的,实测下来接入成本很低。

4.3 接下来我想做的两件“大事”

目前这套流水线已经能挡住“假完成”,但我觉得还有两个明显的升级空间。

第一,把验收清单的生成也交给Agent,再让人确认。现在checklist还得维护者手动写,这是整个流水线里最费人力的部分。我希望让一个“维护者角色”的Agent,基于需求文档和接口契约自动生成checklist,人只需要画勾或补一条。人从“写”变成“审”,效率能高一截。

第二,把人工评审意见结构化。门禁能证明“没崩、覆盖够、行为过了”,但代码可读性、设计合理性这些还是得靠人。我试过让审查Agent基于diff生成结构化评审意见,每条意见带行号、严重级别、修改建议,人工只要挑毛病而不是从头读代码。实测下来,大部分低级问题Agent能先挑出来,人只需要看它拿不准的那几条。这个方向我在持续优化,等效果稳定了再单独写一篇分享。

做完这个项目之后,我最深的体会是:让多Agent跑起来不难,难的是让它“停下来承认自己没做好”。门禁不是为了卡Agent,而是给Agent一面镜子,让失败反馈来得足够快、足够具体。与其逼Agent一次写对,不如把监督和反馈做硬。现在我的团队跑一个中等需求的Agent任务,大概二十到三十分钟,其中大部分时间不是Agent在写代码,而是门禁在反复验证它,人只做最后一道确认。这套路不算花哨,但真能让人从“盯着Agent干活”里解放出来。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 4:11:15

Spring异步流式接口实战:DeferredResult、SseEmitter与WebFlux三方案详解

干接口开发的,几乎都遇到过这种场景:一个查询接口,后端要聚合多个下游服务的数据,平时 200 毫秒就能跑完,一旦某个下游抖动,响应时间直接飙到 30 秒,前端的超时告警一个接一个。尤其是现在的大屏…

作者头像 李华
网站建设 2026/10/3 4:10:09

Comsol中BIC远场偏振与本征模式偏振态计算:算法选型与实操技巧

写这篇东西其实是这几天帮师弟擦屁股擦出来的经验。他做超表面里的BIC仿真,用Comsol算出来的远场偏振图总是不对,本征模式偏振态也说不清楚,拿着结果追着我问。我以为是他模型建错了,结果一查,问题出在算法选择上——他…

作者头像 李华
网站建设 2026/10/3 4:10:08

Arduino结合RS485与软串口实现多台电机稳定通信的完整指南

做电机控制,最让我头疼的不是算法而是通信。Arduino 主控和电机驱动器之间就隔几十厘米,TTL 串口却能乱码到怀疑人生;后来把 TTL 转 RS485 模块加上去,用软串口做总线转发,问题才彻底解决。下面就把完整接线、通信代码…

作者头像 李华
网站建设 2026/10/3 4:09:18

正点原子开发板lwIP+FreeRTOS移植实战与网络应用开发指南

手里有一块正点原子的探索者或者战舰开发板,又想在板子上把 lwIP 协议栈跑起来,配合 FreeRTOS 做一个真正的网络应用,那这应该是你绕不开的一份功课。正点原子官方例程里其实已经给了不少现成代码,但真正到自己做项目时&#xff0…

作者头像 李华
网站建设 2026/10/3 4:08:55

Vue 1.26实战复盘:从环境搭建到路由、流媒体与数据接入全攻略

看到“Vue 1.26”这个标题,先别误会,这真不是 Vue 官方出了 1.26 版本,官方现在还在 3.x 的路线里往前走。这串编号其实是我手里一个真实业务项目的迭代代号,第1轮开发打到第26个里程碑的时候,刚好把 Vue 相关的核心链…

作者头像 李华
网站建设 2026/10/3 4:08:18

char、String、StringBuilder三者的底层原理与性能实战

在Java里,char、String、StringBuilder这三个名字,几乎天天出现在代码里。但你真把它们拿出来对比着用,会发现藏着不少门道。char是基本类型,String是开发中最常用的引用类型,StringBuilder则是处理字符串拼接的首选工…

作者头像 李华