跳到主要内容

AutoCodeRover — 架构与原理

30 秒导读: AutoCodeRover(下称 ACR)是一个全自动修 GitHub issue 的 AI 程序员。给它一段 issue 描述和一个代码仓库,它先用一组懂程序结构的搜索 API(在 AST 里按类名/方法名查,而不是 grep 字符串)把相关代码捞出来,定位到「哪个文件、哪个方法、应该改成什么行为」,再让 LLM 写补丁;有测试时还能写复现脚本、跑通验证、让一个 reviewer agent 把关。它在 SWE-bench Verified 上达到约 46% 的修复率,每个任务成本不到 $0.7。

1. 这是什么(零基础也能懂)

一句话定义: ACR 是一个把「读一个开源 GitHub issue → 在大代码库里找到 bug → 写出能合入的补丁」整条流程自动化的 agent。

解决什么问题 / 给谁用。 假设你维护一个十万行的 Python 项目,收到一条 issue:「调用 get_backend() 会把 rc_context 里建的 figure 弄丢」。一个人类工程师要做的是:读懂 issue → 在代码里搜 get_backendrc_context → 找到出问题的函数 → 改几行 → 跑测试确认。ACR 就是把这一整套交给 LLM 自动跑,产物是一个 .diff 补丁。它面向的是「自动化软件工程 / 自动程序修复」的研究者和想批量解 issue 的人。

它能做什么:

  • 在三种模式下运行:swe-bench(跑基准任务)、github-issue(直接跑一条线上 issue)、local-issue(本地仓库 + 本地 issue 文件)。
  • 用 8 个程序结构感知的搜索 API 检索上下文(见 §第 1 章)。
  • 可选地用 SBFL(基于测试覆盖的统计故障定位)给出可疑方法排名。
  • 生成补丁,并把它模糊地应用回真实代码(容忍 LLM 给的缩进/空白对不齐)。
  • 实验模式下:写复现测试 → 跑「打补丁前 / 后」对比 → 让 reviewer agent 判断补丁和测试是否正确 → 不对就带着反馈重试。
  • 回归验证(补丁有没有弄坏已有测试),最后从多个候选补丁里选一个

用起来什么样。 命令行入口是 app.main(app/main.py:38main),跑一条 GitHub issue 大致是:

python app/main.py github-issue \
--output-dir output \
--setup-dir setup \
--model gpt-4o-2024-05-13 \
--task-id my-task \
--clone-link https://github.com/owner/repo.git \
--issue-link https://github.com/owner/repo/issues/123

跑完在 output/ 下会有一个 .diff 补丁、完整的对话记录(每一轮搜索 / 补丁 / 评审都存成 json)、以及 selected_patch.json 告诉你最终选了哪个补丁、为什么。

一句话直觉 / 类比。 把 ACR 想成一个有 IDE「跳转到定义」能力的盲人程序员:它看不到整个仓库(上下文窗口装不下),但能喊「给我看 WCS 类里的 _array_converter 方法」,系统就从 AST 索引里精确把那段代码递给它。它一边喊一边攒线索,攒够了就说「bug 在这几个方法,应该改成这样」,然后动手改、再让另一个它自己审一遍。

本节不含底层细节。记住一件事:ACR 的核心不是「调用 LLM」,而是「给 LLM 装上一套懂代码结构的眼睛和手」。 这套眼睛(搜索)和手(补丁应用)就是后面各章的主角。

2. 顶层全景(它大概怎么转)

2.1 一次任务的主线

一个任务从 issue 到补丁,要穿过四个阶段。先看整体数据流(从上到下):

issue 文本 + 代码仓库

┌────────────▼─────────────┐
│ 阶段 A:复现(可选) │ 写 reproducer.py,跑出 AssertionError
│ TestAgent │ → 拿到「问题确实存在」的证据 + stderr
└────────────┬─────────────┘
│ repro_stderr / 复现测试内容
┌────────────▼─────────────┐
│ 阶段 B:故障定位(可选) │ 跑测试 + 覆盖率,Ochiai 打分
│ SBFL │ → top-N 可疑方法
└────────────┬─────────────┘
│ sbfl_result
┌────────────▼─────────────┐
│ 阶段 C:上下文检索 │ search agent 反复调搜索 API
│ SearchManager+agents │ → 一组 BugLocation(代码 + 期望行为)
└────────────┬─────────────┘
│ bug_locs + 对话历史
┌────────────▼─────────────┐
│ 阶段 D:补丁生成 + 回路 │ 写补丁 → 模糊应用 → 评审 → 验证
│ ReviewManager+agents │ → 多个候选补丁
└────────────┬─────────────┘

选 1 个补丁 → output/*.diff

这条主线的代码骨架在 _run_one_task(app/inference.py:263):它依次调 TestAgent.write_reproducing_test_without_feedback(复现)、api_manager.fault_localization(SBFL)、search_manager.search_iterative(检索)、再交给 ReviewManager 走补丁回路。最外层 run_one_task(app/inference.py:98)把它包了一层「整体重试 + 模型轮换 + 最后选补丁」。

2.2 部件一句话职责

部件干什么在哪个文件
run_one_task / _run_one_task任务总驱动:整体重试、串起四阶段、最后选补丁app/inference.py
SearchBackendAST 索引 + 8 个搜索 API 的真实执行者app/search/search_backend.py
SearchManager上下文检索主循环的调度者(连接 agent 与 backend)app/search/search_manage.py
agent_searchLLM「选哪些搜索 API / 哪里是 bug」的生成器app/agents/agent_search.py
agent_proxy把 LLM 的自然语言回答抽成结构化 JSONapp/agents/agent_proxy.py
PatchAgent / agent_write_patch让 LLM 写补丁,并判断是否「可应用」app/agents/agent_write_patch.py
patch_utils模糊补丁应用:replace + 缩进双猜 + pylint 守门app/agents/patch_utils.py
TestAgent / agent_reproducer写复现脚本、判断 issue 是否可复现app/agents/agent_reproducer.py
agent_reviewer / ReviewManager评审补丁+测试,组织复现-评审回路app/agents/agent_reviewer.pyapp/api/review_manage.py
sbfl基于覆盖率的 Ochiai 故障定位app/analysis/sbfl.py
validation回归验证(补丁有没有引入新失败)app/api/validation.pyapp/task.py
SweTask / Task任务模型:setup、跑测试、应用补丁app/task.py

2.3 两阶段心智模型

README 把 ACR 概括为两阶段,这也是理解全局的最简模型:

  1. 上下文检索(Context retrieval): 给 LLM 搜索 API,让它在代码库里导航,攒够相关上下文 → 产出 bug 位置。
  2. 补丁生成(Patch generation): 基于检索到的上下文写补丁。

复现、SBFL、评审、验证都是围绕这两阶段加的增强(覆盖/利用测试信号),核心价值仍在「结构感知搜索」和「补丁如何可靠落地」这两点。

3. 阅读地图(建议顺序)

想最快抓住 ACR 的精华,按这个顺序读:

  1. 第 1 章:程序结构感知搜索 — ACR 最独特的地方。AST 索引怎么建、8 个搜索 API 各查什么、proxy agent 怎么把「我想看 X 方法」变成可执行调用。先读这章
  2. 第 2 章:上下文检索主循环 — 把第 1 章的零件接成一个会自我迭代的生成器,以及 bug location 的多级降级解析(模型给的位置很可能不精确,怎么兜底)。
  3. 第 3 章:补丁生成与模糊应用 — 为什么 ACR 不让 LLM 直接写 diff,而是用 <original>/<patched> 标签 + 模糊匹配 + 缩进双猜 + pylint 守门。这是「让 LLM 改代码真的改对」的工程精华。
  4. 第 4 章:复现-评审回路 — 有测试时如何用「执行信号」逼补丁收敛:写复现脚本、跑前后对比、reviewer 同审补丁与测试。
  5. 第 5 章:SBFL / 验证 / 选补丁 — 利用测试套件做统计故障定位(Ochiai),回归验证,多候选投票/agent 选择。

如果你只想读一章:读第 1 章第 3 章——前者是 ACR 的「检索精华」,后者是「落地精华」。

4. 巧妙之处(可带走的精华)

下面每条都在对应章节展开,这里先给指针:

  • AST 索引 + @cache 复用。 索引在 SearchBackend._build_python_index(search_backend.py:74)上挂 functools.cache,同一 project_path 多次构建直接命中,整体重试不重复扫全库。详见第 1 章。
  • 「分两个 agent」:写散文的 agent ≠ 抽 JSON 的 agent。 agent_search 让 LLM 自由分析(更聪明),agent_proxy 单独负责把那段散文抽成可执行 JSON(更可控)。详见第 2 章。
  • 不信任 unified diff,改用「贴原文 + 贴新文」。 LLM 数不准行号、对不齐缩进,所以 ACR 让它贴 <original> 原文片段,再用忽略空白的逐行匹配 + 两种缩进猜测 + pylint 语法校验把改动落回去。详见第 3 章。
  • 用「执行差异」当奖励信号。 reviewer 同时拿到「补丁前 stderr」和「补丁后 stderr」,据此判断补丁是否真的修好——这是把 LLM 评审锚在真实执行上,而不是凭空判断。详见第 4 章。
  • 多级降级定位。 模型给的 bug location 可能只有方法名、或类名拼错,get_bug_loc_snippets_new(search_backend.py:759)按「方法+类 → 方法+文件 → 类+文件 → 类 → 方法 → 文件」逐级退让,尽量捞到一个真实代码单元。详见第 2 章。

5. 边界与局限(诚实)

  • 只支持 Python。 索引构建只解析 .py(search_utils.find_python_files,search_utils.py:21),搜索 API 全部基于 Python ast 模块。其它语言不支持。
  • 复现 / 评审强依赖 SWE-bench 设施。 ReviewManager.generatorassert isinstance(self.task, SweTask)(review_manage.py:69),复现回路只对 SWE-bench 任务开放;回归验证经由 SWE-bench-docker 跑(task.py:288_run_test_suite_docker)。
  • 「复现」的判定很朴素。 ReproResult 认为「returncode != 0 且 stderr 含 AssertionError」才算复现成功(data_structures.py:174),只覆盖「issue 能被一个抛 AssertionError 的脚本触发」这类。
  • 补丁应用对 pylint 有依赖。 apply_edit 用 pylint --errors-only 是否报 syntax-error 来在两种缩进猜测间二选一(patch_utils.py:175);pylint 行为变化会影响落地。
  • select_patch 里有被注释关掉的多数投票。 代码里 if False:(inference.py:183inference.py:212)把「按内容多数票选补丁」分支关掉了,实际走 agent 选择;这是实验留下的状态,读代码时容易误解。

6. 横向对比(同 shelf 兄弟)

ACR 在 coding-agents 这一 area 的取舍:

  • 检索靠「结构化 API」而非「自由 shell」。 很多编码 agent(如给 LLM 一个 bash/grep 工具的方案)让模型自己 grep;ACR 反其道,把检索收窄成 8 个 AST 感知的具名 API,牺牲灵活性换「每次返回的都是一个完整、带行号、带类/方法归属的代码单元」。这让上下文更干净、更省 token。
  • 补丁落地靠「模糊匹配」而非「让模型产出合法 diff」。 对比直接要求 unified diff 的方案,ACR 承认「LLM 写不准 diff」,把可靠性放进确定性的后处理(第 3 章)。
  • 可选地融合「测试信号」。 SBFL + 复现 + 评审让它在有测试套件时能用执行结果校准,这条线是它相对纯「读代码改代码」agent 的差异化。

(待 shelf 总库 doc 落地后,这里链到「程序结构感知检索」「补丁应用 / edit application」「故障定位」三个跨库原理页与 1-3 个兄弟子库。)

7. 代码地图(导航索引)

主题文件关键符号
任务总驱动 / 整体重试app/inference.pyrun_one_task_run_one_taskselect_patch
AST 索引构建app/search/search_backend.pySearchBackend._build_python_index_build_index
8 个搜索 APIapp/search/search_backend.pysearch_classsearch_methodsearch_method_in_classsearch_codeget_code_around_line
AST 解析 / 取代码app/search/search_utils.pyparse_python_fileget_class_signatureget_code_region_containing_code
检索主循环app/search/search_manage.pySearchManager.search_iterative
选 API 的 agentapp/agents/agent_search.pygeneratorSELECT_PROMPTANALYZE_AND_SELECT_PROMPT
抽 JSON 的 proxyapp/agents/agent_proxy.pyrun_with_retriesis_valid_responsePROXY_PROMPT
bug location 降级解析app/search/search_backend.pyget_bug_loc_snippets_new_get_inherited_methods
写补丁 agentapp/agents/agent_write_patch.pyPatchAgent_write_patchUSER_PROMPT_INIT
模糊补丁应用app/agents/patch_utils.pyparse_editsapply_editlint_python_content
原始响应 → diffapp/post_process.pyconvert_response_to_diffExtractStatus
复现 agentapp/agents/agent_reproducer.pyTestAgent_issue_has_reproduction_stepsconvert_response_to_test
评审 agent / 回路app/agents/agent_reviewer.pyapp/api/review_manage.pyagent_reviewer.runReviewManager._generator
SBFL 故障定位app/analysis/sbfl.pyExecStats.ochiairank_linescollate_results
回归验证app/api/validation.pyapp/task.pyevaluate_patchSweTask._run_test_suite_for_regression_docker