剖析 Zed 的编辑预测评测用例:project::Event匹配臂改名场景与多目标期望补丁
【免费下载链接】zedCode at the speed of thought – Zed is a high-performance, multiplayer code editor from the creators of Atom and Tree-sitter.项目地址: https://gitcode.com/GitHub_Trending/ze/zed
本文以 Zed 仓库中的真实评测文件 zed--change-match-arm.md 为主体,完整解读 Zed 内置编辑预测(Zeta)模型评测用例的文档结构:TOML frontmatter 如何钉住代码库版本、Edit History/Cursor Position/Expected Patch三个小节如何映射到ExampleSpec数据模型、[CURSOR_POSITION]游标标记的编码方式,以及“多目标期望补丁”的设计意图。读完本文,你能看懂并独立构造一个可被edit_prediction_cli流水线加载、运行与评分的评测用例。
1. 这个文件是什么:一条 Zeta 编辑预测评测用例
crates/edit_prediction_cli/evals/目录存放着编辑预测模型的手写评测用例,同一目录下还有flask--add-test-function.md、tree-sitter--if-let-to-match.md、vscode--add-async-and-await.md等同名约定文件(仓库名 + 双横线 + 场景短名)。zed--change-match-arm.md 是其中之一,它针对的是 Zed 自身代码库中一次“给match的某个匹配臂改名”的编辑场景。
文件以 TOML frontmatter 开头:
repository_url = "git@github.com:zed-industries/zed" revision = "be5763632dccb33470ca233c36ccd9e5e790e3b2"这两个字段直接对应评测用例的数据模型ExampleSpec(定义于 example_spec.rs):
repository_url:评测所基于的仓库,流水线据此克隆代码;revision:钉住的具体提交。评测必须在“用户编辑之前”的那个代码状态上运行,模型看到的才是编辑前快照。从源码结构看,自动生成的用例同样遵循“指向编辑前基线提交”的约定,例如 split_commit.rs 中会自动生成revision = format!("{}~1", commit_hash)这类父提交引用。
ExampleSpec中与本用例正文三个小节一一对应的字段是edit_history、cursor_path+cursor_position和expected_patches,对应的 Markdown 标题常量也定义在同文件内(EDIT_HISTORY_HEADING/CURSOR_POSITION_HEADING/EXPECTED_PATCH_HEADING,见 example_spec.rs)。此外该结构体还支持uncommitted_diff、rejected_patch(用于 DPO 的负样本)、telemetry、human_feedback等可选字段,手写评测用例可以按需省略。
用例正文第一行还有一句人写的备注:
This prediction requires the model to see the
project::Eventenum.
这句话是整个用例的灵魂,后面第 3 节会专门展开。
2. 用例主体三段式结构完整解读
2.1## Edit History:给模型提供“编辑前情”
用例正文第一个小节是一段 unified diff,模拟用户在做目标编辑之前刚刚完成的改动:
--- a/crates/edit_prediction/src/edit_prediction.rs +++ b/crates/edit_prediction/src/edit_prediction.rs @@ -1035,7 +1035,7 @@ project_state.recent_paths.push_front(path); } } - project::Event::DiagnosticsUpdated { .. } => { + project::Event::Disk { .. } => { if cx.has_flag::<EditPredictionJumpsFeatureFlag>() { self.refresh_prediction_from_diagnostics( project,它的语义是:用户在edit_prediction.rs中刚把一个匹配臂project::Event::DiagnosticsUpdated { .. }改名为project::Event::Disk { .. }。对编辑预测模型而言,这段“编辑历史”是关键上下文——它暗示用户正在进行一次“枚举变体改名”的批量重构,下一步的合理编辑就是把其他匹配臂也同步改到新的变体名上。字段edit_history在ExampleSpec中是纯字符串,允许包含一个或多个 hunk;uncommitted_diff_contains_edit_history布尔字段则用于区分编辑历史是独立给出还是混在未提交 diff 里。
2.2## Cursor Position:预测的锚点
第二小节给出目标文件在预测时刻的局部内容,并用一行注释标记出光标确切位置:
crates/edit_prediction/src/edit_prediction.rs { project_state.recent_paths.remove(ix); } project_state.recent_paths.push_front(path); } } project::Event::Disk { .. } => { // ^[CURSOR_POSITION] if cx.has_flag::<EditPredictionJupsFeatureFlag>() { self.refresh_prediction_from_diagnostics( project,(以上为原文摘录;原文标记行为if cx.has_flag::<EditPredictionJumpsFeatureFlag>() {。)
注意^[CURSOR_POSITION]标记的写法:一个^指向上行中光标所在的字符列,标记本身嵌在代码行内。这个标记格式由zeta_prompt::udiff模块统一管理,example_spec.rs 直接重导出CURSOR_POSITION_MARKER、INLINE_CURSOR_MARKER、encode_cursor_in_patch、extract_cursor_from_patch;在自动拉取用例的路径中,标记按format!("{}[CURSOR_POSITION]{}", before, after)拼进文本(见 pull_examples.rs)。模型侧的提示词最终会基于这个标记确定“可编辑区域”的起点与光标列,评分时再反过来从模型输出中提取光标位置。
另有一个与游标截取相关的约束值得注意:MAX_CURSOR_FILE_SIZE被设为 64KB(example_spec.rs),超过该大小的文件不会直接截取全文作为游标内容,而是回退到基于 git 的加载方式。
2.3## Expected Patch:多目标期望补丁
第三小节是本用例最有意思的部分——它给出了两个并排的期望补丁:
--- a/crates/edit_prediction/src/edit_prediction.rs +++ b/crates/edit_prediction/src/edit_prediction.rs @@ -1032,10 +1032,10 @@ project_state.recent_paths.push_front(path); } } - project::Event::Disk { .. } => { + project::Event::DiskBasedDiagnosticsFinished { .. } => { if cx.has_flag::<EditPredictionJumpsFeatureFlag>() { self.refresh_prediction_from_diagnostics( project,--- a/crates/edit_prediction/src/edit_prediction.rs +++ b/crates/edit_prediction/src/edit_prediction.rs @@ -1032,10 +1032,10 @@ project_state.recent_paths.push_front(path); } } - project::Event::Disk { .. } => { + project::Event::DiskBasedDiagnosticsStarted { .. } => { if cx.has_flag::<EditPredictionJumpsFeatureFlag>() { self.refresh_prediction_from_diagnostics( project,两个补丁把project::Event::Disk { .. }分别改名为DiskBasedDiagnosticsFinished和DiskBasedDiagnosticsStarted。这不是冗余,而是“多目标”设计:ExampleSpec.expected_patches的类型是Vec<String>(example_spec.rs),即一个用例可以声明多个可接受的正确答案,评分逻辑(score.rs)会判断模型输出是否命中其中任意一个。从源码结构看,这种设计承认了“改名重构”场景下存在多个语义上都说得通的目标变体,避免因唯一标准答案造成误判,使评测更贴近真实重构的不确定性。
3. 为什么“模型必须看到project::Event枚举”
用例开头的备注要求模型看到project::Event枚举定义,原因在于:要判断匹配臂该改成哪个变体,模型必须知道这个枚举当前到底有哪些变体。这一点在当前仓库源码中可以直接印证。
project::Event定义于 project.rs,其中与诊断相关的三个变体是:
pub enum Event { // ... DiskBasedDiagnosticsStarted { language_server_id: LanguageServerId, }, DiskBasedDiagnosticsFinished { language_server_id: LanguageServerId, }, DiagnosticsUpdated { paths: Vec<ProjectPath>, language_server_id: LanguageServerId, }, // ... }也就是说,评测钉住的那个历史版本中刚出现的Disk变体,在现行代码里已经演进为DiskBasedDiagnosticsStarted/DiskBasedDiagnosticsFinished两个明确语义的变体(分别对应基于磁盘的诊断开始与结束)。这些事件自下而上的传播链路是:LSP 存储层先发出LspStoreEvent(变体定义与发出点见 lsp_store.rs 及 lsp_store.rs),再由Project统一转发为project::Event(见 project.rs)。
而“看到枚举定义”正是编辑预测提示词构造中“语义上下文检索”要解决的问题:retrieve_context.rs 负责在构造提示词时为模型补齐被引用项(如枚举定义、调用处)的源码,并且该流程依赖example.spec.repository_url非空(retrieve_context.rs);它还会监听LspStoreEvent::DiskBasedDiagnosticsFinished(retrieve_context.rs)来判断语言服务器已完成磁盘扫描、符号信息可以安全使用。换句话说,本用例同时考察了模型对“跨文件引用上下文”的理解能力——这正是备注那句话的测试意图。
事件改名的“牵一发动全身”也可以从事件消费方得到佐证:DiskBasedDiagnosticsFinished至少被 diagnostics.rs、项目面板、工作区 pane 以及协作集成测试(integration_tests.rs)等多处匹配处理,这也解释了为什么一次变体改名会体现在多个match臂上。
4. 评测用例如何被 CLI 加载与运行
edit_prediction_cli是承载整条评测流水线的 crate(Cargo.toml),围绕本用例的核心环节如下:
- 钉版本加载项目:load_project.rs 会按
repository_url定位(或克隆)本地仓库,校验/补建origin远程,git fetch并checkout到revision(必要时创建 worktree 与临时分支),从而精确还原用例声明的代码快照;用例的uncommitted_diff(若有)在此基础上应用,再把光标文件按cursor_path/cursor_position打开。 - 构造提示词并预测:光标文件内容以
[CURSOR_POSITION]标记方式进入提示词(zeta_prompt模块),预测结果记入Example结构的predictions字段(见 example.rs)。 - 评分:
score子命令对比模型实际补丁与expected_patches列表,命中即视为通过;Example结构同时保留了score、qa等字段用于记录多轮评测结果。 - 修复与数据集工具:
repair模块可对失败用例做修复尝试,split_dataset支持按repository_url等字段分层切分数据集(split_dataset.rs),pull_examples则从 git 历史自动挖掘新用例(pull_examples.rs)。
对应这些能力的模块源码都位于 crates/edit_prediction_cli/src/(load_project.rs、predict.rs、score.rs、repair.rs、retrieve_context.rs等),可以直接阅读以了解每一步的实现细节。
5. 构造同类评测用例的要点小结
以本文件为模板,一个合格的编辑预测评测用例需要满足:
- 钉住基线版本:
repository_url+revision指向“目标编辑发生前”的提交,保证评测可复现(frontmatter 见 zed--change-match-arm.md); - Edit History 提供因果:diff 要能解释“用户正在做什么风格的重构”,让期望补丁成为编辑历史的逻辑延续;
- Cursor Position 给出精确锚点:局部代码摘录中用
^...[CURSOR_POSITION]标记光标列,标记格式需与zeta_prompt::udiff的CURSOR_POSITION_MARKER约定一致; - Expected Patch 支持多目标:当存在多个语义合理的正确答案时,并列写出多个 diff 块,对应
expected_patches: Vec<String>; - 必要时声明上下文依赖:像本用例一样用一句话注明模型必须看到的定义(如
project::Event),提示评测执行方该场景依赖语义上下文检索,而非仅靠单文件局部信息。
参考路径
- 评测用例本体:crates/edit_prediction_cli/evals/zed--change-match-arm.md
- 用例数据模型:crates/edit_prediction/src/example_spec.rs
- 项目加载逻辑:crates/edit_prediction_cli/src/load_project.rs
project::Event枚举与事件转发:crates/project/src/project.rs- LSP 事件源:crates/project/src/lsp_store.rs
- 上下文检索:crates/edit_prediction_cli/src/retrieve_context.rs
- 评分与修复:crates/edit_prediction_cli/src/score.rs、crates/edit_prediction_cli/src/repair.rs
【免费下载链接】zedCode at the speed of thought – Zed is a high-performance, multiplayer code editor from the creators of Atom and Tree-sitter.项目地址: https://gitcode.com/GitHub_Trending/ze/zed
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考