首页
/ Zed 编辑预测评测样例(evals)格式深度解析:从 hello-world 改名样例看 `ep` CLI 的数据规范

Zed 编辑预测评测样例(evals)格式深度解析:从 hello-world 改名样例看 `ep` CLI 的数据规范

2026-09-06 20:45:03作者:范垣楠Rhoda

本文以 hello-world--rename-accepted-group-by.md 这一评测样例文件为主线,完整拆解 Zed 编辑预测(Edit Prediction / Zeta)评测数据的 Markdown 格式:TOML front matter、Edit History 中的"用户接受预测"标记、[CURSOR_POSITION] 光标注释的列定位语义,以及 Expected Patch 的游标编码方式;并结合 example_spec.rsexample.rs 的解析实现,说明如何用 edit_prediction_cliep 命令驱动这条评测链路。读完后你可以独立编写、阅读和运行 Zed 的编辑预测评测样例。

1. 这个样例文件在仓库中的位置与作用

该文件位于 crates/edit_prediction_cli/evals/ 目录下,是 Zed 编辑预测模型的评测样例之一。同一目录中还有来自 flask、tree-sitter、vscode、zed 等仓库的样例(如 flask--rename-accepted-prediction.mdtree-sitter--tuple-to-struct-definition.md),命名遵循 仓库名--场景描述.md 的约定。

文件名本身就是样例名:解析入口 read_example_filesexample.rs)在读入 .md 文件后,若 spec.name 为空,就直接取文件主名(去掉扩展名的部分)作为样例名:

"md" => {
    let mut example = parse_markdown_example(&content).unwrap();
    if example.spec.name.is_empty() {
        example.spec.name = filename;
    }
    examples.push(example);
}

也就是说,hello-world--rename-accepted-group-by 这个文件名精确表达了该样例测试的语义:在 hello-world 仓库中,用户先接受了一次自动补全,随后对补全出的函数做改名,模型应当能预测出"连带修改函数体内引用"的完整重命名。

2. 样例全文结构:front matter、编辑历史、光标与期望补丁

下面逐段解析该样例文件的真实内容(与仓库文件完全一致)。

2.1 TOML front matter:锚定到确定性的仓库状态

+++
repository_url = "https://github.com/octocat/hello-world"
revision = "7fd1a60b01f91b314f59955a4e4d4e80d8edf11d"
+++

front matter 用 +++ 定界,内部是 TOML。解析器 ExampleSpec::from_markdownexample_spec.rs)会先 strip_prefix("+++\n") 取出 front matter 并用 toml::from_str::<FrontMatter> 反序列化,可识别的字段包括:

字段 含义
repository_url 样例所锚定的仓库地址。从源码结构看,repo_name()example.rs)会同时解析 git@github.com:owner/repo.githttps://github.com/owner/repo.git 两种形式,并据此推导 worktree 缓存路径(owner/repo
revision 固定到某个提交哈希,保证评测可复现
tags 可选标签数组(可选字段,省略时为空)
uncommitted_diff_requires_edit_history_rollback 可选布尔值,控制加载 Uncommitted Diff 章节时是否需要回滚编辑历史(可选字段,省略时为 false)

该样例只使用了 repository_urlrevision 两个必填字段。

2.2 Edit History:三段 diff 讲述"手写 → 接受预测 → 改名"的完整故事

样例的 ## Edit History 章节包含三段 ```diff 代码块,其中第二段前有一行特殊注释:

--- a/README
+++ b/README
@@ -1,1 +1,6 @@
-Hello World!
+function filterByStatus(items, status) {
+    return items.filter(item => item.status === status);
+}
+
+function groupBy
+

// User accepted prediction:

--- a/README
+++ b/README
@@ -4,3 +4,9 @@

-function groupBy
+function groupByStatus(items) {
+    return items.reduce((groups, item) => {
+        const key = item.status;
+        (groups[key] = groups[key] || []).push(item);
+        return groups;
+    }, {});
+}
--- a/README
+++ b/README
@@ -4,4 +4,4 @@

-function groupByStatus(items) {
+function groupByCat(items) {
     return items.reduce((groups, item) => {

三段 diff 的语义分别是:

  1. 手写编辑:用户把 README 的 Hello World! 替换为 JavaScript 代码,并手敲了 function groupBy(未写完);
  2. 被接受的自动补全// User accepted prediction: 注释标记的 diff,表示模型补全了 groupByStatus 的完整实现,且用户接受了这次预测——这正是样例名中 rename-accepted 的来源;
  3. 后续改名:用户把接受的补全改名为 groupByCat(一个被截断的名字),触发本次评测:模型此时应预测出把名字补全为 groupByCategory、并同步修改函数体内 item.statusitem.category 的编辑。

// User accepted prediction: 这一行不是给人看的普通注释,而是解析协议的一部分。from_markdown 中定义了常量:

const ACCEPTED_PREDICTION_MARKER: &str = "// User accepted prediction:";

example_spec.rs)。解析时,如果某个 diff 代码块之前紧跟该行文本,解析器会把该行标记重新拼回 edit_history 字符串中(example_spec.rs),保证序列化/反序列化往返一致。同文件内的测试 test_from_markdown_accepted_prediction_markerexample_spec.rs)验证了:三段 diff 均被保留、标记恰好出现一次、且只出现在第二段之前。

2.3 Cursor Position:#...^[CURSOR_POSITION] 注释标记光标

```README
function filterByStatus(items, status) {
    return items.filter(item => item.status === status);
}

function groupByCat(items) {
#                  ^[CURSOR_POSITION]
    return items.reduce((groups, item) => {
        const key = item.status;
        (groups[key] = groups[key] || []).push(item);
        return groups;
    }, {});
}


`Cursor Position` 章节的代码块**信息串(info string)就是光标所在文件路径**(这里是 `README`),块内容是光标所在文件的节选。解析逻辑见 [example_spec.rs](https://gitcode.com/GitHub_Trending/ze/zed/blob/b1a7ef0cf66dfbf9d7661170c96d97c7df916c68/crates/edit_prediction/src/example_spec.rs?utm_source=gitcode_repo_files#L393-L396):`cursor_path` 取代码块 info string,`cursor_position` 取块内全文。

光标位置用一条注释行表达,其解析规则([example_spec.rs](https://gitcode.com/GitHub_Trending/ze/zed/blob/b1a7ef0cf66dfbf9d7661170c96d97c7df916c68/crates/edit_prediction/src/example_spec.rs?utm_source=gitcode_repo_files#L417-L473) 的 `cursor_excerpt` 文档注释与实现)为:

- 注释行必须包含 `[CURSOR_POSITION]`(常量 `CURSOR_POSITION_MARKER`,定义于 [udiff.rs](https://gitcode.com/GitHub_Trending/ze/zed/blob/b1a7ef0cf66dfbf9d7661170c96d97c7df916c68/crates/zeta_prompt/src/udiff.rs?utm_source=gitcode_repo_files#L88-L89)),位于光标所在行的**下一行**;
- `^`:光标列 = `^` 字符在该行的列位置(指向上方的光标);
- `<`:光标列 = 该行第一个非空白字符所在列(用于注释前缀较长、`^` 会越界到注释符内部的情况);
- 此外还支持内联标记 `<|user_cursor|>`(常量 `INLINE_CURSOR_MARKER`,同样定义于 [udiff.rs](https://gitcode.com/GitHub_Trending/ze/zed/blob/b1a7ef0cf66dfbf9d7661170c96d97c7df916c68/crates/zeta_prompt/src/udiff.rs?utm_source=gitcode_repo_files#L88-L89)),直接写在光标字节偏移处,例如 `let x = <|user_cursor|>42;`,解析时直接剥掉标记并返回其偏移量。

在本样例中,`#` 后的 `^` 指向 `function groupByCat(items) {` 中 `Cat` 之后的位置——即用户刚敲完 `Cat`、等待模型把名字补全为 `Category` 的时刻。测试 `test_cursor_excerpt_with_caret`([example_spec.rs](https://gitcode.com/GitHub_Trending/ze/zed/blob/b1a7ef0cf66dfbf9d7661170c96d97c7df916c68/crates/edit_prediction/src/example_spec.rs?utm_source=gitcode_repo_files#L521-L657))覆盖了 `^` 与 `<` 两种形式在行首、行中、行尾及文件末尾(无换行)的往返一致性。

注意解析器强制要求该章节存在,缺失会直接报错:

```rust
if spec.cursor_path.as_ref() == Path::new("") || spec.cursor_position.is_empty() {
    anyhow::bail!("Missing cursor position codeblock");
}

example_spec.rs

2.4 Expected Patch:期望补丁里也编码了光标

--- a/README
+++ b/README
@@ -5,7 +5,7 @@
-function groupByCat(items) {
+function groupByCategory(items) {
#                        ^[CURSOR_POSITION]
    return items.reduce((groups, item) => {
-        const key = item.status;
+        const key = item.category;
        (groups[key] = groups[key] || []).push(item);
        return groups;
    }, {});

这是评测的"标准答案":模型应当输出的 unified diff。两个要点:

  • 期望补丁包含连带修改item.statusitem.category),这正是"改名的正确预测"与"只改名不改引用"的分界线,也是该样例的评测价值所在;
  • 补丁中同样有一行 #...^[CURSOR_POSITION] 标记,说明编辑完成后光标应落在哪里groupByCategoryCategory 末尾)。ExampleSpec 提供 expected_patches_with_cursor_positions / set_expected_patches_with_cursor_positionsexample_spec.rs)成对地提取/写回光标偏移,底层由 extract_cursor_from_patch / encode_cursor_in_patch 实现。源码注释说明:在样例的序列化表示中,编辑后的光标位置用"新增 diff 行内的内联标记"表达,偏移量是相对 hunk 起点、在新文本中的字节偏移

此外 ExampleSpec 还支持一个可选的 ## Rejected Patch 章节(example_spec.rs),用于保存"用户拒绝的预测",对应训练中的 DPO 负样本(rejected_patch 字段)。本样例未使用该章节——它考察的是正向预测,而非拒绝场景。

3. 解析链路:从 .md 文件到内存中的 Example

把样例格式与解析实现对照起来,整条链路如下:

  1. CLI 读入read_example_filesexample.rs)按扩展名分派——.mdparse_markdown_example.json 走 serde 直接反序列化 Example.jsonl 逐行反序列化;输入为 - 时从 stdin 读取。
  2. Markdown 解析ExampleSpec::from_markdownexample_spec.rs)基于 pulldown_cmark 事件流,识别 H1(样例名)与七种 H2 章节标题(大小写不敏感):Uncommitted DiffRecently Opened FilesRecently Viewed FilesEdit HistoryCursor PositionExpected PatchRejected Patch,并处理 accepted-prediction 标记的回写。本样例使用了其中 Edit HistoryCursor PositionExpected Patch 三个章节。
  3. 内存结构Exampleexample.rs)= ExampleSpec(打平内嵌)+ prompt_inputs(Zeta2 提示输入)+ prompt(模型输入/期望输出,含 DPO 用的 rejected_output)+ predictions(实际预测列表,含 actual_patchactual_cursor、logprob 等)+ score + qa 结果。前四个运行时字段在磁盘样例文件中不存在,由预测/评分流程填充。

ExampleSpec 的完整字段列表(example_spec.rs)还包括 tagsreasoninguncommitted_diffrecently_opened_filesrecently_viewed_filestelemetry(来自生产遥测的拒绝预测元数据:request_idrejection_reason 等)、human_feedbackrating,覆盖了遥测捕获、用户评分等多种样例来源。

4. 如何运行这个样例

评测样例由 edit_prediction_cli 提供的 ep 命令驱动(二进制名在 Cargo.toml 中定义为 ep)。

4.1 命令与全局参数

ep 使用 clap 定义,全局参数(main.rs)包括:

  • --max-parallelism(默认 10)、--group-by-repo(同一仓库的样例集中处理)、--limit / --offset
  • --name:按样例名过滤,正好可以单独选中 hello-world--rename-accepted-group-by
  • --repo:按仓库 URL 过滤;
  • 输入 inputs:支持普通文件路径(.md / .json / .jsonl)以及 captured-after: / rejected-after: / settled-after: / rated-after: 等从 Snowflake 拉取遥测样例的特殊说明符(见 main.rsINPUTS_HELP);
  • -o/--output--in-place--failfast--failed(keep/skip/skip-no-files);
  • --markdown/-m:输出为 Markdown 而非 JSONL,每个样例写入一个 .md 文件——即 evals/ 目录中这类文件的读写是同构的。

子命令包含 predictscoreqadistillpull-examplessynthesizesplit-commit 等;--provider 可选 Mercury、Zeta1、Zeta2、Baseten、Teacher 系列及 Repair(main.rs)。

4.2 针对本样例的最小用法

对应当前仓库结构,运行编辑预测评测的典型形式是把样例文件作为输入传给 ep 的子命令,例如:

# 对该样例跑一次编辑预测
cargo run -p edit_prediction_cli -- predict \
    crates/edit_prediction_cli/evals/hello-world--rename-accepted-group-by.md

# 用 --name 在整批样例中按名过滤
cargo run -p edit_prediction_cli -- score --name hello-world--rename-accepted-group-by \
    crates/edit_prediction_cli/evals/

# 结果以 Markdown 形式写回(-o 指定目录)
cargo run -p edit_prediction_cli -- predict --markdown \
    -o /tmp/ep-out \
    crates/edit_prediction_cli/evals/hello-world--rename-accepted-group-by.md

其中 score 子命令复用 PredictArgsmain.rs),用于把模型输出与 Expected Patch 对照打分;synthesize 子命令的默认输出目录即 crates/edit_prediction_cli/evals-generatedmain.rs),用于从真实仓库提交中批量合成同类样例。另外,仓库还提供了一个独立的单元测试评测脚本 script/run-unit-evals,通过 cargo nextest run --workspace --features unit-eval -E 'test(::eval_)' 运行带 unit-eval feature 的 eval_ 前缀单元测试(run-unit-evals),它面向的是各 crate 内嵌的单元级评测,与 evals/ 目录这批 Markdown 样例分属两条评测通道。

5. 小结:这个样例教给模型的到底是什么

把各要素串起来,hello-world--rename-accepted-group-by 构造了一条最小但完整的"上下文 → 行为"评测链:

  • 上下文由 Edit History 给出:先有手写编辑,再有一次已被接受的预测// User accepted prediction: 标记),说明模型此前给出的 groupByStatus 实现已成为当前文件状态的一部分;
  • 触发点由 Cursor Position 精确定位到 groupByCat 这个名字的中间——用户改名的意图刚刚暴露;
  • 标准答案由 Expected Patch 给出:不仅把名字补全为 groupByCategory,还连带把函数体内 item.status 改为 item.category,并在补丁中标注编辑完成后的光标位置。

从源码结构看,ep CLI 的评分与 QA 流程(scoreqa 子命令及 score.rsqa.rs)正是围绕"实际补丁 vs expected_patches"这类字段展开的,而 rejected_patch 字段则为负样本(DPO)预留了同一格式通道。掌握本文介绍的 front matter、四个 H2 章节、光标注释(^/</<|user_cursor|> 三种形式)与 accepted-prediction 标记,即可在该目录下编写新的评测样例,并用 example_spec.rs 中的往返测试方法验证其可解析性。

关键文件索引

文件 作用
crates/edit_prediction_cli/evals/hello-world--rename-accepted-group-by.md 本文剖析的评测样例
crates/edit_prediction/src/example_spec.rs ExampleSpec 定义、Markdown 解析、光标标记语义与往返测试
crates/edit_prediction_cli/src/example.rs Example 结构、多格式文件读入、仓库名解析
crates/edit_prediction_cli/src/main.rs ep CLI 参数、子命令与 provider 定义
crates/zeta_prompt/src/udiff.rs [CURSOR_POSITION]<|user_cursor|> 常量及补丁光标编解码
script/run-unit-evals 单元级评测的 nextest 运行脚本
登录后查看全文
热门项目推荐
相关项目推荐