# 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。 ```bash git clone https://git.meta-stone.net/sora/EvalHarness.git cd EvalHarness pip install . ``` **一条命令装完即可跑全部 28 个 benchmark,没有任何可选依赖。** | 官方判分逻辑形态 | 涉及 benchmark | 位置 | |---|---|---| | 纯 Python 算法 | 数学、DROP、MCQ | 核心依赖(sympy/numpy/scipy) | | BFCL 官方 AST 判定器 | bfcl_v3 | 已内置(`evalharness/third_party/bfcl/`,Apache-2.0) | | 官方执行环境 | humaneval、bigcodebench、live_code_bench、swe_bench | Docker 镜像,判分时按需拉取 | 另有本地引擎类(`tau2_bench`)使用官方 tau2 包(本地源码安装,无重依赖)。 离线验证安装(不需要模型、不联网): ```bash evalharness eval run gsm8k --model mock:boxed --limit 8 # acc 100% —— mock 适配器直接输出金标答案,证明 # 数据 → prompt → 生成 → 判分 → 报告 全链路可用 ``` 代码执行类 benchmark 需要宿主机有 Docker。 ## 快速开始 ### 跑一个 benchmark(CLI) ```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 SPEC` | LLM-judge 模型 spec(hle / simple_qa / imo 等 judge 类 recipe 需要) | | `--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/.json` + `viz/.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/?` | 单端点(vLLM、SGLang、lmdeploy、ollama、云 API) | | `openai-pool/?` | 端点池:轮询 + 自适应并发 + failover | | `deploy:/` | 经 Deployer 解析(钉版本的推理环境) | | `mock` / `mock:boxed` / `mock:fc` | 离线适配器(管线自检) | | `!nothink` `!perf` `!textools` 后缀 | spec 内联开关(与 CLI 参数等价) | API key 按端点从环境变量读取(`OPENAI_API_KEY`、`ANTHROPIC_API_KEY` …)。 ## 内置 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 走官方协议 (如 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/*.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`)。 数据插件还可以导出 `_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.05(mmlu_pro 分差 0.0000)。 残差分歧是定性而非掩盖:已知原因包括金标集差异(DROP 的 validated answers)、排列敏感性(GPQA)、 以及 benchmark 侧缺陷(evalscope 的 bigcodebench 执行器空跑、BFCL 单轮格式提示被剥 —— 均有 代码级证据记录,我方侧已规避且未改动 evalscope)。 ## License 尚未声明 —— 公开发布前请添加 `LICENSE`。第三方 benchmark 数据与内置的官方判分片段 (如 BBH CoT 范例、SimpleQA judge prompt)保留上游许可;各数据集插件头部记录了数据来源。