跳到主要内容

Aider — 架构与原理

30 秒导读: Aider 是一个在终端里跑的「AI 结对编程」工具。你把几个文件加进会话、说一句"把 X 改成 Y",它把你的代码 + 一张全仓库地图发给 LLM,LLM 回一段带文件名的 SEARCH/REPLACE 补丁,Aider 把补丁容错地应用到磁盘、跑 lint/测试、并自动 git 提交——出错就把错误喂回模型让它自己改。


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

一句话定义: Aider 是一个命令行工具,让你用自然语言驱动 LLM 修改你本地 git 仓库里的真实代码。

解决什么问题 / 给谁用: 假设你在终端里维护一个几百个文件的项目,想让 AI 帮你"给 app.pyget_factorial() 换成 math.factorial"。直接把整个仓库贴给 ChatGPT 既超上下文又难把回复落回文件。Aider 解决的就是这"最后一公里"——让模型的输出能被机器可靠地应用到磁盘,并且让模型不用看到全部代码也知道仓库里有什么

它能做什么:

  • 接收自然语言改动请求,产出对多个文件的精确编辑。
  • 自动把编辑写盘、git commit,可 /undo 回退。
  • 给大仓库生成一张「重要符号地图」(repo map),让模型有全局观。
  • 自动跑 linter / 测试,把错误反馈回模型让它自纠。
  • 支持几乎任意 LLM(通过 litellm),并按模型能力切换编辑格式。

用起来什么样: 一段真实的终端交互(示意):

$ aider mathweb/flask/app.py
> Change get_factorial() to use math.factorial

# 模型回复里夹着一个 SEARCH/REPLACE 块:
mathweb/flask/app.py
```python
<<<<<<< SEARCH
return str(factorial(n))
=======
return str(math.factorial(n))
>>>>>>> REPLACE
```

Applied edit to mathweb/flask/app.py
Commit a1b2c3d Change get_factorial to use math.factorial

上面那段 <<<<<<< SEARCH ... ======= ... >>>>>>> REPLACE 就是 Aider 的核心"编辑语言"——它长得像 git 冲突标记,模型最熟悉、最不容易写错。

一句话直觉/类比: 把 LLM 当成一个只会说话、看不见你磁盘的资深程序员。Aider 是它的「手和眼」:

  • =把相关代码 + 一张仓库地图念给它听;
  • =把它口述的"把这几行换成那几行"容错地落到真实文件,并用 git 兜底。

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

主要部件

部件干什么在哪
Coder(基类)主循环、上下文组装、反思自纠、提交编排aider/coders/base_coder.py
*Coder 子类实现一种编辑格式的解析与应用(diff / udiff / 整文件…)aider/coders/editblock_coder.py
RepoMap用 tree-sitter 抽符号 + PageRank 排重要性,生成仓库地图aider/repomap.py
GitRepo暂存、生成提交信息、按规则改作者/提交者归属aider/repo.py
Model把模型名映射到能力(默认编辑格式、缓存、提醒位置…)aider/models.py
Commands斜杠命令(/add /undo /test …)aider/commands.py
InputOutput终端 IO、确认提示、读写文件aider/io.py

主线走一遍(高层)

你打一句话


┌──────────────────────────────────────────────────────────┐
│ Coder.run_one (base_coder.py:924) │
│ 预处理:识别斜杠命令 / 文件提及 / URL │
└───────────────┬────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────┐
│ format_chat_chunks → 组装上下文: │
│ 系统提示 + 例子 + repo map + 只读文件 + 会话文件 + 历史 │
│ (chat_chunks.py 决定拼接顺序与缓存点) │
└───────────────┬────────────────────────────────────────────┘

send → LLM(litellm,流式)

┌──────────────────────────────────────────────────────────┐
│ 回复文本 = 解释 + 若干 SEARCH/REPLACE 块 │
│ get_edits 解析出 (文件, 旧文本, 新文本) │
│ apply_edits 容错地把旧文本定位并替换(多级降级匹配) │
└───────────────┬────────────────────────────────────────────┘

成功?──否──► reflected_message = 错误详情 ──┐
│是 │ 回灌给模型
▼ │ 再来一轮
git auto-commit → lint → test │ (≤3 次)
│ │
└── lint/test 失败也可触发反思 ────────────┘

这张图就是 Aider 的灵魂:一个"发请求 → 落编辑 → 验证 → 出错就把错误喂回去再来一轮"的有限次自纠循环。循环上限是 3 次(base_coder.py:101 max_reflections = 3)。

三个最值得理解的设计点

  1. 编辑即语言。 模型不调用"写文件"工具,而是在普通回复里夹一段约定格式的补丁;Aider 负责解析与落地。
  2. 容错匹配是核心难点。 模型"记忆"的旧代码几乎从不和磁盘逐字一致,所以应用补丁要一层层降级匹配(见第 2 章)。
  3. 反思替代多智能体。 没有复杂的 agent 编排——靠"把 lint/test/匹配失败的报错原样回灌"形成一个朴素但有效的自纠环。

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

这是大型项目,拆成四章,由浅入深

顺序文件讲什么适合谁
101-edit-formats.mdLLM 怎么"说出"一次改动:三种编辑格式 + 解析想懂"模型输出长啥样、怎么被切出来"
202-apply-and-match.md把模型口述的旧代码容错地落到真实文件:多级降级匹配想懂"为什么改不准也能改对"——精华所在
303-repo-map.mdtree-sitter 抽符号 + PageRank 排序,压出仓库地图想懂"模型没看全部代码却有全局观"
404-loop-and-git.md主循环 / 反思自纠 / 上下文与缓存 / git 提交与归属想懂"端到端怎么转、怎么兜底"

建议至少读 1 → 2:这两章是 Aider 区别于"套壳聊天"的真正工程含量所在。


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

主题文件关键符号
主循环 / 反思aider/coders/base_coder.pyrun_onesend_messagemax_reflectionsreflected_message
编辑格式工厂aider/coders/base_coder.pyCoder.create
SEARCH/REPLACE 解析aider/coders/editblock_coder.pyfind_original_update_blocksfind_filename
容错匹配aider/coders/editblock_coder.pyreplace_most_similar_chunkreplace_part_with_missing_leading_whitespacetry_dotdotdots
udiff 应用aider/coders/udiff_coder.pyaider/coders/search_replace.pyfind_diffsflexible_search_and_replaceudiff_strategies
仓库地图aider/repomap.pyRepoMap.get_ranked_tagsget_ranked_tags_map_uncachedget_tags_raw
上下文组装 / 缓存aider/coders/base_coder.pyaider/coders/chat_chunks.pyformat_chat_chunksChatChunks.all_messagesadd_cache_control_headers
git 提交与归属aider/repo.pyGitRepo.commitget_commit_message
模型能力映射aider/models.pyModel.configure_model_settingsedit_format