真实反馈驱动的AutoDebug Agent:从失败测试到自动修复
发布时间:2026/10/25 22:02:46 锦皓数字建站

凌晨两点半线上告警把我和枕头拆散打开日志一瞧又是空指针而且就在同事上周重构完的支付模块里。这种时候我脑子里反复滚着一句话能不能写个Agent让它自己把bug给修了。说干就干这就是AutoDebug Agent的由来——一个基于真实运行反馈、能自主定位缺陷并生成修复方案的Agent项目。这里说的缺陷是软件世界里的bug不是工业视觉里那种轴承、试管的外观缺陷检测。AutoDebug Agent做的是把一段真实的失败信息丢给Agent让它自己去复现、诊断、写补丁、跑测试然后再根据新的测试结果决定是收工还是继续修。它解决的是开发过程中最磨人的那部分——大量重复的、低级的、但又必须有人蹲在电脑前盯着的bug修复工作适合日常开发被bug反复纠缠的程序员、维护一堆历史工程的老兵、想往CI/CD流水线里塞自动修复能力的DevOps以及想把Agent产品化成内部工具的技术负责人。1. 这个Agent到底在修什么核心思路与场景拆解1.1 为什么写AutoDebug Agent它和其他自动化工具差在哪很多AI自动修复的演示是在精心准备的代码库里跑通一个预埋好的缺陷反馈也是设计好的跑通了就录个视频发出来。真拿这种思路去修真实项目里的bug基本会撞得头破血流——真实世界里哪有那么多读一遍代码就能看出来的缺陷。AutoDebug Agent的思路不是读代码找bug而是让Agent进入一个观察-诊断-修复-验证的闭环把每次代码真实执行产生的信号当成下一轮判断的依据。用最简单的话说它不是靠猜而是靠试。你给它一个失败的测试它先看为什么失败改完再跑一遍看是不是真的解决了没解决就带着新的失败信息重新诊断。这跟传统自动化测试工具有本质区别。静态扫描工具能告诉你这里可能越界但它不会替你写补丁再跑一遍回归验证。CI系统能把失败的构建甩到你脸上但它不会自己动手把问题修好再重新提交。AutoDebug Agent真正想接手的是定位-修改-验证这一整条动作链而不仅仅是发现。1.2 真实反馈是什么以及它为什么是会修的关键真实反馈是AutoDebug Agent标题里最关键的定语。什么是真实反馈就是代码真实执行之后产生的信号不是人脑模拟出来的理想输出也不是人工标注好的标准答案。最典型的一类反馈是测试失败信息。拿Python项目举例pytest跑完一个用例失败输出里通常包含断言实际值和期望值、抛异常的行号、堆栈里经过哪些函数。这套信息对Agent来说就是雷达信号模型看到期望值是True实际值是False、看到traceback定位到某个文件它才有条件把问题和代码位置关联起来。除了测试输出还有几类反馈同样重要编译和类型错误TS类型不匹配、Rust借用检查失败、Go空指针解引用警告运行时堆栈和日志线上服务异常日志带request_id和调用链上下文静态分析告警lint规则触发、安全扫描报告、复杂度告警版本控制历史git blame、最近的提交记录能告诉Agent这个文件最近被谁动过、改了什么用户行为信息复现步骤、异常截图、一句话描述尤其是UI类缺陷这些反馈的共同特点是它们是一手信息直接来自代码运行的结果。让Agent基于这些信号去修而不是基于它对代码的感觉这是AutoDebug Agent区别于其他demo级Agent的本质差异。反馈的颗粒度直接决定了修复能力的上限。给Agent的信息越接近一个真实工程师调试时看到的信息它就越能做出靠谱的判断。如果你只丢给它一个测试失败的结论再强的模型也只能是在黑暗中瞎摸。1.3 适用场景和不适用场景先把丑话说前面我得先泼一盆冷水AutoDebug Agent不是万能的也不是拿来替代程序员的。实测下来最适合的是这几类场景单元测试和集成测试覆盖较高的模块覆盖率至少60%以上否则反馈太稀疏Agent根本没法定位异常类型明确的bug比如空指针、越界、超时、版本兼容性问题能在几个文件范围内定位的中小型缺陷修完之后有回归测试可以验证的模块不适合的场景也很明确设计层面的架构问题比如缓存一致性设计错了这不是一次修复循环能解决的跨十几个服务、几十张数据表的链路故障反馈信息爆炸而且大量缺失依赖外部硬件、特殊设备或真实用户流的环境Agent没法在本地复现完全没有测试覆盖的遗留系统一个文件三千行Agent盲修的风险极高这个工具的价值不是替代工程师而是把修bug这个动作里最耗时的可复现、可定位、可验证环节压缩到分钟级。我在设计AutoDebug Agent时的目标很朴素让Agent像实习生一样先把能查的查了能改的改了把真正需要人类判断的部分留给我。2. 技术架构与关键设计Agent的骨架怎么搭2.1 感知层把缺陷信息变成模型看得懂的输入Agent要干活第一步是拿到它能用的信息。感知层是所有反馈信号的入口也是我最先设计、后来改动最多的模块。设计原则只有一条把原始信号做结构化处理、压缩体积、按诊断优先级排序再喂给模型。原始信号是各种格式混杂的pytest输出是纯文本GitHub Actions日志是一大坨时间戳加着色控制字符线上日志是JSON堆栈是带文件路径和行号的多行文本。如果直接把拷贝的原始输出丢给模型效果会非常差——上下文被无关信息塞满模型反而找不到重点。我的做法分三步第一步归一化。写一组解析器把不同测试框架的失败输出转成统一结构包括测试名称、失败类型、断言详情、堆栈列表。这一步的作用是把机器输出变成结构化事件。第二步压缩。堆栈按规则排序后只保留应用内代码的frame删除框架内部和第三方库的frame日志按级别过滤保留ERROR和WARN去掉INFO和DEBUG的噪音。压缩的目标是把原始10KB的反馈压到2KB以内同时保住关键信息。第三步排序。把信息按诊断价值排序先给核心异常类型和消息再给出现异常的文件和行号接着是相关堆栈然后才是测试上下文和额外日志。模型读token是有位置偏置的开头的内容权重最高所以一定要把最该让模型看到的东西放前面。这一步看起来不起眼但直接决定了后面修复循环能不能跑通。我的经验是80%的Agent失败不是模型不够聪明而是喂给模型的信息不对或不够。2.2 决策与行动层修复循环的设计AutoDebug Agent的核心是一个修复循环像人的调试流程一样分阶段推进。第一步是复现。Agent拿到反馈后先尝试运行相关测试或命令确认缺陷能不能复现。这个阶段如果复现不了Agent应该停下来问人而不是瞎猜。第二步是诊断。模型基于反馈信息和代码上下文生成假设指出最可能的根因。这个阶段的输出不直接是代码而是自然语言的诊断结论包括证据和怀疑点。第三步是修复。根据诊断结论生成一个最小化代码变更。我要求Agent尽量只改相关行不要顺手重构不相关的代码。第四步是验证。跑测试、跑lint、编译收集新的反馈。如果测试通过进入回归检查如果失败带着新反馈回到第二步形成循环。这个循环的本质是一个假设-验证-修正的迭代过程。每轮迭代模型都根据最新的测试结果修正自己对根因的判断。我把核心骨架写成伪代码def autodebug_fix_loop(issue, feedback_store, model, tools): context build_context(issue, feedback_store) for attempt in range(MAX_ATTEMPTS): if attempt 0: diagnosis model.diagnose(context, feedback_store.last_failure()) if diagnosis.get(unreproducible): return {status: needs_human, reason: 无法复现} else: diagnosis model.refine_diagnosis(diagnosis, new_feedback) patch model.generate_patch(diagnosis, codebase_context) if not validate_patch(patch): continue # 补丁语法都不对直接重来 apply_patch(patch) result run_tests_and_checks() if result.failed: new_feedback feedback_store.record(result) continue if run_regression().failed: # 当前测试过了但回归挂了 new_feedback feedback_store.record(run_regression()) continue return {status: fixed, patch: patch, attempts: attempt 1} return {status: timeout, last_feedback: feedback_store.latest()}关键点在于循环不是无限跑的。我给MAX_ATTEMPTS设定默认值5最多尝试五轮。原因有两层一是每轮都会消耗token和计算资源二是从实践中看五轮还修不好继续下去大概率是模型在同一个错误假设里打转不如收集好上下文交给人类。另一个设计细节是validate_patch这一步。不要直接让模型生成的补丁覆盖文件——先用AST解析、语法检查甚至先看diff的改动范围。语法不对立刻丢弃这个环节能拦截掉一大部分模型幻觉式输出。2.3 记忆层让Agent跨会话学习Agent记忆确实是能力提升的隐藏杠杆。AutoDebug Agent的短期记忆就是当前修复会话的上下文这没问题真正有价值的是长期记忆。我自己的长期记忆实现思路是这样的用一个向量库或者简单的JSON文件记录三类经验。第一类是修复过的缺陷特征包括错误类型、发生在什么模块、根因是什么、最后怎么修的。第二类是有用的命令和查询模板比如查这个服务全部日志用grep -r order_id logs/这样的固定模式。第三类是项目特有的约定比如这个仓库提交前必须跑make format。每次修复成功后把这次的诊断结论和补丁摘要写进记忆库。下次遇到类似错误Agent先检索记忆库把历史相似案例作为上下文注入。这个设计在积累几十个案例后效果明显相当于Agent在自己给自己加经验。记忆库要防止脏数据。我只记录经过验证的成功案例不记录失败尝试。失败的尝试往往带有误导性会让Agent把错误的修复路径当成可能方案。2.4 安全边界Agent能做什么、不能做什么搞Agent的人最容易忽略的就是安全边界。AutoDebug Agent能执行命令、能改代码、能跑测试这在本地开发环境还好一旦接入CI或者给了它一定的仓库权限风险立即放大。我做的第一道防线是沙箱执行。Agent通过一个受限的runtime执行命令只允许白名单内的操作跑测试、读文件、改仓库内文件、执行git diff和git log。禁止网络请求、禁止访问其他目录、禁止安装系统级依赖。第二道防线是文件修改策略。补丁应用到真实代码前先在临时分支上应用、跑验证、通过后再合回当前分支。我习惯用git stash加临时branch的流程这样即使Agent闯祸一条checkout就能完全恢复。第三道防线是审批门槛。涉及删除、push、合入主干这些高影响操作全部停下来等人点确认。Agent可以当好实习生但不能让它掌握发布权限。还有一点容易被漏超时控制。每条命令必须有超时上限比如跑测试最多10分钟。没有超时的Agent就像没有辞退机制的实习生能把整个CI卡死。我见过把Agent挂在一条等待输入的命令上拖了半个小时的场景别问我怎么知道的。3. 实操记录从零搭一个能修缺陷的Agent3.1 环境准备与框架选型实际操作前先说环境。我建议的起步配置是Python 3.11以上写Agent骨架本身用Python最顺手一个支持工具调用的强模型目标是修复的代码仓库带测试套件再加Docker或venv做沙箱。框架这块我经历过三个阶段最早直接用function calling手写后来用LangChain现在留在自研轻量框架加LangChain组件混搭。这里先给结论如果你的目标是给一个特定仓库做自动修bug没有必要上大型编排器一个函数调用循环足够了。起步阶段需要的东西不多核心三个文件一个工具定义文件、一个修复循环主文件、一个反馈解析器目录。工具定义文件负责给模型描述你能调用什么修复循环主文件控制整个流程反馈解析器目录放各个测试框架的解析逻辑。3.2 核心代码实现修复循环逐行拆解我把最核心的实现拆开讲。第一步是工具定义以跑测试为例用function calling风格定义TOOLS [ { type: function, function: { name: run_test, description: 运行指定测试用例并返回 pytest 输出, parameters: { type: object, properties: { test_path: {type: string, description: 测试文件或测试用例路径} }, required: [test_path] } } }, { type: function, function: { name: read_file, description: 读取仓库内指定文件的指定行范围, parameters: { type: object, properties: { path: {type: string}, start_line: {type: integer}, end_line: {type: integer} }, required: [path] } } }, { type: function, function: { name: apply_patch, description: 以 unified diff 格式应用代码修改, parameters: { type: object, properties: { diff: {type: string} }, required: [diff] } } } ]主循环是核心部分实际代码里要注意每个函数调用的结果必须完整存进消息历史模型才能基于真实结果继续推理。很多人写Agent时犯一个错误只把工具调用的结果字符串塞进日志但忘了告诉模型这个结果是来自哪次调用、对应哪个动作。我建议每个工具调用结果前加一个结构化标记比如命令: pytest tests/test_user.py exit_code: 1 输出: ...这样模型能准确理解反馈和动作的对应关系。补丁应用和验证环节同样关键。模型输出的patch可能格式不对需要专门的解析器把unified diff解析成实际的文件修改。我踩过的坑是模型会在patch里写假的行号标记但实际diff内容对不上文件行数。这时不应该强套apply而应该直接把patch原样返回给模型让它重新生成。理解和容忍模型输出的这种不稳定性是写Agent的基本功。3.3 接入真实反馈日志、测试、堆栈的一手处理接入真实反馈是AutoDebug Agent的重头戏。以最常见的pytest场景为例测试运行后用subprocess拿到的输出是这样的import subprocess, json, re def run_tests(test_path): proc subprocess.run( [pytest, test_path, -p, no:cacheprovider, --tbshort], capture_outputTrue, textTrue, timeout600 ) return parse_pytest_output(proc.stdout proc.stderr)parse_pytest_output函数要提取以下字段失败用例列表、每个失败用例的断言消息、截取的堆栈、异常类型比如AssertionError、IndexError。这个结构化结果会直接作为新反馈进入修复循环。我强烈建议把原始输出也留一份在日志里方便人工排查时对照。接入线上运行时错误的思路也类似把错误日志、堆栈、关联的上下文信息一起打包。关键点是让反馈带上时间和空间上下文而不是丢一个孤零零的异常消息。比如2025-01-15T02:30:00Z 服务order-svc 请求req_88ab2f1 出现KeyError: status比KeyError: status给模型的信号多得多。反馈处理管线做成一个独立模块后还可以做一件事给每种反馈类型打标签。比如按编译错误单元测试失败集成测试失败运行时崩溃静态分析告警分类。这个标签会作为上下文的一部分传给模型让它在诊断时知道当前面对的是什么性质的缺陷。3.4 评价指标与参数调整AutoDebug Agent跑起来之后你得知道它干得好不好。我用的评价指标有五项修复成功率验证通过并完成回归的patch数除以尝试修复的问题总数平均修复轮数每次成功修复平均跑了多少轮回归率通过了目标测试、但让其他测试挂掉的patch比例单次修复耗时从输入issue到输出patch的平均分钟数单次修复成本token消耗量直接关系成本这几个指标相互关联。比如压低了平均修复轮数token成本自然下降但也可能提高回归率追求高修复成功率就要允许更多轮迭代。我的默认参数是MAX_ATTEMPTS5temperature0.3top_p0.9。temperature太低容易机械重复太高容易发散。还有一个我观察到的细节模型的诊断质量比直接生成patch质量更重要。与其让模型直接给修复代码不如先强制它用自然语言描述根因、列出证据、写出验证计划再进入代码生成。这一步相当于给模型一个思考工作区诊断质量上去了后面的patch质量会明显提升。这也是AutoDebug Agent里Agent部分的核心价值——它不是一个单次生成的prompt工具而是有完整推理链的执行者。4. 常见问题与排查技巧实录4.1 反馈信号太弱Agent反复瞎改这是我遇到最多的失败模式。Agent第一轮修复失败后第二轮给出的patch跟第一轮几乎一样说明它没有得到足以修正判断的新信息。根因是反馈太稀疏比如日志只写了测试失败没有具体断言。排查思路是先看给Agent的反馈里到底有没有差异信息。如果Agent已经定位到某个函数但不知道函数内部实际执行到哪一步可以在反馈里加入traceback的中间层。另一个有效手段是让Agent自己加调试输出——比如让它往代码里插临时的print跑一次测试后再删掉。这个方法在实践中非常有用相当于把调试这个动作也交给了Agent。但要注意临时调试输出必须在最终patch里清除干净。我有一条规则最终diff里不允许出现print、debugger关键字否则自动拒绝。这条规则把不少看起来修好了但其实是靠打印撑着的垃圾补丁挡在了门外。4.2 上下文塞满了日志模型开始失忆长日志、大堆栈、多轮工具调用结果都会把上下文塞满。模型失忆的症状是明明前面已经定位到文件了后面生成patch时却引用了错误的行号或者开始重复前面已经试过的失败方案。应对策略有两个方向。第一是压缩日志超出阈值后让模型先做一个日志摘要把几KB的原始输出压缩成关键的几百字再进入下一轮。这个先总结再推理的两阶段法效果很好。第二是重构上下文结构不要无限追加对话历史而是维护三个独立槽位——问题描述、当前诊断结论、最近一轮反馈。每轮只把这三个槽位刷新给模型而不是把所有历史消息全部丢进去。这样上下文长度可控也符合人调试时的认知方式。4.3 误修复测试过了但坑更深了这是AutoDebug Agent最危险的问题——目标测试通过了但Agent的修改破坏了别的行为或者把一个表面的断言修成了更隐蔽的错误。我遇到过一个典型案例一个函数原本应返回列表Agent改成返回None恰好有个测试断言结果为空时返回None于是过了单测但整个模块的调用方全炸了。这类问题的核心是验证范围不够。我的强制方案是目标测试通过后必须再做两步。第一步跑当前文件所在的整个测试文件不只是失败的那一条第二步有条件的话跑一次完整测试套件。另外提交前会要求Agent给出patch的安全自述解释为什么这个改动是安全的、改动影响哪些模块。这个自述会作为人工审查的参考。效果好但会增加一两分钟成本值得。4.4 安全和连接问题Agent会话、超时那些坑实操中还会遇到一类跟Agent本身运行环境有关的问题。比如某些Agent运行时在建立会话时会返回空标识和无效服务名的错误常见原因是服务端版本与客户端不匹配、会话过期或者网络链路不稳定。这类错误往往不是Agent逻辑的问题而是基础设施。我建议做好两件事一是所有工具调用都带超时和重试二是注意会话的持久化和恢复。Agent在中间断了上下文丢失就只能从头再来。给修复循环加一个checkpoint机制每轮结束后把上下文快照存下来重连时从最近一个快照恢复能省大量时间。沙箱环境还有一个容易被忽视的安全点不要给Agent无限的文件系统权限。我发生过一次Agent好心修改了配置文件的事故。从那以后我在apply_patch工具里强制加了路径白名单检查凡目标路径不在仓库目录内的一律拒绝。这是成本极低的微调但能挡住很多低级事故。5. 工具选型与前沿参考站在现有框架和产品上做5.1 主流Agent框架怎么选LangChain、Dify、CrewAI做AutoDebug Agent之前我先研究了一圈现有框架。个人选型观如下LangChain生态最全适合有工程能力、想深度定制的团队。它的工具调用、记忆模块都比较成熟但抽象层偏厚调试起来麻烦。尤其当你需要精确控制每轮上下文的时候反而会被框架束缚。Dify适合快速原型和业务侧落地可视化编排让你不用写太多代码就能搭一个Agent工作流。但它的抽象不太适合精细控制代码修复这种高交互、多循环的任务。CrewAI擅长老多Agent协作比如让诊断Agent和修复Agent分工。我试验过把诊断和修复拆成两个角色效果有一点但开销翻倍收益不大后来还是合并成一个完整循环。我的选择是用原生function calling写核心循环必要时用LangChain的消息模型来管理历史但不是整个流程都套框架。这种半自研的好处是核心逻辑完全可控坏处是需要自己维护一些边缘逻辑。考虑到要写的是生产可用的工具可控性比省事重要。5.2 向Codex CLI与Claude Skills学什么产品化的编码Agent给AutoDebug Agent提供了很好的设计参考。Codex CLI这类工具给我的启发是命令行优先的交互形态。它把Agent做成了终端里的一条命令开发者在代码目录里就能启动拿到上下文后直接开始诊断和修改。AutoDebug Agent也应该保持这种最小侵入——不要求把整个开发流程搬到某个IDE或平台而是作为一条命令、一个API接入现有工作流。我在实现时也选择了类似的形态在仓库根目录执行一条命令Agent自行读取git状态、定位最近变更、跑测试、开始修复循环。Claude Skills的思路更值得研究把技能看作可复用的、经过验证的能力单元。放到AutoDebug Agent里就是我可以把收集pytest反馈、检查git历史、运行静态分析封装成独立的技能模块并给每个技能写清楚适用条件和输出格式。这样Agent在遇到特定场景时能精准调用而不是每次都在原始信息里重新摸索。技能化的另一个好处是可组合、可分享团队里积累的调试技能可以沉淀下来复用。5.3 轻量自研 vs 大型编排器我的建议如果要给一个明确的建议我的答案是一开始不要先买来一堆框架先用最原始的function calling把修复循环跑通再根据痛点决定要不要引入更重的抽象。原因很现实Agent的核心是基于反馈做推理而不是编排了多少个节点。你拖出10个节点但如果每个节点喂给模型的信息都是一坨没处理的原始日志效果还不如一条精心设计的循环。框架能帮你管理边缘情况但替代不了对反馈信号的处理。我自己现在保留了一个约300行的核心循环加上一堆针对不同测试框架的反馈解析器。它比任何框架都稳定因为在那个循环里我知道每一步在干什么模型看到的每一条反馈我都亲手验证过格式。如果后续要扩展到多仓库、多Agent协作再考虑把循环包装成服务接入更完善的编排层不迟。我个人在实际项目里最深刻的体会是AutoDebug Agent这个东西真正的瓶颈不是模型而是反馈信号的质量和循环设计的稳健性。你给Agent喂什么它就修什么你让它在什么边界内活动它就在什么边界内靠谱。我给团队交付这个工具的时候特意把无法复现就停下来问人和通过测试后必须跑全量回归写成两条不可绕过的规则。踩过几次坑之后这两条规则救过我太多次。如果你也想搞一个自己的AutoDebug Agent建议从最小的修复循环开始先把反馈管好技能化封装跟上再慢慢把边界往外扩。等到你的Agent能稳稳地修完一批又一批历史issue并且在修完的同时没有引入新的坑你会觉得这几十个小时的折腾值了。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。