Zed 编辑预测管线深度解析:repair.md 提示词与 LLM 自修复机制
本文以 Zed 仓库中 repair.md 这份"修复请求"提示词模板为核心,完整解析其字段结构、NO_EDITS 与 KEEP_PREVIOUS 两个哨兵输出的设计,并结合 repair.rs 的源码说明模板占位符如何被填充、质量反馈如何从 QA 评审或自动评分中派生,以及 ep repair 命令在多轮对话中如何驱动 LLM 对低质量代码编辑预测进行二次修复。读完本文,你能完整理解 Zed 编辑预测(edit prediction)离线评测管线中"预测—评审—修复"闭环的提示词工程细节与执行流程。
修复请求在管线中的定位
Zed 的 edit_prediction_cli(命令行名为 ep)是一套离线评测与蒸馏工具,围绕"观察程序员最近的编辑历史、预测下一次编辑"这一任务组织。其典型管线为:
- 构造上下文与提示词(
context/format-prompt); - 由教师模型(teacher)生成预测(
predict,对应 teacher.md); - 由 LLM-as-a-judge 评审预测质量(
qa,对应 qa.md)或用自动指标打分(score); - 对质量不佳的预测发起修复(
repair,对应本文的 repair.md)。
repair.md 是这条链路的第四个提示词文件(与 teacher.md、teacher_jumps.md、qa.md 并列,见 prompts/ 目录)。从 main.rs 中 Command::Repair(repair::RepairArgs) 的定义可以看到,它的用途是"Repair predictions that received poor QA scores by generating improved predictions"——针对收到较差 QA 评分的预测生成改进后的预测。
它的关键设计前提是:修复不是重新出题,而是"续写对话"。原始教师提示词(Turn 1)和教师的原始回答(Turn 2)会被原样保留在上下文中,repair 消息作为第三轮用户消息追加,要求模型在"知道自己上一轮做了什么、且看到了质量反馈"的前提下重做一遍。这样既避免了修复请求与原始任务规则之间的规则漂移,也让模型能对照自身此前的错误进行自我纠正。
模板结构逐段解析
repair.md 全文很短,但每一段都对应 repair.rs 中的一个数据源或一条硬性约束。
开场白:声明这是修复请求
Your previous prediction has quality issues that need to be addressed. Please generate an improved prediction.
这句话确立了本轮的角色:模型此前已给出一条预测,现在要基于反馈产出改进版。它隐含了"上一轮输出仍在你的记忆中"这一前提,这正依赖多轮对话结构(下文详述)。
第一段填充:质量反馈 {quality_feedback}
## Quality Feedback
{quality_feedback}
{quality_feedback} 由 build_repair_message 填入,其内容取决于该样本质量信号的来源,二选一:
-
QA 反馈优先。若样本已有 QA 结果,
build_qa_feedback从 QA JSON 中提取三项信息,格式化为:- **Reverts user edits**: yes/no/unknown - **Confidence score**: N/5 - **Reasoning**: <QA 模型的推理文字>这三个字段与 qa.md 要求的评审输出完全对应:
reverts_edits(预测是否撤销了用户在编辑历史中刻意做的修改)、confidence(用户接受该建议的可能性,1=必然拒绝 … 5=必然接受)以及reasoning(推理说明)。QA 评审时模型看到的就是"编辑历史 + 带<|editable_region_start|>/<|editable_region_end|>标记的当前文件 + 以 word-diff 形式呈现的预测补丁",因此 QA 的结论天然与修复请求共享同一份证据。 -
自动评分兜底。若没有 QA 结果,
build_score_feedback改用score命令计算出的自动指标,按规则生成问题清单:reversal_ratio > 0.9:提示"预测可能在撤销用户刻意做的修改",同时允许模型自证——若这些修改其实是意图上的延续而非回退,可以保持原预测不变;wrong_editable_region == true:提示"预测可能修改了可编辑区域之外的代码,或与可编辑区域边界未对齐";discarded_chars > 80 且 exact_lines_fp > 5:提示"预测可能过大或过于投机",并给出更聚焦的示例(只预测函数轮廓不预测函数体、只预测第一个逻辑步骤),最后强调"预测越小,越有可能正确"。
该兜底反馈的尾部固定附加一句:如果此前预测其实是对的,输出
KEEP_PREVIOUS;如果完全不该做修改且不知道如何改进,输出NO_EDITS。这两个出口是刻意设计的,防止修复环节"为了修复而修复"。
第二段填充:上一轮预测的 word-diff 形式
## Your Previous Prediction (word-diff format)
{actual_patch_word_diff}
```
`{actual_patch_word_diff}` 由 `unified_to_word_diff`(见 [word_diff.rs](https://gitcode.com/GitHub_Trending/ze/zed/blob/b1a7ef0cf66dfbf9d7661170c96d97c7df916c68/crates/edit_prediction_cli/src/word_diff.rs?utm_source=gitcode_repo_files))生成,把行级 unified diff 转换为词级 diff:删除内容以 `[-...-]` 标记、插入内容以 `{+...+}` 标记。这与 QA 评审时看到的格式一致([qa.md](https://gitcode.com/GitHub_Trending/ze/zed/blob/b1a7ef0cf66dfbf9d7661170c96d97c7df916c68/crates/edit_prediction_cli/src/prompts/qa.md?utm_source=gitcode_repo_files) 开头声明"All diffs are in the word-diff format"),使模型在反馈与证据之间无需再做格式转换。
### 第三段填充:Token 变更统计 `{token_change_info}`
```
{token_change_info}
```
由 `count_patch_token_changes`(定义在 [edit_prediction_metrics](https://gitcode.com/GitHub_Trending/ze/zed/blob/b1a7ef0cf66dfbf9d7661170c96d97c7df916c68/crates/edit_prediction_metrics/src/edit_prediction_metrics.rs?utm_source=gitcode_repo_files),经 [metrics.rs](https://gitcode.com/GitHub_Trending/ze/zed/blob/b1a7ef0cf66dfbf9d7661170c96d97c7df916c68/crates/edit_prediction_cli/src/metrics.rs?utm_source=gitcode_repo_files) 再导出)计算补丁的删除/插入 token 数,格式为:
```
## Token Change Statistics
- **Deleted tokens**: N
- **Inserted tokens**: M
```
若删除或插入 token 数任一超过 100,还会追加一条提示:"token 变更量偏高,考虑产出更聚焦的编辑,只改真正需要改的行,而不是重写大段代码"。这是把"补丁规模"作为一条显式信号喂给模型,与上面兜底反馈中"预测越小越可能正确"的原则呼应。
### 核心规则:继承并重申三条底线
```
## Instructions
Generate an improved prediction following the same rules and output format from the original instructions. The key rules remain:
- **NEVER undo or revert the user's recent edits** — if a line was removed in the edit history, do NOT restore it
- If your prediction would make the code more similar to what it was BEFORE the user's edit, output `NO_EDITS` instead
- When uncertain, predict only the minimal, high-confidence portion of the edit
```
这段是 repair 提示词中最"有立场"的部分。它刻意不复述 [teacher.md](https://gitcode.com/GitHub_Trending/ze/zed/blob/b1a7ef0cf66dfbf9d7661170c96d97c7df916c68/crates/edit_prediction_cli/src/prompts/teacher.md?utm_source=gitcode_repo_files) 的全部规则(多轮对话里原始规则已在 Turn 1),只钉住最常被修复场景违反的三条:
1. **绝不撤销用户的最近编辑**——编辑历史中被删掉的行(`-` 开头)不得恢复。这与 teacher.md 中"即使删除让代码看起来残缺也不要'补全'被删内容"的规则一致;
2. **"向编辑前状态靠拢"即失败**——如果改进后的预测会让代码更像用户编辑之前的样子,直接输出 `NO_EDITS`。这条给了模型一个可操作的判据,而不是抽象的"尊重用户意图";
3. **不确定时只预测最小的高置信片段**——针对修复轮次中模型倾向于"大改以挽回面子"的常见失效模式。
### 输出格式:两个不相交的哨兵
```
## Output Format
Follow the same output format as before, with one addition:
- If the code is complete as-is and no edits should be made, output `NO_EDITS`
- **NEW: If your previous prediction was actually correct** (the quality feedback was overly cautious), output `KEEP_PREVIOUS`:
KEEP_PREVIOUS
Use `KEEP_PREVIOUS` when you determine the original prediction correctly addresses the user's intent despite the feedback.
**Important:** `NO_EDITS` and `KEEP_PREVIOUS` are NOT interchangeable:
- `NO_EDITS` = make zero changes to the code (discard the previous prediction)
- `KEEP_PREVIOUS` = the previous prediction is correct, use it as-is
```
这是 repair 相对 teacher 输出格式的唯一增量,设计上非常值得注意:
- 常规情况仍按 teacher 的输出格式返回:一段意图解释 + 一个以 `<|editable_region_start|>` 开头、`<|editable_region_end|>` 结尾的代码块(可含 `<|user_cursor|>` 标记),代码块内容被解析为对可编辑区域的统一 diff;
- `NO_EDITS` 表示"代码已完整,零修改",即**丢弃上一轮预测**;
- `KEEP_PREVIOUS` 表示"上一轮预测其实是对的,反馈过于谨慎",即**原样采用上一轮预测**。
模板用加粗的 "NOT interchangeable" 段落显式区分二者,因为从语义看它们极易混淆:都是"不产生新的补丁文本",但对数据管线而言一个意味着回退、一个意味着确认。这种"在提示词里消除歧义"的做法,直接对应解析侧的哨兵识别逻辑(见后文 `parse` 函数)。
### 收尾:先解释后输出
```
## Your Improved Prediction
Briefly explain what was wrong with your previous prediction (or why it was actually correct), then provide the improved output.
```
要求模型先做一句自评(错在哪,或为何原本就对),再给改进输出。这是典型的"先推理后答案"(self-critique)技巧:强制模型在生成补丁前显式对齐反馈内容,降低"无视反馈、原样重吐一遍"的概率。
## 源码视角:模板如何被装配与投递
### 占位符填充
[repair.rs](https://gitcode.com/GitHub_Trending/ze/zed/blob/b1a7ef0cf66dfbf9d7661170c96d97c7df916c68/crates/edit_prediction_cli/src/repair.rs?utm_source=gitcode_repo_files) 中的 `build_repair_message` 完成了模板的全部装配:
```rust
let quality_feedback = build_qa_feedback(example)
.or_else(|| build_score_feedback(example))
.context("no quality feedback available (need either QA results or computed scores)")?;
let actual_patch_word_diff = unified_to_word_diff(actual_patch);
// ...
let prompt_template = crate::prompt_assets::get_prompt("repair.md");
Ok(prompt_template
.replace("{actual_patch_word_diff}", &actual_patch_word_diff)
.replace("{quality_feedback}", &quality_feedback)
.replace("{token_change_info}", &token_change_info))
```
要点:
- 质量反馈来源是**有序的**:先尝试 QA 反馈,取不到再落到自动评分;两者都没有会直接报错("no quality feedback available"),即 `repair` 命令要求样本至少已完成 `qa` 或 `score` 步骤;
- 上一轮预测必须已存在 `actual_patch`(即先跑过 `predict` 并解析输出),否则报错提示 "run predict first";
- 模板文件的加载走 [prompt_assets.rs](https://gitcode.com/GitHub_Trending/ze/zed/blob/b1a7ef0cf66dfbf9d7661170c96d97c7df916c68/crates/edit_prediction_cli/src/prompt_assets.rs?utm_source=gitcode_repo_files) 的 `get_prompt("repair.md")`:默认构建把 `src/prompts` 目录下的文件嵌入二进制;启用 `dynamic_prompts` feature 后则改为运行时从磁盘读取并缓存,方便不重新编译就迭代提示词。
### 需要修复的判定:`needs_repair`
并非每个样本都要送修复。`needs_repair(example, confidence_threshold)` 的判定顺序是:
1. **有 QA 结果时只看 QA**:`reverts_edits == Some(true)` 直接判为需要修复;否则若 `confidence <= confidence_threshold` 判为需要修复;QA 存在但两项都不命中则**不需要**修复(不再看自动评分);
2. **无 QA 时回退到自动评分**:`reversal_ratio > 0.9` 或 `wrong_editable_region == Some(true)` 时判为需要修复;
3. 都没有则不修复。
这里的阈值由命令行参数 `--confidence-threshold` 控制(1–5 的置信度尺度,默认 2),语义是"修复所有置信度 <= 该值的预测"。换句话说,默认只修复 QA 置信度 1 或 2 的预测——也就是"大概率/必然被用户拒绝"的那一档;`reverts_edits` 为真则不受阈值影响,一律修复。
### 多轮对话的组装
`run_repair` 向 LLM 发送的是一次三消息(四轮角色)对话:
| 轮次 | 角色 | 内容 |
| --- | --- | --- |
| Turn 1 | User | 原始教师提示词 `example.prompt.input`(即 [teacher.md](https://gitcode.com/GitHub_Trending/ze/zed/blob/b1a7ef0cf66dfbf9d7661170c96d97c7df916c68/crates/edit_prediction_cli/src/prompts/teacher.md?utm_source=gitcode_repo_files) 装配好的完整提示) |
| Turn 2 | Assistant | 教师模型的原始回答 `example.predictions[0].actual_output` |
| Turn 3 | User | `build_repair_message` 生成的修复请求(即 repair.md 装配结果) |
| Turn 4 | Assistant | 改进后的预测(解析目标) |
Anthropic 与 OpenAI 两个后端各自按 API 的消息格式组装同样的三轮(见 [repair.rs](https://gitcode.com/GitHub_Trending/ze/zed/blob/b1a7ef0cf66dfbf9d7661170c96d97c7df916c68/crates/edit_prediction_cli/src/repair.rs?utm_source=gitcode_repo_files) 中 `anthropic::Message` 与 `open_ai::RequestMessage` 两条分支),`max_tokens` 均为 16384。
### 后端与模型选择
`model_for_backend` 把后端映射到具体模型:
```rust
match backend {
BatchProvider::Anthropic => "claude-sonnet-4-6",
BatchProvider::Openai => "gpt-5.2",
}
```
默认后端为 `anthropic`。客户端有四种静态实例(`OnceLock` 缓存):Anthropic/OpenAI 各分 batch 与 plain 两套;batch 模式会把请求登记到 `LLM_CACHE_DB`(见 [paths.rs](https://gitcode.com/GitHub_Trending/ze/zed/blob/b1a7ef0cf66dfbf9d7661170c96d97c7df916c68/crates/edit_prediction_cli/src/paths.rs?utm_source=gitcode_repo_files))走提供商的批量推理接口,plain 模式(`--no-batch`)直接同步调用。
### 幂等性与前置检查
`run_repair` 开头有两道快速返回:
- `has_successful_repair(example)`:样本中已存在 `provider == PredictionProvider::Repair` 且 `actual_patch` 非空的预测则跳过,保证重复运行不重复消耗 LLM 调用;
- `!needs_repair(...)` 同样跳过。
随后是前置依赖校验,任一不满足都会携带明确的动作提示失败:缺 `prompt_inputs` → "run context retrieval first";`predictions` 为空或缺 `actual_patch` → "run predict first";缺 `prompt` → "run format_prompt first";教师回答为空 → "run predict first"。这些检查把 repair 在管线中的先后顺序固化成了错误信息。
### 批处理同步与报告
批处理模式下,`sync_batches` 上传待处理请求并下载已完成结果;`--wait` 时 `wait_for_batches` 以 30 秒为间隔轮询 `pending_batch_count`,归零后退出,再由 `reprocess_after_batch_wait` 对尚未成功修复且仍需修复的样本重跑解析落库。全部完成后 `print_report` 输出汇总:
```
Repair summary (N examples):
X/N didn't need repair (confidence > T)
M/N needed repair:
R repaired successfully
F failed to repair
```
修复结果作为一条新的 `ExamplePrediction` 追加到 `example.predictions`,`provider` 标记为 `PredictionProvider::Repair`(在 [main.rs](https://gitcode.com/GitHub_Trending/ze/zed/blob/b1a7ef0cf66dfbf9d7661170c96d97c7df916c68/crates/edit_prediction_cli/src/main.rs?utm_source=gitcode_repo_files) 的 provider 序列化中显示为 `"repair"`,与 `teacher`、`zeta2` 等并列),从而与原始教师预测共存,便于后续对比评估修复前后的指标变化。
## 输出解析:KEEP_PREVIOUS 哨兵如何落地
repair 的响应由 [parse_output.rs](https://gitcode.com/GitHub_Trending/ze/zed/blob/b1a7ef0cf66dfbf9d7661170c96d97c7df916c68/crates/edit_prediction_cli/src/parse_output.rs?utm_source=gitcode_repo_files) 中的分发函数路由到 `repair::parse`:
```rust
PredictionProvider::Repair => repair::parse(example, actual_output),
```
`parse` 的逻辑([repair.rs](https://gitcode.com/GitHub_Trending/ze/zed/blob/b1a7ef0cf66dfbf9d7661170c96d97c7df916c68/crates/edit_prediction_cli/src/repair.rs?utm_source=gitcode_repo_files)):
```rust
if actual_output.contains(KEEP_PREVIOUS) {
// 直接复制上一轮预测的 actual_patch 与 actual_cursor
return Ok((patch, cursor));
}
TeacherPrompt::parse(example, actual_output)
```
- 出现 `KEEP_PREVIOUS` 时,不重新解析补丁,而是**原样拷贝**原始预测的 `actual_patch` 和 `actual_cursor`,保证"保留"与原始数据逐字节一致;
- 否则委托 `TeacherPrompt::parse`,即与首轮预测完全相同的解析路径(意图文字 + 可编辑区域代码块 → 统一 diff 补丁 + 光标位置)。`NO_EDITS` 也在这条路径中被识别为空补丁。
配套的单元测试 `test_parse_keeps_previous_when_sentinel_appears_outside_last_codeblock` 验证了一个关键边界:即使模型在解释文字里写出 `` `KEEP_PREVIOUS` ``(且最后还有一个无关代码块),只要整段输出包含该哨兵,就按"保留原预测"处理。这与 repair.md 中哨兵放在独立代码块里的展示方式互为补充——模板教模型"干净地"输出哨兵,解析器则宽容地做包含匹配,双保险。
## 实操:如何运行 repair
在仓库根目录用 `ep`(即 `edit_prediction_cli`)执行,命令形如:
```bash
# 对 JSONL 数据集做修复(Anthropic 批量推理,等待批处理完成)
cargo run -p edit_prediction_cli -- repair --wait --in-place examples.jsonl
# 同步 API、指定后端与更激进的修复阈值
cargo run -p edit_prediction_cli -- repair --no-batch --backend openai \
--confidence-threshold 3 --in-place examples.jsonl
```
[RepairArgs](https://gitcode.com/GitHub_Trending/ze/zed/blob/b1a7ef0cf66dfbf9d7661170c96d97c7df916c68/crates/edit_prediction_cli/src/repair.rs?utm_source=gitcode_repo_files) 的参数一览:
| 参数 | 类型 / 默认值 | 说明 |
| --- | --- | --- |
| `--no-batch` | bool | 使用同步 API 而非批处理接口 |
| `--confidence-threshold` | u8,默认 `2` | 修复置信度 <= 该值(1–5 尺度)的预测;QA 缺失时回退到自动评分信号 |
| `--backend` | `anthropic` \| `openai`,默认 `anthropic` | LLM 提供商,分别映射到 `claude-sonnet-4-6` / `gpt-5.2` |
| `--wait` | bool | 等待所有批处理完成后解析结果并重新处理 |
此外,`ep` 的全局参数(见 [main.rs](https://gitcode.com/GitHub_Trending/ze/zed/blob/b1a7ef0cf66dfbf9d7661170c96d97c7df916c68/crates/edit_prediction_cli/src/main.rs?utm_source=gitcode_repo_files) 的 `EpArgs`)同样适用,如 `--limit`、`--offset`、`--name`、`--repo` 过滤样本,`--max-parallelism` 控制并发(默认 10),`-o`/`--markdown` 控制输出形态。典型执行顺序为:
```bash
ep context ... && ep format-prompt ... && ep predict ... && ep qa ... && ep repair --wait
# 或无 QA 时:
ep context ... && ep format-prompt ... && ep predict ... && ep score ... && ep repair
```
`repair` 本身不改写原始预测记录,只追加 `provider=repair` 的新预测条目;是否用修复版替换或对比评估,交由后续的评分与指标流程完成(例如 [edit_prediction_metrics](https://gitcode.com/GitHub_Trending/ze/zed/blob/b1a7ef0cf66dfbf9d7661170c96d97c7df916c68/crates/edit_prediction_metrics/src/edit_prediction_metrics.rs?utm_source=gitcode_repo_files) 中的 ΔchrF、kept rate、`reversal.rs` 中的 `compute_prediction_reversal_ratio_from_history` 等)。
## 小结
[repair.md](https://gitcode.com/GitHub_Trending/ze/zed/blob/b1a7ef0cf66dfbf9d7661170c96d97c7df916c68/crates/edit_prediction_cli/src/prompts/repair.md?utm_source=gitcode_repo_files) 这份不足 45 行的模板,是 Zed 编辑预测离线管线中一个设计密度很高的提示词:
- **结构上**,它把"质量反馈(QA 优先、自动评分兜底)+ 上一轮预测的 word-diff + token 规模统计"三类证据压缩进第三轮消息,让修复建立在原始任务规则与原始输出仍在上下文中的多轮对话之上,而非孤立的重写请求;
- **约束上**,它用"绝不撤销用户编辑、向编辑前靠拢即 `NO_EDITS`、不确定则最小化"三条可判据规则遏制修复轮的过度修改倾向,并用 token 统计的 100 阈值显式约束补丁规模;
- **出口上**,`NO_EDITS`(丢弃旧预测、零修改)与 `KEEP_PREVIOUS`(确认旧预测、原样采用)两个哨兵把"反馈可能误报"这一情形纳入了协议,解析侧用包含匹配 + 原样拷贝 + 单元测试闭环承接;
- **工程上**,`needs_repair` 的 QA/评分双通道判定、`confidence_threshold` 阈值、批处理缓存与幂等跳过,使修复成为管线中可重复、可低成本重放的独立阶段。
对从事代码补全/编辑预测系统研发的人来说,这份模板与其装配代码([repair.rs](https://gitcode.com/GitHub_Trending/ze/zed/blob/b1a7ef0cf66dfbf9d7661170c96d97c7df916c68/crates/edit_prediction_cli/src/repair.rs?utm_source=gitcode_repo_files))共同提供了一个可参考的范式:如何把"LLM 自评失败 → 定向修复 → 可解析哨兵输出"做成一个稳定、幂等且可审计的闭环。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00