跳到主要内容

第 4 章 · 主循环、反思自纠、上下文与 git 兜底

本章把前三章串成端到端:一句话进来,怎么组装上下文、发模型、落编辑、提交、验证,以及出错怎么自动再来一轮

4.1 反思循环:Aider 的"agent"

Aider 没有复杂的多智能体编排。它的"自主性"就来自一个朴素循环:run_onebase_coder.py:924)。

run_one(message):
while message:
reflected_message = None
send_message(message) # 发模型 → 落编辑 → 验证
if not reflected_message: break # 没有要反思的事,结束
if num_reflections >= 3: 停止 # 上限 max_reflections=3
num_reflections += 1
message = reflected_message # 把"反思消息"当作下一轮输入

什么会写 reflected_message(触发再来一轮)? 这是理解 Aider 的关键——所有"失败"都收敛到同一个字段:

触发源写入处
模型提到了不在会话里的文件,需补充base_coder.py:1561-1566check_for_file_mentions
编辑格式错误 / SEARCH 没匹配上base_coder.py:23152327apply_updates 捕获 ValueError
lint 报错且用户同意修base_coder.py:1606
测试失败且用户同意修base_coder.py:1622

一句话:Aider 把所有"出岔子"统一成"再给模型一条说明,让它自己改"。 这就是它的自纠本质——简单、可解释、有硬上限防止死循环。

4.2 一次 send_message 都干了啥

send_messagebase_coder.py:1419)是单轮的全部动作,顺序很重要:

1. 把用户消息加进 cur_messages
2. format_messages → 组装上下文 chunks(见 4.3)
3. check_tokens:超限就提示并征求是否硬发
4. warm_cache:后台线程定期 ping,保活 prompt 缓存
5. send:流式调 LLM(litellm),带重试/超限/中断处理
6. check_for_file_mentions:模型要更多文件?→ 反思
7. apply_updates:解析+落编辑(第 1、2 章)
8. 有编辑 → auto_commit(git 提交)
9. auto_lint → 失败可反思
10. run_shell_commands:执行模型建议的 shell(需确认)
11. auto_test → 失败可反思

注意第 5 步的健壮性(base_coder.py:1456-1512):指数退避重试可重试异常、ContextWindowExceededError 标记 exhaustedFinishReasonLength(输出被截断)会用 assistant prefill 续写(前提是模型支持,base_coder.py:1492-1505)。

4.3 上下文组装:为缓存而生的固定顺序

format_chat_chunksbase_coder.py:1226)把上下文拆成命名段落,装进 ChatChunkschat_chunks.py)。all_messages 的拼接顺序是写死的chat_chunks.py:16-26):

system → examples → readonly_files → repo → done → chat_files → cur → reminder
(系统提示)(示例对话) (只读文件) (repo map)(历史) (会话文件) (本轮)(提醒)
└──────────── 越靠前越稳定、越该被缓存 ───────────┘ └─ 每轮在变 ─┘

为什么这个顺序? 为了 prompt 缓存。稳定不变的内容(系统提示、示例、repo map、只读文件)放前面,变化的(本轮消息、提醒)放后面。add_cache_control_headerschat_chunks.py:28-41)在几个稳定段的末尾cache_control: ephemeral 标记,让 Anthropic 等支持前缀缓存的提供商命中缓存、省钱省延迟。

还有个后台"保活"细节:warm_cachebase_coder.py:1340)起一个守护线程,每隔约 5 分钟发一个 max_tokens=1 的请求 ping 一下,避免缓存过期(base_coder.py:1357-1390)。

提醒位置因模型而异。 system_reminder(SEARCH/REPLACE 规则)放系统消息还是塞进最后一条用户消息,取决于 main_model.reminderbase_coder.py:1320-1329)——有些模型对系统消息里的指令更听话,有些则相反。

4.4 选择围栏:避开和代码冲突

回复里的代码用什么"围栏"包?默认 ```,但如果文件内容本身就含三反引号(比如 Markdown 文件),就会冲突。choose_fencebase_coder.py:609)扫一遍所有会话文件,从候选列表 all_fencesbase_coder.py:77-85:三反引号、四反引号、<source><code> 等)里挑一个文件内容里没出现过的(base_coder.py:620-624)。这是个小而务实的健壮性设计。

4.5 git 兜底:自动提交与归属

有编辑落地后,auto_commitbase_coder.py:2375)调 GitRepo.commitrepo.py:131)。两个要点:

提交信息由 LLM 生成。 get_commit_messagerepo.py:326)把 diff 喂给(通常更弱更便宜的)模型,让它写一句 commit message,支持指定语言(repo.py:336-339)。

作者/提交者归属有一套规则。 commit 里区分"AI 改的(aider_edits=True)"与"用户改的",再叠加多个 --attribute-* 开关(repo.py:218-267),决定:

维度默认行为(AI 编辑)
Author改成 You (aider)
Committer改成 You (aider)
Co-authored-by 尾注不加(除非显式开 --attribute-co-authored-by
commit message 前缀不加 aider:(除非开对应开关)

实现上用 contextlib.ExitStack 临时设 GIT_AUTHOR_NAME/GIT_COMMITTER_NAME 环境变量再提交(repo.py:296-311),提交完自动还原。还支持 --no-verify 跳过 pre-commit 钩子(repo.py:278-279)。

这套 git 集成换来一个大好处:每一步 AI 编辑都是一个可回退的提交,用户随时 /undo,等于给"让 AI 改我代码"上了保险。

4.6 编辑格式工厂与"架构师/编辑"分工

工厂: Coder.createbase_coder.py:125)按 edit_formatcoders.__all__ 里找匹配的子类实例化(base_coder.py:190-194)。切换格式时若历史消息会"教坏"新模型,还会先把历史摘要掉base_coder.py:161-168),避免新模型模仿旧格式。

架构师/编辑分工(architect/editor split): ArchitectCoderarchitect_coder.py)让一个强模型先"出方案"(不直接产编辑),用户确认后,Coder.create 出一个 editor coder(可用另一个更擅长输出 diff 的模型)把方案落成实际编辑(architect_coder.py:20-44)。这是一种"规划与执行"分离的轻量编排——仍然没有跳出"Coder + 反思循环"的框架,只是把一轮拆成两个角色。

4.7 横向对比(同 shelf)

维度Aider 的取舍
编辑落地文本补丁 + 容错匹配,不靠 function-calling 写文件——赌"模型最熟文本格式"
自主性朴素反思环(≤3 轮),不做长程 ReAct/计划树——可解释、可控、不易跑飞
上下文PageRank 仓库地图 + 缓存友好的固定段落顺序——为大仓库和省钱优化
安全兜底git 每步提交 + 用户确认 shell——对"AI 改真实代码"的现实让步

相比走 function-calling + 长程规划的编码 agent,Aider 更像"把 LLM 当成一个会写 diff 的同事,自己负责把 diff 安全落地"。工程重心在落地的可靠性而非编排的复杂度

4.8 边界与局限

  • 反思上限 3 轮:复杂错误可能修不完就停(base_coder.py:101939-941)。
  • 强依赖 SEARCH 段质量:模型若大段记错代码,容错阶梯也救不回,只能反复回灌。
  • shell 命令需人工确认:不会自动执行(base_coder.py:2456-2462),安全但不全自动。
  • repo map 在超大仓库可能被自动禁用(见第 3 章)。

代码地图

主题文件符号
反思循环aider/coders/base_coder.pyrun_onereflected_messagemax_reflections
单轮编排aider/coders/base_coder.pysend_messageapply_updatesrun_shell_commands
上下文段落aider/coders/base_coder.pyaider/coders/chat_chunks.pyformat_chat_chunksChatChunks.all_messages
prompt 缓存aider/coders/chat_chunks.pyaider/coders/base_coder.pyadd_cache_control_headerswarm_cache
围栏选择aider/coders/base_coder.pychoose_fenceall_fences
git 提交aider/repo.pyGitRepo.commitget_commit_message
归属规则aider/repo.pycommitattribute_* 段)
工厂aider/coders/base_coder.pyCoder.create
架构师/编辑aider/coders/architect_coder.pyArchitectCoder.reply_completed