Zed 编辑预测单元测试样例格式详解:flask--add-import-statement 案例精读
【免费下载链接】zedCode at the speed of thought – Zed is a high-performance, multiplayer code editor from the creators of Atom and Tree-sitter.项目地址: https://gitcode.com/GitHub_Trending/ze/zed
本文以 flask--add-import-statement.md 这个真实评测样例为切入点,逐段拆解 Zed 编辑预测(Edit Prediction)单元测试样例的完整结构——前置元数据、编辑历史、光标位置标注与多个候选期望补丁——并结合 example_spec.rs、example.rs 等源码说明这些 Markdown 字段如何被解析为ExampleSpec结构体、又如何参与预测与打分流程,帮助读者读懂乃至独立编写合规的评测样例。
1. 什么是编辑预测单元测试样例
Zed 的编辑预测功能(即行内代码续写)依赖一套完整的评测基础设施:给定"用户在某个仓库的某个修订版本上、光标处于某处、之前刚做过一串编辑"这一上下文,让模型产出一个补丁(patch)与新的光标位置,再与预先记录的"期望补丁"对比打分。crates/edit_prediction_cli/evals/目录存放的就是这一流程的单元测试样例,目录中按仓库名--行为描述的命名规则组织了 19 个样例,例如flask--add-import-statement、tree-sitter--if-let-to-match、vscode--add-async-and-await等。
从源码看,样例支持三种文件扩展名。example.rs 中的read_example_files函数按扩展名分发:.json与.jsonl直接反序列化为Example(后者每行一条),而.md文件则走parse_markdown_example,最终委托给ExampleSpec::from_markdown完成解析。本案例所属的.md格式正是人工撰写、代码评审和版本管理最友好的形态。
2. 样例文件逐段精读
下面完整列出 flask--add-import-statement.md 的全部内容,然后分段解释。该样例取材于 Flask 仓库的src/flask/logging.py:用户把一行合法的from werkzeug.local import LocalProxy误敲成了imfrom werkzeug.local import LocalProxy,光标停在坏行行首,期望模型把这行重写为正确的 import 语句。
2.1 前置元数据:锁定源仓库与修订版本
+++ repository_url = "https://github.com/pallets/flask" revision = "2fec0b206c6e83ea813ab26597e15c96fab08be7" +++文件以+++分隔的 TOML 前置块开头。example_spec.rs 中对应的FrontMatter结构体定义了两个必填字段与两个可选字段:
| 字段 | 必填 | 说明 |
|---|---|---|
repository_url | 是 | 评测上下文所属的 git 仓库地址,评测时会据此拉取 worktree 加载真实项目 |
revision | 是 | 精确的 commit 哈希,保证上下文可复现 |
tags | 否 | 样例标签列表,用于筛选 |
uncommitted_diff_requires_edit_history_rollback | 否 | 标记未提交 diff 是否包含需要回滚的编辑历史 |
2.2 Edit History:用户此前的编辑序列
## Edit History ```diff --- a/src/flask/logging.py +++ b/src/flask/logging.py @@ -4,7 +4,7 @@ import sys import typing as t -from werkzeug.local import LocalProxy +imfrom werkzeug.local import LocalProxy from .globals import request`## Edit History` 段落用一段(或多段)统一 diff 记录"预测发生之前用户刚刚做过的编辑"。这是编辑预测与静态补全的本质区别:模型看到的不仅是文件内容,还有用户行为的"最近趋势"。本样例中,用户把正确的 import 行改成了 `imfrom ...` 这种残缺写法,模型需要判断这是输入中的中间态并给出整行重写。 该段落还支持一个可选约定:在某段 diff 之前单独写一行 `// User accepted prediction:`。同目录的 [flask--rename-accepted-prediction.md](https://link.gitcode.com/i/68d3abf83b140e35774155a1a9f9084b) 就用了它,表示紧随其后的那段 diff 并非用户手打、而是"用户接受了一条模型预测"产生的编辑——这对训练与蒸馏是有价值的信号。解析逻辑在 [example_spec.rs](https://link.gitcode.com/i/c8731f66782f34f30026fc785d190fbd#L79) 中以常量 `ACCEPTED_PREDICTION_MARKER` 实现,`from_markdown` 状态机识别到该标记后会在合并编辑历史时重新插入这行注释,保证序列化/反序列化往返一致(同文件测试 `test_from_markdown_accepted_prediction_marker` 验证了这一点)。 ### 2.3 Cursor Position:文件节选与光标标记 ```text ## Cursor Position ```src/flask/logging.py from __future__ import annotations import logging import sys import typing as t imfrom werkzeug.local import LocalProxy # ^[CURSOR_POSITION] from .globals import request if t.TYPE_CHECKING: # pragma: no cover from .sansio.app import App`## Cursor Position` 段落的代码围栏由两部分构成: 1. **围栏 info string 是光标所在文件路径**(本例为 `src/flask/logging.py`)。解析器把围栏 info 直接存入 `cursor_path`,见 [example_spec.rs](https://link.gitcode.com/i/c8731f66782f34f30026fc785d190fbd#L393-L396) 中 `Section::CursorPosition` 分支:`spec.cursor_path = Path::new(block_info)`。 2. **围栏内容是该文件的光标周边节选(excerpt),并内嵌一行光标标记**。标记行以语言注释形式写在光标行的下一行,包含 `[CURSOR_POSITION]` 字符串和一个指向光标列的箭头。 箭头有两种语法,定义在 [cursor_excerpt()](https://link.gitcode.com/i/c8731f66782f34f30026fc785d190fbd#L417-L473) 的文档注释与实现中: | 箭头 | 光标列语义 | | --- | --- | | `^` | 光标列 = `^` 字符在标记行中的位置(本例 `# ^[CURSOR_POSITION]` 中 `^` 位于第 0 列,即 `imfrom` 行行首) | | `<` | 光标列 = 光标行上第一个非空白字符的位置(用于列位置小于注释前缀长度的场景) | 解析时,`cursor_excerpt()` 会定位 `[CURSOR_POSITION]`(常量 `CURSOR_POSITION_MARKER`,定义于 [udiff.rs](https://link.gitcode.com/i/4c8837e9856d65da1788b34bbdfec3cc)),算出标记行范围,按 `^` 或 `<` 规则得出光标列,把光标定位在**标记行的上一行**,然后删除标记行、修剪尾部空行,返回"纯文本节选 + 节选内光标字节偏移"。若文本中直接内嵌了 `<|user_cursor|>`(常量 `INLINE_CURSOR_MARKER`),则走更简单的内联分支:删除标记、其位置即光标偏移。[example_spec.rs](https://link.gitcode.com/i/c8731f66782f34f30026fc785d190fbd#L520-L657) 的 `test_cursor_excerpt_with_caret` 测试覆盖了行首、行内、行尾、文件末尾等边界列位,`test_cursor_excerpt_with_inline_marker` 则验证内联标记。 ### 2.4 Expected Patch:多个可接受的期望补丁 ```diff ## Expected Patch ```diff --- a/src/flask/logging.py +++ b/src/flask/logging.py @@ -1,21 +1,21 @@ from __future__ import annotations import logging import sys import typing as t -imfrom werkzeug.local import LocalProxy +import # ^[CURSOR_POSITION] +from werkzeug.local import LocalProxy from .globals import request--- a/src/flask/logging.py +++ b/src/flask/logging.py @@ -1,21 +1,21 @@ from __future__ import annotations import logging import sys import typing as t - -imfrom werkzeug.local import LocalProxy +import werkzeug +# ^[CURSOR_POSITION] +from werkzeug.local import LocalProxy from .globals import request本样例的 `## Expected Patch` 段包含**两个** diff,这是该格式的关键能力之一:同一个 prompt 允许存在多个都被判为正确的期望输出。两个候选补丁都完成"把 `imfrom` 坏行修成正确 import"这一意图,但终态与光标落点不同: - 候选一:该行变为 `import`,光标停在 `import` 之后(第 6 列,`# ^[CURSOR_POSITION]` 中 `^` 的位置); - 候选二:该行变为 `import werkzeug`,光标停在 `werkzeug` 之后(第 15 列)。 注意期望补丁里的光标标注复用了与 Cursor Position 段完全相同的"标记注释行"语法,只是出现在 diff 的 `+` 新增行之后。`ExampleSpec` 中 `expected_patches` 的类型是 `Vec<String>`([example_spec.rs](https://link.gitcode.com/i/c8731f66782f34f30026fc785d190fbd#L45)),解析时每个独立的 diff 代码块都会 push 进这个向量。`expected_patches_with_cursor_positions()`([example_spec.rs](https://link.gitcode.com/i/c8731f66782f34f30026fc785d190fbd#L489-L502))则把每个补丁展开为 `(补丁, Option<光标偏移>)` 二元组,文档注释明确:光标偏移是"应用补丁后的新文本中、相对于 hunk 起点的偏移"。与之配对的 `encode_cursor_in_patch` / `extract_cursor_from_patch` 实现在 [zeta_prompt 的 udiff 模块](https://link.gitcode.com/i/8cf86432955fccb741be205f3fa44e30),负责"含光标标注的补丁文本"与"干净补丁 + 光标偏移"之间的双向转换;同文件测试 `test_expected_patches_with_cursor_positions` 还验证了编码的幂等性。 ## 3. 源码级解析:Markdown 如何变成 ExampleSpec `ExampleSpec` 结构体([example_spec.rs](https://link.gitcode.com/i/c8731f66782f34f30026fc785d190fbd#L25-L54))是样例的核心数据模型,除本节已讲到的字段外,还包括: - `name`:样例名,缺省时取文件名(不含扩展名),见 [example.rs](https://link.gitcode.com/i/2432e3998eb78e151c970bd15799da1c#L270-L276) 中 `"md"` 分支对 `example.spec.name` 的兜底填充; - `reasoning` / `uncommitted_diff`:可选的"推理说明"与"未提交 diff"段落,分别对应 `## Reasoning`、`## Uncommitted Diff` 标题; - `recently_opened_files` / `recently_viewed_files`:对应 `## Recently Opened Files`、`## Recently Viewed Files` 段落,每行一个路径(可附 tab 分隔的光标偏移); - `rejected_patch`:对应 `## Rejected Patch` 段落,供 DPO(拒绝采样偏好训练)使用——[example.rs](https://link.gitcode.com/i/2432e3998eb78e151c970bd15799da1c#L64-L73) 的 `ExamplePrompt.rejected_output` 字段注释即标明 "For DPO"。 全部合法的二级标题常量集中在 [example_spec.rs](https://link.gitcode.com/i/c8731f66782f34f30026fc785d190fbd#L71-L79),解析器 `from_markdown`([L258](https://link.gitcode.com/i/c8731f66782f34f30026fc785d190fbd#L258) 起)用 `pulldown_cmark` 遍历 Markdown 事件流,维护一个 `Section` 状态机(`Start / UncommittedDiff / RecentlyOpenedFiles / RecentlyViewedFiles / EditHistory / CursorPosition / ExpectedPatch / RejectedPatch / Other`),按当前标题把代码块内容归位到对应字段。约束方面: - 标题层级只允许 H1(作为样例名)与 H2(作为段落),出现 H5 及更深层级会直接报错;缩进代码块(非围栏)也会报错; - **Cursor Position 是唯一硬性必填的段落**:解析结束时若 `cursor_path` 或 `cursor_position` 为空,`anyhow::bail!("Missing cursor position codeblock")`([example_spec.rs](https://link.gitcode.com/i/c8731f66782f34f30026fc785d190fbd#L410-L412)); - Edit History 可以为空,`to_markdown` 序列化空历史时会输出 `(No edit history)` 占位文本。 `Example` 结构([example.rs](https://link.gitcode.com/i/2432e3998eb78e151c970bd15799da1c#L21-L54))在 `ExampleSpec` 之上还挂载了运行期产物:`prompt_inputs`(光标节选、光标偏移、关联文件等 Zeta prompt 输入)、`prompt`(格式化后的 prompt 与期望输出)、`predictions`(模型实际预测)、`score`(与期望补丁的匹配分数)以及 `qa` 结果。也就是说,`.md` 样例只是"输入规格",跑完预测与打分后整个 `Example` 可序列化为 JSON 留档复现。 ## 4. 样例如何被消费:从格式化到打分 评测流程由 `edit_prediction_cli`(CLI 命令名 `ep`,见 [main.rs](https://link.gitcode.com/i/7699f3e72c612f4de19562f23ce4eeb6))串联,与本样例直接相关的环节有两个: **(1)格式化 prompt。** [format_prompt.rs](https://link.gitcode.com/i/65df2e2cc29f0e32f6b3db846ff844d8) 不存在——正确路径是 [crates/edit_prediction_cli/src/format_prompt.rs](https://link.gitcode.com/i/65df2e2cc29f0e32f6b3db846ff844d8)。其中 `TeacherPrompt` 把 `ExampleSpec` 渲染为教师模型提示词:编辑历史最多保留最后 128 行(`MAX_HISTORY_LINES`),光标节选被 `<|editable_region_start|>` / `<|editable_region_end|>` 包围、光标位置注入 `<|user_cursor|>` 标记([format_prompt.rs](https://link.gitcode.com/i/65df2e2cc29f0e32f6b3db846ff844d8#L143-L152)),关联文件上下文按 1024 token 预算截断。教师模型的响应再由 `TeacherPrompt::parse` 还原为统一 diff 与实际光标位置。 **(2)预测与打分。** [score.rs](https://link.gitcode.com/i/d51ca66bc0c97155ccea0ecd4c529feb) 的 `run_scoring` 先执行预测(可复用 JSON 中已存的 `predictions`),随后在后台任务中调用 `expected_patches_with_cursor_positions()` 取出所有期望补丁,经 `edit_prediction_metrics::prepare_expected_patches` 归一化后逐条调用 `score_prediction` 比对。因此本样例中"两个候选补丁"的语义就是:模型预测的补丁与光标位置只要与其中任意一个匹配,即被视为正确。评测的上下文检索量默认受 `EVAL_RELATED_CONTEXT_TOKENS_LIMIT = 4000`([score.rs](https://link.gitcode.com/i/c9bf31fa812bf0fae11ea1a14100e7e7))约束。 ## 5. 如何运行这些单元测试 仓库提供了现成的 CI 入口 [script/run-unit-evals](https://link.gitcode.com/i/73ea51ca0a59388ccd6bad4ddb4146a9),其核心就是一行 nextest 调用: ```bash GPUI_TEST_TIMEOUT=1500 cargo nextest run --workspace --no-fail-fast \ --features unit-eval --no-capture -E 'test(::eval_)'即:开启unit-evalfeature、用::eval_过滤器只跑评测相关测试、不捕获输出以便观察进度,并把测试超时放宽到 1500 秒。脚本还支持UNIT_EVAL_COMMIT环境变量,先git fetch并切换到指定提交再跑,用于对历史版本做回归评测。
若要针对单个样例做更细粒度的分析,可直接使用epCLI。main.rs 定义了全局参数:--name按样例名过滤、--repo按仓库过滤、--limit/--offset控制处理数量、--max-parallelism(默认 10)、--output/-o指定输出、--markdown把输出写成每样例一个.md文件、--failfast遇错即停、--in-place原地更新样例文件(把预测与分数写回)。输入文件即本样例所在目录下的.md/.json/.jsonl路径。
6. 编写合规样例的要点清单
综合以上解析逻辑,手写一个评测样例时需满足:
- 以
+++包裹的 TOML 前置块开头,repository_url与revision必填; ## Cursor Position必须存在:围栏 info string 写光标文件路径,围栏内是包含光标行的节选文本,下一行用语言注释加^[CURSOR_POSITION](或行首列场景的<[CURSOR_POSITION])标注光标列;## Edit History用统一 diff 描述用户此前的编辑;若其中某段来自被接受的预测,前缀一行// User accepted prediction:;## Expected Patch可写一个或多个 diff,光标用同样的标记注释行标在+新增行上;多个 diff 表示多个可接受答案;- 只使用 H1/H2 标题,代码块一律用围栏形式;
- 文件名遵循
仓库名--行为描述命名(如flask--add-import-statement),样例名缺省即取文件名。
掌握这套格式后,无论是要读懂 Zed 编辑预测评测如何复现真实编辑场景,还是要为新场景补充单元测试样例、定位某次预测质量回退,都可以直接以evals/目录中的这些 Markdown 文件为蓝本进行扩展。
【免费下载链接】zedCode at the speed of thought – Zed is a high-performance, multiplayer code editor from the creators of Atom and Tree-sitter.项目地址: https://gitcode.com/GitHub_Trending/ze/zed
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考