首页
/ Zed 编辑预测评测样例解析:以 tree-sitter 的 if-let 改 match 重构为例理解 Zeta 评测格式

Zed 编辑预测评测样例解析:以 tree-sitter 的 if-let 改 match 重构为例理解 Zeta 评测格式

2026-09-06 10:50:28作者:韦蓉瑛

本文围绕 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,其中 mdparse_markdown_example()
  • parse_markdown_example() 调用 ExampleSpec::from_markdown()example_spec.rs),用 pulldown_cmark 遍历 Markdown 事件流,按二级标题切分章节;
  • 识别的章节标题常量与本文档完全对应:Edit HistoryEDIT_HISTORY_HEADING)、Cursor PositionCURSOR_POSITION_HEADING)、Expected PatchEXPECTED_PATCH_HEADING),另支持 Uncommitted DiffRecently Opened FilesRejected 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.rssetup_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.rscursor_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 块,对应三种都算对的模型输出:

  1. + Err(error) => {} —— 用命名绑定 error 但空作用体丢弃;
  2. + Err(_) => {} —— 用 _ 显式忽略;
  3. 多行未闭合形态:
+                Err(e) => {
+
+                }
光标落在空作用体内部——模拟"用户刚开括号、准备往里敲东西"的中间编辑态。

三个补丁块中都出现了形如 # ^[CURSOR_POSITION] 的标记行,它标注的是补丁应用完之后光标应停在的位置^ 的列对应新增文本中的列,例如第三个变体里光标落在空行上,即新 Err 臂作用体的开头)。ExampleSpec::expected_patches_with_cursor_positions()example_spec.rs)会对每个补丁调用 udiff.rsextract_cursor_from_patch() 恢复出 (干净补丁, 期望光标偏移) 二元组:逐行解析 diff,把内联光标标记从 + 行中剔除、并按上下文/新增行的累计字节数换算出光标在新文本中的绝对偏移。从源码结构看,# 开头的标记行不属于标准 diff 内容行,评测工具链(如同文件的 strip_diff_metadata())会将其与 index/diff --git 等元数据一并剥离,只保留可用于应用与比对的 hunk 内容行。

"多个 expected patch"的意义在于:真实编辑预测的正确答案天然不唯一。if letmatchErr 臂的写法因人而异,若评测只认一种拼法,会把大量行为正确的预测误判为失败。打分时 score.rsrun_scoring() 会把每个 expected patch 都经 edit_prediction_metrics::prepare_expected_patches() 预演(在光标摘录原文上试应用)后,逐条调用 score_prediction() 与模型的 actual_patch 比对,多个期望答案共同构成该样例的"及格线"。

六、跑这条样例:ep CLI 的最小工作流

结合 main.rsCommand 枚举,针对单条 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(默认)、zeta1teacher:<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 中还原出的编辑事件,填入 Zeta2PromptInputscore.rs 中明确提示 prompt_inputs is required for scoring - run prediction first,即跳步运行 eval/score 会直接报错。

七、这条样例的设计意图小结

  • 单一仓库 + 钉死 revision:Front Matter 把评测锚定在 tree-sitter 的 17e3c7a 提交,编辑历史与光标摘录都出自该版本的 crates/loader/src/loader.rsep 通过 git worktree 机制现场重建,保证任何人任何时间跑出的输入状态一致;
  • 重构进行到一半的"半态":Edit History 刻意停在 matchErr 臂的位置,考察模型能否依据上下文(fs::read_dir 返回 Result<ReadDir, _>、既有 Ok 臂结构)补全惯用的错误分支,而非补全任何"看起来像"的语句;
  • 多可接受答案 + 期望光标:三个 expected patch 覆盖了 Err(_)、命名绑定与未闭合臂三种形态,且每处都标注了补丁后光标落点,使打分不仅看"改得对不对",还能看"改完后光标在不在合理位置"(对应 ActualCursoreditable_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_markdowncursor_excerpt、期望补丁光标提取)
crates/zeta_prompt/src/udiff.rs 光标标记常量与补丁光标解码(extract_cursor_from_patchstrip_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 的相应凭证/配置。

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