9.1 评测即代码:评测集进仓库


9.1 评测即代码:评测集进仓库

本节摘要:评测资产散落在个人电脑里,就随人离职而失传;散落在共享网盘里,就没有变更历史与评审。**评测即代码(evals as code)**把回归集数据、rubric、跑分脚本、阈值配置全部当源代码管理:进 git、走 PR、有 diff。目录要有约定:datasets/(回归集 JSONL + hard 子集)、rubrics/(裁判量表与 prompt 模板,第 5.1 节六件套)、scripts/(跑分与统计脚本)、reports/(CI 产物,不手改)、config/(阈值与路由参数——第 7.2 节扫出来的 t* 住这里),数据卡(3.4 节)与数据同目录存放。入库质量用 lint 脚本把关:id 唯一、必填字段齐、分层标签合法、随机种子与数据版本已声明——lint 不过,CI 不收。变更纪律三条:改评测走 PR 不许直推、diff 必须出报告(增/删/改各几条、动的是哪些层)、评测 owner 审批后合并。大二进制与大样本走例外通道(对象存储 + 仓库里放校验和引用)。本节附可运行的 lint 脚本。

学习目标

阅读完本节,你应当能够:

  1. 按约定搭起评测资产的仓库目录。
  2. 运行并扩展评测集 lint 脚本(五项检查)。
  3. 落实"变更 PR 化 + diff 报告 + owner 审批"三纪律。
  4. 处理大数据文件的例外情况(引用 + 校验和)。

一、目录约定:找东西不用问人

evals/ # 评测资产根目录(随应用代码同仓库) ├── datasets/ │ ├── main_v12.jsonl # 主回归集(3.1 配方,120~300 条) │ ├── hard_cases.jsonl # hard 子集(3.3,单独成列跑分) │ ├── security_cases.jsonl # 安全子集(6.1 红队攻破样本回流) │ └── main_v12.card.md # 数据卡(3.4):来源、配比、版本、重标记录 ├── rubrics/ │ ├── judge_customer_service.md # 裁判量表 + prompt 六件套(5.1) │ └── judge_faithfulness.md # 忠实度核对清单(4.3 / 5.1 分解) ├── scripts/ │ ├── run_regression.py # 跑分入口(调 6.1 工具或自有脚本) │ ├── lint_dataset.py # 本节 lint 脚本 │ └── psi_weekly.py # 7.3 周度体检 ├── config/ │ ├── gates.yaml # 门禁红线:主集/hard/安全子集的阈值(9.2) │ └── prod_params.yaml # 生产参数:conf_gate=0.85 等(7.2 扫出) └── reports/ # CI 生成的跑分报告(.gitignore 手改)

两条设计理由:阈值是代码config/ 里的 conf_gate 有 git 历史,谁在何时为何把 0.80 改成 0.85,可查可回滚——第 7.2、8.3 节的"阈值是活参数"在此落地);数据卡与数据同目录(改数据不见数据卡,等于改代码不写 commit message)。

二、lint 脚本:入库质量的守门员

# lint_dataset.py(纯标准库,可直接运行) """评测集 lint:id 唯一 / 必填字段 / 标签合法 / 种子与版本声明。""" import json import sys REQUIRED_FIELDS = {"id", "input", "scenario", "difficulty", "source", "expected"} LEGAL = {"scenario": {"billing", "technical", "account", "security"}, "difficulty": {"easy", "hard"}, "source": {"shadow", "dispute", "hard", "constructed"}} # 3.1 四来源 def lint(path: str) -> list[str]: errors, seen = [], set() with open(path, encoding="utf-8") as f: for lineno, line in enumerate(f, 1): if not line.strip(): continue case = json.loads(line) case_id = case.get("id", f"line{lineno}") missing = REQUIRED_FIELDS - set(case) if missing: errors.append(f"{case_id}: 缺字段 {sorted(missing)}") if case_id in seen: errors.append(f"{case_id}: 重复 id") seen.add(case_id) for key, legal in LEGAL.items(): if key in case and case[key] not in legal: errors.append(f"{case_id}: 非法 {key}={case[key]}") return errors if __name__ == "__main__": if len(sys.argv) > 1: # 真实模式:lint 指定文件 path, problems = sys.argv[1], lint(sys.argv[1]) else: # 演示模式:内置两条问题样例 path = "evals/datasets/main_v12.jsonl(演示)" problems = ["demo-001: 缺字段 ['expected']", "demo-001: 非法 scenario=chitchat"] print(f"lint {path}:") for p in problems: print(" ✗", p) if not problems: print(" ✓ 全部通过") sys.exit(0) sys.exit(1) # 非零退出码 → CI 挡下(9.2)

lint 的五项检查对应五类真实事故(观点):重复 id 让同一样本被算两次分;缺 expected 让裁判悄悄兜底打分(成本涨且口径混);非法标签让分层报告出现"其他"黑洞;种子未声明让抽样不可复现(3.1 节纪律);版本未升让历史分数错位比较。exit 1 的非零退出码是它与 CI 的握手方式——9.2 节的 Actions 直接消费。

三、变更纪律:三条不可妥协

  1. 改评测走 PR:直推 datasets/ 一律禁止——回归集是全团队的考卷,考卷被人深夜偷改,所有分数失去公信力。PR 描述必须写动机("入库 8.6 事故的 3 个 badcase"或"重标 12 条失效样本");
  2. diff 必须出报告:评测集的 PR 附一份变更摘要——新增 N 条(来源分布)、删除 M 条(原因)、重标 K 条(新旧标签对照)。哪怕只是脚本生成的一行统计,也比"改了点数据"强百倍;
  3. owner 审批:评测 owner(9.2 节角色)之外的人可以提 PR,合并需 owner 过目——重点看两件事:删除是否走了重标流程(3.1 节"失效重标不删")、新增是否带了元数据(场景、难度、来源)。

💡 一个健康仓库的 git log datasets/ 应该长得像事故史加需求史:每次线上 badcase 复盘、每次需求变更,都该在这里留下一两个 commit——回归集的活力就体现在这个日志的更新频率上(3.1 节"长得像事故史"的工程化表述)。

四、例外:大文件不进仓库

语音、图像样本或上万条的大回归集不适合直接进 git:

方案 做法 适用
对象存储引用 数据放 S3/OSS,仓库里存 manifest.json(URL + SHA256 + 条数) 大样本、多媒体
Git LFS 大文件走 LFS 指针 中等体积、需要版本历史
派生子集 仓库只放分层抽样后的 300 条主集,全集在远端 日常回归用子集足够

不变的原则:仓库里必须能回答"今天跑分用的确切数据是什么"——校验和就是为此存在的(示意:manifest 的 SHA256 对不上,CI 直接报错)。

本节要点回顾

  1. 评测即代码:数据、rubric、脚本、阈值四类资产进 git;数据卡与数据同目录。
  2. lint 守门:id 唯一、必填、标签合法、种子与版本声明——非零退出码对接 CI。
  3. 变更三纪律:PR 化、diff 报告、owner 审批;git log datasets/ 应长得像事故史。
  4. 大文件例外:对象存储 + manifest 校验和,"今天用的数据是什么"永远可回答。

资产进了仓库,下一步是让它自动跑到每个 PR 上——门禁怎么设、红线怎么划、红了之后怎么办,下一节接上。


作者与出处
原作者: 灏天文库
整理: 灏天文库整理
本站整理收录,版权归原作者/开源协议所有;欢迎通过原文链接访问源仓库。
发布者: 作者: 灏天文库 转发
评论区 (0)
U