首页
/ Zed 编辑预测评测集实战:以 vscode--add-async-and-await 为例解析 Edit Prediction 评测样例的格式与执行原理

Zed 编辑预测评测集实战:以 vscode--add-async-and-await 为例解析 Edit Prediction 评测样例的格式与执行原理

2026-09-04 18:36:40作者:田桥桑Industrious

本文以 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.mdvscode--add-class-decorator.mdtree-sitter--if-let-to-match.mdzed--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 结构看,一个用例还可携带 reasoninguncommitted_diffrecently_opened_filesrecently_viewed_filesrejected_patch(用于 DPO 训练数据)、telemetry 来源信息、human_feedbackrating 等字段;本例只使用了最精简的必填子集。

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.tsCommandsRegistry.registerCommandhandler 回调改为 async 函数,并对内部的 getThreadAndRun(...) 调用加上 await,分别作用于 REVERSE_CONTINUE_IDSTEP_BACK_IDTERMINATE_THREAD_ID 三个命令。

运行评测时,这段 diff 会被真实应用到克隆出来的工作树上(见第 4 节),从而把历史编辑事件注入 Zed 的 EditPredictionStore,成为模型上下文的一部分。解析侧对应 example_spec.rsEDIT_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 {

这一节的代码块有两个关键约定:

  1. 代码块信息串是光标所在文件路径```src/vs/workbench/contrib/debug/browser/debugCommands.ts 中的路径会被解析为 cursor_pathexample_spec.rsSection::CursorPosition 分支把 block_info 直接当作路径)。
  2. [CURSOR_POSITION] 标记行 + 箭头指示光标列:标记写在光标行的下一行,行内的 ^ 字符位置就是光标所在列;若光标落在行首等 ^ 无法精确表示的位置,则改用 <,表示光标位于该行第一个非空白字符处(example_spec.rscursor_excerpt 文档注释明确定义了这两种箭头语义,测试代码 覆盖了 ^ 在行中、行首、行尾及文件末尾的各种情况)。本例中 ^ 位于 handler:h 之前,即光标停在 handler 标识符开头。

此外,系统还支持更简洁的行内标记 <|user_cursor|> 直接嵌入代码(INLINE_CURSOR_MARKER,见 example_spec.rscursor_excerpt 实现与对应测试),解析时优先匹配行内标记,未命中再回退到 [CURSOR_POSITION] 标记行。

片段文本必须能在缓冲区中唯一匹配:load_project.rscursor_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.rsread_example_files 按扩展名分派解析:.mdparse_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.rsrun_load_project)完成四件事:

  1. 还原工作树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
  2. 应用编辑历史apply_edit_history):调用 edit_prediction::udiff::apply_diff 把 Edit History 的 diff 应用到 Project,打开涉及的缓冲区,使这些编辑作为真实事件进入 EditPredictionStore
  3. 解析光标:按第 2.3 节描述的方式,在缓冲区中定位片段、计算字节偏移并转为 Anchor
  4. 构建提示词输入:把光标上下文交给 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_patchexpected_patches 对比,计算一组指标写入 example.scoreprint_reportscore.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 汇总为 SummaryJsoncompute_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.rsopenai_client.rs),无凭据时可至少完成 readload-project 阶段验证样例本身的可解析性与片段唯一性;
  • 失败样例无论 --failed keep/skip 都会落入运行目录的 failed 子目录(main.rs 注释),便于排查。

编写新用例时,参照本文件的骨架即可:一个 +++ front matter 固定 repository_url + revision## Edit History 放若干 ```diff 块构建编辑模式;## Cursor Position 用"路径作为代码块信息串 + [CURSOR_POSITION] 箭头标记"给出唯一可匹配的片段;## Expected Patch 放标准答案 diff。可选章节包括 ReasoningUncommitted DiffRecently Opened/Viewed FilesRejected 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.rsload_project.rsscore.rs 三处源码,就等于掌握了为 Zed 编辑预测功能新增回归评测用例的完整方法。

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