首页
/ Zed 编辑预测评测样本解析:从 tree-sitter 元组转 struct 定义示例看 `ep` eval 文件格式

Zed 编辑预测评测样本解析:从 tree-sitter 元组转 struct 定义示例看 `ep` eval 文件格式

2026-09-06 14:33:43作者:毕习沙Eudora

本文以 Zed 仓库中的评测样本 tree-sitter--tuple-to-struct-definition.md 为主体,完整拆解 Zed 编辑预测(Edit Prediction)评测样本的 Markdown 格式规范:front matter 元数据、Edit History 编辑历史、Cursor Position 光标标记语法、以及多解 Expected Patch 的设计意图,并结合 crates/edit_prediction/src/example_spec.rscrates/edit_prediction_cli/src/main.rs 的源码说明这些字段如何被解析、加载,最终驱动 ep 命令完成预测与评分流水线。读完后你将能够独立编写、校验和运行这类评测样本。

样本定位:ep CLI 的评测输入

该文件位于 crates/edit_prediction_cli/evals/ 目录下。edit_prediction_cli 是 Zed 内部用于开发、评测和迭代编辑预测模型(即 Zeta 系列)的命令行工具,其可执行入口名为 ep,定义见 crates/edit_prediction_cli/Cargo.toml

[[bin]]
name = "ep"
path = "src/main.rs"

evals/ 目录下共收录 19 个样本,命名遵循 仓库名--任务名.md 的约定,例如 flask--add-import-statement.mdzed--add-eprintln.md。其中围绕 tree-sitter 仓库的一组样本(tree-sitter--tuple-to-struct-definitiontree-sitter--tuple-to-struct-destructuringtree-sitter--tuple-to-struct-field-accesstree-sitter--tuple-to-struct-for-looptree-sitter--tuple-to-struct-literaltree-sitter--if-let-to-match)共同描述了一个真实的重构场景:把 Vec<(PathBuf, OnceCell<Language>, Option<Vec<PathBuf>>)> 这类匿名元组改造为具名结构体 LanguageEntry。本文聚焦其中的"定义元"——即模型需要在正确位置补出 LanguageEntry 的 struct 定义。

Front matter:仓库与版本锚点

样本文件以 +++ 分隔的 TOML 块开头,完整内容为:

+++
repository_url = "git@github.com:tree-sitter/tree-sitter"
revision = "24007727d42b4caceda3095ac685c463fae1ba1a"
+++

这两个字段把样本钉死在一个确定的上游代码状态上:repository_url 指定被测仓库,revision 指定精确的 commit。ep 工具加载样本时会按仓库与 revision 创建 git worktree(ep load-project 子命令,见 main.rs 中 Command 枚举),从而还原出与真实用户编辑现场一致的缓冲区内容。

解析侧对应的是 example_spec.rs 中的 FrontMatter 结构,除 repository_urlrevision 外还支持 tagsuncommitted_diff_requires_edit_history_rollback 两个可选字段。注意本样本的 front matter 只包含必需的两项,其余字段均可缺省。

Edit History:让模型"看见"的重构上下文

## Edit History 小节记录光标编辑发生之前、编辑器内已发生的连续编辑(即用户会话中的真实编辑流),以 unified diff 的形式给出:

--- a/tree-sitter/crates/loader/src/loader.rs
+++ b/tree-sitter/crates/loader/src/loader.rs
@@ -604,7 +604,7 @@

 pub struct Loader {
     pub parser_lib_path: PathBuf,
-    languages_by_id: Vec<(PathBuf, OnceCell<Language>, Option<Vec<PathBuf>>)>,
+    languages_by_id: Vec<LanguageEntry>,
     language_configurations: Vec<LanguageConfiguration<'static>>,
     language_configuration_ids_by_file_type: HashMap<String, Vec<usize>>,
     language_configuration_in_current_path: Option<usize>,
--- a/tree-sitter/crates/loader/src/loader.rs
+++ b/tree-sitter/crates/loader/src/loader.rs
@@ -621,6 +621,8 @@
     wasm_store: Mutex<Option<tree_sitter::WasmStore>>,
 }

+str
 pub struct CompileConfig<'a> {
     pub src_path: &'a Path,
     pub header_paths: Vec<&'a Path>,

这段历史包含两个语义要点:

  1. 第一个 hunk 显示用户已把 Loader::languages_by_id 的类型从匿名三元组改为 Vec<LanguageEntry>——此时 LanguageEntry 尚未定义,编译器视角下是一个悬空类型引用;
  2. 第二个 hunk 显示用户在 pub struct CompileConfig<'a> 上方敲入了一行孤立的 str——这正是一次被"截获"的中途输入:用户正在补写 struct LanguageEntry { ... },只输出了 str 前缀,编辑预测即被触发。

这个构造非常贴近真实使用场景:预测模型不是凭空补全,而是基于"用户刚刚把元组替换成了具名类型 + 正在敲 struct 关键字"这两个信号,推断下一步应当产出一段完整的 struct 定义。这也解释了样本名为 tuple-to-struct-definition 的由来。

Cursor Position:光标片段的标记语法

## Cursor Position 小节用一个围栏代码块描述编辑发生瞬间的文件上下文。代码块的 info string(反引号后的语言标注位)是光标所在文件的相对路径,块内是文件片段,其中一行标记指示精确的行列位置:

```tree-sitter/crates/loader/src/loader.rs
    sanitize_build: bool,
    force_rebuild: bool,

    #[cfg(feature = "wasm")]
    wasm_store: Mutex<Option<tree_sitter::WasmStore>>,
}

str
// ^[CURSOR_POSITION]
pub struct CompileConfig<'a> {
    pub src_path: &'a Path,
    pub header_paths: Vec<&'a Path>,
    pub parser_path: PathBuf,
    pub scanner_path: Option<PathBuf>,
    pub external_files: Option<&'a [PathBuf]>,
```

解析逻辑在 ExampleSpec::cursor_excerpt 中,标记规则有两条:

  • ^ 形式:光标位于标记行的上一行,列位置等于 ^ 在标记行中的字符下标。本例中 // ^[CURSOR_POSITION]^ 位于第 3 列,对应上一行 str 末尾(第 3 列),即光标停在用户刚敲完的 str 之后;
  • < 形式:当光标列小于注释前缀长度时,用 < 表示光标位于该行第一个非空白字符处;
  • 行内标记 <|user_cursor|>:直接内嵌在文本流中,不占用独立标记行,解析时优先于上述两种形式(同文件中的 cursor_excerpt 首段实现)。

片段本身在构建 prompt 前会经过 cursor_excerpt() 剥除标记行,还原为"纯文件内容 + 光标字节偏移",因此标记只是人类可读的序列化形式。

Expected Patch:四个并行的可接受解

## Expected Patch 小节是评分基准。该样本给出了 4 个 围栏 diff 块,每一个都是一种"算对"的预测输出。四者共同的骨架是把触发行的 -str 替换为 LanguageEntry 定义:

解法一(推荐字段名 external_files):

--- a/tree-sitter/crates/loader/src/loader.rs
+++ b/tree-sitter/crates/loader/src/loader.rs
@@ -621,6 +621,8 @@
     wasm_store: Mutex<Option<tree_sitter::WasmStore>>,
 }

-str
+struct LanguageEntry {
+    path: PathBuf,
+    language: OnceCell<Language>,
+    external_files: Option<Vec<PathBuf>>,
+}
+
 pub struct CompileConfig<'a> {
     pub src_path: &'a Path,
     pub header_paths: Vec<&'a Path>,

解法二(字段名 dependencies):

--- a/tree-sitter/crates/loader/src/loader.rs
+++ b/tree-sitter/crates/loader/src/loader.rs
@@ -621,6 +621,8 @@
     wasm_store: Mutex<Option<tree_sitter::WasmStore>>,
 }

-str
+struct LanguageEntry {
+    path: PathBuf,
+    language: OnceCell<Language>,
+    dependencies: Option<Vec<PathBuf>>,
+}
+
 pub struct CompileConfig<'a> {
     pub src_path: &'a Path,
     pub header_paths: Vec<&'a Path>,

解法三(字段名 extra_files):

--- a/tree-sitter/crates/loader/src/loader.rs
+++ b/tree-sitter/crates/loader/src/loader.rs
@@ -621,6 +621,8 @@
     wasm_store: Mutex<Option<tree_sitter::WasmStore>>,
 }

-str
+struct LanguageEntry {
+    path: PathBuf,
+    language: OnceCell<Language>,
+    extra_files: Option<Vec<PathBuf>>,
+}
+
 pub struct CompileConfig<'a> {
     pub src_path: &'a Path,
     pub header_paths: Vec<&'a Path>,

解法四(元组结构体写法,零字段名歧义):

--- a/tree-sitter/crates/loader/src/loader.rs
+++ b/tree-sitter/crates/loader/src/loader.rs
@@ -621,6 +621,8 @@
     wasm_store: Mutex<Option<tree_sitter::WasmStore>>,
 }

-str
+struct LanguageEntry(PathBuf, OnceCell<Language>, Option<Vec<PathBuf>>);
+
 pub struct CompileConfig<'a> {
     pub src_path: &'a Path,
     pub header_paths: Vec<&'a Path>,

多解设计是这个 eval 格式的刻意特性:ExampleSpec::expected_patches 的类型是 Vec<String>,解析器会把 ## Expected Patch 下的每个代码块依次 push 进去(from_markdown 中 ExpectedPatch 分支)。从评分侧的源码结构看,一次预测输出只需命中其中任一可接受补丁即通过——因为"第三个字段该叫什么"在编辑现场是不可唯一判定的:external_files 贴合 tree-sitter 源码中外部扫描器文件的语义,而 dependenciesextra_files、匿名元组写法同样自洽。若只收录单一答案,会把大量语义正确的预测误判为失败,稀释评测信号。这种"一个编辑现场、多个等价解"的标注方式,对于评估模型的泛化补全能力比单答案精确匹配更合理。

对比同族的 tree-sitter--tuple-to-struct-destructuring.md 可以看到同一重构的"对偶样本":那边假设 struct 定义已经存在,要求模型把 let (path, language, externals) = &self.languages_by_id[id] 改写为结构体解构。两个样本共享同一 revision,分别考察定义与使用两侧——这是评测集按"真实 PR 的多步编辑"拆分的典型做法。

源码视角:从 Markdown 到 Example 对象

样本从文件到可运行对象的完整链路是:

  1. 入口分发read_example_files 按扩展名分派:.json 直接反序列化,.jsonl 逐行反序列化,.mdparse_markdown_example- 表示 stdin。
  2. Markdown 解析ExampleSpec::from_markdown 先用 pulldown_cmark 定位 +++ 包裹的 front matter(TOML 反序列化为 FrontMatter),再按 H2 标题切换到对应解析状态机:UncommittedDiffEditHistoryCursorPositionExpectedPatchRejectedPatch 等;H1 标题作为样本名,缺省时由文件名主干兜底(example.rs 中 name 的默认化逻辑)。
  3. 光标还原CursorPosition 分支记录代码块 info string 为 cursor_path、块文本为 cursor_position(本例中即 tree-sitter/crates/loader/src/loader.rs 与含 // ^[CURSOR_POSITION] 的片段)。
  4. 组装 Exampleparse_markdown_exampleExampleSpec 包装进 Example,附带空的 predictionsscore 列表,等待后续流水线填充。

ExampleSpec 的完整字段集还包括本样本未使用的部分:reasoning(标注者对意图的说明)、uncommitted_diff(工作区未提交改动)、recently_opened_files / recently_viewed_files(近期文件列表,支持 路径\t偏移 的行内光标记录)、rejected_patch(DPO 负例)等,均见 ExampleSpec 定义。此外,Edit History 中还支持 // User accepted prediction: 行内标记,用于在编辑流中标记"该次编辑是用户接受的预测结果"(解析测试用例),本样本未使用。

如何运行这个样本

ep 的子命令覆盖了样本的完整生命周期(Command 枚举):read(读取/规范化)、load-project(按 front matter 建 worktree 加载文件)、context(收集相关上下文)、format-prompt(生成模型 prompt)、predict(调用预测提供方)、parse-output(把原始输出解析为 unified diff)、score(对比实际与期望补丁打分)、eval(聚合评分)。针对本样本的典型用法(命令形式取自 main.rs 的 INPUTS_HELP):

# 读取并校验 evals 目录下的 Markdown 样本
ep read crates/edit_prediction_cli/evals/tree-sitter--tuple-to-struct-definition.md -o out.jsonl

# 按名称过滤单个样本运行评测
ep eval crates/edit_prediction_cli/evals/tree-sitter--tuple-to-struct-definition.md --name tree-sitter--tuple-to-struct-definition

# 以 Markdown 形式写回(每个样本一个 .md 文件,便于人工复核预测结果)
ep predict examples.jsonl --markdown -o out_dir/

全局参数 --limit--offset--name--repo--max-duplicates--failed 等可用于裁剪与去重样本集(EpArgs 定义)。运行 load-project 需要能够访问 front matter 中声明的 repository_url(本例为 tree-sitter 的 SSH 地址)并检出到 revision 指定的 commit,这是样本可复现性的前提;若环境无法克隆该仓库,样本只能做格式层面的读取与校验。

小结

这份看似只有百余行的样本文件,完整体现了 Zed 编辑预测评测的四个设计点:版本锚定(front matter 锁定仓库与 commit)、意图可溯(Edit History 呈现触发编辑之前的用户动作序列)、位置精确^[CURSOR_POSITION] / <[CURSOR_POSITION] / <|user_cursor|> 三种标记覆盖不同缩进与行内场景,由 cursor_excerpt() 统一还原为字节偏移)、多解宽容expected_patches 向量承载多个语义等价的可接受补丁)。围绕 tuple-to-struct 重构拆分的六个同族样本进一步说明,评测集以"一个真实 PR 的多个编辑点"为粒度组织,既考察定义补全也考察使用侧改写,构成对编辑预测模型较完整的回归面。

登录后查看全文
热门项目推荐
相关项目推荐