Zed 编辑预测评测集实战:以 vscode--add-async-and-await 为例解析 Edit Prediction 评测样例的格式与执行原理
本文以 Zed 仓库中的评测样例 vscode--add-async-and-await.md 为主线,讲清这个 Markdown 文件各字段(front matter、Edit History、Cursor Position、Expected Patch)的确切语义、ep 评测 CLI 如何把它加载成可执行的预测任务,以及预测结果如何被自动打分。读完后,你可以独立读懂并编写 Zed edit prediction 的评测用例,并能追踪从 Markdown 样例到工作树还原、提示词构建、指标计算的完整调用链。
1. 评测样例在 Zed 编辑预测体系中的定位
Zed 的编辑预测(edit prediction,即"预测下一步编辑"功能)需要通过回归评测集来量化模型行为:给定一段历史编辑和一个光标位置,模型应当给出与"Expected Patch"一致(或接近)的下一步 diff。评测用例集中存放在 crates/edit_prediction_cli/evals/,覆盖 VS Code、Flask、tree-sitter、Zed 自身等仓库的真实代码场景,例如 vscode--add-async-and-await.md、vscode--add-class-decorator.md、tree-sitter--if-let-to-match.md、zed--add-eprintln.md。
每个 .md 文件都是一个自包含的评测用例:它声明了源代码仓库、精确的 revision、编辑历史 diff、光标位置片段和期望 patch。文件名本身(如 vscode--add-async-and-await)会成为该用例的名称——在 example.rs 的解析逻辑中,name 为空时会回退为文件的 stem 名称。
2. 逐段解读样例文件:front matter、Edit History、Cursor Position、Expected Patch
完整文件只有四个部分,下面逐一说明其语义与来源。
2.1 Front matter:锁定外部仓库与 revision
+++
repository_url = "https://github.com/microsoft/vscode"
revision = "29e6da6efa2287aaa981635a475d425ff4fd5d5c"
+++
这段 +++ 包裹的 TOML 是样例的元数据,由 example_spec.rs 中的 FrontMatter 结构解析,核心字段包括:
repository_url:评测所用的上游仓库地址。本例使用 VS Code 仓库,而非 Zed 自身代码,说明评测集有意覆盖"非本仓库"的真实大型 TypeScript 代码库;revision:精确 commit,保证任何人在任何时候运行评测都基于同一份源码,可复现;tags(可选):用例标签,用于数据集过滤;uncommitted_diff_requires_edit_history_rollback(可选):标记未提交 diff 中是否已包含编辑历史,影响回放策略。
从 ExampleSpec 结构看,一个用例还可携带 reasoning、uncommitted_diff、recently_opened_files、recently_viewed_files、rejected_patch(用于 DPO 训练数据)、telemetry 来源信息、human_feedback 与 rating 等字段;本例只使用了最精简的必填子集。
2.2 Edit History:模型看到的"前情提要"
--- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts
+++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts
@@ -304,8 +304,8 @@
id: REVERSE_CONTINUE_ID,
- handler: (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
- getThreadAndRun(accessor, context, thread => thread.reverseContinue());
+ handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
+ await getThreadAndRun(accessor, context, thread => thread.reverseContinue());
}
Edit History 是一段标准 unified diff(```diff 代码块),描述用户在触发预测之前已经完成的一系列编辑。本例中共有三个 hunk,全部是同一模式的机械变换:把 debugCommands.ts 中 CommandsRegistry.registerCommand 的 handler 回调改为 async 函数,并对内部的 getThreadAndRun(...) 调用加上 await,分别作用于 REVERSE_CONTINUE_ID、STEP_BACK_ID、TERMINATE_THREAD_ID 三个命令。
运行评测时,这段 diff 会被真实应用到克隆出来的工作树上(见第 4 节),从而把历史编辑事件注入 Zed 的 EditPredictionStore,成为模型上下文的一部分。解析侧对应 example_spec.rs 的 EDIT_HISTORY_HEADING = "Edit History" 常量;此外还支持在两个 diff 之间插入 // User accepted prediction: 标记来表示"该次编辑来自被接受的预测"(见 example_spec.rs 与测试 test_from_markdown_accepted_prediction_marker)。
2.3 Cursor Position:带光标标记的代码片段
weight: KeybindingWeight.WorkbenchContrib,
primary: isWeb ? (KeyMod.Alt | KeyCode.F10) : KeyCode.F10,
when: CONTEXT_DEBUG_STATE.isEqualTo('stopped'),
handler: (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
// ^[CURSOR_POSITION]
const contextKeyService = accessor.get(IContextKeyService);
if (CONTEXT_DISASSEMBLY_VIEW_FOCUS.getValue(contextKeyService)) {
getThreadAndRun(accessor, context, (thread: IThread) => thread.next('instruction'));
} else {
这一节的代码块有两个关键约定:
- 代码块信息串是光标所在文件路径:
```src/vs/workbench/contrib/debug/browser/debugCommands.ts中的路径会被解析为cursor_path(example_spec.rs 中Section::CursorPosition分支把block_info直接当作路径)。 [CURSOR_POSITION]标记行 + 箭头指示光标列:标记写在光标行的下一行,行内的^字符位置就是光标所在列;若光标落在行首等^无法精确表示的位置,则改用<,表示光标位于该行第一个非空白字符处(example_spec.rs 的cursor_excerpt文档注释明确定义了这两种箭头语义,测试代码 覆盖了^在行中、行首、行尾及文件末尾的各种情况)。本例中^位于handler:的h之前,即光标停在handler标识符开头。
此外,系统还支持更简洁的行内标记 <|user_cursor|> 直接嵌入代码(INLINE_CURSOR_MARKER,见 example_spec.rs 的 cursor_excerpt 实现与对应测试),解析时优先匹配行内标记,未命中再回退到 [CURSOR_POSITION] 标记行。
片段文本必须能在缓冲区中唯一匹配:load_project.rs 的 cursor_position 函数会用 match_indices 在完整文件内容中查找该片段,匹配不到或匹配多处都会直接报错,因此编写用例时要保证摘录具有唯一性。
2.4 Expected Patch:标准答案
--- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts
+++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts
@@ -467,10 +467,10 @@
weight: KeybindingWeight.WorkbenchContrib,
primary: isWeb ? (KeyMod.Alt | KeyCode.F10) : KeyCode.F10,
when: CONTEXT_DEBUG_STATE.isEqualTo('stopped'),
- handler: (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
+ handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
const contextKeyService = accessor.get(IContextKeyService);
if (CONTEXT_DISASSEMBLY_VIEW_FOCUS.getValue(contextKeyService)) {
- getThreadAndRun(accessor, context, (thread: IThread) => thread.next('instruction'));
+ await getThreadAndRun(accessor, context, (thread: IThread) => thread.next('instruction'));
} else {
- getThreadAndRun(accessor, context, (thread: IThread) => thread.next());
+ await getThreadAndRun(accessor, context, (thread: IThread) => thread.next());
}
}
Expected Patch 是"正确模型的下一步应该输出什么"的标准答案。注意它与 Edit History 的呼应:历史编辑把前三个命令(reverse continue、step back、terminate thread)改成了 async/await,而光标正停在第四个同构命令(F10 对应的 step over,位于第 467 行附近)的 handler 处——期望 patch 正是对这一处执行同样的 async/await 改写。这是一个典型的"重复模式延续"(repetitive edit continuation)任务:编辑历史提供模式,光标位置提供锚点,模型需要外推。
Expected Patch 一节支持多个 ```diff 代码块,对应 ExampleSpec.expected_patches: Vec;若某个 patch 中带有 <|user_cursor|> 标记,则额外声明了"预测完成后光标应停在何处",由 expected_patches_with_cursor_positions() 与 udiff 的 encode/extract_cursor_in_patch 处理,用于评估模型输出的光标落点。
3. CLI 加载流程:从 Markdown 到 Zeta 提示词
example.rs 的 read_example_files 按扩展名分派解析:.md 走 parse_markdown_example(内部调用 ExampleSpec::from_markdown,用 pulldown_cmark 逐事件解析 H2 标题分节与 fenced 代码块),.json/.jsonl 直接反序列化为 Example 结构。解析完成后,ep CLI(见 main.rs 的子命令定义)会按 read → load-project → context → format-prompt → predict → parse-output → score → eval 的流水线下一步步加工该样例。
针对本例,load-project 阶段(load_project.rs 的 run_load_project)完成四件事:
- 还原工作树(setup_worktree):按
repository_url解析出owner/repo目录,若仓库不存在则git init+remote add,然后fetch指定revision,用git worktree add创建一个以文件名(vscode--add-async-and-await)为分支名的独立工作树,并做clean/reset/checkout保证状态确定。同一owner/repo目录被多个用例共享,靠文件锁(git::lock_repo)串行化,并清理上次崩溃残留的index.lock/HEAD.lock/config.lock; - 应用编辑历史(apply_edit_history):调用
edit_prediction::udiff::apply_diff把 Edit History 的 diff 应用到Project,打开涉及的缓冲区,使这些编辑作为真实事件进入EditPredictionStore; - 解析光标:按第 2.3 节描述的方式,在缓冲区中定位片段、计算字节偏移并转为
Anchor; - 构建提示词输入:把光标上下文交给
compute_cursor_excerpt生成窗口化摘录(excerpt),连同语法范围(syntax_ranges)、编辑历史事件列表(events)一起组装为 zeta_prompt::Zeta2PromptInput。后续format-prompt子命令再按所选模型/格式渲染成最终 prompt 字符串。
从源码结构看,LoadProject 会禁用工作树扫描器(disable_worktree_scanner),并对光标路径手动 refresh_worktree_entries——因为评测场景下缓冲区是按需打开的,不能依赖后台文件监听,这也是为什么片段匹配要在 open_buffers 与磁盘两条路径之间做回退(load_project.rs)。
4. 预测结果的打分:这个样例的"及格线"
ep score 阶段在 score.rs 中执行:先(若尚未运行)触发预测,再把模型输出的 actual_patch 与 expected_patches 对比,计算一组指标写入 example.score。print_report(score.rs)输出的报表列即指标集:
- DeltaChrF:对 patch 做字符级 chrF(默认 β 参数见报表行 "Delta chrF (β=...)"),衡量"改动的字符"与期望的吻合度,而非全文逐字相等;
- Brace:括号失衡度,捕捉生成半截 diff/花括号不闭合这类结构性错误;
- F1:exact lines 精确行匹配的 F1;
- Revert:反向重放(reversal)比例,即预测 patch 逆操作能否回到编辑历史状态;
- QaRev / QaConf:可选的 LLM-as-a-judge QA 结果(
ep qa子命令产生,qa.rs); - Cursor:若期望 patch 携带
<|user_cursor|>标记,则比对模型光标落点,精确命中记✓,否则记±距离; - WrgER:wrong editable region,模型是否选错了可编辑区域。
对本例而言,由于 Expected Patch 中没有内联光标标记,Cursor 指标不参与评判,核心考察的是 DeltaChrF/F1:模型能否只输出第 467 行附近那一处三行变更(handler: async ... + 两处 await getThreadAndRun),不多改、不漏改。
聚合层面,eval 子命令把多个样例的 score 汇总为 SummaryJson(compute_summary),除平均值外还统计 token 变更的 p25/p50/p75/p90/p99 分位(score.rs),用于观察不同模型"平均改多大"。
5. 如何复现与扩展这类评测
复现本用例只需在 Zed 仓库根目录下构建并运行 ep CLI,输入即为本样例文件路径(main.rs 的全局参数支持 --name 过滤、--limit、--max-parallelism、--failed 等,见 main.rs):
# 查看该用例解析结果(read 会输出 JSONL 形式)
cargo run -p edit_prediction_cli -- read crates/edit_prediction_cli/evals/vscode--add-async-and-await.md -o /tmp/example.jsonl
# 加载工作树并构建提示词、对模型跑预测(provider 由 format-prompt 阶段指定)
cargo run -p edit_prediction_cli -- load-project /tmp/example.jsonl
cargo run -p edit_prediction_cli -- score /tmp/example.jsonl -o /tmp/scored.jsonl
# 聚合报表
cargo run -p edit_prediction_cli -- eval /tmp/scored.jsonl
注意事项(均来自源码约束,而非推测):
- 运行需要能访问
repository_url(本例为 VS Code 仓库)的网络权限与 git,因为setup_worktree会实际 fetch 指定 revision; predict/score依赖模型供应商凭据(CLI 内置 Anthropic/OpenAI 客户端,见 anthropic_client.rs、openai_client.rs),无凭据时可至少完成read与load-project阶段验证样例本身的可解析性与片段唯一性;- 失败样例无论
--failed keep/skip都会落入运行目录的 failed 子目录(main.rs 注释),便于排查。
编写新用例时,参照本文件的骨架即可:一个 +++ front matter 固定 repository_url + revision;## Edit History 放若干 ```diff 块构建编辑模式;## Cursor Position 用"路径作为代码块信息串 + [CURSOR_POSITION] 箭头标记"给出唯一可匹配的片段;## Expected Patch 放标准答案 diff。可选章节包括 Reasoning、Uncommitted Diff、Recently Opened/Viewed Files、Rejected Patch,解析器对 H2 标题大小写不敏感(example_spec.rs 使用 eq_ignore_ascii_case),但 Cursor Position 一节是硬性要求——缺失会直接 bail!("Missing cursor position codeblock")(example_spec.rs)。
6. 小结
vscode--add-async-and-await.md 虽然篇幅短,却完整体现了 Zed 编辑预测评测的设计哲学:以"真实仓库 + 精确 revision + 可回放的 diff 历史 + 唯一光标片段 + 标准答案 patch"五要素定义一个可复现任务,由 ep CLI 完成工作树还原、提示词构建、预测与多维打分(DeltaChrF、行级 F1、反向重放、光标精度)的闭环。理解该文件及其背后的 example_spec.rs、load_project.rs、score.rs 三处源码,就等于掌握了为 Zed 编辑预测功能新增回归评测用例的完整方法。
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