dsh-livebench-panel 0.2.18 → 0.2.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -1
- package/lib/client.js +137 -26
- package/lib/index.js +223 -12
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -42,11 +42,43 @@ cd livebench
|
|
|
42
42
|
| 模型 | harness 全部 provider 的全部模型(含内置 deepseek-official);分组展示 | 首次验证选小任务 + 便宜模型 |
|
|
43
43
|
| 推理强度 | 该模型的 reasoning effort(无配置则禁用);编码进条目名,不同强度独立出分 | 日常 `medium`/`high`;对比测试固定同一档 |
|
|
44
44
|
| 题集 release | LiveBench 题目发布批次 | 公开题目最全的是 `2024-11-25`(推荐) |
|
|
45
|
+
| **Baseline 题库** | 参考模型的实测对错题集,**可脱离分类/任务单独使用**(见下节) | 快速探针:参考模型「做错」的题 |
|
|
45
46
|
| 分类 | 六大类:coding / math / reasoning / language / data_analysis / instruction_following | 首次验证选 `language` |
|
|
46
47
|
| 任务 | 分类下的具体任务(如 language/typos 拼写纠错) | 首次验证选 `typos`(短平快) |
|
|
47
|
-
| 题目序号范围 |
|
|
48
|
+
| 题目序号范围 | 起止下标(0 起,**含首尾**);选 Baseline 时序号相对该题库 | 冒烟测试填 0–1(只跑 2 题) |
|
|
48
49
|
| max-tokens | 单次回答的 token 上限 | 推理模型给 8192+,否则思考被截断判 0 分 |
|
|
49
50
|
|
|
51
|
+
## Baseline 题库(探针)
|
|
52
|
+
|
|
53
|
+
用**一个参考模型的实测对错**当尺子,快速判断别的模型处在哪个档位。位于「分类 / 任务」之上,
|
|
54
|
+
三层可选:
|
|
55
|
+
|
|
56
|
+
1. **参考模型**:`Baseline(glm-5.3-flash@max)`
|
|
57
|
+
2. **任务**:该参考模型已经跑过全量的任务(如 `olympiad(36)`)
|
|
58
|
+
3. **做错 / 做对**:各自标注题目数量(如 `做错(8)` / `做对(28)`),可多选
|
|
59
|
+
|
|
60
|
+
选中后**不需要也不应该再选分类/任务**——"不选 = 全部分类"这条规则对 Baseline 不适用。
|
|
61
|
+
「题目序号范围」仍然生效,序号相对 Baseline 题库(0 起、含首尾);超出上界不会报错,
|
|
62
|
+
会按实际题数截断。
|
|
63
|
+
|
|
64
|
+
**为什么默认勾「做错」**:探针的价值在于"参考模型做不出来的题"。参考模型能轻松做对的题,
|
|
65
|
+
对更强的模型没有分辨力,混进来只会稀释信号。「做对」也保留,用于反向验证
|
|
66
|
+
(例如确认某模型确实强于参考模型)。
|
|
67
|
+
|
|
68
|
+
**当前内置的数据**(2026-09-12 实测,release `2024-11-25`):
|
|
69
|
+
|
|
70
|
+
| 参考模型 | 任务 | 全量 | 做对 | 做错 |
|
|
71
|
+
|---|---|---|---|---|
|
|
72
|
+
| `zai-coding-cn__glm-5.3-flash@max` | `math/olympiad` | 36 | 28 | 8 |
|
|
73
|
+
|
|
74
|
+
实测效果(跑「做错」那 8 题):`aiportx-gg/grok-4.6@xhigh` 明显强于参考模型;
|
|
75
|
+
`deepseek-official/deepseek-flash@max` 明显更弱。失败的 8 题全部属于 olympiad 的
|
|
76
|
+
**公式还原**题型——把解答里的公式挖空让模型补全,是纯符号推理,很吃"智力"。
|
|
77
|
+
|
|
78
|
+
后续任务按同样方式补进 `lib/index.js` 的 `BASELINE_SETS.tasks` 即可:
|
|
79
|
+
只需填 `passed` / `failed` 两组 question id(取自 LiveBench 的 `question.jsonl`),
|
|
80
|
+
面板用 `--question-id` 直接指定,题目在题库里是否连续都无所谓。
|
|
81
|
+
|
|
50
82
|
## 功能
|
|
51
83
|
|
|
52
84
|
- **标签页位置**:`conversation.view` 槽位 `id: "livebench"`、`order: 20`(对话 = 0,轨迹 = 10,LiveBench 排最右)。
|
package/lib/client.js
CHANGED
|
@@ -121,7 +121,7 @@ window.__ModuleLoader__.load({
|
|
|
121
121
|
function LiveBenchView() {
|
|
122
122
|
const [config, setConfig] = useState(null);
|
|
123
123
|
const [configError, setConfigError] = useState(null);
|
|
124
|
-
const [sel, setSel] = useState({ models: [], efforts: {}, cats: [], tasks: [], release: "2024-11-25", begin: "", end: "", maxTokens: "32000" });
|
|
124
|
+
const [sel, setSel] = useState({ models: [], efforts: {}, cats: [], tasks: [], release: "2024-11-25", begin: "", end: "", maxTokens: "32000", baseline: null });
|
|
125
125
|
const [modelDdOpen, setModelDdOpen] = useState(false);
|
|
126
126
|
const [busy, setBusy] = useState(false);
|
|
127
127
|
const [running, setRunning] = useState(false);
|
|
@@ -131,8 +131,8 @@ window.__ModuleLoader__.load({
|
|
|
131
131
|
const [homeBusy, setHomeBusy] = useState(false);
|
|
132
132
|
const [results, setResults] = useState(null);
|
|
133
133
|
const [pickedModels, setPickedModels] = useState(() => new Set());
|
|
134
|
-
const [sortBy, setSortBy] = useState(
|
|
135
|
-
const [sortDir, setSortDir] = useState("
|
|
134
|
+
const [sortBy, setSortBy] = useState("time");
|
|
135
|
+
const [sortDir, setSortDir] = useState("desc");
|
|
136
136
|
const [modelOrder, setModelOrder] = useState(() => {
|
|
137
137
|
try { return JSON.parse(localStorage.getItem("dlb_model_order_v1")) ?? []; } catch { return []; }
|
|
138
138
|
});
|
|
@@ -243,24 +243,54 @@ window.__ModuleLoader__.load({
|
|
|
243
243
|
return { providerId: pid, modelId: mid, modelValue: value, efforts: model?.efforts ?? [] };
|
|
244
244
|
}), [sel.models, config]);
|
|
245
245
|
|
|
246
|
+
// Baseline 三层选择:参考模型 -> 任务 -> 做对/做错
|
|
247
|
+
const baselineObj = useMemo(
|
|
248
|
+
() => (config?.baselines ?? []).find((b) => b.id === sel.baseline) ?? null,
|
|
249
|
+
[config, sel.baseline]);
|
|
250
|
+
const baselineTaskObj = useMemo(
|
|
251
|
+
() => (baselineObj?.tasks ?? []).find((t) => t.task === sel.baselineTask) ?? null,
|
|
252
|
+
[baselineObj, sel.baselineTask]);
|
|
253
|
+
// 当前勾选真正会跑几题:题号范围的上界就是它 - 1
|
|
254
|
+
const baselineSelectedCount = useMemo(() => {
|
|
255
|
+
if (baselineTaskObj === null) return 0;
|
|
256
|
+
let n = 0;
|
|
257
|
+
if (sel.baselinePicks.includes("failed")) n += baselineTaskObj.failedCount;
|
|
258
|
+
if (sel.baselinePicks.includes("passed")) n += baselineTaskObj.passedCount;
|
|
259
|
+
return n;
|
|
260
|
+
}, [baselineTaskObj, sel.baselinePicks]);
|
|
261
|
+
|
|
246
262
|
const onStart = async () => {
|
|
247
263
|
setStartError(null);
|
|
248
264
|
if (selectedModelEntries.length === 0) {
|
|
249
265
|
setStartError("请先在模型下拉中至少勾选一个模型。");
|
|
250
266
|
return;
|
|
251
267
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
268
|
+
// Baseline 题库:绕开分类/任务的"不选 = 全部"推导,
|
|
269
|
+
// 由服务端按「参考模型 + 任务 + 做对/做错」解析出 question id。
|
|
270
|
+
let benchNames = null;
|
|
271
|
+
if (baselineObj !== null) {
|
|
272
|
+
if (baselineTaskObj === null) {
|
|
273
|
+
setStartError("已选 Baseline,请再选择它下面的任务(例如 olympiad)。");
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (sel.baselinePicks.length === 0) {
|
|
277
|
+
setStartError("已选 Baseline 任务,请至少勾选「做错」或「做对」之一。");
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
257
280
|
} else {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
281
|
+
benchNames = [];
|
|
282
|
+
if (sel.cats.length === 0) {
|
|
283
|
+
benchNames.push("live_bench");
|
|
284
|
+
} else if (sel.tasks.length === 0) {
|
|
285
|
+
for (const cat of sel.cats) benchNames.push(`live_bench/${cat}`);
|
|
286
|
+
} else {
|
|
287
|
+
for (const t of sel.tasks) benchNames.push(`live_bench/${t}`);
|
|
288
|
+
}
|
|
289
|
+
const zeroTask = sel.tasks.find((t) => countFor(t.split("/")[0], t.split("/")[1]) === 0);
|
|
290
|
+
if (zeroTask) {
|
|
291
|
+
setStartError(`任务 ${zeroTask.split("/")[1]} 在 release ${sel.release} 下没有可用题目(该批题目已退役),请取消勾选或换 release。`);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
264
294
|
}
|
|
265
295
|
setBusy(true);
|
|
266
296
|
try {
|
|
@@ -274,7 +304,9 @@ window.__ModuleLoader__.load({
|
|
|
274
304
|
provider: entry.providerId,
|
|
275
305
|
model: entry.modelId,
|
|
276
306
|
reasoningEffort: sel.efforts[entry.modelValue] ?? "default",
|
|
277
|
-
|
|
307
|
+
...(baselineObj !== null
|
|
308
|
+
? { baseline: baselineObj.id, baselineTask: sel.baselineTask, baselinePicks: sel.baselinePicks }
|
|
309
|
+
: { benchNames }),
|
|
278
310
|
release: sel.release,
|
|
279
311
|
begin: sel.begin,
|
|
280
312
|
end: sel.end,
|
|
@@ -359,15 +391,20 @@ window.__ModuleLoader__.load({
|
|
|
359
391
|
const groupStart = new Set(groups.map((g) => g.tasks[0]));
|
|
360
392
|
const cells = new Map();
|
|
361
393
|
const startTimes = new Map();
|
|
394
|
+
// 哪些 模型×任务 是 Baseline 子集跑出来的:同一列里会混着"全量"和
|
|
395
|
+
// "题库子集"两种分数,必须在界面上标出来,否则没法解读。
|
|
396
|
+
const baselineCells = new Set();
|
|
362
397
|
for (const row of results.rows) {
|
|
363
|
-
|
|
398
|
+
const key = `${row.model}__@__${taskKey(row.category, row.task)}`;
|
|
399
|
+
cells.set(key, row);
|
|
400
|
+
if (row.baselineLabel) baselineCells.add(key);
|
|
364
401
|
const fromMeta = row.runStartedAt ? Math.floor(Date.parse(row.runStartedAt) / 1000) : null;
|
|
365
402
|
const candidate = (Number.isFinite(fromMeta) ? fromMeta : null) ?? runStampSeconds(row.model) ?? row.time ?? null;
|
|
366
403
|
const prev = startTimes.get(row.model);
|
|
367
404
|
if (candidate && (prev === undefined || candidate < prev)) startTimes.set(row.model, candidate);
|
|
368
405
|
}
|
|
369
406
|
const taskTotals = (results && results.taskTotals) || {};
|
|
370
|
-
return { models, tasks, groups, groupStart, cells, startTimes, taskTotals };
|
|
407
|
+
return { models, tasks, groups, groupStart, cells, startTimes, taskTotals, baselineCells };
|
|
371
408
|
}, [results]);
|
|
372
409
|
const cellOf = (model, key) => matrix.cells.get(`${model}__@__${key}`);
|
|
373
410
|
// 排序:sortBy=null 时用拖拽自定义顺序;点击表头在 正序/倒序 间切换
|
|
@@ -380,8 +417,13 @@ window.__ModuleLoader__.load({
|
|
|
380
417
|
}
|
|
381
418
|
if (sortBy === "time") {
|
|
382
419
|
base.sort((a, b) => {
|
|
420
|
+
// 没有时间戳的排在最后,不管升序降序 —— 否则空值会被当成 0
|
|
421
|
+
// 混在中间/开头,看起来像"最旧的一次评测"。
|
|
383
422
|
const ta = matrix.startTimes.get(a) ?? 0;
|
|
384
423
|
const tb = matrix.startTimes.get(b) ?? 0;
|
|
424
|
+
if (ta === 0 && tb === 0) return a.localeCompare(b);
|
|
425
|
+
if (ta === 0) return 1;
|
|
426
|
+
if (tb === 0) return -1;
|
|
385
427
|
return sortDir === "asc" ? ta - tb : tb - ta;
|
|
386
428
|
});
|
|
387
429
|
return base;
|
|
@@ -398,7 +440,7 @@ window.__ModuleLoader__.load({
|
|
|
398
440
|
// 点击表头:切换排序字段(再次点击同一列则反转方向)
|
|
399
441
|
const toggleSort = (field) => {
|
|
400
442
|
if (sortBy === field) setSortDir((d) => (d === "asc" ? "desc" : "asc"));
|
|
401
|
-
else { setSortBy(field); setSortDir("asc"); }
|
|
443
|
+
else { setSortBy(field); setSortDir(field === "time" ? "desc" : "asc"); }
|
|
402
444
|
};
|
|
403
445
|
const sortMark = (field) => (sortBy === field ? (sortDir === "asc" ? " ↑" : " ↓") : "");
|
|
404
446
|
|
|
@@ -536,7 +578,7 @@ window.__ModuleLoader__.load({
|
|
|
536
578
|
),
|
|
537
579
|
h("div", { className: c("field") },
|
|
538
580
|
h("label", { className: c("label") }, "max-tokens(默认 32000)"),
|
|
539
|
-
h("input", { className: c("input"), type: "number", min: 256, max:
|
|
581
|
+
h("input", { className: c("input"), type: "number", min: 256, max: 200000, value: sel.maxTokens, onChange: setField("maxTokens"), title: "单题输出上限。推理模型在难题上容易把预算全花在思考里、正文为空(成绩表括号里记为「没做出来」),这类题可以把上限调大后重跑。" }),
|
|
540
582
|
),
|
|
541
583
|
h("div", { className: c("field") },
|
|
542
584
|
h("label", { className: c("label") }, "题目序号范围(可选,0 起,含首尾)"),
|
|
@@ -544,6 +586,67 @@ window.__ModuleLoader__.load({
|
|
|
544
586
|
h("input", { className: c("input"), type: "number", min: 0, placeholder: "起(含)", title: "起始题号(0 起,包含该题)", value: sel.begin, onChange: setField("begin"), style: { flex: "1", minWidth: "0" } }),
|
|
545
587
|
h("input", { className: c("input"), type: "number", min: 0, placeholder: "止(含)", title: "结束题号(包含该题);只填起不填止 = 从该题跑到底", value: sel.end, onChange: setField("end"), style: { flex: "1", minWidth: "0" } }),
|
|
546
588
|
),
|
|
589
|
+
// Baseline 题库:位于「分类 / 任务」之上的独立条目,三层选择。
|
|
590
|
+
// 选中后走它自己的任务 + 一组 question id,因此不需要(也不应该)
|
|
591
|
+
// 再受"不选分类 = 全部分类"这条规则约束。
|
|
592
|
+
h("div", { className: c("field") },
|
|
593
|
+
h("label", { className: c("label") }, "Baseline 题库(探针,可单独使用,不必选分类/任务)"),
|
|
594
|
+
// 第 1 层:参考模型
|
|
595
|
+
h("div", { className: c("chips") },
|
|
596
|
+
(config?.baselines ?? []).map((b) => h("button", {
|
|
597
|
+
key: b.id,
|
|
598
|
+
type: "button",
|
|
599
|
+
className: c("chip") + (sel.baseline === b.id ? " " + c("chipOn") : ""),
|
|
600
|
+
title: `参考模型 ${b.referenceModel}\n已实测 ${b.tasks.length} 个任务`,
|
|
601
|
+
onClick: () => setSel((prev) => (prev.baseline === b.id
|
|
602
|
+
? { ...prev, baseline: null, baselineTask: null, baselinePicks: [] }
|
|
603
|
+
: { ...prev, baseline: b.id, baselineTask: null, baselinePicks: [] })),
|
|
604
|
+
}, b.label)),
|
|
605
|
+
),
|
|
606
|
+
// 第 2 层:任务(该参考模型已实测过的任务)
|
|
607
|
+
baselineObj !== null && h("div", { className: c("chips"), style: { marginTop: "6px" } },
|
|
608
|
+
h("span", { className: c("hint"), style: { alignSelf: "center" } }, "任务:"),
|
|
609
|
+
(baselineObj.tasks ?? []).map((t) => h("button", {
|
|
610
|
+
key: t.task,
|
|
611
|
+
type: "button",
|
|
612
|
+
className: c("chip") + (sel.baselineTask === t.task ? " " + c("chipOn") : ""),
|
|
613
|
+
title: `${t.task} · release ${t.release}\n该任务全量 ${t.total} 题(做对 ${t.passedCount} / 做错 ${t.failedCount})`,
|
|
614
|
+
onClick: () => setSel((prev) => (prev.baselineTask === t.task
|
|
615
|
+
? { ...prev, baselineTask: null, baselinePicks: [] }
|
|
616
|
+
// 默认勾「做错」:探针的用途就是拿参考模型做不出来的题去量别人
|
|
617
|
+
: { ...prev, baselineTask: t.task, baselinePicks: ["failed"] })),
|
|
618
|
+
}, `${t.taskName}(${t.total})`)),
|
|
619
|
+
),
|
|
620
|
+
// 第 3 层:做对 / 做错,各带题目数量
|
|
621
|
+
baselineTaskObj !== null && h("div", { className: c("chips"), style: { marginTop: "6px" } },
|
|
622
|
+
h("span", { className: c("hint"), style: { alignSelf: "center" } }, "题目:"),
|
|
623
|
+
[["failed", "做错"], ["passed", "做对"]].map(([key, text]) => {
|
|
624
|
+
const count = key === "failed" ? baselineTaskObj.failedCount : baselineTaskObj.passedCount;
|
|
625
|
+
const on = sel.baselinePicks.includes(key);
|
|
626
|
+
return h("button", {
|
|
627
|
+
key,
|
|
628
|
+
type: "button",
|
|
629
|
+
className: c("chip") + (on ? " " + c("chipOn") : ""),
|
|
630
|
+
disabled: count === 0,
|
|
631
|
+
title: key === "failed"
|
|
632
|
+
? `${baselineTaskObj.referenceModel} 得 0 分的 ${count} 题(探针首选:做不出来的题才有分辨力)`
|
|
633
|
+
: `${baselineTaskObj.referenceModel} 判分 ≥0.5 的 ${count} 题(反向验证用)`,
|
|
634
|
+
onClick: () => setSel((prev) => ({
|
|
635
|
+
...prev,
|
|
636
|
+
baselinePicks: prev.baselinePicks.includes(key)
|
|
637
|
+
? prev.baselinePicks.filter((p) => p !== key)
|
|
638
|
+
: [...prev.baselinePicks, key],
|
|
639
|
+
})),
|
|
640
|
+
}, `${text}(${count})`);
|
|
641
|
+
}),
|
|
642
|
+
),
|
|
643
|
+
baselineTaskObj !== null && h("span", { className: c("hint") },
|
|
644
|
+
`已选 ${baselineTaskObj.referenceModel} 的 ${baselineTaskObj.taskName}:`
|
|
645
|
+
+ `本次只跑勾选的题(共 ${baselineSelectedCount} 题,有效题号 0–${Math.max(0, baselineSelectedCount - 1)}),`
|
|
646
|
+
+ "上面的「分类/任务」被忽略;"
|
|
647
|
+
+ "「题目序号范围」仍然生效(序号相对本题库,从 0 起、含首尾;超出上界不会报错,会按实际题数截断);"
|
|
648
|
+
+ `release 固定为 ${baselineTaskObj.release}。`),
|
|
649
|
+
),
|
|
547
650
|
h("div", { className: c("field") },
|
|
548
651
|
h("label", { className: c("label") }, "分类(可多选,不选 = 全部分类;括号内为该 release 下有效题数)"),
|
|
549
652
|
h("div", { className: c("chips") },
|
|
@@ -601,7 +704,7 @@ window.__ModuleLoader__.load({
|
|
|
601
704
|
h("div", { className: c("row") },
|
|
602
705
|
h("div", { className: c("cardHead") },
|
|
603
706
|
h("span", { className: c("label") }, `评测成绩(行 = 模型,列 = 任务)· ${sortedModels.length} 个模型 × ${matrix.tasks.length} 个任务`),
|
|
604
|
-
h("span", { className: c("hint") }, "分数 = 该任务平均分 ×100;括号 (-没做或没做完的题数/用户选择的题数,即题目序号范围的题数);× = 未执行该任务;— = 无有效判分(如全部访问失败,不计入正确率);颜色 ≥75 绿 / 40–75 橙 / <40
|
|
707
|
+
h("span", { className: c("hint") }, "分数 = 该任务平均分 ×100;括号 (-没做或没做完的题数/用户选择的题数,即题目序号范围的题数);× = 未执行该任务;— = 无有效判分(如全部访问失败,不计入正确率);颜色 ≥75 绿 / 40–75 橙 / <40 红;默认按 time 倒序(最新在前),点表头可在模型名/时间间切换排序,拖 ⠿ 则回到本机保存的自定义行序。"),
|
|
605
708
|
),
|
|
606
709
|
h("button", {
|
|
607
710
|
className: c("btn") + " " + c("btnDanger"),
|
|
@@ -692,12 +795,20 @@ window.__ModuleLoader__.load({
|
|
|
692
795
|
h("div", { className: c("dim") }, "×"));
|
|
693
796
|
}
|
|
694
797
|
const sub = `(-${row.notDone ?? 0}/${row.configured ?? 0})`;
|
|
695
|
-
const
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
798
|
+
const emptyCount = row.empty ?? 0;
|
|
799
|
+
// 「没做出来」= API 失败 + 空答案(思考吃满 max-tokens 没产出正文),
|
|
800
|
+
// 两者都不进正确率分母
|
|
801
|
+
const notProduced = row.notProduced ?? row.errors ?? 0;
|
|
802
|
+
const allFailed = row.answered > 0 && notProduced >= row.answered;
|
|
803
|
+
const basePrefix = row.baselineLabel ? `【${row.baselineLabel}】` : "";
|
|
804
|
+
const reasons = [
|
|
805
|
+
row.errors > 0 ? `${row.errors} 题 API 失败` : null,
|
|
806
|
+
emptyCount > 0 ? `${emptyCount} 题思考占满 max-tokens、未产出答案` : null,
|
|
807
|
+
].filter(Boolean).join(",");
|
|
808
|
+
const title = basePrefix + (allFailed
|
|
809
|
+
? `本次选择 ${row.configured ?? 0} 题,全部没做出来(${reasons}),不计入正确率 ${sub}`
|
|
810
|
+
: `本次选择 ${row.configured ?? 0} 题,做出来 ${row.done ?? 0} 题,没做出来 ${row.notDone ?? 0} 题 ${sub}`
|
|
811
|
+
+ (reasons ? `(其中 ${reasons})` : "")); if (allFailed || row.judged === 0) {
|
|
701
812
|
return h("td", { key: taskKeyCol, className: cls, title },
|
|
702
813
|
h("div", { className: c("dim") }, "—"),
|
|
703
814
|
h("div", { className: c("cellSub") }, sub));
|
package/lib/index.js
CHANGED
|
@@ -102,9 +102,64 @@ const RELEASES = [
|
|
|
102
102
|
];
|
|
103
103
|
/** Sane cap so a runaway run cannot eat memory with its log. */
|
|
104
104
|
const LOG_MAX_LINES = 600;
|
|
105
|
-
/** One evaluation at a time per model, but several models may run
|
|
105
|
+
/** One evaluation at a time per model, but several models may run together. */
|
|
106
106
|
const MAX_CONCURRENT_RUNS = 9;
|
|
107
107
|
|
|
108
|
+
/**
|
|
109
|
+
* 「Baseline」题库:记录某个**参考模型**在各任务上的实测对错,用来给别的模型一把同样的尺子。
|
|
110
|
+
*
|
|
111
|
+
* 结构是三层可选:参考模型(baseline)→ 任务(task)→ 对/错(passed / failed)。
|
|
112
|
+
* 作为"探针"用时通常只跑 failed —— 参考模型做不出来的题,才对更强的模型有分辨力;
|
|
113
|
+
* passed 一并留存,便于反向验证(例如确认某模型确实强于参考模型)。
|
|
114
|
+
* 目前只有 olympiad 做过全量实测,后续任务按同样方式补进 tasks 即可。
|
|
115
|
+
*
|
|
116
|
+
* 实现要点:题目 id 取自 LiveBench 的 question.jsonl。面板用 `--question-id` 直接指定,
|
|
117
|
+
* 因此不要求这些题在题库里连续;`--question-begin/--question-end` 仍然会作用在
|
|
118
|
+
* **id 过滤之后**的列表上,所以「题目序号范围」对 baseline 一样有效。
|
|
119
|
+
*/
|
|
120
|
+
const BASELINE_SETS = {
|
|
121
|
+
glm53flash: {
|
|
122
|
+
id: "glm53flash",
|
|
123
|
+
label: "Baseline(glm-5.3-flash@max)",
|
|
124
|
+
referenceModel: "zai-coding-cn__glm-5.3-flash@max",
|
|
125
|
+
// 参考模型在每个任务上的实测结果:passed = 判分 >= 0.5 的题,failed = 判分 0 的题。
|
|
126
|
+
// 作为"探针"用时通常只跑 failed —— 参考模型做不出来的题,才对更强的模型有分辨力;
|
|
127
|
+
// passed 也一并留存,便于反向验证(例如确认某模型确实强于参考模型)。
|
|
128
|
+
// 新增任务时只在这里加一项:id 取自 LiveBench question.jsonl 的 question_id,
|
|
129
|
+
// 面板用 --question-id 直接指定,所以题目在题库里是否连续都无所谓。
|
|
130
|
+
tasks: [
|
|
131
|
+
{
|
|
132
|
+
task: "live_bench/math/olympiad",
|
|
133
|
+
category: "math",
|
|
134
|
+
taskName: "olympiad",
|
|
135
|
+
release: "2024-11-25",
|
|
136
|
+
// 2026-09-12 实测:glm-5.3-flash@max 跑满该任务全量 36 题(并发 4),
|
|
137
|
+
// 通过 28、失败 8;失败的 8 题全部属于"公式还原"题型。
|
|
138
|
+
passed: [
|
|
139
|
+
"ef6c6830ebf0e71d74e7ceb45a242f2b87228933eebfe47db8d798d5738cdaeb", "6e12ab2219a4051c396de5afc4228be52c2d8617c7f5621be8c364f50016c80d",
|
|
140
|
+
"c03217423ccb2181ef9e3106b4c116aaf1bcdbd45b3a97e04110665690177eca", "698d9bd9ad134096358dad82317dae610d9f265255aeee52c644871b83eb7a85",
|
|
141
|
+
"56155e5b8c1dc430cc262691aea0a75d71f7dee78b6e15304607caf0b56ecfbd", "3f55db366e970e1965bbba20e9958a4c0ea52e4224d11d875d390983485dd32b",
|
|
142
|
+
"3649fb05ed87c2f3ed8a933e7c8f032b54a5d6f0d23504cdd578541dd60c0f08", "e5b72d9cdb39c6e5f8798a557f119fff35d98fd165f55659e71bf6ac38b5f178",
|
|
143
|
+
"bc3551b5f643d8920f428909104fe2640a50f6b8a17fff6196aa78f25df28bb6", "2dd75e080f3e276f3dcf304d970e6a03973ddf777537422c9aba59559ddc9594",
|
|
144
|
+
"558c3c463860fcaa2b08edd9d5f0f11071944bf7d9229574862a92e6d91f1a21", "e81b96eed50d6b9b43f041d6a36ad0d9a4d00038c42c47a16efd4465d79d08a4",
|
|
145
|
+
"2191e02b65ac78dd0d28bc13f41786d2596dbaa560ad9d0658a27ae2814faef3", "fc903ff1f1f66a6096c9dea58c278b48f54c80a606b65f82af1730559d73755f",
|
|
146
|
+
"e56e4d8ed64cb6d0e43ce3c8edda0a77d769b4bda5230c1aba57442e27214694", "749116060528890530a40d111c5506f4cdec004a7c52a87ef0a4a01206c187ec",
|
|
147
|
+
"6d9bd0806e27f34ff88209075cad638ca0ed6662f499c74bdd3a256d3511b9fe", "9a199388f2e2bd43deac789cd248363bdcb32611480ac27cf62420b4c351759e",
|
|
148
|
+
"a908da1d873928f9f872dddd33c29620dfa0a54646b79bfde672bd15e6986ec9", "2c17f46f8323e8e692a1827bb18c9a9a43fcff9860b72372cb9551b7cdd532f5",
|
|
149
|
+
"d805a5c9f398cf5126ee84e6899cc9a3d34444a7e2337d15cdf9cc2a949649ba", "f4fdca7e355eaf9362449ebf7df78a366e239091bdb4c4a34b9dfcf9882f23de",
|
|
150
|
+
"aba8388e52d25b4006e78d4010c26cf8848c3e3145b0c050b9d55c8aff211fe8", "95b0921a4de64f6e510ac36b5426ecea550c4d3a6033dfbd0d93dd2213fea714",
|
|
151
|
+
"6df4693c156b7581ffb9a1267a84c055bc9edd8170681b9021bb837f1e9d1167", "1f6132e867a52e5c7b084011484f7fcfec9195f407c9bfb06a3f13f5b709d333",
|
|
152
|
+
"c6675bf6647188f84ee445590a494a8d516635bad77ace98ec61d28746839a8d", "2c5982946e20e2a85531ac00121697b263591e055ac19586db59a890fdaf98b1",
|
|
153
|
+
],
|
|
154
|
+
failed: [
|
|
155
|
+
"0d3a48049dc5da31531654f59e7866832579f812b892610bd1b178fc4d010d0c", "5b9c56e9392dd999162474f10addf1a3a72f1d8b1c8bafc999ccec6f22194c18",
|
|
156
|
+
"11f95734f602e7d1481f9887ca7fc8bed83258e22fd5c443449ac159a4732115", "2a82215ea19fcbded36fa95df35b6e7c5f6ed28b0b5f6c3460b4223f1904a536",
|
|
157
|
+
"527d5f9f9cf27824b84109a495eeced7c7c097fb324b872212f6813a660e5ee4", "f22ff1f6c067d0e085eb79fe5c2e35df01f9edb2bcc73e59e1e15eefd32b8f07",
|
|
158
|
+
"6dfb6aade6429e2cca0718a497a442299a57199dfd547813471f4cf30ed6c7c7", "0499deda2f068008d488551abf96b4b758c6ed6b79cd2ec6a204d1250b140421",
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
], },
|
|
162
|
+
};
|
|
108
163
|
/** Resolve the profile directory from the config-tree anchor (plugin-market pattern). */
|
|
109
164
|
function resolveProfileDir(ctx) {
|
|
110
165
|
if (typeof ctx.baseUrl === "string" && ctx.baseUrl.startsWith("file:")) {
|
|
@@ -299,6 +354,51 @@ function scanCategoryTasks(dataDir) {
|
|
|
299
354
|
return tasks;
|
|
300
355
|
}
|
|
301
356
|
|
|
357
|
+
/**
|
|
358
|
+
* 缓存版的 scanCategoryTasks:/config 每次都要它,/results 也要用它算"某 release 下的
|
|
359
|
+
* 有效题数"。question.jsonl 是静态文件,扫描结果缓存 60s 足够。
|
|
360
|
+
*/
|
|
361
|
+
let taskScanCache = { at: 0, data: null };
|
|
362
|
+
function scanCategoryTasksCached(dataDir) {
|
|
363
|
+
const now = Date.now();
|
|
364
|
+
if (taskScanCache.data !== null && now - taskScanCache.at < 60000) return taskScanCache.data;
|
|
365
|
+
const data = scanCategoryTasks(dataDir);
|
|
366
|
+
taskScanCache = { at: now, data };
|
|
367
|
+
return data;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* 某个任务在指定 release 下的**有效题数**。
|
|
372
|
+
* LiveBench 的 --question-begin/--question-end 作用在 release 过滤后的列表上,
|
|
373
|
+
* 所以边界裁剪必须用这个数,而不是 question.jsonl 的原始行数。
|
|
374
|
+
* @returns {number|null} null 表示没有该任务的数据(调用方回退到原始题数)。
|
|
375
|
+
*/
|
|
376
|
+
function releaseQuestionCount(tasks, category, task, release) {
|
|
377
|
+
const meta = (tasks[category] ?? {})[task];
|
|
378
|
+
if (!meta || !Array.isArray(meta.buckets) || typeof release !== "string" || release.length === 0) return null;
|
|
379
|
+
let n = 0;
|
|
380
|
+
for (const b of meta.buckets) {
|
|
381
|
+
const releasedOk = b.r !== "" && b.r <= release;
|
|
382
|
+
const notRemoved = b.rm === "" || b.rm > release;
|
|
383
|
+
if (releasedOk && notRemoved) n += b.n;
|
|
384
|
+
}
|
|
385
|
+
return n;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Python `list[begin:end]` 的长度,含越界裁剪。
|
|
390
|
+
* 面板的「止」是闭区间,所以 end 先 +1 再按切片语义算:
|
|
391
|
+
* len = min(total, end+1) - min(total, begin)
|
|
392
|
+
* 只写 min(total, end-begin+1) 会在 begin 也越界时算多
|
|
393
|
+
* (8 题填 6-9,实际跑 2 题却算成 4 题)。
|
|
394
|
+
*/
|
|
395
|
+
function clampedRangeLength(total, begin, end) {
|
|
396
|
+
if (!Number.isFinite(total) || total <= 0) return null;
|
|
397
|
+
const b = Math.max(0, Math.min(total, Number(begin)));
|
|
398
|
+
const e = Math.max(0, Math.min(total, Number(end) + 1));
|
|
399
|
+
return Math.max(0, e - b);
|
|
400
|
+
}
|
|
401
|
+
|
|
302
402
|
function readDirSafe(dir) {
|
|
303
403
|
try {
|
|
304
404
|
return readdirNames(dir);
|
|
@@ -510,6 +610,23 @@ function parseAnswerLineLight(line) {
|
|
|
510
610
|
*/
|
|
511
611
|
let resultsCache = { at: 0, data: null };
|
|
512
612
|
|
|
613
|
+
/**
|
|
614
|
+
* 答案行是否"没产出内容"。
|
|
615
|
+
*
|
|
616
|
+
* 两类:
|
|
617
|
+
* 1. `eval_status: token_exhaustion` —— 推理模型把 max_tokens 全烧在思考上,正文为空;
|
|
618
|
+
* 2. `turns` / `choices` 是空串或空数组 —— 上游 200 但没给内容。
|
|
619
|
+
*
|
|
620
|
+
* 这两种都不是"做错了",而是**没做出来**,不该进正确率的分母(用户口径:
|
|
621
|
+
* 做对的题 / 做出来的题)。它们也不是 $ERROR$(那是 API 失败),所以单列一类。
|
|
622
|
+
*
|
|
623
|
+
* 用正则直接扫原始行,避免为了判定空答案去 JSON.parse 上百万字节的答案文件。
|
|
624
|
+
*/
|
|
625
|
+
const EMPTY_ANSWER_RE = /"eval_status"\s*:\s*"token_exhaustion"|"turns"\s*:\s*\[\s*(?:""\s*)?\]|"choices"\s*:\s*\[\s*""\s*\]/;
|
|
626
|
+
function isEmptyAnswerLine(line) {
|
|
627
|
+
return EMPTY_ANSWER_RE.test(line);
|
|
628
|
+
}
|
|
629
|
+
|
|
513
630
|
async function readTextFileAsync(path) {
|
|
514
631
|
const fsPromises = await import("node:fs/promises");
|
|
515
632
|
try {
|
|
@@ -551,11 +668,12 @@ async function computeResults(dataDir) {
|
|
|
551
668
|
const text = await readTextFileAsync(join(answerDir, file));
|
|
552
669
|
if (text === null) continue;
|
|
553
670
|
const key = `${model}\u0000${task}`;
|
|
554
|
-
const stat = answers.get(key) ?? { answered: 0, errors: 0 };
|
|
671
|
+
const stat = answers.get(key) ?? { answered: 0, errors: 0, empty: 0 };
|
|
555
672
|
for (const line of text.split("\n")) {
|
|
556
673
|
if (line.trim().length === 0) continue;
|
|
557
674
|
stat.answered += 1;
|
|
558
675
|
if (line.includes('"$ERROR$"')) stat.errors += 1;
|
|
676
|
+
else if (isEmptyAnswerLine(line)) stat.empty += 1;
|
|
559
677
|
}
|
|
560
678
|
// 空文件(尚未产出答案,或成绩已被删除只留下 0 字节壳)不计入结果:
|
|
561
679
|
// 否则删掉的记录会在下一次扫描时凭“文件名仍在”重新冒出来。
|
|
@@ -591,8 +709,13 @@ async function computeResults(dataDir) {
|
|
|
591
709
|
const category = entry?.category ?? catByTask.get(task) ?? "";
|
|
592
710
|
const total = totals.get(task) ?? 0;
|
|
593
711
|
const judgedCount = entry?.n ?? 0;
|
|
594
|
-
//
|
|
595
|
-
|
|
712
|
+
// 「做出来」的题 = 尝试数 - API 失败 - 空答案。
|
|
713
|
+
// 关键:思考吃满 max_tokens、正文为空的题属于"没做出来",不是"做错了",
|
|
714
|
+
// 必须从正确率分母里剔除,否则一个模型 8 题里只答出 1 题(0.9167 分)
|
|
715
|
+
// 会被算成 11.5% 而不是 91.7%。
|
|
716
|
+
const empty = stat.empty ?? 0;
|
|
717
|
+
const notProduced = stat.errors + empty;
|
|
718
|
+
const done = Math.max(0, stat.answered - notProduced);
|
|
596
719
|
// 选择题数 = 用户在「题目序号范围」里选择的题数(end 含端点)。
|
|
597
720
|
// 有元数据就直接用范围大小;没有元数据(历史数据)时用本次实际写入的答案行数兜底:
|
|
598
721
|
// LiveBench 会为范围内每一题写一行(访问失败也写 $ERROR$),所以跑完后
|
|
@@ -608,15 +731,27 @@ async function computeResults(dataDir) {
|
|
|
608
731
|
return true;
|
|
609
732
|
});
|
|
610
733
|
if (inScope) {
|
|
734
|
+
// 「可选题目总数」的基准,按优先级取:
|
|
735
|
+
// baseline 子集大小 > 该任务在本次 release 下的有效题数 > question.jsonl 原始行数。
|
|
736
|
+
// 基准取错会把越界的范围当成有效选择(题库只有 8 题而用户填 0-8 时算出 9 题),
|
|
737
|
+
// 于是 notDone 永远多 1,全做对也显示 (-1/8)。
|
|
738
|
+
const byRelease = releaseQuestionCount(scanCategoryTasksCached(dataDir), category, task, meta.release);
|
|
739
|
+
const baselineTotal = Number.isFinite(Number(meta.baselineSize)) && Number(meta.baselineSize) > 0
|
|
740
|
+
? Number(meta.baselineSize)
|
|
741
|
+
: null;
|
|
742
|
+
const scopeTotal = baselineTotal ?? byRelease ?? total;
|
|
611
743
|
const hasRange = meta.begin !== null && meta.begin !== undefined && meta.end !== null && meta.end !== undefined;
|
|
612
|
-
if (hasRange)
|
|
744
|
+
if (hasRange) {
|
|
745
|
+
const len = clampedRangeLength(scopeTotal, Number(meta.begin), Number(meta.end));
|
|
746
|
+
if (len !== null) configured = len;
|
|
747
|
+
}
|
|
613
748
|
}
|
|
614
749
|
}
|
|
615
750
|
// 实际尝试数比设定还多(断点重跑等)时,以实际为准
|
|
616
751
|
if (configured < stat.answered) configured = stat.answered;
|
|
617
752
|
const notDone = Math.max(0, configured - done);
|
|
618
|
-
// 正确率 = 做对的题 /
|
|
619
|
-
const judgedDone = Math.max(0, judgedCount -
|
|
753
|
+
// 正确率 = 做对的题 / **做出来的题**(API 失败、思考吃满 token 没产出答案的都不进分母)
|
|
754
|
+
const judgedDone = Math.max(0, judgedCount - notProduced);
|
|
620
755
|
const score = entry && judgedDone > 0 ? (entry.sum / judgedDone) * 100 : null;
|
|
621
756
|
return {
|
|
622
757
|
model,
|
|
@@ -628,11 +763,15 @@ async function computeResults(dataDir) {
|
|
|
628
763
|
time: entry && entry.time > 0 ? entry.time : null,
|
|
629
764
|
answered: stat.answered,
|
|
630
765
|
errors: stat.errors,
|
|
766
|
+
empty,
|
|
767
|
+
notProduced,
|
|
631
768
|
done,
|
|
632
769
|
configured,
|
|
633
770
|
notDone,
|
|
634
771
|
// 本次运行的开始时间(ISO);旧数据没有元数据时为 null,前端退回解析 displayName 里的运行戳
|
|
635
772
|
runStartedAt: meta !== undefined && typeof meta.startedAt === "string" ? meta.startedAt : null,
|
|
773
|
+
// 非 null 表示这一行跑的是 Baseline 题库子集,不是该任务全量
|
|
774
|
+
baselineLabel: meta !== undefined && typeof meta.baselineLabel === "string" ? meta.baselineLabel : null,
|
|
636
775
|
};
|
|
637
776
|
});
|
|
638
777
|
// taskTotals 同时给出裸任务名与 `category/task` 两种键,便于前端按列取题目总数
|
|
@@ -743,7 +882,7 @@ function apply(ctx) {
|
|
|
743
882
|
}
|
|
744
883
|
const providerId = typeof body.provider === "string" ? body.provider : "";
|
|
745
884
|
const modelId = typeof body.model === "string" ? body.model.trim() : "";
|
|
746
|
-
|
|
885
|
+
let release = typeof body.release === "string" ? body.release : "2024-11-25";
|
|
747
886
|
const category = typeof body.category === "string" && body.category.length > 0 ? body.category : null;
|
|
748
887
|
const task = typeof body.task === "string" && body.task.length > 0 ? body.task : null;
|
|
749
888
|
const reasoningEffort = typeof body.reasoningEffort === "string" && body.reasoningEffort.length > 0 && body.reasoningEffort !== "default" ? body.reasoningEffort : null;
|
|
@@ -764,8 +903,44 @@ function apply(ctx) {
|
|
|
764
903
|
// Multi-select support: the client resolves its category/task chips into
|
|
765
904
|
// an explicit bench-path list ("live_bench", "live_bench/<cat>",
|
|
766
905
|
// "live_bench/<cat>/<task>"); gen_api_answer accepts nargs="+".
|
|
906
|
+
// Baseline 题库则完全绕开分类/任务选择("不选 = 全部分类"不适用),
|
|
907
|
+
// 锁定到它自己的任务 + 一组 question id。
|
|
908
|
+
const baselineKey = typeof body.baseline === "string" && body.baseline.length > 0 ? body.baseline : null;
|
|
909
|
+
const baseline = baselineKey !== null ? (BASELINE_SETS[baselineKey] ?? null) : null;
|
|
910
|
+
if (baselineKey !== null && baseline === null) {
|
|
911
|
+
return { status: 400, payload: { ok: false, error: `unknown baseline: ${baselineKey}` } };
|
|
912
|
+
}
|
|
767
913
|
let benchNames = null;
|
|
768
|
-
|
|
914
|
+
// Baseline 是三层选择:参考模型 + 任务 + 对/错。服务端负责把选择解析成 question id 列表,
|
|
915
|
+
// 客户端只送选择,不送 id —— 免得界面与题库数据脱节。
|
|
916
|
+
let baselinePick = null;
|
|
917
|
+
if (baseline !== null) {
|
|
918
|
+
const taskKey = typeof body.baselineTask === "string" ? body.baselineTask : "";
|
|
919
|
+
const picks = Array.isArray(body.baselinePicks)
|
|
920
|
+
? body.baselinePicks.filter((p) => p === "passed" || p === "failed")
|
|
921
|
+
: [];
|
|
922
|
+
const baselineTask = baseline.tasks.find((t) => t.task === taskKey) ?? null;
|
|
923
|
+
if (baselineTask === null) {
|
|
924
|
+
return { status: 400, payload: { ok: false, error: `baseline ${baselineKey}: 未选择任务(或任务不存在):${taskKey}` } };
|
|
925
|
+
}
|
|
926
|
+
if (picks.length === 0) {
|
|
927
|
+
return { status: 400, payload: { ok: false, error: `baseline ${baselineKey}/${taskKey}: 请至少选择「做对」或「做错」之一` } };
|
|
928
|
+
}
|
|
929
|
+
const ids = [];
|
|
930
|
+
const seen = new Set();
|
|
931
|
+
for (const pick of picks) {
|
|
932
|
+
for (const qid of baselineTask[pick]) {
|
|
933
|
+
if (!seen.has(qid)) { seen.add(qid); ids.push(qid); }
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
if (ids.length === 0) {
|
|
937
|
+
return { status: 400, payload: { ok: false, error: `baseline ${baselineKey}/${taskKey}: 所选分组没有题目` } };
|
|
938
|
+
}
|
|
939
|
+
// baseline 的 question id 属于特定 release,跟着用户选别的 release 会一题都匹配不到
|
|
940
|
+
release = baselineTask.release;
|
|
941
|
+
benchNames = [baselineTask.task];
|
|
942
|
+
baselinePick = { label: baseline.label, task: baselineTask.task, picks, ids };
|
|
943
|
+
} else if (Array.isArray(body.benchNames) && body.benchNames.length > 0) {
|
|
769
944
|
if (body.benchNames.length > 24) {
|
|
770
945
|
return { status: 400, payload: { ok: false, error: "too many bench names (max 24)" } };
|
|
771
946
|
}
|
|
@@ -824,13 +999,23 @@ function apply(ctx) {
|
|
|
824
999
|
"--bench-name",
|
|
825
1000
|
...(benchNames !== null ? benchNames : [benchParts.join("/")]),
|
|
826
1001
|
"--livebench-release-option", release,
|
|
827
|
-
|
|
1002
|
+
// 上限放到 200k:推理模型在难题上会把 max_tokens 全烧在思考里,正文为空
|
|
1003
|
+
// (eval_status=token_exhaustion),此时"做不出来"其实是预算不够而不是能力不够。
|
|
1004
|
+
// 旧上限 32768 在 olympiad 这类题上会直接把参考模型卡死。
|
|
1005
|
+
"--max-tokens", String(asInt(body.maxTokens, 256, 200000, 32000)),
|
|
828
1006
|
"--parallel-requests", String(asInt(body.parallel, 1, 8, 1)),
|
|
829
1007
|
// 流式:中转网关(Cloudflare 等)对非流式请求有 ~100s 超时(524),
|
|
830
1008
|
// 高推理强度模型思考数分钟必然超时。流式保持字节流动可规避。
|
|
831
1009
|
"--stream",
|
|
832
1010
|
"--mode", "single",
|
|
833
1011
|
];
|
|
1012
|
+
// Baseline:直接点名 question id。--question-id 是 nargs="+",后面跟一串 id;
|
|
1013
|
+
// 它和后面的 --question-begin/--question-end 叠加时,范围作用在**id 过滤后**的
|
|
1014
|
+
// 列表上(gen_api_answer: load_questions(..., question_id) 之后才做 [begin:end]),
|
|
1015
|
+
// 所以「题目序号范围」对 baseline 题库同样有效。
|
|
1016
|
+
if (baselinePick !== null) {
|
|
1017
|
+
args.push("--question-id", ...baselinePick.ids);
|
|
1018
|
+
}
|
|
834
1019
|
if (body.begin !== undefined && body.begin !== null && `${body.begin}`.length > 0) {
|
|
835
1020
|
args.push("--question-begin", String(asInt(body.begin, 0, 100000, 0)));
|
|
836
1021
|
}
|
|
@@ -897,6 +1082,17 @@ function apply(ctx) {
|
|
|
897
1082
|
benchNames: benchNames !== null ? benchNames : [benchParts.join("/")],
|
|
898
1083
|
begin: body.begin ?? null,
|
|
899
1084
|
end: body.end ?? null,
|
|
1085
|
+
// 算"本次设定题数"要用 release 过滤后的有效题数来裁剪边界,所以必须记下来
|
|
1086
|
+
release,
|
|
1087
|
+
// baseline 评测标出来:成绩表里同一列会混着"全量"和"题库子集"两种分数,
|
|
1088
|
+
// 不标注就没法解读。
|
|
1089
|
+
baseline: baselinePick !== null ? baselineKey : null,
|
|
1090
|
+
baselineLabel: baselinePick !== null
|
|
1091
|
+
? `${baselinePick.label} · ${baselinePick.task.split("/").slice(-1)[0]} · ${baselinePick.picks.map((p) => (p === "passed" ? "做对" : "做错")).join("+")}`
|
|
1092
|
+
: null,
|
|
1093
|
+
baselineTask: baselinePick !== null ? baselinePick.task : null,
|
|
1094
|
+
baselinePicks: baselinePick !== null ? baselinePick.picks : null,
|
|
1095
|
+
baselineSize: baselinePick !== null ? baselinePick.ids.length : null,
|
|
900
1096
|
startedAt: new Date().toISOString(),
|
|
901
1097
|
}), "utf8");
|
|
902
1098
|
} catch { /* 元数据写入失败不影响评测 */ }
|
|
@@ -1005,7 +1201,22 @@ function apply(ctx) {
|
|
|
1005
1201
|
available: layout.available,
|
|
1006
1202
|
root: layout.root,
|
|
1007
1203
|
releases: RELEASES,
|
|
1008
|
-
tasks:
|
|
1204
|
+
tasks: scanCategoryTasksCached(layout.dataDir),
|
|
1205
|
+
// Baseline 题库:参考模型在各任务上的实测对错,供"探针"式快速评测选用
|
|
1206
|
+
baselines: Object.values(BASELINE_SETS).map((b) => ({
|
|
1207
|
+
id: b.id,
|
|
1208
|
+
label: b.label,
|
|
1209
|
+
referenceModel: b.referenceModel,
|
|
1210
|
+
tasks: b.tasks.map((t) => ({
|
|
1211
|
+
task: t.task,
|
|
1212
|
+
category: t.category,
|
|
1213
|
+
taskName: t.taskName,
|
|
1214
|
+
release: t.release,
|
|
1215
|
+
total: t.passed.length + t.failed.length,
|
|
1216
|
+
passedCount: t.passed.length,
|
|
1217
|
+
failedCount: t.failed.length,
|
|
1218
|
+
})),
|
|
1219
|
+
})),
|
|
1009
1220
|
providers: providers.map(({ id, name: pname, models, api, baseURL }) => ({
|
|
1010
1221
|
id,
|
|
1011
1222
|
name: pname,
|
|
@@ -1223,4 +1434,4 @@ function apply(ctx) {
|
|
|
1223
1434
|
}), `${name}: delete route`);
|
|
1224
1435
|
}
|
|
1225
1436
|
|
|
1226
|
-
export { name, inject, apply, writeGeneratedModelConfig, readProviders, validateBenchName };
|
|
1437
|
+
export { name, inject, apply, writeGeneratedModelConfig, readProviders, validateBenchName, clampedRangeLength, releaseQuestionCount, isEmptyAnswerLine };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-livebench-panel",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "DSH web plugin: a LiveBench tab in the Trajectory view (right of 对话/轨迹). Run LiveBench evaluations against every model configured in the DeepSeek Harness — pick provider/model, category, task, release and question range from dropdowns, watch progress, and read scores in place.",
|
|
3
|
+
"version": "0.2.21",
|
|
4
|
+
"description": "DSH web plugin: a LiveBench tab in the Trajectory view (right of 对话/轨迹). Run LiveBench evaluations against every model configured in the DeepSeek Harness — pick provider/model, category, task, release and question range from dropdowns, watch progress, and read scores in place. Ships a Baseline probe set built from the questions a reference model cannot solve.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "cszr (Vithrive)",
|
|
7
7
|
"type": "module",
|