sora 27cf8b3c7e Usability round: progress plugin, CLI provider flags, top-level run(), vendored BFCL checker
- progress/: Rich per-sample terminal progress plugin (Run Plan panel,
  in-flight/rate/ETA bar); shared console + log-through-live to avoid
  interleaved writes, rollback() pairs begin_sample on the retry path,
  begin moved inside the semaphore (in-flight = actually generating),
  graceful degradation when rich is absent
- cli.py: --provider/--api-url/--model composition (openai-chat |
  openai-pool), --disable-thinking/--perf/--textools as first-class
  flags, per-bench phase lines and done/failed result lines
- __init__: top-level run()/arun() entries (event-loop safe for notebooks)
- third_party/bfcl: vendored official BFCL ast_checker + type mappings
  (Apache-2.0, provenance in __init__.py); imports rerouted locally,
  underscore_to_dot parameterized; verified bit-identical with the
  bfcl-eval package on 100 real rows -- removes the heavy extra
  (pinned numpy + cloud SDK wall) from the install path
- runner: progress/status hooks through generate+evaluate, checkpoint
  key scheme fix (empty-store falsy bug), tiered retry backoff,
  multi-segment pool {range} expansion fix, adapter-instance passthrough
- pyproject: tree_sitter family joins core deps; [bfcl] extra retired
- README: rewritten (zh) -- install/quickstart/flags reference/bench
  table/reliability/extension/architecture/validation

Co-Authored-By: Claude <noreply@anthropic.com>
2026-09-10 05:46:45 +00:00

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。

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 包(本地源码安装,无重依赖)。

离线验证安装(不需要模型、不联网):

evalharness eval run gsm8k --model mock:boxed --limit 8
# acc 100% —— mock 适配器直接输出金标答案,证明
# 数据 → prompt → 生成 → 判分 → 报告 全链路可用

代码执行类 benchmark 需要宿主机有 Docker。

快速开始

跑一个 benchmarkCLI

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);或直接给完整 specopenai/http://h:8000/v1?qwen3-8b
--provider {openai-chat,openai-pool} 协议/提供方,默认 openai-chat;端点池用 openai-pool
--judge SPEC LLM-judge 模型 spechle / simple_qa / imo 等 judge 类 recipe 需要)
--profile NAME 命名生成参数集(内置 dp4-nothinkqwen3-es-parityt1-short,或任意 @register_gen_profile 名);优先级:插件默认 < profile 默认 < profile 单 bench 覆盖 < 显式参数
--disable-thinking 发送 enable_thinking=falseQwen3 类思考模型推荐;带 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 与自适应并发:

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 跑

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')

也可以分阶段自己驱动 —— 数据集是一等公民,判分永远不会重新调模型:

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 名自动解析

查看与渲染结果

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> 单端点vLLM、SGLang、lmdeploy、ollama、云 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 按端点从环境变量读取(OPENAI_API_KEYANTHROPIC_API_KEY …)。

内置 benchmark

benchmark
数学 gsm8kcompetition_mathaime24/25/26hmmt26imo_answerbench
知识 / 选择题 mmlummlu_procmmlugpqa_diamondarchellaswagwinograndebbh
问答 trivia_qadropsimple_qahle
长上下文 longbench_v2openai_mrcr
代码(沙箱执行) humanevalbigcodebenchlive_code_bench
Agent / 工具 bfcl_v3general_fctau2_benchswe_bench_verified
evalharness data list     # 全部数据集:数据源、子集、默认 few-shot、split
evalharness eval list     # 全部判分 recipe

各族注意事项:

  • LLM-judge 类hlesimple_qaimo_answerbench):传 --judge <spec>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/hmmttemperature=1.0 跑多次取均值 —— 用 evalharness.run 五行循环。

可靠性模型

  • 断点 —— 每条预测完成即追加进当次运行的 checkpoint--resume。key 含 prompt 语义; 改了模板要删旧断点(rm ~/.cache/evalharness/ckpt/<bench>*.jsonl),否则会复用旧预测。
  • 分层重试 —— 连接超时 15 秒快速失败;池内换端点(自适应闸门自动降载、病端点冷却); 单样本分钟级退避重试 6 次,路由抖动不会杀死批次。
  • 预测不可变 —— 判分是对存量输出的确定性计算;随便换 grader / recipe。

扩展

加数据集 —— 单文件丢进 evalharness/data/datasets/,自动发现注册:

@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 是注册原语的声明式组合:

@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_envagent 世界)、@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-8B28 个 benchmark 中 23 个同题分差 < 0.05。
  • DeepSeek-V4-Flash25 个中 20+ 个分差 < 0.05mmlu_pro 分差 0.0000)。

残差分歧是定性而非掩盖已知原因包括金标集差异DROP 的 validated answers、排列敏感性GPQA、 以及 benchmark 侧缺陷evalscope 的 bigcodebench 执行器空跑、BFCL 单轮格式提示被剥 —— 均有 代码级证据记录,我方侧已规避且未改动 evalscope

License

尚未声明 —— 公开发布前请添加 LICENSE。第三方 benchmark 数据与内置的官方判分片段 (如 BBH CoT 范例、SimpleQA judge prompt保留上游许可各数据集插件头部记录了数据来源。

Description
No description provided
Readme 2.7 MiB
Languages
Python 99.2%
Shell 0.8%