EvalHarness/README.md
sora c78b0d6f0f Add --api-key/--judge-api-key: explicit keys override env resolution, never serialized
Explicit key flows only into request headers (adapter attribute / pool
member), so it cannot leak into the spec string, EvalReport, or logs --
verified by scanning a report produced with a sentinel key. Two-key
setups run twice with different --api-key, or use per-host env vars.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-09-10 06:50:18 +00:00

287 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# EvalHarness
**插件式 LLM / Agent 评测框架。** 28 个主流 benchmark 开箱即用:官方口径的 prompt 模板与判分器、
任意 OpenAI 兼容推理端点、断点续跑、沙箱化代码执行 —— 每一层都可以用单文件插件扩展。
```
你只需提供: 一个 OpenAI 兼容端点vLLM / SGLang / lmdeploy / ollama / 云 API
框架完成: 拉取数据 → 渲染官方 prompt → 并发生成 → 官方口径判分 → 产出报告
```
## 核心特性
- **28 个内置 benchmark** —— 数学、知识/选择题、问答、长上下文、代码执行、agent 工具调用,
全部对接官方数据源注册。
- **官方口径评测** —— prompt 模板、few-shot 范例渲染(含按科目域匹配选范例)、判分器全部复刻
官方实现:数学用 PRM800K sympy 等价、DROP 用匈牙利对齐、SimpleQA 用官方 A/B/C judge 协议、
BFCL 用官方 AST 判定、代码题在 docker 沙箱执行。已在 Qwen3-8B 与 DeepSeek-V4-Flash 上
与 evalscope 逐题对齐[验证](#对齐验证)。
- **任意推理栈** —— 单端点或端点池轮询分发、自适应并发AIMD、坏端点冷却、自动 failover。
- **可断点、抗抖动** —— 每条预测完成即落盘,中断重跑只补缺失样本;分钟级断网靠分层重试扛过
而不丢批次。
- **换判分器不用重新生成** —— 原始预测是不可变 artifact改 recipe、换 grader 直接对存量输出
重新判分,模型永不被重复调用。
- **万物皆插件** —— 数据集、prompt 渲染器、抽取器、判分器、聚合器、recipe、模型适配器、沙箱、
agent 环境,全部 `@register_*` 单文件注册,没有需要修改的中央清单。
- **评测 agent 而非扮演 agent** —— 被测模型负责思考;框架只把它的 tool_calls 交给 `Environment`
插件执行并回灌观察,全程记录轨迹。
## 安装
要求 Python ≥ 3.10,一条命令装完即可跑全部 28 个 benchmark无任何可选依赖
(代码执行类需要宿主机有 Docker镜像判分时自动拉取
```bash
git clone https://git.meta-stone.net/sora/EvalHarness.git
cd EvalHarness
conda create -n evalharness python=3.10 -y
conda activate evalharness
pip install .
```
离线验证安装(不需要模型、不联网):
```bash
evalharness eval run gsm8k --model mock-boxed --limit 8
# acc 100% —— mock 适配器直接输出金标答案,证明
# 数据 → prompt → 生成 → 判分 → 报告 全链路可用
```
## 快速开始
### 跑一个 benchmarkCLI
```bash
evalharness eval run gsm8k \
--api-url http://localhost:8000/v1 \
--model qwen3-8b \
--limit 200 --resume
```
**完整参数表**`evalharness eval run --help` 的整理版):
**模型接入**
| 参数 | 作用 |
|---|---|
| `--api-url URL` | OpenAI 兼容端点;与 `--model` 搭配使用(不用手拼 spec 字符串) |
| `--model NAME` | 服务端模型名(配合 `--api-url`);或直接给完整 spec`openai/http://h:8000/v1?qwen3-8b` |
| `--provider {openai-chat,openai-pool}` | 协议/提供方,默认 `openai-chat`;端点池用 `openai-pool` |
| `--judge NAME` + `--judge-api-url URL` | LLM-judge 模型hle / simple_qa / imo 需要);也可单给完整 spec `--judge openai/http://...?m` |
| `--api-key KEY` | 显式指定主模型端点的 key优先于环境变量推断只进请求头不写入 spec/报告) |
| `--judge-provider {openai-chat,openai-pool}` | judge 协议;多 judge 端点负载均衡用 `openai-pool` |
| `--judge-api-key KEY` | 显式指定 judge 端点的 key |
| `--profile NAME` | 命名生成参数集(内置 `dp4-nothink``qwen3-es-parity``t1-short`,或任意 `@register_gen_profile` 名);优先级:插件默认 < profile 默认 < profile bench 覆盖 < 显式参数 |
| `--disable-thinking` | 发送 `enable_thinking=false`Qwen3 类思考模型推荐 tools 的请求自动退回模板安全的软开关 |
| `--textools` | 工具以文本形式随 prompt 下发而非原生 tool_calls |
| `--perf` | 采集流式 TTFT / ITL / 重试率写入报告 `perf` |
**采样与选样**
| 参数 | 作用 |
|---|---|
| `--limit N` | 只跑全局前 N |
| `--limit-per-task N` | 每个 subset / 科目取前 N 多科目 bench 的语义可与 `--limit` 组合取交集 |
| `--subset NAME` | 覆盖子集 mmlu `anatomy`bbh `word_sorting` |
| `--split NAME` | 覆盖 split |
| `--source PATH` | 覆盖数据源指向本地目录/文件离线可用 |
| `--cache-dir DIR` | 缓存根目录默认 `$EVALHARNESS_CACHE` `~/.cache/evalharness` |
**运行控制**
| 参数 | 作用 |
|---|---|
| `--concurrency N` | 并发请求数默认 32长输出 bench 建议 8-16 |
| `--resume [PATH]` | 断点续跑默认路径自动推导可显式给路径 |
| `--env NAME` | agent 环境 `bfcl_mock`)→ 走多轮消息泵 |
| `--progress` / `--no-progress` | Rich 每样本进度条默认开重定向日志时建议 `--no-progress`未装 rich 自动降级纯文本 |
**输出**
| 参数 | 作用 |
|---|---|
| `--out FILE` | benchmark 时把 EvalReport JSON 存到此处 |
| `--out-dir DIR` | benchmark 运行落盘`reports/<name>.json` + `viz/<name>.txt` + `summary.md` |
| `--style {text,md,radar,errors}` | 结果渲染样式 |
| `--verbose` | benchmark 时也打印每个 bench 的完整渲染 |
端点池跨机器跨端口自带 failover 与自适应并发
```bash
evalharness eval run mmlu \
--provider openai-pool \
--api-url 'http://gpu1:{8123..8130}/v1,http://gpu2:{8200..8203}/v1' \
--model qwen3-8b --disable-thinking
```
### 用 Python 跑
```python
import evalharness
rep = evalharness.run('gsm8k', 'openai/http://localhost:8000/v1?qwen3-8b', limit=200)
print(rep.metrics['acc'])
rep.save('gsm8k.report.json')
# 已在事件循环里notebook用 await 版:
rep = await evalharness.arun('mmlu', '...', subset='anatomy')
```
也可以分阶段自己驱动 —— 数据集是一等公民判分永远不会重新调模型
```python
from evalharness import get_dataset
from evalharness.eval import evaluate
ds = get_dataset('mmlu', subset='anatomy') # 惰性句柄;首次使用才物化
preds = [json.loads(l)['raw'] for l in open('preds.jsonl')]
rep = evaluate(ds, preds) # recipe 按 bench 名自动解析
```
### 查看与渲染结果
```bash
evalharness viz show gsm8k.report.json # 控制台表格
evalharness viz show a.json b.json --style md_compare # 多模型对照
evalharness viz show report.json --style excel # 4-sheet 仪表盘
evalharness viz show report.json --style errors # 失败样本下钻
```
每样本结果保留原始预测抽取说明分数明细token 用量agent bench 另有完整轨迹
`extraction_failure_rate` 作为健康指标上报 —— 抽取失败不会被静默记零分
## 模型接入
任意 OpenAI 兼容端点模型用一条 spec 字符串描述或用等价的 `--provider/--api-url/--model` 参数
| spec | 含义 |
|---|---|
| `openai/<base_url>?<model_id>` | 单端点vLLMSGLanglmdeployollama API |
| `openai-pool/<base{8000..8007}/v1,...>?<model_id>` | 端点池轮询 + 自适应并发 + failover |
| `deploy:<engine>/<model>` | Deployer 解析钉版本的推理环境 |
| `mock` / `mock-boxed` / `mock-fc` | 离线适配器管线自检回声 / 回放金标 / 回放工具调用 |
| `!nothink` `!perf` `!textools` 后缀 | spec 内联开关 CLI 参数等价 |
API key judge按端点域名自动从环境变量读取`api.openai.com``OPENAI_API_KEY`
`anthropic.com``ANTHROPIC_API_KEY``dashscope``DASHSCOPE_API_KEY``bigmodel``ZAI_API_KEY`
其余域名回退 `OPENAI_API_KEY`自建端点无鉴权时无需设置
同一服务有多个 key 时用 `--api-key` 显式指定跑两次各用一个 key 即可对比key 只进请求头
不会出现在 spec报告或日志里)。
## 内置 benchmark
| | benchmark |
|---|---|
| 数学 | `gsm8k``competition_math``aime24/25/26``hmmt26``imo_answerbench` |
| 知识 / 选择题 | `mmlu``mmlu_pro``cmmlu``gpqa_diamond``arc``hellaswag``winogrande``bbh` |
| 问答 | `trivia_qa``drop``simple_qa``hle` |
| 长上下文 | `longbench_v2``openai_mrcr` |
| 代码沙箱执行 | `humaneval``bigcodebench``live_code_bench` |
| Agent / 工具 | `bfcl_v3``general_fc``tau2_bench``swe_bench_verified` |
```bash
evalharness data list # 全部数据集:数据源、子集、默认 few-shot、split
evalharness eval list # 全部判分 recipe
```
各族注意事项
- **LLM-judge **`hle``simple_qa``imo_answerbench` `--judge <模型名> --judge-api-url <端点>`与主模型同款分离参数风格judge 走官方协议
SimpleQA 的分级正确性 + NOT_ATTEMPTED 兜底)。
- **代码执行类**模型生成的代码在硬隔离 Docker 中运行`--network none`cgroup 上限只读 rootfs
swe 的逐实例 `sweb.eval.*` 镜像用 `evalharness sandbox prefetch swe_bench_verified` 预取
- **Agent **`--env bfcl_mock` 驱动多轮消息泵 + 官方 call-sequence 判分`tau2_bench` /
`swe_bench_verified` 走官方引擎 bundle自跑环境路径)。
- **长上下文**`gen_kwargs={'max_input_tokens': ...}` token 预算中位截断保头尾 evalscope 同构
- **小样本高方差集**aime/hmmt`temperature=1.0` 跑多次取均值 —— `evalharness.run` 五行循环
## 可靠性模型
- **断点** —— 每条预测完成即追加进当次运行的 checkpoint`--resume`)。key prompt 语义
改了模板要删旧断点`rm ~/.cache/evalharness/ckpt/<bench>*.jsonl`否则会复用旧预测
- **分层重试** —— 连接超时 15 秒快速失败池内换端点自适应闸门自动降载病端点冷却
单样本分钟级退避重试 6 路由抖动不会杀死批次
- **预测不可变** —— 判分是对存量输出的确定性计算随便换 grader / recipe
## 扩展
**加数据集** —— 单文件丢进 `evalharness/data/datasets/`自动发现注册
```python
@register_dataset(DatasetSpec(
name='mybench',
source='org/mybench', # HF id / ModelScope id / 本地路径
split='test',
task_type='mcq', # 决定默认判分 recipe 的大类路由
))
def mybench():
return FieldSpec(input='question', choices='options', target='answer_key')
# 需要清洗/重排时改为返回 record -> Sample 函数
```
**绑定判分** —— recipe 是注册原语的声明式组合
```python
@register_eval('mybench')
def mybench():
return EvalRecipe(
name='mybench',
extract=['my_answer', 'answer_phrase'], # 级联:首个成功者胜
scorers={'acc': 'exact'}, # 或 math_equal / em_f1 / execution / env_reward / llm_judge
aggregators={'acc': 'mean'}, # 或 pass_at_k / grouped_avg / binned_avg
)
```
同一模式覆盖全部扩展点 —— `@register_prompt_renderer`题面如何渲染 system 契约)、
`@register_extractor` / `@register_scorer` / `@register_aggregator``@register_adapter`模型协议)、
`@register_sandbox`执行环境)、`@register_env`agent 世界)、`@register_renderer`报告呈现)。
生成参数预设注册为 profile`--profile`)。
数据插件还可以导出 `<name>_few_shot(split, subset, n)` 钩子注入官方手写范例BBH CoT 即此实现
DatasetSpec 声明每 bench 的默认值few-shot 数量/split`gen_config`prompt 风格
常见场景零配置
## 架构
```
evalharness/
├── cli.py data | eval | sandbox | viz 子命令
├── progress/ Rich 每样本终端进度(无 rich 自动降级)
├── data/ 统一 Sample schema、惰性物化、内容寻址缓存
│ └── datasets/ 28 个单文件插件
├── model/ adapter怎么调+ deployer怎么部署
│ ├── pool.py 端点池、自适应闸门、failover
│ ├── prompt_renderers 逐 bench 官方 prompt 模板
│ ├── gen_profiles.py 命名生成参数预设
│ └── runner.py 异步生成 → 同步判分;断点、重试、进度钩子
├── eval/ extract → score → aggregate 流水线 + recipes
├── sandbox/ docker硬隔离执行/ local镜像引用计数
├── agent/ 消息泵 + Environment 插件bfcl / tau2 / swe
└── viz/ text / md / md_compare / radar / excel / errors
```
设计原则
- **层间严格分离** —— 数据层回答"题目与金标是什么"判分层回答"如何评判一个回复"模型层回答
"如何触达模型"。下游只见 `Sample`不见原始数据格式
- **声明优先于执行** —— 样本携带沙箱/工具声明由执行层物化数据层永不执行任何东西
- **注册表而非配置文件** —— 插件 import 即注册`list` 命令枚举不触网
## 对齐验证
prompt 模板与判分器在两个层面与 evalscope 对齐过字符串级同一条记录过两侧管线prompt 逐字节
一致与分数级同题同生成参数
- **Qwen3-8B**28 benchmark 23 个同题分差 < 0.05
- **DeepSeek-V4-Flash**25 个中 20+ 个分差 < 0.05mmlu_pro 分差 0.0000)。
残差分歧是定性而非掩盖已知原因包括金标集差异DROP validated answers)、排列敏感性GPQA)、
以及 benchmark 侧缺陷evalscope bigcodebench 执行器空跑BFCL 单轮格式提示被剥 —— 均有
代码级证据记录我方侧已规避且未改动 evalscope)。
## License
尚未声明 —— 公开发布前请添加 `LICENSE`第三方 benchmark 数据与内置的官方判分片段
BBH CoT 范例SimpleQA judge prompt保留上游许可各数据集插件头部记录了数据来源