Zed 编辑预测评测样例解析:以 tree-sitter 的 if-let 改 match 重构为例理解 Zeta 评测格式
本文围绕 Zed 仓库中的编辑预测(edit prediction / Zeta)评测样例 tree-sitter--if-let-to-match.md 展开。你将看到这条"把 Rust 的 if let 重写为 match"的真实重构是如何被拆解为编辑历史、光标摘录与多个可接受补丁的,以及 ep 评测 CLI 如何解析、重放并给模型输出打分——读完后你可以独立读懂并书写 Zed 编辑预测的 Markdown 评测样例,并将其纳入评测流水线运行。
一、这个文件在 Zed 评测体系中是什么
crates/edit_prediction_cli/evals/ 目录存放的是编辑预测模型的手写评测样例(eval examples),当前目录中共有 19 个 .md 样例,覆盖 tree-sitter、zed、flask、vscode 等多个上游仓库的典型编辑场景。文件名约定为 <仓库>--<任务>.md,本文件即"在 tree-sitter 仓库上完成 if-let to match 重构"这一评测项。
这些 Markdown 文件不是给人读的技术文档,而是可直接被 CLI 消费的评测输入。在 Cargo.toml 中,edit_prediction_cli crate 声明了二进制 ep([[bin]] name = "ep"),其入口 main.rs 定义了全局参数与子命令;当输入是 .md 文件时,读取链路如下:
read_example_files()(example.rs)按扩展名分派:json/jsonl/md,其中md走parse_markdown_example();parse_markdown_example()调用ExampleSpec::from_markdown()(example_spec.rs),用 pulldown_cmark 遍历 Markdown 事件流,按二级标题切分章节;- 识别的章节标题常量与本文档完全对应:
Edit History(EDIT_HISTORY_HEADING)、Cursor Position(CURSOR_POSITION_HEADING)、Expected Patch(EXPECTED_PATCH_HEADING),另支持Uncommitted Diff、Recently Opened Files、Rejected Patch等可选章节(见 example_spec.rs 的常量定义); Expected Patch下的每个```diff代码块都会追加为expected_patches中的一项——这正是本样例能给出三种可接受补丁的机制来源。
二、Front Matter:用 repository_url + revision 钉死外部仓库状态
文件开头是一个 TOML 前置块:
+++
repository_url = "git@github.com:tree-sitter/tree-sitter"
revision = "17e3c7a5c56527a179fa6e37ce7ee934493e5047"
+++
ExampleSpec::from_markdown() 会以 +++ 为界剥离前置块,反序列化为 FrontMatter 结构(example_spec.rs):
| 字段 | 本样例取值 | 作用 |
|---|---|---|
repository_url |
git@github.com:tree-sitter/tree-sitter |
评测代码来自哪个外部仓库(SSH 或 HTTP 形式均可,Example::repo_name() 两种都能解析) |
revision |
17e3c7a5… |
精确到该仓库的某次提交,保证评测可复现 |
tags(可选) |
无 | 分类标签 |
uncommitted_diff_requires_edit_history_rollback(可选) |
无 | 控制 uncommitted diff 与编辑历史的回滚语义 |
评测运行时会依据这两个字段重建现场:load_project.rs 的 setup_worktree() 会把该仓库克隆到本地(或复用已有克隆并 fetch 指定 revision),再为样例创建一个独立的 git worktree 并 checkout 到钉死的 revision——这意味着本样例重放的是 tree-sitter 仓库在 17e3c7a 这个提交时的 crates/loader/src/loader.rs,而非当前 Zed 仓库中的任何文件。
三、Edit History:重放"用户手动重构到一半"的现场
## Edit History 章节是一个 unified diff,描述模型被调用之前用户在编辑器里已完成的编辑。本样例的编辑历史包含两个 hunk,内容是:
// 原始形态(删除行)
if let Ok(entries) = fs::read_dir(parser_container_dir) {
for entry in entries { ... }
}
// 目标形态(新增行,重构进行到一半)
match fs::read_dir(parser_container_dir) {
Ok(entries) => {
for entry in entries { ... } // Ok 分支已展开
// ← 此处尚未补充 Err 分支
}
即:用户在 loader.rs 里把 if let Ok(entries) = fs::read_dir(...) 逐行改写为 match fs::read_dir(...) { Ok(entries) => { ... },Ok 分支的语句体已经整体展开(可以看到内部 find_language_configurations_at_path(...) 调用的缩进也随 match 臂加深了一级),但 Err 分支还缺失——这正是等待模型续写的位置。
这个历史会被真实"重放"进缓冲区:run_load_project() 中 apply_edit_history() 直接调用 edit_prediction::udiff::apply_diff()(load_project.rs),把 diff 逐 hunk 应用到一个临时打开的 buffer 上;应用完成后,EditPredictionStore 还会收集出本次会话的编辑事件序列(edit_history_for_project()),最终组装进 Zeta2PromptInput 作为模型的上下文事件流——也就是说,模型看到的不是静态 diff 文件,而是"用户刚刚做过的这些编辑"。
四、Cursor Position:光标摘录与 ^[CURSOR_POSITION] 标记行
## Cursor Position 章节的代码块语言标注为文件路径 crates/loader/src/loader.rs(解析器会把它存入 spec.cursor_path),块内是一段带光标标记的代码摘录:
if let Some(parser_dir_name) = entry.file_name().to_str() {
if parser_dir_name.starts_with("tree-sitter-") {
self.find_language_configurations_at_path(
&parser_container_dir.join(parser_dir_name),
false,
)
.ok();
}
// ^[CURSOR_POSITION]
}
}
}
}
}
标记约定由 example_spec.rs 的 cursor_excerpt() 精确定义:
- 标记行是紧随光标行之后的一行,包含字符串
[CURSOR_POSITION](CURSOR_POSITION_MARKER); - 行内第一个
^字符所在的列,就是光标在"标记行上一行"中的列偏移;若光标列早于注释前缀长度,则退化为<[CURSOR_POSITION]形式,取该行第一个非空白字符位置; - 另有一种内联标记
<|user_cursor|>(INLINE_CURSOR_MARKER,见 udiff.rs),把光标直接嵌在文本中间,解析时直接按内联偏移计算。
在本样例中,^ 指向 .ok(); 所在行的行尾(即整个 Ok(entries) => { ... } 块结束、} 闭合 match 之前),精确刻画了"用户敲完 Ok 臂、光标悬在等待补 Err 臂"的瞬间。
解析后 load_project::cursor_position()(load_project.rs)还要做一件关键校验:用纯摘录文本在 buffer 全文中做 match_indices,要求恰好匹配一次("More than one cursor position match found" 会直接报错),然后把摘录偏移加上摘录内偏移,得到最终的 Anchor 光标锚点。这一"摘录必须唯一"的设计,保证了人工编写的 Cursor Position 摘录不能太短而撞车。
五、Expected Patch:一个样例,三种可接受答案
## Expected Patch 下本样例给出了三个独立 diff 块,对应三种都算对的模型输出:
+ Err(error) => {}—— 用命名绑定error但空作用体丢弃;+ Err(_) => {}—— 用_显式忽略;- 多行未闭合形态:
+ Err(e) => {
+
+ }
光标落在空作用体内部——模拟"用户刚开括号、准备往里敲东西"的中间编辑态。
三个补丁块中都出现了形如 # ^[CURSOR_POSITION] 的标记行,它标注的是补丁应用完之后光标应停在的位置(^ 的列对应新增文本中的列,例如第三个变体里光标落在空行上,即新 Err 臂作用体的开头)。ExampleSpec::expected_patches_with_cursor_positions()(example_spec.rs)会对每个补丁调用 udiff.rs 的 extract_cursor_from_patch() 恢复出 (干净补丁, 期望光标偏移) 二元组:逐行解析 diff,把内联光标标记从 + 行中剔除、并按上下文/新增行的累计字节数换算出光标在新文本中的绝对偏移。从源码结构看,# 开头的标记行不属于标准 diff 内容行,评测工具链(如同文件的 strip_diff_metadata())会将其与 index/diff --git 等元数据一并剥离,只保留可用于应用与比对的 hunk 内容行。
"多个 expected patch"的意义在于:真实编辑预测的正确答案天然不唯一。if let 改 match 时 Err 臂的写法因人而异,若评测只认一种拼法,会把大量行为正确的预测误判为失败。打分时 score.rs 的 run_scoring() 会把每个 expected patch 都经 edit_prediction_metrics::prepare_expected_patches() 预演(在光标摘录原文上试应用)后,逐条调用 score_prediction() 与模型的 actual_patch 比对,多个期望答案共同构成该样例的"及格线"。
六、跑这条样例:ep CLI 的最小工作流
结合 main.rs 的 Command 枚举,针对单条 Markdown 样例的典型流程为:
# 1. 读取并校验样例(输出规范化 JSONL)
ep read crates/edit_prediction_cli/evals/tree-sitter--if-let-to-match.md -o out.jsonl
# 2. 为样例建 git worktree、重放编辑历史、解析光标、生成 prompt_inputs
ep load-project out.jsonl -o out.jsonl
# 3. 调模型生成预测(provider 字符串见 main.rs 的 PredictionProvider::from_str)
ep predict out.jsonl --provider=zeta2
# 4. 或直接一步到位:预测 + 对 actual/expected 补丁打分
ep eval out.jsonl --provider=zeta2
几个可复用的筛选与调参开关(均为 EpArgs 全局参数):
--name tree-sitter--if-let-to-match:按样例名子串过滤,只处理这一条;--repo tree-sitter:按repository_url子串过滤;--max-parallelism N(默认 10)、--limit N、--offset N:控制并发与取样窗口;--markdown(-m):输出改为每样例一个.md文件(命名取自样例名),方便回看评测结果;--failed=keep|skip|skip-no-files:失败样例在输出中的处理方式(失败样例无论如何都会落到 run 的failed/目录)。
predict 阶段的 provider 字符串支持 zeta2(默认)、zeta1、teacher:<backend>、teacher-jumps:<backend> 等形态(解析逻辑见 main.rs,含 sonnet45/sonnet46/gpt52/gpt54/gpt55 等 teacher 后端别名)。
load-project 这一步产出的 prompt_inputs 是评分的前置条件:run_load_project() 在解析出光标后,会基于 buffer 快照计算光标摘录与语法范围(compute_cursor_excerpt / compute_syntax_ranges),连同 EditPredictionStore 中还原出的编辑事件,填入 Zeta2PromptInput;score.rs 中明确提示 prompt_inputs is required for scoring - run prediction first,即跳步运行 eval/score 会直接报错。
七、这条样例的设计意图小结
- 单一仓库 + 钉死 revision:Front Matter 把评测锚定在 tree-sitter 的
17e3c7a提交,编辑历史与光标摘录都出自该版本的crates/loader/src/loader.rs,ep通过 git worktree 机制现场重建,保证任何人任何时间跑出的输入状态一致; - 重构进行到一半的"半态":Edit History 刻意停在
match缺Err臂的位置,考察模型能否依据上下文(fs::read_dir返回Result<ReadDir, _>、既有Ok臂结构)补全惯用的错误分支,而非补全任何"看起来像"的语句; - 多可接受答案 + 期望光标:三个 expected patch 覆盖了
Err(_)、命名绑定与未闭合臂三种形态,且每处都标注了补丁后光标落点,使打分不仅看"改得对不对",还能看"改完后光标在不在合理位置"(对应ActualCursor与editable_region_offset的评分输入,见 example.rs)。
八、关键文件索引
| 文件 | 角色 |
|---|---|
| crates/edit_prediction_cli/evals/tree-sitter--if-let-to-match.md | 本文剖析的评测样例本体 |
| crates/edit_prediction/src/example_spec.rs | Markdown 样例的序列化/解析规范(from_markdown、cursor_excerpt、期望补丁光标提取) |
| crates/zeta_prompt/src/udiff.rs | 光标标记常量与补丁光标解码(extract_cursor_from_patch、strip_diff_metadata) |
| crates/edit_prediction_cli/src/example.rs | 样例读取(.md/.json/.jsonl)与 Example 数据模型 |
| crates/edit_prediction_cli/src/load_project.rs | worktree 重建、编辑历史重放、光标解析与 prompt 输入组装 |
| crates/edit_prediction_cli/src/score.rs | 期望补丁预演与打分入口 |
| crates/edit_prediction_cli/src/main.rs | ep CLI 参数、子命令与 provider 解析 |
适用前提:以上命令与格式均基于当前仓库的 edit_prediction_cli 实现;ep 是仓库内二进制,需在工作区中通过 Cargo 构建运行,且涉及外部仓库克隆(依赖可访问的 git 远端)与模型 provider 的相应凭证/配置。
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 StartedRust0623
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