EvalHub:LLM 与 Agent 评测平台
GitHub:NEDONION/evalhub
Python 3.11+ FastAPI React 19 SQLite Ollama Docker Local-first

1 项目定位
一句话:把公开数据集、本地模型、持久化工作流、样本证据和六维能力报告放进同一条可复现评测链路。
它要解决的痛点很具体——大多数评测工具只给你一个最终分数,但真正需要回答的问题是:
- 这个分数是怎么来的?哪些样本失败了?
- 换个模型重跑,配置真的一样吗?
- 0 分是「模型答错了」还是「根本没跑起来」?
所以 EvalHub 的核心设计目标是留证据:每次 运行都保留样本结果、节点状态、资源指标和审计事件。
| 能力 | 提供的行为 |
|---|---|
| 模型 Benchmark | 运行真实公开数据集,通过 Ollama 调本地模型,保留样本级结果和聚合得分 |
| Agent 评测 | 通过受控 Registry 运行 Pi CLI 或 MiniClaw 完整 Agent,共用同一套任务和隐藏 Verifier |
| 可复现工作流 | 持久化 DAG 记录节点状态、检查点、重试、资源指标和审计事件 |
| 六维能力画像 | 分维度展示,不把未评测维度算作 0 分 |
2 整体架构
分层原则是领域核心不依赖具体基础设施:核心层不直接依赖 FastAPI、Celery、PostgreSQL 或 MinIO,所以 MVP 可以先用 SQLite + 子进程跑通,之后替换成 Scheduler + PostgreSQL + Celery Worker 时,EvaluationRunner 和任务 API 的语义都不用改。
3 核心设计一:持久化评测 DAG
评测任务不是一个黑盒进程,而是一张有状态的节点图:
节点状态为 pending / running / success / failed / blocked / canceled。三条关键规则:
1. 成功节点不重复执行。 服务异常重启时,中断节点恢复为待执行,并跳过已有评分结果的样本检查点— —注意是「已有评分结果」而不是「已通过」,所以不会因为答案得分低就重新推理。这个区分很关键:
status=failed 且 result_json 存在 → 已评分,答案未通过,不重跑
status=failed 且 result_json 为空 → 未完成评分,需要重跑
2. 数据变了就阻塞,不静默复用。 资产节点记录真实文件或目录内容的 SHA-256,Benchmark 执行前后再次校验;期间发生变化就清空该节点样本检查点并明确阻塞,禁止跨数据 revision 复用结果。
3. 事件表是追加式的。 状态变化、自动重试、人工重试和服务恢复都产生事件,旧事件不覆盖。出问题时能完整回放。

4 核心设计二:空回答阻塞,而不是记零分
这是整个项目最重要的一条判断:
Ollama 返回空文本时,
generation_incomplete或empty_model_response会阻塞节点;非空但答错的回答仍按统一标准记零分。
为什么这条重要:如果把「模型没输出」和「模型答错了」都记成 0 分,最后的排行榜就是垃圾——你分不清模型能力差还是评测环境挂了。基础设施不可用必须阻塞任务,而不是被记录为模型零分。
同样的逻辑贯穿 Agent 评测:0 分被进一步区分为运行失败 / 未修改工作区 / 修改了但没通过校验,靠的是过程证据而不是 Agent 的自然语言自述。
5 核心设计三:协议轴分离
Hexagon 1.2 把「模型怎么生成」和「答案怎么评分」拆成两条互不放宽的协议轴:
| 协议轴 | 决定什么 |
|---|---|
ModelGenerationProfile | Ollama 顶层 think 行为(思考模型显式 think=false) |
BenchmarkSpec | 回答协议和 num_predict 预算 |
七 项 Benchmark 的生成预算依次固定为 256 / 1024 / 512 / 512 / 1024 / 256 / 256 tokens,选择题、数值、BBH、IFEval 和 HumanEval 各走自己的评分边界。组合后的有效配置、模型协议版本和答案协议版本全部写入节点输入、可复现性账本和比较指纹。
三个生成协议组(普通 Ollama、显式 think=false 的思考模型、OpenAI-compatible API)使用同一套样本和评分器,但跨协议组排名仅供观察——这个限定写在报告里,避免结论被误用。
6 实测报告:11 个模型的六维画像
2026-08-05 在 evalhub-hexagon-v1 1.2.0 固定套件上的快照:9 个本地 Ollama 模型 + 2 个线上 DeepSeek 模型均完成 30/30 样本,共 330 次模型评测。
| 排名 | 模型 | 运行方式 | 通过样本 | 综合分 |
|---|---|---|---|---|
| 1 | deepseek-v4-pro | DeepSeek API | 25 / 30 | 83.33% |
| 2 | gemma4:12b | Ollama | 21 / 30 | 70.00% |
| 3 | granite4.1:3b | Ollama | 20 / 30 | 66.67% |
| 3 | qwen3:14b | Ollama | 20 / 30 | 66.67% |
| 3 | deepseek-v4-flash | DeepSeek API | 20 / 30 | 66.67% |
| 6 | granite3.3:8b | Ollama | 16 / 30 | 53.33% |
| 7 | qwen2.5-coder:7b | Ollama | 15 / 30 | 50.00% |
| 8 | qwen2.5:1.5b | Ollama | 12 / 30 | 40.00% |
| 9 | deepseek-r1:1.5b | Ollama | 10 / 30 | 33.33% |
| 10 | qwen2.5:0.5b | Ollama | 8 / 30 | 26.67% |
| 11 | qwen3:4b | Ollama | 6 / 30 | 20.00% |
几个可以复述的观察:
- 指令遵循在多数中大型模型上已经饱和(普遍 100 分),区分度基本消失。
- 代码与综合推理最能拉开差距,是当前更有信息量的维度。
deepseek-v4-flash呈现明显的代码强、数学弱特征(代码 100 / 数学 0),说明单看综合分会丢失重要信息。
这是一组 Mini Suite 能力画像,不是上游 Benchmark 的完整官方成绩,不能当作论文或排行榜成绩复述。
7 Agent 评测:把完整 Agent 当作被测对象
模型评测评的是模型,Agent 评测评的是**「模型 + 外壳」的整体**。EvalHub 的做法是让不同 Agent 跑同一套 coding-mini-v3(6 道分级编码任务)+ 同一个隐藏 Verifier:
三条纪律:
- 评分只采用最终工作区的隐藏校验结果,不看 Agent 自述。
- 时间线只保存白名单外部事件,不保存内部思维链,也不在 Agent 完成前暴露隐藏断言。
- 基础设施不可用则阻塞任务,不会被记录为模型零分。

实测:同样是 0 分,原因完全不同
| 排名 | 模型 | 协议预检 | 通过样例 | 工具调用 | 工具错误 | 平均耗时/题 |
|---|---|---|---|---|---|---|
| 1 | deepseek-v4-pro | compatible | 6 / 6 | 64 | 10 | 61.78 s |
| 2 | moonshotai/Kimi-K2.7-Code | compatible | 5 / 6 | 59 | 11 | 83.00 s |
| 2 | zai-org/GLM-5.2 | compatible | 5 / 6 | 62 | 9 | 122.41 s |
| 2 | deepseek-ai/DeepSeek-V4-Flash | compatible | 5 / 6 | 75 | 19 | 133.97 s |
| 5 | qwen3:14b | compatible | 1 / 6 | 7 | 2 | 175.23 s |
| 5 | qwen3:4b | compatible | 1 / 6 | 13 | 1 | 169.05 s |
| 7 | gemma4:12b | compatible | 0 / 6 | 19 | 0 | 155.52 s |
| 7 | granite4.1:3b | compatible | 0 / 6 | 12 | 8 | 11.73 s |
| 7 | granite3.3:8b | incompatible | 0 / 6 | 0 | 0 | 17.42 s |
| 7 | deepseek-r1:1.5b | incompatible | 0 / 6 | 0 | 0 | 30.11 s |
同样是 0 分,过程证据把它们分成了两类:
gemma4:12b/granite4.1:3b:通过协议预检、实际调用了工具,但最终工作区没通过隐藏校验 → 能力不足granite3.3:8b/deepseek-r1:1.5b:未完成结构化工具协议 → 协议不兼容,压根没开始干活
这就是为什么必须记录过程指标。只看分数,这四个模型看起来一样差。
排除运行也要留档
| Job ID | 模型 | 观测 | 排除原因 |
|---|---|---|---|
job_4507d33dac62 | DeepSeek-V4-Flash | 0 / 6 | 修复前 Provider 解析器拒绝 SiliconFlow SSE 中空 function.name 的续传片段,属于框架 bug 而非模型能力 |
排除运行只保留为协议边界证据,不进入正式结果或排名。每个模型只采用首个完成全部 6 题、且没有任务级基础设施故障的正式任务;不按分数重跑或挑选最佳值。

8 凭据安全
远程模型服务商的凭据处理,是一个很容易做错的地方:
- 公开配置和 Fernet 密文独立保存到
.runtime/model_providers.sqlite3; - 主密钥优先来自
EVALHUB_CREDENTIAL_KEY,否则用权限0600的本地密钥文件; - 浏览器只获得「是否已配置」和末四位提示,没有读取明文凭据的 HTTP 接口;
- 任务只冻结
provider_id、模型 ID 与 Base URL,不保存 API Key——Worker 运行时再按 ID 解析最新凭据,因此密钥轮换不要求改写已排队任务; - 边界拒绝含用户信息、查询或 fragment 的地址;远程服务只允许 HTTPS,HTTP 仅允许回环主机;
- 错误进入任务记录前精确移除当前 API Key。
最后这条尤其容易被忽略:异常堆栈里带着 Authorization header 直接写进数据库,是最常见的凭据泄漏路径。
9 沉淀下来的经验
1. 评测系统的价值在证据,不在分数。 一个数字回答不了「为什么」。样本级结果 + 节点状态 + 过程指标 + 审计事件,才能让评测结果可被质疑、可被复现。
2. 「跑失败」和「答错了」必须分开。 把基础设施故障记成 0 分,会污染整个排行榜。空回答阻塞节点,是这个原则最直接的落地。
3. 可复现性要靠指纹,不靠约定。 把 suite 版本、数据 SHA-256、提示模板版本、generation config 全部写进比较指纹。只有指纹一致的结果才允许放在一张表里比。
4. 断点续跑的判据是「有没有结果」,不是「结果好不好」。
用 result_json 是否存在判断,而不是用 status=failed。这个细节做错了,就会出现"低分样本被反复重跑刷分"的严重问题。
5. 评 Agent 要评最终工作区,不评自述。 Agent 会说自己完成了。隐藏 Verifier 检查最终工作区,是唯一可信的判据。
6. 小样本单次运行不足以推断因果。 Pi 与 MiniClaw 的对照实验里,三组 6 题结果显示 MiniClaw 工具调用更少、耗时更低,但不足以证明外壳本身有普遍优势。把这个限定写进报告,比给出一个爽快的结论更负责任。
参考
- 仓库文档:系统架构 · 数据模型 · Agent 评测路线图
- 相关笔记:Agent 面试题汇总 · Lobster0 项目沉淀