OpenAI Evals — 架构与原理
30 秒导读: OpenAI Evals 是一个评测 LLM(或基于 LLM 的系统)的框架。你给它一份 JSONL 数据集和一条"怎么算答对"的规则,它就并行地把每个样本喂给模型、判分、把全过程逐条记成日志,最后汇总出准确率这类指标。它的精髓是"声明式":大多数评测不用写代码,只写一个 YAML 把现成的评测模板和数据集拼起来。
1. 这是什么(零基础也能懂)
一句话定义: Evals 是一个测 LLM 好不好用的框架——给定一批题目和"标准答案/评分规则",自动跑模型、对答案、出分数。
解决什么问题 / 给谁用。 假设你在用 GPT 做"把用户问题分类"或"翻译",换了个模型版本后,你怎么知道是变好了还是变差了?靠人工抽看几条不靠谱。Evals 让你把"判断标准"固化成一个可复跑的测试集:换模型、改 prompt,跑一遍就有可比的数字。它面向用 LLM 搭东西的工程师,以及想给模型能力建基准(benchmark)的研究者。
它能做什么。
- 内置一个评测注册表(registry):仓库里有 400+ 个现成 eval(
evals/registry/evals/下有 463 个 YAML),覆盖数学、翻译、推理、安全等维度。 - 提供几类评测模板,让常见评测零代码:精确匹配、包含匹配、模糊匹配、JSON 匹配、以及"模型当裁判"。
- 支持自定义评测逻辑(写一个 Eval 子类)和自定义被测系统(写一个 CompletionFn / Solver),也能接非 OpenAI 的模型。
- 把每次运行的每一步(采样、判分、指标)流式写成 JSONL 日志,可事后分析。
用起来什么样。 一条命令就能跑(README 的 "Running evals" 节):
# 用 gpt-3.5-turbo 跑名为 test-match 的评测
oaieval gpt-3.5-turbo test-match
左边 gpt-3.5-turbo 是"谁来答题"(completion_fn),右边 test-match 是"考哪张卷子"(eval 名字,去注册表里查)。跑完结果汇总打到日志、明细落到 /tmp/evallogs/<run_id>_....jsonl(见 evals/cli/oaieval.py:197-201 的 record_path)。
一句话直觉/类比。 把它想成自动化考试系统:注册表是"题库",eval 是"一张卷子+判分标准",completion_fn/solver 是"考生",recorder 是"监考+成绩单"。框架负责把考生叫来、发卷、收卷、判分、登记成绩——你只需要声明用哪张卷子、哪个考生。
本节不碰任何底层细节。记住三个角色就行:卷子(Eval)/ 考生(CompletionFn/Solver)/ 成绩单(Recorder)。
2. 顶层全景(它大概怎么转)
这一节看懂"一条 oaieval 命令从敲下到出分"的大盘。
怎么读下面这张图: 从上到下是一次运行的时间顺序;oaieval 是总指挥,先去注册表把两个字符串名 字解析成真实对象,再交给 Eval 主循环并行跑样本,每跑一步就经 Recorder 落盘。
oaieval <completion_fn> <eval> ← 命令行两个名字(字符串)
│
▼
┌──────────────────────┐
│ Registry(注册表) │ 读 evals/registry/**/*.yaml
│ 名字 → 规格 → 对象 │ 解引用别名、构造实例
└──────────────────────┘
│ │
完成对象 ▼ ▼ 完成对象
┌───────────────┐ ┌──────────────────┐
│ CompletionFn │ │ Eval 子类实例 │ 如 Match / ModelBasedClassify
│ /Solver(考生)│ │ (卷子+判分) │
└───────────────┘ └──────────────────┘
│ eval.run(recorder)
▼
┌─────────────────────────────┐
│ 主循环 eval_all_samples │ 线程池并行
│ 对每个样本: eval_sample() │ → 调考生 → 判分
└─────────────────────────────┘
│ 每步 record_*()
▼
┌─────────────────────────────┐
│ Recorder(成绩 单) │ 事件流 → JSONL
│ sampling / match / metrics │ 最后 record_final_report
└─────────────────────────────┘
部件一句话职责:
| 部件 | 干什么 | 在哪个文件 |
|---|---|---|
oaieval CLI | 解析参数,串起注册表→构造→运行→落盘 | evals/cli/oaieval.py(run) |
Registry | 把名字(模型名或注册表 key)解引用成 CompletionFn/Eval 实例 | evals/registry.py(Registry) |
Eval 基类 | 定义"加载数据→并行跑样本→汇总"的骨架 | evals/eval.py(Eval) |
| 基础模板(Match 等) | 用一行规则判对错(startswith / in / 模糊) | evals/elsuite/basic/ |
ModelBasedClassify | 模型当裁判,把开放式回答判成单选 | evals/elsuite/modelgraded/classify.py |
CompletionFn / Solver | 被测系统:prompt→文本 / TaskState→SolverResult | evals/api.py、evals/solvers/solver.py |
Recorder | 把每步事件流式写成 JSONL,汇总最终报告 | evals/record.py |
主线走一遍(高层、不进代码):
- 解析名字。
oaieval gpt-3.5-turbo test-match:run()让Registry把test-match查成一个EvalSpec(指向某个 Eval 类 + 数据集路径),把gpt-3.5-turbo造成一个OpenAIChatCompletionFn。 - 构造卷子。 用 spec 里的
cls实例化 Eval 子类(如Match),把考生(completion_fn)和数据集路径塞进去。 - 跑。
eval.run(recorder)→ 读 JSONL 样本 → 线程池并行,对每个样本调eval_sample:调考生拿回答、和ideal对比、record_match记一笔。 - 汇总。
run收集所有match事件,算accuracy和bootstrap_std,record_final_report写进日志末尾。
看懂这条主线,后面四章只是把每个方框拆开看内部。
3. 阅读地图(建议顺序)
按"由浅入深"读:
- 01-cli-and-registry.md —— 先搞懂"名字怎么变对象":CLI 参数、注册表的 YAML 加载与别名解引用、动态构造。这是整个框架的"装配车间"。
- 02-eval-loop.md —— 评测主循环的契约:
eval_sample/run两个方法、并行执行、确定性随机种子;再看Match/Includes这类最简单的"对答案"规则。 - 03-model-graded.md —— 当答案开放、没法字符串比对时,怎么"让模型当裁判":
ModelBasedClassify+cot_classify+ 选项解析。这是 Evals 最有借鉴价值的一块。 - 04-completion-fns-and-solvers.md —— 被测系统的两层抽象演化(CompletionFn → Solver)、为什么要分,以及事件记录器的设计。
4. 代码地图(导航索引)
| 主题 | 文件路径 | 符号名 |
|---|---|---|
| CLI 主流程 | evals/cli/oaieval.py | run、get_parser、build_recorder |
| 注册表 | evals/registry.py | Registry、make_completion_fn、_dereference、_load_registry |
| 规格数据类 | evals/base.py | EvalSpec、CompletionFnSpec、RunSpec |
| 评测基类 | evals/eval.py | Eval、SolverEval、eval_all_samples、_index_samples |
| 基础模板 | evals/elsuite/basic/match.py、includes.py | Match、Includes |
| 匹配工具 | evals/api.py | record_and_check_match、CompletionFn |
| 模型裁判 | evals/elsuite/modelgraded/classify.py、classify_utils.py | ModelBasedClassify、classify、get_choice、ANSWER_PROMPTS |
| 裁判规格 | evals/elsuite/modelgraded/base.py | ModelGradedSpec |
| Solver 抽象 | evals/solvers/solver.py | Solver、SolverResult、NestedSolver、create_solver |
| 任务状态 | evals/task_state.py | TaskState、Message |
| 记录器 | evals/record.py | RecorderBase、LocalRecorder、record_match、record_sampling |
| 指标 | evals/metrics.py | get_accuracy、get_bootstrap_accuracy_std、get_confusion_matrix |