Zed 编辑预测评测样本解析:从 tree-sitter 元组转 struct 定义示例看 `ep` eval 文件格式
本文以 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.rs 与 crates/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.md、zed--add-eprintln.md。其中围绕 tree-sitter 仓库的一组样本(tree-sitter--tuple-to-struct-definition、tree-sitter--tuple-to-struct-destructuring、tree-sitter--tuple-to-struct-field-access、tree-sitter--tuple-to-struct-for-loop、tree-sitter--tuple-to-struct-literal、tree-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_url、revision 外还支持 tags 与 uncommitted_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>,
这段历史包含两个语义要点:
- 第一个 hunk 显示用户已把
Loader::languages_by_id的类型从匿名三元组改为Vec<LanguageEntry>——此时LanguageEntry尚未定义,编译器视角下是一个悬空类型引用; - 第二个 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 源码中外部扫描器文件的语义,而 dependencies、extra_files、匿名元组写法同样自洽。若只收录单一答案,会把大量语义正确的预测误判为失败,稀释评测信号。这种"一个编辑现场、多个等价解"的标注方式,对于评估模型的泛化补全能力比单答案精确匹配更合理。
对比同族的 tree-sitter--tuple-to-struct-destructuring.md 可以看到同一重构的"对偶样本":那边假设 struct 定义已经存在,要求模型把 let (path, language, externals) = &self.languages_by_id[id] 改写为结构体解构。两个样本共享同一 revision,分别考察定义与使用两侧——这是评测集按"真实 PR 的多步编辑"拆分的典型做法。
源码视角:从 Markdown 到 Example 对象
样本从文件到可运行对象的完整链路是:
- 入口分发。read_example_files 按扩展名分派:
.json直接反序列化,.jsonl逐行反序列化,.md走parse_markdown_example;-表示 stdin。 - Markdown 解析。ExampleSpec::from_markdown 先用
pulldown_cmark定位+++包裹的 front matter(TOML 反序列化为FrontMatter),再按 H2 标题切换到对应解析状态机:UncommittedDiff、EditHistory、CursorPosition、ExpectedPatch、RejectedPatch等;H1 标题作为样本名,缺省时由文件名主干兜底(example.rs 中 name 的默认化逻辑)。 - 光标还原。
CursorPosition分支记录代码块 info string 为cursor_path、块文本为cursor_position(本例中即tree-sitter/crates/loader/src/loader.rs与含// ^[CURSOR_POSITION]的片段)。 - 组装 Example。parse_markdown_example 将
ExampleSpec包装进Example,附带空的predictions与score列表,等待后续流水线填充。
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 的多个编辑点"为粒度组织,既考察定义补全也考察使用侧改写,构成对编辑预测模型较完整的回归面。
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