Zed 编辑预测评测样例解析:flask--add-test-function.md 如何定义 Zeta 模型的输入与期望输出
本文以 Zed 仓库中编辑预测(Edit Prediction,即 Zeta 功能)的评测样例 flask--add-test-function.md 为剖析对象,完整解读这类 Markdown 评测样例的 front matter 与三个核心小节(Edit History、Cursor Position、Expected Patch)的语义,并结合 example_spec.rs 与 load_project.rs 等源码说明评测 CLI 如何把这份静态文本还原成真实的"仓库 + 缓冲 + 光标"现场、再对模型预测进行打分。读完你可以独立读懂 crates/edit_prediction_cli/evals/ 目录下的任意样例,并掌握编写新评测样例的格式约束。
1. 评测样例在 edit_prediction_cli 中的角色
Zed 的编辑预测模型需要通过大量"给定历史编辑与光标位置,补全下一段文本"的评测样例(example)来验证效果。crates/edit_prediction_cli/evals/ 目录就是这类样例的存放地,其中包含多组按"仓库--场景"命名规范组织的文件:
- Flask 系列:flask--add-import-statement.md、flask--add-and-rename-test-function.md、flask--rename-accepted-prediction.md 及本文主角 flask--add-test-function.md;
- 其他仓库系列:
tree-sitter--*.md、vscode--*.md、zed--*.md、codex-acp--*.md、hello-world--*.md、terraform--*.md等。
从 example.rs 中的 read_example_files 可以看到,CLI 支持三种输入格式:.md(走 parse_markdown_example → ExampleSpec::from_markdown)、.json(单个 Example 反序列化)、.jsonl(每行一个样例);样例的 name 默认取文件名主干(如 flask--add-test-function),这也解释了 evals 目录的命名约定。
每个样例最终被解析为 ExampleSpec 结构体,核心字段包括:name、repository_url、revision、tags、reasoning、uncommitted_diff、recently_opened_files/recently_viewed_files、cursor_path、cursor_position、edit_history、expected_patches、rejected_patch 等。
2. 逐段拆解 flask--add-test-function.md
2.1 Front matter:锁定上游仓库与提交版本
+++
repository_url = "https://github.com/pallets/flask"
revision = "2fec0b206c6e83ea813ab26597e15c96fab08be7"
+++
front matter 用 +++ 包裹的 TOML 块描述评测现场所在的外部仓库(这里是 Flask 上游仓库)与精确的 git 修订。from_markdown 解析的 FrontMatter 结构还支持可选字段 tags(标签列表)和 uncommitted_diff_requires_edit_history_rollback(布尔值)。锁定 revision 的意义在于:后续 CLI 会 clone 该仓库并 checkout 到这一提交,保证所有评测者在完全一致的代码快照上复现同一评测——这是结果可重复的基础。
2.2 Edit History:用户刚刚敲下的编辑
--- a/tests/test_basic.py
+++ b/tests/test_basic.py
@@ -1376,5 +1376,8 @@
def test_static_files(app, client):
rv = client.get("/static/index.html")
assert rv.status_code == 200
assert rv.data.strip() == b"<h1>Hello World!</h1>"
with app.test_request_context():
assert flask.url_for("static", filename="index.html") == "/static/index.html"
rv.close()
+de
+
+
def test_static_url_path():
## Edit History 小节下的 diff 代码块记录了触发预测前用户在 tests/test_basic.py 中完成的编辑:在 test_static_files 与 test_static_url_path 两个测试函数之间插入了一段新代码,且只输入了两个字符 de(一个新测试函数名的开头)。这一小节会被 run_load_project 中的 apply_edit_history 原样应用到 worktree 缓冲上(底层调用 edit_prediction::udiff::apply_diff,见 load_project.rs 末尾),从而在内存中重建"用户刚编辑完"的现场。此外解析器还支持在 Edit History 小节中用 // User accepted prediction: 标记行夹带"被用户接受的预测"片段,用于复现多轮连续编辑场景(见 example_spec.rs 中的 ACCEPTED_PREDICTION_MARKER 常量及对应测试)。
2.3 Cursor Position:光标位置与上下文摘录
```tests/test_basic.py
def test_static_files(app, client):
rv = client.get("/static/index.html")
...
de
# ^[CURSOR_POSITION]
def test_static_url_path():
`## Cursor Position` 小节有两个关键要素,均被解析器(`from_markdown` 中的 `Section::CursorPosition` 分支)提取:
1. **代码块 info string 即文件路径**:解析器把代码块围栏后的信息字符串直接当作 `cursor_path`(`spec.cursor_path = Path::new(block_info).into()`),所以这里写的是 `tests/test_basic.py`。
2. **`^[CURSOR_POSITION]` 标记行**:光标所在行的下一行是一条注释,`^` 字符的水平位置指向上方光标列。从 `cursor_excerpt` 的文档注释可知两种写法:
- `^`:光标列就是 `^` 字符所在列(本例中 `de` 行下、`d` 之后);
- `<`:当光标列小于注释前缀长度时使用,表示光标在该行第一个非空白字符处;
- 还有一种内联标记 `<|user_cursor|>`,直接内嵌在文本流中标记光标字节偏移(`cursor_excerpt` 会优先检查它)。
这段摘录(excerpt)不是摆设:`load_project` 会用**整段摘录文本**在打开的缓冲中做子串匹配来定位光标偏移,并且**要求匹配必须恰好唯一**(`"More than one cursor position match found"` 会直接报错,见 [load_project.rs](https://gitcode.com/GitHub_Trending/ze/zed/blob/b1a7ef0cf66dfbf9d7661170c96d97c7df916c68/crates/edit_prediction_cli/src/load_project.rs?utm_source=gitcode_repo_files) 的 `cursor_position` 函数)。因此编写样例时,Cursor Position 摘录必须包含足够的上下文使其在整个文件中只出现一次——这也是摘录里带上前后相邻测试函数的原因。
### 2.4 Expected Patch:多候选期望补全 + 期望光标位置
```diff
--- a/tests/test_basic.py
+++ b/tests/test_basic.py
@@ -1372,15 +1372,15 @@
-de
+def test_():
# ^[CURSOR_POSITION]
+
## Expected Patch 小节允许包含多个 diff 代码块,解析器会把它们全部收进 expected_patches: Vec<String>——即"以下任何一种补全都算模型答对"。本样例列出了 10 个候选,构成一条清晰的能力阶梯:
| 候选 | 考察点 |
|---|---|
def test_(): / def test_(): + pass |
最小合法补全:补全关键字与函数头 |
def test_(app, client): / 带 pass |
识别相邻测试函数使用的 app, client fixture 并补入参数 |
def test_static_(): / def test_static_(app, client): |
从文件名 test_basic.py 的上下文(test_static_files 等)推断函数名以 test_static_ 开头 |
def test_static_folder(): + pass |
更具体的函数名猜测 |
def test_static_route_with_host_matching(app, client): |
完整函数名 + 参数,且光标落在 d 之前 |
注意每个期望补丁中都带有一条 # ^[CURSOR_POSITION] 标记行,它编码了编辑完成后光标应该移动到的位置。源码中 expected_patches_with_cursor_positions 通过 extract_cursor_from_patch 从补丁文本中提取出(补丁本体, 期望光标偏移)二元组;encode_cursor_in_patch 则负责反向编码且保证幂等(example_spec.rs 末尾的测试 test_encode_cursor_in_patch_is_idempotent 验证了重复编码不会叠加标记)。这意味着评测不仅比"补全内容对不对",还比"补全后光标落点准不准",完整覆盖 Zeta 的双输出(文本补丁 + 光标跳转)。
对照 flask--add-import-statement.md 可以看出同一套机制的另一种用法:Edit History 记录了用户在 src/flask/logging.py 中把 from werkzeug.local import LocalProxy 误删成 imfrom ... 的错误编辑,Cursor Position 停在 imfrom 行,期望补丁则是把它还原为 import + from ... 两行——说明这套样例同样覆盖"修复误删"类场景,而 hello-world--rename-accepted-group-by.md、flask--rename-accepted-prediction.md 等文件名则暗示还存在"接受预测后再重命名"的多轮场景。
3. CLI 如何消费这份样例:从 Markdown 到现场还原
edit_prediction_cli 的命令行工具在 main.rs 中通过 clap 声明为 ep(#[command(name = "ep")]),子命令覆盖完整评测流水线:read(读取/拉取样例)、load-project(建 worktree、加载文件)、context(检索相关上下文)、format-prompt(按 provider 生成 prompt)、predict(跑预测)、parse-output(把模型输出解析回 unified diff)、score(打分)、distill(蒸馏数据集)、eval(聚合评分)、synthesize(从 git 提交自动生成样例)、split-commit、truncate-patch、split、filter-languages、qa(LLM-as-a-judge 质量评估)、repair 等。全局参数包括 --name/--repo 过滤、--limit/--offset 分页、--markdown(按样例输出 md 文件)、--max-parallelism(默认 10)等,因此可以直接用 ep eval <evals 目录中的 md 文件> 这类方式针对单个样例跑通全流程。
以本 Flask 样例为例,load-project 阶段(run_load_project)的执行链条是:
- 建 worktree:
setup_worktree按repository_url计算本地仓库目录,必要时git init+remote add origin,fetch_if_needed拉取 revision,然后git worktree add出一个以样例文件名为分支名的独立工作树(分支名来自example.spec.filename(),即对name做字符清洗)。 - 应用 Edit History:
apply_diff(&example.spec.edit_history, ...)把+de这段编辑写入缓冲,此时缓冲中tests/test_basic.py已包含半成品的de。 - 定位光标:先按
cursor_path(含去前缀回退,兼容旧样例的zed/crates/...写法)打开缓冲,再用 Cursor Position 摘录做唯一子串匹配换算出Anchor光标。 - 构造 prompt 输入:
compute_cursor_excerpt/compute_syntax_ranges计算光标摘录与语法范围,连同项目编辑历史事件、最近打开文件列表一起装入Zeta2PromptInput(见 load_project.rs 中prompt_inputs的组装代码),这就是喂给模型的最终输入结构。
4. 打分:expected_patches 如何参与评分
score/eval 阶段的入口是 score.rs 的 run_scoring,关键步骤:
- 先跑一次
run_prediction拿到模型的actual_output(或由parse_prediction_output解析出actual_patch与actual_cursor); - 调
spec.expected_patches_with_cursor_positions()取出全部 10 个候选补丁及其期望光标偏移,再由edit_prediction_metrics::prepare_expected_patches把补丁应用回原文本,得到"期望编辑后的完整文本"; - 对每条 prediction 调
score_prediction,输入包括原文本original_text、期望补丁集合、实际补丁、实际光标、编辑历史事件(用于判定"预测是否反转了用户刚做的编辑")、以及检索到的相关上下文; - 相关上下文参与评分时有 token 上限,常量
EVAL_RELATED_CONTEXT_TOKENS_LIMIT为 4000(可在eval子命令的--related-context-limit覆盖)。
由于本样例的 10 个候选覆盖从"只补关键字"到"补全函数名+fixture 参数"的不同粒度,评分可以区分模型"能给出语法合法的最低限度补全"与"能像人类开发者一样选择 test_static_* 命名并带上 app, client fixture"两种质量层次——这正是编辑预测评测要度量的核心能力。
5. 编写新评测样例的格式要点
结合解析源码(ExampleSpec::from_markdown)可以总结出编写 .md 样例必须满足的约束:
- front matter 必填
repository_url与revision;revision 必须是可checkout的完整提交,Cursor Position 摘录必须能在该提交对应文件(应用 Edit History 后)中唯一匹配; - 小节标题固定:
## Edit History、## Cursor Position、## Expected Patch为常用三节,另支持Reasoning、Uncommitted Diff、Recently Opened Files、Recently Viewed Files、Rejected Patch(后者提供 DPO 负样本);未知小节会被当作Section::Other忽略,但 H5/H6 级别标题会直接bail!; - Cursor Position 代码块:info string 写相对文件路径;正文中必须含
^[CURSOR_POSITION](或<[CURSOR_POSITION]、内联<|user_cursor|>)标记行;若缺少 cursor 代码块,解析会报Missing cursor position codeblock; - Expected Patch 代码块:
diff块内用注释形式的^[CURSOR_POSITION]标记行编码期望光标落点,一个样例可写任意多个候选补丁;补丁必须能应用在"应用了 Edit History 后的缓冲"上,否则评分阶段会以Expected patch did not apply报错; - 命名:文件主干即样例名,建议沿用
仓库名--场景描述.md的既有约定,便于--name/--repo过滤与失败日志检索。
这套"Markdown 声明评测现场 + git worktree 精确复现 + 光标双通道评分"的机制,使得 crates/edit_prediction_cli/evals/ 中每个文件都是一个可独立复现、可回归的编辑预测测试用例;flask--add-test-function.md 作为其中覆盖面较广的一条(多候选期望、fixture 参数推断、光标落点校验),是理解整个评测体系的理想入口。
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