☰
AI Agent驱动开发闭环:构建-测试-修复循环实战指南
2026/9/26 20:58:10 网站建设 项目流程

1. 为什么要让 Agent 自己跑完开发闭环

先说个实际的场景。很多团队现在已经让 AI 帮着写代码了,但写出来的代码总是要有人接手去构建、去跑测试、去修失败。修完之后再跑一轮,可能又挂了,再修,再跑。这么一个“构建→测试→修复→循环”的过程,如果全靠人肉盯着,那 AI 写代码省下来的那点时间,又全填回调试里了。我自己最早接触这个概念是在做一个内部工具的时候:团队产出越来越快,可 CI 上的红灯越来越频繁,每天光看构建失败日志就占用大量精力。后来我干脆把整条链路交给一个 AI Agent 去跑——它负责构建、它负责发现问题、它负责提修复,甚至它负责确认自己改对了没有。效果比预期好很多,不是说它能一次把所有问题都修完,而是它能把人从重复性的“看日志→猜原因→改代码→重新跑”里彻底解放出来。

这篇文章想跟你聊的就是怎么把“构建→测试→修复→循环”这八个字,落成一个真实的、可运行的 AI Agent 工作流。内容适合已经在用 LLM 写代码的开发者,也适合在做 CI/CD 流水线优化的人。我会把我实际搭过的方案、踩过的坑、以及一些不太容易在文档里找到的细节全部摊开讲。

这里使用的核心关键词是 AI Agent、构建、测试、修复、循环。先把这几个词串起来理解:AI Agent 是大脑,构建是入口,测试是裁判,修复是动作,循环是流程。

2. Agent 驱动开发闭环的架构设计

2.1 这个闭环到底是什么

很多人一听到“AI Agent 自己跑开发闭环”,第一反应是:是不是让 AI 完全替代程序员?不是的。这里说的闭环,指的是让 Agent 在一个受限的范围里,自动完成“写代码→验证→修复→再验证”的循环,直到通过预定义的验收标准,或者达到某个退出条件。

一个典型的闭环包含这几个环节:

  • 构建:Agent 在给定代码库上执行构建命令,比如mvn compile、npm run build、python -m build。构建失败信息是 Agent 后续行动的输入。
  • 测试:构建通过后,Agent 接着运行测试集。测试可以是已有的测试套件,也可以是 Agent 根据需求新生成的测试用例。
  • 修复:Agent 分析失败原因,定位到相关文件,生成修复补丁。
  • 循环:修复后再次执行构建和测试,如果还是失败,基于新的失败信息继续修复。

这里的关键不是“Agent 能写多少代码”,而是“Agent 能不能可靠地收敛”。如果它一直在同一个错误上打转,或者越改越乱,那这个闭环就是失败的。所以架构设计的重心不是在“调用大模型的能力”上,而是在如何为 Agent 构建一个良好的“感知—决策—行动”循环。

2.2 核心组件拆解

我梳理了一下,一个能真正跑起来的闭环至少需要五个组件。缺一个,循环就有断层。

第一个是代码仓库工作区。Agent 需要一个隔离的、干净的工作目录。不能直接在主干分支上修代码,否则一次错误的修复可能污染整个仓库。最好用临时分支或者临时目录,Agent 每次修改都生成 patch,由外部系统决定要不要合入。

第二个是执行器。Agent 本身不直接执行命令,而是通过执行器调用构建工具和测试工具。执行器需要捕获命令的输出、退出码、耗时等元数据,并把它们结构化之后回传给 Agent 的上下文。

第三个是事件日志系统。这个组件是我强烈建议加的。记录每一次尝试的情况,包括命令、输出摘要、退出码、Agent 的决策、生成的补丁内容。不光是用来排查问题,还能在 Agent 陷入循环时给人工介入提供判断依据。

第四个是 Agent 运行框架。这个框架负责编排整个流程。它会告诉 Agent 当前处于哪个阶段、下一步应该做什么、有哪些约束。它的核心是一套状态机,状态之间有明确的切换条件。

第五个是大模型接口层。这是大家最熟的部分,但也是导致很多 Agent 跑不起来的重灾区。API 的稳定性、token 成本、上下文长度管理,都要在这一层处理。

把这五个组件事先捋清楚,后面写 Agent 逻辑的时候就会顺很多。我见过不少项目,一上来就写 prompt,结果跑两步就卡住,再回头补结构,反而更浪费时间。

2.3 为什么 LLM 不等于 Agent

说到 AI Agent,很多人会误以为“接一个大模型的 API 就是 Agent”。这也是热搜词里“agent 和 llm 和 ai模型 有什么区别”这个问题出现频率高的原因。

LLM 是一个静态的知识映射器。你给它一段输入,它给你一段输出,它本身不具备持续行动的能力。而 Agent 不一样,它有循环、有工具调用、有内外状态反馈、有目标驱动的行为规划。

用一个不太严谨但很好理解的类比:LLM 像是一个很聪明的实习生,你问他问题他答得头头是道;但 Agent 更像是一条流水线——它把一个大的任务拆开,每完成一步就检查一次结果,然后根据检查结果决定下一步动作。

所以在这个闭环里,LLM 承担的是“生成修复建议”、“生成代码 diff”、“解读失败日志”这些具体动作,而 Agent 框架承担的是状态流转、条件判断、退出机制、工具调用等控制逻辑。两者缺一不可。

2.4 工具选型与执行环境

在实际落地时,执行环境怎么选,直接影响闭环的稳定性。我用的比较多的是 Docker 容器方案。为什么要用容器?因为构建环境、测试依赖、系统库版本,这些必须得锁定。同一个项目,本地跑得好好的,一上 CI 就挂,往往就是环境差异导致的。

Docker 的执行方式也很简单:Agent 通过 Docker SDK 启动一个容器,容器内挂载代码目录,执行预设命令,然后把日志和退出码返回给 Agent。有一点要注意:容器不要用特权模式,尤其当代码仓库是不可信来源时,要给容器加上资源限制,比如 CPU、内存、磁盘配额。理由是防止 Agent 在循环中跑出一些失控的测试程序,把宿主机资源打满。

另外,对于测试环境,我建议尽量用真实环境。少用 mocks。原因很直接:Agent 修代码依据的是测试的失败信息,如果测试用的 mock 和真实行为差太远,那修复出来的代码很可能是错的。比如一个接口返回数据格式变了,mock 里面还是旧格式,那测试永远测不出这个 bug,Agent 自然也就无从修起。

在编程语言的选择上,我没做太多限制。Python、Java、Node 项目都跑过。关键在于你的 Agent 框架要能适配不同语言的构建工具。拿我自己写的框架来说,构建命令不是硬编码的,而是通过项目文件自动识别的:看到pom.xml就调 Maven;看到package.json就调 npm;看到pyproject.toml就调 pip 加 pytest。这样一套体系可以覆盖大多数主流项目。

3. 构建环节的实现:让 Agent 看见失败

3.1 构建信息的结构化解构

构建环节是整个闭环的入口,也是最容易被低估的一环。很多 Agent 项目失败,不是修代码的能力不行,而是对构建失败信息的理解太浅。

先说说通常的执行结果长什么样。一个构建命令跑完,无非三种结果:成功、失败、异常中断。成功最好处理,直接进入测试阶段。异常中断比如超时、内存溢出、环境错误,这种通常不是代码问题,Agent 就不该去改代码。真正需要重点处理的是失败——但失败的信息非常“脏”。

你拿到的是一大坨终端输出,里面混着编译错误、警告、日志、堆栈,甚至还有一些无关的装饰性字符。如果直接把这一大坨全部塞给 LLM,有几个问题。第一是 token 消耗巨大,一次失败日志几百上千行,几次循环下来上下文就爆了。第二是噪音太大,真正有用的错误信息被淹没,模型容易“跑偏”。

所以我在这个环节做的事情是:把原始输出结构化成几条核心信息。这一条我会详细展开,因为直接影响后面修复阶段的效果。

三条核心信息分别是:错误类型、错误位置、错误描述。

错误类型用来区分是语法错误、类型错误、依赖错误、还是测试断言失败。这个分类决定了 Agent 后续的修复策略。比如依赖错误可能需要改配置而不是改代码;测试断言失败则要先看断言逻辑,再决定是修产品代码还是修测试代码。

错误位置通常包括文件路径和行列号。很多构建工具已经把这些信息打印出来了,比如File.java:42: error: cannot find symbol。这些信息要单独提取出来,作为 Agent 定位代码的锚点。

错误描述就是真正说明原因的那句话。像 Java 里的cannot find symbol、Python 里的NameError: xxx is not defined、前端构建里的Module not found: 'xxx',这些一看就能大概猜到问题方向。

结构化工作做在前面,后面 Agent 的决策质量会高很多。实操中可以通过两个思路来做:要么写正则提取,要么直接调用构建工具的 JSON 输出模式。像 TypeScript 编译器有--json参数,ESLint 也有 JSON 格式输出,能拿到结构化数据就尽量用。

3.2 构建失败的分级响应策略

有了结构化的信息还不够,你得给 Agent 定义“什么情况下该做什么事”的分级响应策略。这是我反复调整后总结出来的经验。

我把构建失败分成三个级别。初级:单文件语法错误、缺失 import、拼写错误。这类问题修复风险低,Agent 可以直接改,不用请示。中级:跨文件接口变更、依赖版本冲突、构建脚本问题。这类问题建议让 Agent 先出一份修复方案,再动手改,方案里有风险说明。高级:构建环境异常、系统级依赖缺失、权限问题。这类问题 Agent 不应该碰代码,直接中止并通知人来处理。

这个分级机制看起来简单,但在实际效果上非常显著。没有分级之前,Agent 碰到任何错误都会尝试改代码,结果遇到系统级问题,改了一通代码根本不是问题根源,浪费好几轮循环。有了分级,Agent 在“行动”之前先判断“该不该行动”,路径清晰多了。

3.3 依赖管理与本地仓库准备

构建失败的另一个高频原因是依赖问题。代码在开发者本地能编译,但在 Agent 的工作区里报“包找不到”,十有八九是依赖缓存没有准备好。

我踩过一个大坑:在构建环节没有做依赖预热,Agent 第一次构建时下载依赖花了十几分钟,然后超时了,它就以为是代码问题,疯狂改 codepointer,结果越改越乱。

正确的做法是分两步。第一步,提前构建一个带依赖缓存的镜像或工作区。比如 Java 项目先跑一遍mvn dependency:go-offline把依赖拉全;Node 项目用npm ci安装完后把node_modules做成快照;Python 项目用pip cache加上预装虚拟环境。这个工作在 Agent 循环开始之前做,一次性投入。

第二步,给 Agent 的执行环境加上网络策略。如果项目依赖都是内部仓库,确保容器能访问内部镜像源;如果项目依赖的是公网包,那就让容器有必要的默认路由。但我个人建议,尽量把依赖层面的事情放在前置准备阶段,不要在 Agent 的循环过程中频繁访问网络。

4. 测试环节:Agent 的质检员

4.1 测试套件的接入与选择

构建通过之后,Agent 要面对的就是测试。测试在这里起到质检员的作用——它决定了 Agent 的修复到底算不算有效。

测试套件的选择,不是越大越好,而是要跟“修复验证”这个目标匹配。这个点是我在实际跑闭环后深刻体会到的。最开始我把整个项目能跑的测试全塞给 Agent,单测、集成测试、端到端测试全跑。结果就是:每次修复都要跑好久,而且失败信息五花八门,有时候是集成测试挂了,Agent 跑去修单测的逻辑,改了半天单测过了集成还是挂,反馈太慢,收敛效率极低。

后来我调整了策略,把测试分成两个层级。

第一层级是快速验证层。只跑跟改动文件有直接关联的测试用例,优先用测试覆盖率扫描出来的关联关系。比如改了一个 Python 函数,就只跑调用这个函数的那几个测试文件里的用例。这一层讲究的是快,一两分钟内要能给出来结果,用于判断基础修复是否生效。

第二层级是完整回归层。全部测试跑一遍,用于最后验收。这一层慢,但是必须跑,防止 Agent 在修复一个 bug 的时候破坏了别的东西。

这个分层的逻辑其实跟人开发时做“先跑局部测试再看全部回归”是一样的,只不过 Agent 需要你用代码把这种决策固化下来。

4.2 测试失败信息的二次加工

测试失败信息的处理,比构建失败信息更讲究。原因是测试失败通常不是“编译不过”这么直接,而是跟断言逻辑强相关。你经常会看到一堆 assertEquals 失败的信息,拿给 Agent 看,它可能自作聪明地觉得“既然测试期望的值是 A,而实际是 B,那我把测试改成 A 不就过了吗”——这就是经典的“作弊修复”。

为了避免这个问题,我对测试失败信息做了一层“可信度标注”的处理。分两步:

第一步:如果某个测试用例在本次修复之前就已经失败了(历史失败),那这个失败信息对 Agent 来说是不可信的,不参与当前修复的反馈。因为可能是之前那个迭代引入的问题,Agent 参考它会得到错误的方向。

第二步:如果某个测试用例是本次“因为 Agent 的修改”而失败的,也就是上次通过、这次挂了,那这个失败信息才是 Agent 需要重点关注的。

为了拿到这个信息,你需要记录每一个测试用例在历次循环中的执行结果,做一次差分。这个数据结构类似:{用例ID: [上一次结果, 这一次结果]}。如果出现[PASS, FAIL],就是“回归失败”,Agent 必须处理;如果是[FAIL, FAIL],就说明这个用例一直没修复好,Agent 需要继续修,但同时也说明 Agent 的修复方案可能没到位。

这个二次加工的逻辑,很多人会忽略,但它对 Agent 决策质量的影响是决定性的。没有这层差分,Agent 就像一个没有历史记忆的测试工人,每次都从零开始判断,完全无法利用“这个用例之前是过的”这条关键线索。

4.3 测试提示词的设计技巧

如果你用的是 LLM 来生成测试用例——比如给 Agent 布置“为这个功能补充单元测试”的任务——那提示词的设计就有讲究了。我不推荐上来就写“请为这个函数写测试”,这种太宽泛,生成出来的测试往往是套模板的废代码。

我一般会用三层提示结构。

第一层是任务目标,明确指定测试对象和方法。比如“针对order_service.py中的create_order函数,用 pytest 编写单元测试,覆盖正常下单、库存不足、用户不存在三个场景”。

第二层是基线约束,指定测试的预期行为。比如不能改变业务代码来将就测试、不能 mock 被测函数本身、测试数据要使用独立的临时数据源等。这些约束可以在一定程度上抑制 Agent 的“作弊”倾向。

第三层是成功标准。告诉 Agent 什么样的测试算完成:测试全部通过才算完成;如果业务代码有 bug 导致测试失败,不要修改业务代码来掩盖问题,而是报告问题。

这一节有一个经验可以直接抄:在使用这条 prompt 之后,我需要随后运行一次覆盖率工具,看看 Agent 生成的测试有没有真正覆盖到关键分支。不要依赖 Agent 自己的描述。覆盖率工具不会说谎。

5. 修复环节:让 Agent 拥有“靠谱的动手能力”

5.1 修复补丁的生成与落地

修复环节是整个闭环里技术含量最高的部分。Agent 需要在理解失败原因的基础上,生成一个可以落地的补丁。这个补丁必须满足三个条件:格式正确、改动最小、效果可验证。

格式正确这一点,很多人会忽略。实际场景里,LLM 生成的修复代码,直接应用到代码库上,经常出现缩进错乱、语法残缺、甚至文件编码问题。所以我建议不要把 LLM 输出的文本直接当作补丁,而是交给一个统一的补丁服务去处理。

我的做法是让 Agent 输出标准 diff 格式,之后用git apply来应用。在应用之前,先做一次 dry-run 检查是否可以干净合入。如果无法合入,报错反馈给 Agent,让 Agent 基于冲突信息重新生成。

改动最小是第二个约束。很多 LLM 在修复的时候容易“发挥过度”,比如修复一个函数,顺手把整个文件的格式调了一遍;修复一个 bug,顺手重构了相邻模块。这种超出范围的改动,在 CI 合入时会造成大量冲突,也让 review 变得困难。我加的约束是:只允许修改与失败原因直接相关的行,其他改动一律不允许。

第三个约束,效果可验证,这个已经在测试环节覆盖了。Agent 修复完,必须跑相关的测试来证明修复有效。不能让 Agent 修完就交差,没有验证的修复只是猜测。

5.2 避免“越改越乱”的机制

“越改越乱”是 AI Agent 自动化修复中最大的痛点。具体现象就是:Agent 修好了 A 问题,又把 B 弄坏了;再修 B,又把 A 弄坏了。典型振荡。我做了两个机制来压低这个概率。

第一个机制是“修改前快照”。每次 Agent 尝试修复之前,先把当前工作区的状态做一个快照。如果这一次修复导致的可通过测试数比上一次少了,那就回滚到上一次的快照,让 Agent 在干净的基础上重新换一个修复策略。

第二个机制是“差异限制”。计算 Agent 本次修改的 diff 行数,如果超过预设阈值,比如 30 行,就要求 Agent 解释为什么需要这么大的改动,并把解释和 diff 放入人工评审队列。它本身就是一个认知偏误的纠正机制——大改动往往是因为 Agent 没有准确定位问题。

这两个机制加在一起,让 Agent 的振荡收敛速度明显提升。你可以把整个循环失败率降到可接受的范围,关键是不要让一个失败“滚雪球”。

5.3 修复策略的上下文管理

还有一个经常被忽视的技术细节:上下文管理。

Agent 在修复的时候,它的输入包括失败日志、文件内容、测试报告、上次修复的记录等。如果这些信息全部塞进 prompt,很容易超长,而且大量的旧信息会干扰模型对当前问题的判断。

我做了两层处理。第一层是“只保留最近两轮”的信息。比如 round 3 的决策,只看 round 2 的失败信息和 round 3 自己改了什么;更早的信息通过摘要带入,不要全量堆在 prompt 里。第二层是“定向提取文件片段”。因为 Agent 要修改的文件可能很大,我不会把整个文件传给模型,而是根据失败信息里的行列号和符号名,用 AST 解析出相关函数或类的代码片段,只把这段代码给 Agent 看。

这两层处理让修复阶段的输入变得很精炼,模型判断也更聚焦。实测下来,相同任务下,token 消耗降低了约一半,而且修复成功率反而更高——因为信息噪音少了。

6. 循环控制:如何优雅地停在一个好结果上

6.1 退出条件的设定

循环不是无限转的,你得给 Agent 设定退出条件。这是我个人认为整个闭环设计中最需要经验的地方。

太宽松的退出条件,比如“只要测试全绿就算完成”,会放大假装有效的风险。太严格的退出条件,比如“任何失败都不允许”,会让循环无法收敛。

我实际采用的是一组三元退出条件:

  • 成功退出:所有测试通过,构建正常,Agent 在预设轮次内完成。
  • 放弃退出:达到了最大尝试轮次,比如 5 次,仍然有失败项。
  • 危险退出:出现了不可控的异常,比如 Agent 不断修改同一个函数但结果越来越差、或者测试环境本身崩了。

第三种退出条件很多时候会被遗漏,但它恰恰是防止 Agent 把项目改坏的关键。我自己最开始搭的时候,只设了前两种,结果有一次 Agent 在一个错误上来回横跳了十个轮次,把项目状态搞得一团糟。加了危险退出检测之后,只要检测到“上一轮失败项数量 > 前一轮失败项数量 + 1”且连续发生三次,就立即熔断,标记为高危失败,人工介入。

6.2 循环过程中的成本控制

成本控制也是不可回避的话题。调用大模型 API 是有费用的,而 Agent 的自动循环会放大调用量。每轮循环,Agent 可能要调用 3 到 5 次大模型——一次解释失败日志,一次生成修复方案,一次处理测试反馈,有时候还要再调用一次处理补丁冲突。十轮下来,调用量相当可观。

我做了三件事来压成本。

第一,启用缓存机制。把相同或高度相似的请求做缓存,比如相同的一份失败日志,就不要重复丢给模型解析,直接复用上一次的结构化结果。实际场景里,构建失败在循环中重复出现的概率非常高。

第二,设置模型分级。便宜的普通模型做首次分析,复杂的修复策略生成用强模型。不要一个模型打天下。这个策略的效果挺明显,大约能省 30% 到 40% 的开销。

第三,限制单轮上下文长度。每轮循环尽量把输入控制在模型输入窗口的一半以内,避免因为超长要求额外扩容计费,也减少模型在长上下文中的“健忘”问题。

6.3 与 CI/CD 流水线的集成方案

最后说一下这个闭环如何和现有的 CI/CD 流水线结合。我见过两种主流方式。

第一种是“后置触发器”模式。流水线跑完后,如果发现失败,就触发 Agent 闭环来处理。注意这里的实现要点:流水线必须输出可解析的失败报告,而不是只有一堆打印日志。Agent 需要的是结构化信息,比如失败用例列表、对应代码位置、是编译失败还是测试失败。这种方式适合从不稳定的项目起步,让 Agent 处理最容易处理的失败。

第二种是“前置门禁”模式。在代码合入之前,Agent 先跑一轮完整的“构建→测试→修复→验证”,确定没有潜在问题后再合入。这种模式对 Agent 的质量要求更高,也不太适合刚从零开始的项目。

我个人的建议是:先从后置触发器模式开始跑。让 Agent 处理那些被 CI 抓到的基本错误,比如低级语法问题、资源泄漏、缺失边界判断。这些修复是小而明确的,Agent 的效果会很好。等 Agent 在闭环上稳定了,再逐步扩大它的职责范围。

7. 实操案例:一个 Python 项目的完整闭环

光讲架构不够,这一节用一个真实的 Python 项目作为示例,展示从零构建这个闭环的过程。

7.1 项目背景与初始状态

这个项目是一个简单的“用户积分管理系统”,代码量不大,只有一个模块points_service.py。我用它来验证闭环的可行性,是因为它的逻辑足够简单,问题却不简单:有几个明显的 bug,比如用户积分可能变成负数、数据库锁竞争导致死锁、以及一个接口参数没有做类型校验。

初始状态下,我写了一批测试用例,大部分能过,但有三个用例失败。我的目标是:让 Agent 自己跑完构建、测试、修复、再验证的过程,把这三个失败用例修到全绿。

7.2 Agent 的启动指令与首次循环

我给 Agent 下达的指令简化如下:

你的工作目录是/workspace/project,网关命令是python -m pytest tests/ -x,构建命令是python -m compileall .。你的任务:让所有测试通过。每次修复后重新执行测试命令以确认结果。

第一轮循环中,Agent 执行了构建命令,编译失败并没有出现——这个项目语法上是好的。接着执行了 pytest,拿到了第一个失败用例的堆栈:test_negative_balance报错 “ValueError: Insufficient balance”。

Agent 分析后认为,问题出在deduct_points()函数没有做余额校验。于是它修改了函数,在扣减前加了一个if balance < points: raise ValueError(...)的判断。

第二轮循环,测试执行结果:test_negative_balance通过,但另一个用例test_concurrent_deduction挂掉了。它是有意设计的一个复杂场景:两个线程同时扣减,需要保证数量一致。

这时候 Agent 面临一个典型困难:并发问题的修复,光靠“发现在赋值前后加锁”很容易忽略锁粒度。第一版修复它只是在deduct_points函数内部加了一个threading.Lock(),但锁是每次调用都新建的,等于没有锁,测试还是失败。

7.3 第二轮循环中的修复优化

第三轮循环,Agent 拿到第二次失败的堆栈:测试期望最终积分为 0,实际却是负数。这说明两个线程的读取和写入交错执行了。Agent 这次意识到了问题,把锁提升为模块级单例,并对整个“读余额→扣减→写回”操作包成临界区。

第四轮循环,测试全部通过。第三个用例test_invalid_user_id实际上在第一轮就被 Agent 顺手修复了——它看到函数入口缺少类型校验,自己加了一个if not isinstance(user_id, int)的判断。

这个案例的过程非常清晰地展示了 Agent 闭环的价值:不是一次就能把代码改对,而是通过“失败→分析→修复→再失败→再分析→再修复”的循环,逐步逼近正确解。

7.4 案例复盘:Agent 表现好与不好的瞬间

复盘这个案例,有几个细节值得展开。

好的方面:Agent 在没有人工干预的情况下,识别了三个独立问题的修复优先级,没有出现来回横跳。这是因为它能看到每个测试用例的独立状态,而不是只看“测试总量”。

不好的方面:在并发修复的那一轮,Agent 第一次的锁方案是不对的,但它没有能力提前判断。真正让它收敛的是“循环中看到测试失败→生成新的修复尝试→再验证”的过程。这也印证了我的观点:Agent 的修复质量是循环淘汰出来的,不是一次生成出来的。

另外有一点很关键:Agent 在这个项目里的角色被限制在了“修代码让测试通过”,而不是让它自己重新设计整个系统的架构。这个限制是必要的。如果让 Agent 自由发挥,它很可能把整个模块重新写一遍,引入大量不相关的变更,反而让验证失效。

8. 遇到的问题与排查技巧

8.1 构建环境不一致导致的假失败

这是我跑第一个 Agent 闭环时遇到的高频问题。本地构建通过,Agent 环境里构建失败。排查之后发现,原因是 Agent 容器的 Python 版本比项目目标版本低,语法解析失败了。

解决的思路有两个。第一个思路是把“构建环境锁定”前置化。在启动 Agent 之前,用项目自带的环境配置文件(比如requirements.txt、pyproject.toml、.nvmrc)生成一个标准的镜像和环境,再做快照。第二个思路是,给 Agent 的构建命令前面加一个环境自检步骤。自检内容包括系统版本、解释器版本、依赖包版本。如果自检失败,Agent 停止一切修复行为,直接上报环境问题。

为什么这个环节要单独设置一个“不得修改代码”的规则?因为环境问题不属于业务代码 bug,Agent 修改代码无法解决,而且很可能引入新问题。按照我之前讲的分级响应策略,这就是典型的高级问题。

8.2 测试用例不稳定(Flaky Test)的干扰

Flaky Test,也就是测试本身不稳定,时好时坏,是 Agent 闭环里最让人头疼的问题之一。原因很简单:Agent 基于测试结果做决策,如果结果本身不稳定,Agent 的决策就失去了依据。它可能这次修好了,下次跑又是失败,于是又修一遍,修完又多出一堆无意义的改动。

我应对这个问题的方式是在“结果差分”模块里增加一个标记机制。同一个用例,在连续两轮中出现了 PASS/FAIL/PASS 这种模式,就自动标记为“不稳定用例”,从 Agent 的决策依据中降权。同时把它单独放到一次“干扰排除”任务里去跑,不再让 Agent 基于它的结果继续修复。

这类问题的另一个处理思路,是给测试用例加稳定化改造。比如消除随机数、固定时间种子、避免真实网络调用、设置超时。这些都是测试工程里老生常谈的方法,但在 Agent 闭环里它的意义更多了一层——你不想让 Agent 在无用信息上浪费轮次。

8.3 模型幻觉导致的错误修复

LLM 修复代码时,偶尔会“一本正经地胡说八道”。最典型的是:Agent 声称某个文件需要加一个不存在的模块,然后在代码里写了一个完全不存在的 API 调用。测试当然继续失败,Agent 看到失败后再编一个理由,再改,陷入死循环。

针对这类问题,我做了两件事。

第一,在 Agent 的修复指令中加入一条硬性约束:不得使用项目中不存在的依赖、API、类或方法。如果引用了新的依赖,必须先更新依赖配置文件,否则视为非法修改。

第二,加强验证环节的反馈。当 Agent 的修复包含不存在的符号时,运行完测试后把报错信息“Cannot find module”或者“ImportError”完整反馈给 Agent。让它在下一轮中基于真实的报错去修正,而不是靠记忆去猜。

这两件事都是为了让 Agent 的“决策依据”尽量来自真实环境反馈,而不是来自模型的内部先验知识。说到底,Agent 修复代码的本质是“试探—验证”的循环,模型幻觉只能让试探变慢,但只要你让验证的反馈足够清晰和结构化,Agent 最终还是会走到正确方向上的。

8.4 长时间运行的上下文失控

一个复杂的修复任务,Agent 可能需要跑十几轮循环。每轮循环都会产生大量的中间信息,包括失败日志、测试输出、生成的补丁、分析结论。如果不做上下文管理,token 会很快超限,而 Agent 会进入“记忆错乱”状态——它开始引用之前几轮的错误信息,而不是当前这轮的真实信息。

我前面讲过的“只保留最近两轮信息”是一个基础手段。这里再补充一个更细的技巧:在进入每一轮修复之前,强制 Agent 生成一份“当前状态摘要”,包含三块内容:已经修改了哪些文件、当前剩余的失败用例清单、基于最近一次失败信息得出的下一步计划。摘要生成后,前几轮的完整历史就可以被清理掉,只保留这份摘要作为新上下文的起点。

这样处理之后,上下文长度始终是可控的,而且模型的“决策状态”也被压缩得更干净。这个操作在 Agent 框架里叫“状态压缩”或者“记忆摘要”,它对于跑长链任务的 Agent 几乎是一种必须的手段。

8.5 补丁冲突与修改回滚

Agent 在连续多轮修改后,可能会在同一个文件的多个位置留下修改痕迹。此时如果再生成一个新的补丁,补丁和当前文件状态可能冲突。受限于大模型的上下文限制,它也未必能记住每个位置现在是什么状态。

我的应对策略是放弃“让 Agent 记住所有状态”的思路,转而“在补丁应用之前强制刷新状态”。具体来说:每一轮修复开始之前,Agent 都会基于当前磁盘上的文件实际内容重新生成补丁,而不是基于上一轮轮结束时它记忆中的文件快照。这样可以降低补丁应用失败的概率。

如果冲突还是发生,我不会让 Agent 直接重试修改,而是让它重新解析当前文件内容,再重新生成补丁。冲突发生时干燥运行一次,确认没有冲突再正式应用。这套机制非常简单,但极其有效。

8.6 常见问题速查表

现象可能原因处理方式
构建失败但本地通过Agent 环境与开发环境不一致前置环境自检、锁定镜像版本
同一测试忽好忽坏Flaky Test标记不稳定用例,从决策依据中降权
Agent 引用不存在的 API模型幻觉硬性约束,不存在的依赖必须先改配置
上下文越跑越乱循环过多信息堆叠每隔几轮做一次状态摘要,清理历史
补丁应用报冲突基于旧记忆生成补丁每轮强制刷新磁盘状态,再生成补丁
循环中出现大量无意义修复目标不明确检查退出条件和本轮目标定义
测试全绿但功能实际上还是坏的测试覆盖不足检查覆盖率,补充关键分支测试

9. 经验总结与扩展方向

9.1 不要一上来就追求全自动

我记得效果最好的跑法不是“扔给 Agent 一个项目,让它自己从头到尾做完”,而是先把闭环链路拆成几个可控的环节,每个环节单独验证。先验证“构建失败信息能不能结构化成 Agent 看得懂的输入”,再验证“Agent 生成的补丁能不能干净合入”,最后再逐步放开循环轮次。这个阶段很像训练一个新来的同事:先给固定的、简单的小任务,等它对环境熟悉了,再把更大的事情交出去。盲目追求一步到位,到最后只会让排查问题时无从下手。

9.2 构建失败信息是 Agent 最好的老师

如果把整个闭环的运转比作一场手术,那构建失败信息就是手术台上的监测仪。信号越清晰,手术就越安全。所以在这套体系里,真正要花时间打磨的,不是让大模型的 prompt 更花哨,而是把构建和测试的输出,整理成 Agent 能快速理解的决策依据。

9.3 让 Agent 自己记录自己的每一步

我给 Agent 加过一个指令:每一轮修改之后,必须用一句话说明自己改了什么、为什么改、期望解决什么问题。这些记录会自动写入到运行日志里,同时也是后续人工介入时的参考依据。实际效果是,Agent 每做一件事之前都会先想清楚逻辑,日志的可用性大大提升。

9.4 这个闭环还能怎么扩展

如果你已经跑通了这个闭环,下一步可以考虑几个扩展方向。

第一个方向是多语言支持。把构建、测试、补丁校验这些能力抽象成与语言无关的接口,让同一个 Agent 框架能对接 Python、Java、Go 等不同生态。

第二个方向是多 Agent 协作。不是让一个 Agent 从构建盯到修复,而是拆分成“构建检测 Agent”和“修复 Agent”——前者专职分析失败原因,后者专职生成补丁,再有一个“验证 Agent”收尾。这个模式在处理大型项目时会更有优势,因为每个 Agent 的上下文负载都更小。

第三个方向是沉淀修复知识库。把 Agent 每次成功修复的问题类型、修复策略、涉及的模式存下来,在后续类似问题上直接做相似度匹配,大幅减少试错轮次。这个方向我觉得很有意思,本质上是在给 Agent 积累“项目经验”。

我自己的体会是,AI Agent 跑开发闭环这条路,越走越像在带实习生:你对反馈质量和边界定义得越清楚,它就越靠得住;你越是偷懒、越是让它自由发挥,后面收拾烂摊子的成本就越高。构建→测试→修复→循环,这套闭环的价值不是让 Agent 替你聪明,而是让 Agent 在一次一次反馈中变得可靠。把它当成一条工程流来建设,才是它真正能落地的关键。

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

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

立即咨询