跳转至

OpenAI Evals 拆解的维护记录

这是 OpenAI Evals 背后的事。正常阅读不用看,要对照上游更新、核对出处时再看。

版本和素材

  • 上游:openai/evals,MIT 许可,许可原文不随附
  • 读的版本:8eac7a7(2026-04-14 提交,钉 pre-commit 钩子版本),2026-10-20 读。各页不逐处标版本,上游更新了再对照这一个点
  • 源码存档:projects/openai-evals/assets/evals-src/ 只存细讲页 --8<-- 引到的文件,逐字未改,路径和原仓库一致:核心框架、两个字符串模板和模型打分模板、答题方框架、二十问和工具循环两个多轮样例、引到的几份注册 YAML。docs/ 下的项目文档不存档,原话在正文摘句,脚注钉这个 commit 的永链
  • 题库样本:上游的 registry/data/ 全在 Git LFS 里,克隆时跳过了 LFS,只用 git lfs pull --include 取了引到的三份 JSONL(test_fuzzy_match/samples.jsonl、test_metaeval/joke_fruits_labeled.jsonl、test_multiio/battles/joke_animals_vs_fruits.jsonl)和 registry/data/README.md,存档逐字
  • 行号:模版标题里的 路径:起-止 是原仓库同版本的行号,存档逐字,两边一致
  • 前辈对照:lm-evaluation-harness 和 BIG-bench 两句取自各自 README(2026-10-20 读),见入口页脚注
  • 调研:主会话读了核心框架约 3000 行,外加 elsuite/ 下二十问、bugged_tools 两个多轮样例;两轮盘问定下领域、baseline、五条线、设计重心和译名,再写入口篇和五篇细讲

计数口径

入口页和「登记」页的几个数,是在上游克隆里数 evals/registry/evals/*.yaml 得来的:

  • 841 个注册 eval:带 class 字段的条目,也就是能直接跑的全名条目;只写 id 的短名(别名)不算
  • 502 个只用字符串模板:class 是 Match(290)、Includes(174)、FuzzyMatch(38)的条目
  • 61 个模型打分:class 是 ModelBasedClassify 的条目
  • 另有 684 个不带点的短名,全都只写 id、不写 class;17 个带点的条目也只写 id,是指向别的全名的别名。这两类都不计入
计数脚本(在上游克隆根目录跑)
import collections, glob, yaml

by_class = collections.Counter()
for f in glob.glob("evals/registry/evals/*.yaml"):
    for name, spec in (yaml.safe_load(open(f)) or {}).items():
        if "." in name and isinstance(spec, dict) and "class" in spec:
            by_class[spec["class"]] += 1
print(sum(by_class.values()))
for cls, n in by_class.most_common(12):
    print(n, cls)

实验设置

只读了源码,没有实跑:没装上游的包,没调任何模型,模型用量为零。各细讲页每节只贴源码模版,不放效果块。

文中几处行为判断靠读代码推出,没跑过验证,上游更新后要重新对照:

  • Match 不传 separator,标准答案 4 碰上回答 42 判对;Includes 用 rfind,4 落在 14 里判对
  • 只给一个被测接口时,模型打分的裁判就是被测模型自己(classify.py 取 completion_fns[-1])
  • battle 比的是同一个模型对两道题的回答:两份回答都由同一个被测接口采样,上游样本也都是 input1、input2 两道题
  • 二十问按角色改写对话时,eval 插的提示在双方的视角里都成了 user(format_msg 里 is_system_msg 那一支走不到)
  • hidden_data_fields 只有 LocalRecorder 的构造参数,build_recorder 没把它接到命令行