任务规格格式:评估契约的 JSONL 形状 本节摘要:评估框架的好坏,取决于它的任务遵守什么契约。本节在写任何打分函数之前先冻结 JSONL 形状与指标词表。一份任务是一条单行 JSON 对象,固定字段( 、 、 、 、 、 )覆盖算术、多选、代码执行、分类、自由文本摘要五类任务;指标名是封闭词表,下游(7173 节)按单字段分派;few-shot 与后处理规则是任务的一部分而非运行器的,让同一提示跨模型产出同一目标。配套一个严格校验器,畸形记录在进运行器前就被拒,另附 10 条覆盖每条规则分支的固定集。读完本节,你能像对待数据库迁移一样对待规格——新增类别必须同时加指标、后处理规则与至少一条固定任务。 对应原课程:Phase 19 · Lesson 70 · (原英文 )。
本节摘要:评估框架的好坏,取决于它的任务遵守什么契约。本节在写任何打分函数之前先冻结 JSONL 形状与指标词表。一份任务是一条单行 JSON 对象,固定字段(
task_id、category、prompt、targets、metric_name、post_process)覆盖算术、多选、代码执行、分类、自由文本摘要五类任务;指标名是封闭词表,下游(71~73 节)按单字段分派;few-shot 与后处理规则是任务的一部分而非运行器的,让同一提示跨模型产出同一目标。配套一个严格校验器,畸形记录在进运行器前就被拒,另附 10 条覆盖每条规则分支的固定集。读完本节,你能像对待数据库迁移一样对待规格——新增类别必须同时加指标、后处理规则与至少一条固定任务。
对应原课程:Phase 19 · Lesson 70 ·
task-spec-format(原英文phases/19-capstone-projects/70-task-spec-format/docs/en.md)。本节属第 20 章「毕业项目」的评估赛道(Track G),是本赛道的地基。
阅读完本节,你应当能够:
研究代码库积累评估脚本的速度比积累测试还快。半年后,每个 notebook 有自己的 JSON 形状,每个指标被实现两次,跨运行什么都比不了。解法很无聊:挑一个模式,写一个校验器,拒绝其余一切。这就是本节做的。
形状借鉴 BIG-bench、HELM、lm-eval 风格的框架,但字段名是我们自己的。每个字段有唯一主人:运行器读任务,指标读 targets,后处理步归一化生成,任何字段在流水线中途不可变。
任务是单行上的一个 JSON 对象。框架读 tasks.jsonl,逐行独立校验——坏行只中止该记录,不中止整次运行。
{ "task_id": "arith_001", "category": "arithmetic", "prompt": "计算结果。问题:17 + 24\n答案:", "targets": ["41"], "metric_name": "exact_match", "few_shot_examples": [ {"prompt": "问题:2 + 2\n答案:", "completion": "4"} ], "post_process": "strip_whitespace", "metadata": {"difficulty": "easy"} }
必填字段是 task_id、category、prompt、targets、metric_name、post_process;few_shot_examples 与 metadata 可选;未知顶层字段校验失败。
task_id:无空白字符串,校验器在文件内强制唯一。category:arithmetic、mcq、code_exec、classification、summary 之一。类别约束哪个「指标 + 后处理」组合合法:code_exec 必须用 metric_name = code_exec,mcq 必须用 metric_name = exact_match 对单字母 target。prompt:非空串,校验器禁止尾随空白,拒绝 prompt 体里已含 few-shot 块的记录——few-shot 渲染发生在运行器,不在作者。targets:非空字符串列表。exact_match 任一元素匹配即算;f1 与 rouge_l 取最高分 target;mcq 恰好一个元素。metric_name:exact_match、f1、bleu_4、rouge_l、accuracy、code_exec 之一。词表封闭,新指标需要新课与新条目。few_shot_examples:{prompt, completion} 对列表,校验器限 8 条以保持提示有界。post_process:none、strip_whitespace、lower、extract_letter、extract_code_block、extract_first_line 之一,每条规则单一确定行为,禁止组合。校验器返回两个列表:已校验记录;错误记录(含出错行、违反的规则、出错字段)。除非显式 --allow-bad-tasks,错误列表非空时运行器拒绝启动。
运行器把 few-shot 示例以空行分隔拼在 prompt 前,同一代码路径对每个模型跑,所以方差唯一来源是模型本身;作者写一次示例,而非每个提供商写一次。
def render(task): parts = [] for ex in task.get("few_shot_examples", []): parts.append(ex["prompt"] + " " + ex["completion"]) parts.append(task["prompt"]) return "\n\n".join(parts)
后处理在生成之后、指标之前跑,确定且无状态:
none:原样返回。strip_whitespace:去首尾空白。lower:转小写。extract_letter:返回匹配 [A-E] 的首字符,用于 MCQ。extract_code_block:返回首个三反引号围栏块的体,用于代码执行。extract_first_line:返回首个非空行,用于摘要分类。需要此列表外规则的任务,属于新课。
| 框架 | 任务形状 | 指标词表 | 后处理位置 |
|---|---|---|---|
| BIG-bench | JSON,任务自带 metric_fn |
开放(任务定义) | 任务内 |
| HELM | 严格模式,中央指标矩阵 | 封闭,中央定义 | 运行器 |
| lm-eval-harness | YAML/Py 类,每任务一个 Metric |
半封闭 | 任务类内 |
| 本节 | 单行 JSONL,字段固定 | 封闭,单字段分派 | 任务声明,运行器执行 |
💡 本节的取舍:形状比 BIG-bench严、比 HELM 轻。封闭指标词表 + 任务声明后处理是关键——它让下游 71~73 节按
metric_name单字段分派,运行器对指标名一无所知。业界(HELM、OpenCompass)也朝这个方向收敛:中央指标矩阵 + 任务声明配套。
TaskSpec + validate_file:本节的校验器是 71~73 节指标与 75 节运行器的共同入口——运行器只读已校验记录,指标只读 targets。render + 后处理助手:与校验同模块,让 75 节运行器只 import 一个模块。tasks_bad.jsonl:固定集覆盖算术 2、MCQ 2、代码执行 2、分类 2、摘要 2;坏集每条规则各踩一次,校验器恰好返回那么多错误。main.py 定义 TaskSpec、validate_task、validate_file 与 CLI 入口;固定加载器是 load_fixtures;渲染与后处理助手紧挨校验,让 75 节运行器只 import 单模块。从顶到底读 main.py,再读 code/tests/test_spec.py——测试钉死每条校验规则与每个后处理行为。
本节不打分、不调模型、不跑代码——那些在 71、72、75 节。本节冻结的是它们共同遵守的契约。
⚠️ 真实评估套件增加类别就像数据库加列。清醒的做法是:拒绝增加类别,除非同时加一个指标、一条后处理规则、至少一条固定任务。把规格当数据库迁移,每次改动经评审、版本化、配测试。本节校验器就是那道门。
translation,定义其合法 metric_name(应指向 71 节的 BLEU-4)与后处理规则,扩校验器,加两条固定任务。--allow-bad-tasks 语义:让运行器在错误列表非空时仍启动,但把坏记录数与分类写进报告头。metadata 里可覆盖的字段,校验器读取并强制。spec_version,校验器拒绝不匹配版本,演示一次破坏性升级。task_id 唯一性从单文件扩到多文件目录,校验器扫目录。task_id/category/prompt/targets/metric_name/post_process),未知顶层字段即拒。code_exec→code_exec,mcq→exact_match 对单字母。exact_match/f1/bleu_4/rouge_l/accuracy/code_exec,新指标需新课。--allow-bad-tasks)。下一节,我们将进入「经典指标」——从第一性原理实现 exact-match、F1、accuracy、BLEU-4、ROUGE-L,让本节
metric_name字段指向的每个名字都有可审计的实现。