Zed 编辑预测评测样例(evals)格式深度解析:从 hello-world 改名样例看 `ep` CLI 的数据规范
本文以 hello-world--rename-accepted-group-by.md 这一评测样例文件为主线,完整拆解 Zed 编辑预测(Edit Prediction / Zeta)评测数据的 Markdown 格式:TOML front matter、Edit History 中的"用户接受预测"标记、[CURSOR_POSITION] 光标注释的列定位语义,以及 Expected Patch 的游标编码方式;并结合 example_spec.rs 与 example.rs 的解析实现,说明如何用 edit_prediction_cli 的 ep 命令驱动这条评测链路。读完后你可以独立编写、阅读和运行 Zed 的编辑预测评测样例。
1. 这个样例文件在仓库中的位置与作用
该文件位于 crates/edit_prediction_cli/evals/ 目录下,是 Zed 编辑预测模型的评测样例之一。同一目录中还有来自 flask、tree-sitter、vscode、zed 等仓库的样例(如 flask--rename-accepted-prediction.md、tree-sitter--tuple-to-struct-definition.md),命名遵循 仓库名--场景描述.md 的约定。
文件名本身就是样例名:解析入口 read_example_files(example.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_markdown(example_spec.rs)会先 strip_prefix("+++\n") 取出 front matter 并用 toml::from_str::<FrontMatter> 反序列化,可识别的字段包括:
| 字段 | 含义 |
|---|---|
repository_url |
样例所锚定的仓库地址。从源码结构看,repo_name()(example.rs)会同时解析 git@github.com:owner/repo.git 与 https://github.com/owner/repo.git 两种形式,并据此推导 worktree 缓存路径(owner/repo) |
revision |
固定到某个提交哈希,保证评测可复现 |
tags |
可选标签数组(可选字段,省略时为空) |
uncommitted_diff_requires_edit_history_rollback |
可选布尔值,控制加载 Uncommitted Diff 章节时是否需要回滚编辑历史(可选字段,省略时为 false) |
该样例只使用了 repository_url 与 revision 两个必填字段。
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 的语义分别是:
- 手写编辑:用户把 README 的
Hello World!替换为 JavaScript 代码,并手敲了function groupBy(未写完); - 被接受的自动补全:
// User accepted prediction:注释标记的 diff,表示模型补全了groupByStatus的完整实现,且用户接受了这次预测——这正是样例名中rename-accepted的来源; - 后续改名:用户把接受的补全改名为
groupByCat(一个被截断的名字),触发本次评测:模型此时应预测出把名字补全为groupByCategory、并同步修改函数体内item.status→item.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_marker(example_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");
}
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.status→item.category),这正是"改名的正确预测"与"只改名不改引用"的分界线,也是该样例的评测价值所在; - 补丁中同样有一行
#...^[CURSOR_POSITION]标记,说明编辑完成后光标应落在哪里(groupByCategory的Category末尾)。ExampleSpec提供expected_patches_with_cursor_positions/set_expected_patches_with_cursor_positions(example_spec.rs)成对地提取/写回光标偏移,底层由extract_cursor_from_patch/encode_cursor_in_patch实现。源码注释说明:在样例的序列化表示中,编辑后的光标位置用"新增 diff 行内的内联标记"表达,偏移量是相对 hunk 起点、在新文本中的字节偏移。
此外 ExampleSpec 还支持一个可选的 ## Rejected Patch 章节(example_spec.rs),用于保存"用户拒绝的预测",对应训练中的 DPO 负样本(rejected_patch 字段)。本样例未使用该章节——它考察的是正向预测,而非拒绝场景。
3. 解析链路:从 .md 文件到内存中的 Example
把样例格式与解析实现对照起来,整条链路如下:
- CLI 读入:
read_example_files(example.rs)按扩展名分派——.md走parse_markdown_example,.json走 serde 直接反序列化Example,.jsonl逐行反序列化;输入为-时从 stdin 读取。 - Markdown 解析:
ExampleSpec::from_markdown(example_spec.rs)基于pulldown_cmark事件流,识别 H1(样例名)与七种 H2 章节标题(大小写不敏感):Uncommitted Diff、Recently Opened Files、Recently Viewed Files、Edit History、Cursor Position、Expected Patch、Rejected Patch,并处理 accepted-prediction 标记的回写。本样例使用了其中Edit History、Cursor Position、Expected Patch三个章节。 - 内存结构:
Example(example.rs)=ExampleSpec(打平内嵌)+prompt_inputs(Zeta2 提示输入)+prompt(模型输入/期望输出,含 DPO 用的rejected_output)+predictions(实际预测列表,含actual_patch、actual_cursor、logprob 等)+score+qa结果。前四个运行时字段在磁盘样例文件中不存在,由预测/评分流程填充。
ExampleSpec 的完整字段列表(example_spec.rs)还包括 tags、reasoning、uncommitted_diff、recently_opened_files、recently_viewed_files、telemetry(来自生产遥测的拒绝预测元数据:request_id、rejection_reason 等)、human_feedback 与 rating,覆盖了遥测捕获、用户评分等多种样例来源。
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.rs 的INPUTS_HELP); -o/--output、--in-place、--failfast、--failed(keep/skip/skip-no-files);--markdown/-m:输出为 Markdown 而非 JSONL,每个样例写入一个.md文件——即evals/目录中这类文件的读写是同构的。
子命令包含 predict、score、qa、distill、pull-examples、synthesize、split-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 子命令复用 PredictArgs(main.rs),用于把模型输出与 Expected Patch 对照打分;synthesize 子命令的默认输出目录即 crates/edit_prediction_cli/evals-generated(main.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 流程(score、qa 子命令及 score.rs、qa.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 运行脚本 |
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 StartedRust0624
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