aider — 本课题摘录
读了哪几篇: 04-loop-and-git(主循环、反思自纠、上下文组装)、01-edit-formats(编辑格式)。
其余几篇本轮没读。
这一家对本课题第一个决定("一轮的输入怎么拼")给了最硬的一条约束,而且理由是钱。
它对本课题回答了什么
决定一:段落顺序是写死的,为缓存服务
上下文被拆成命名段落,拼接顺序写死:
系统提示 → 示例对话 → 只读文件 → 仓库地图 → 历史 → 会话文件 → 本轮 → 提醒
└──────────── 越靠前越稳定、越该被缓存 ────────────┘ └── 每轮在变 ──┘
在几个稳定段的末尾打缓存标记,让支持前缀缓存的厂商命中缓存。 (依据:Agent 库 · Aider · 主循环、反思自纠、上下文与 git 兜底 —— ChatChunks 的拼接顺序写死为 system→examples→readonly_files→repo→done→chat_files→cur→reminder,稳定的在前变化的在后,并在稳定段末尾打 cache_control 标记以命中前缀缓存)
这是本课题第一个决定的一条可直接落地的规矩: "一轮的输入怎么拼"不是随意的,顺序由"哪些会变"决定,而不是由"哪些重要"决定。
前面 kun 说"请求被硬切成两段"、agentscope 说"变的东西追加在末尾"——aider 这一份给出了具体的段落清单。 我们的最小原型可以直接照这张单子排,只是段落更少。
一个后台细节:起一个守护线程,每隔约五分钟发一个只要一个 token 的请求 ping 一下,避免缓存过期。
"缓存会过期,所以要保活"这件事别家没提。 长时间没动作后第一次请求会全价重算。 对我们的最小原型不需要,但知道有这回事。
还有一条很实在 的经验:提醒(格式规则)放系统消息还是塞进最后一条用户消息,取决于模型。 有些模型对系统消息里的指令更听话,有些则相反。
这条打破了一个想当然:"规则当然写系统提示里"。 实际是同一条规则放不同位置,遵从率不一样——这跟 onyx 的"换个位置遵从率从 30% 变 90%"是同一个发现。
决定四:所有失败收敛成同一个字段,循环靠它转
它的循环朴素到只有五行:
while 还有话要说:
这一轮的反思消息 = 空
发一轮(调模型 → 落改动 → 验证)
如果没有反思消息 → 结束
如果反思次数 ≥ 3 → 停
下一轮的输入 = 这条反思消息
关键在:什么会写这个字段。四类失败全收敛到同一处——
| 触发源 | 说的是 |
|---|---|
| 模型提到了不在会话里的文件 | 需要补料 |
| 编辑格式错误 / 要改的旧代码没匹配上 | 它自己发明的格式解析失败 |
| 代码检查报错且用户同意修 | 外部验证失败 |
| 测试失败且用户同意修 | 外部验证失败 |
(依据:Agent 库 · Aider · 主循环、反思自纠、上下文与 git 兜底 —— run_one 的循环以 reflected_message 是否被写为唯一续转理由,上限 max_reflections=3;文件缺失、编辑格式错误、lint 失败、测试失败四类全收敛到这一个字段)
"所有出岔子统一成:再给模型一条说明,让它自己改"——这就是一个 agent 循环的最小形态。 它甚至没有工具调用:模型只是回一段带补丁的文字。
这一条对我们有直接启发: 本课题第四个决定("什么时候停")的另一面是"什么时候不停"。 aider 把"不停"的所有理由收敛成一个字段,于是"什么时候停"就变成一句话:这个字段是空的。
这比"检查五个条件"清爽得多,值得抄。
硬上限三轮防死循环。
决定二:不用工具调用,让模型在回复里夹一段补丁文本
它的理由跟 agenticseek 同源但更具体:
模型在海量代码/文档上见过的"diff""冲突标记""代码块",远多于某个具体工具的参数格式。 用模型最熟悉的文本格式,它出错率最低。
所以默认格式设计成酷似版本控制的冲突标记。 (依据:Agent 库 · Aider · 编辑格式:LLM 怎么「说出」一次改动 —— 不让模型 function-calling 写文件,而是让它在自然语言回复里嵌一段约定格式的补丁块,理由是 LLM 见过的 diff/冲突标记远多于某个具体工具的 JSON schema)
而且格式按模型能力分三档: 强模型用"旧块换新块",爱偷懒省略的模型用标准差异格式,弱模型直接回完整文件。
"按模型能力选表达格式"这条跟 open-interpreter 的"按模型伪装成它熟悉的外壳"同源。 本课题第二个决定("怎么认出模型要调工具")因此多了一个维度:不同模型可能要用不同的方式问。
解析失败立即变成可回灌的报错:把"已处理到的文本 + 一个指向出错位置的标记"拼进异常——这条报错最终成为下一轮的输入。
又一次印证 agenticseek 那条:给模型的错误信息要是一条修改指令。 aider 更进一步:错误信息里带"你写到这里出的错"的定位。
一条小而务实的健壮性设计:围栏要挑一个文件里没出现过的
回复里的代码用什么符号包起来?默认三个反引号,但如果文件内容本身就含三个反引号(比如说明文档),就会冲突。
做法:扫一遍所有会话文件,从候选清单里挑一个文件内容里没出现过的。
凡是"用分隔符从文本里切结构"的方案,都要处理"分隔符出现在内容里"。 这是自定义格式这条路的第二笔税(第一笔是 agenticseek 的反缩进)。
一条健壮性:输出被截断时用"续写"接上
发请求那一步的处理:可重试的异常做指数退避、超窗错误单独标记、输出被截断则用"让模型接着上次写"的方式续写(前提是模型支持)。
对比 kun 的"截断了就发一条警告然后停"——aider 是"截断了就接着写"。 两种都对,取决于场景: kun 是长任务要人来判断,aider 是一次编辑必须写完。
它没回答什么
- 并发——它是单模型单轮,没有一批工具并行的问题。
- 历史怎么压——这两篇只讲了拼接顺序和超限提示,没讲压缩。
- 工具调用的结果怎么回填——它没有工具调用。
坑与代价
- "所有失败收敛成一个字段"的代价是失败原因被抹平。 格式错和测试挂被同等对待,都只是"再来一轮"。
判断(无锚): 我们可以保留这个结构但让字段带一个原因标签,这样"连续三次都是格式错"和"三次都是测试挂"能被区分开。 如果错,会错在: 如果三轮上限本来就很短、且每次都会把原文回灌,那模型自己看得到区别,加标签是多余的抽象。
- 上限三轮是硬编码的经验值。
- 段落顺序写死意味着加一类新内容要决定它排在哪。 排错位置就会毁掉缓存——这个代价不显式,不会报错,只是账单变贵。