dsh-livebench-panel 0.2.20 → 0.2.22
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/lib/client.js +17 -5
- package/lib/index.js +78 -7
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -578,7 +578,7 @@ window.__ModuleLoader__.load({
|
|
|
578
578
|
),
|
|
579
579
|
h("div", { className: c("field") },
|
|
580
580
|
h("label", { className: c("label") }, "max-tokens(默认 32000)"),
|
|
581
|
-
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: "单题输出上限。推理模型在难题上容易把预算全花在思考里、正文为空(成绩表括号里记为「没做出来」),这类题可以把上限调大后重跑。" }),
|
|
582
582
|
),
|
|
583
583
|
h("div", { className: c("field") },
|
|
584
584
|
h("label", { className: c("label") }, "题目序号范围(可选,0 起,含首尾)"),
|
|
@@ -795,12 +795,24 @@ window.__ModuleLoader__.load({
|
|
|
795
795
|
h("div", { className: c("dim") }, "×"));
|
|
796
796
|
}
|
|
797
797
|
const sub = `(-${row.notDone ?? 0}/${row.configured ?? 0})`;
|
|
798
|
-
const
|
|
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;
|
|
799
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 missing = Array.isArray(row.missingIndexes) ? row.missingIndexes : null;
|
|
809
|
+
const missingText = missing !== null && missing.length > 0
|
|
810
|
+
? `\n没做出来的题号(0 起,可复制去重跑):${missing.join("、")}`
|
|
811
|
+
: "";
|
|
800
812
|
const title = basePrefix + (allFailed
|
|
801
|
-
? `本次选择 ${row.configured ?? 0}
|
|
802
|
-
: `本次选择 ${row.configured ?? 0}
|
|
803
|
-
(
|
|
813
|
+
? `本次选择 ${row.configured ?? 0} 题,全部没做出来(${reasons}),不计入正确率 ${sub}`
|
|
814
|
+
: `本次选择 ${row.configured ?? 0} 题,做出来 ${row.done ?? 0} 题,没做出来 ${row.notDone ?? 0} 题 ${sub}`
|
|
815
|
+
+ (reasons ? `(其中 ${reasons})` : "")) + missingText; if (allFailed || row.judged === 0) {
|
|
804
816
|
return h("td", { key: taskKeyCol, className: cls, title },
|
|
805
817
|
h("div", { className: c("dim") }, "—"),
|
|
806
818
|
h("div", { className: c("cellSub") }, sub));
|
package/lib/index.js
CHANGED
|
@@ -610,6 +610,50 @@ function parseAnswerLineLight(line) {
|
|
|
610
610
|
*/
|
|
611
611
|
let resultsCache = { at: 0, data: null };
|
|
612
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
|
+
|
|
630
|
+
/** 从答案行里抽 question_id(只做正则,不 JSON.parse)。 */
|
|
631
|
+
const QUESTION_ID_RE = /"question_id"\s*:\s*"([A-Za-z0-9_.:-]{8,120})"/;
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Baseline 评测里"没做出来的题号"。
|
|
635
|
+
*
|
|
636
|
+
* 编号是 **该题库内的 0 起下标**,与「题目序号范围」的编号一致,所以可以直接复制去重跑。
|
|
637
|
+
* 范围先按题库实际题数裁剪 —— 「0-1000」这种越界输入不会把编号算歪(基准永远是实际题数)。
|
|
638
|
+
*
|
|
639
|
+
* @param {object|undefined} meta 运行元数据(需要 baselineIds / begin / end)
|
|
640
|
+
* @param {Set<string>} seen 答案文件里实际出现的 question_id
|
|
641
|
+
* @returns {number[]|null} 不是 baseline 运行时返回 null
|
|
642
|
+
*/
|
|
643
|
+
function missingBaselineIndexes(meta, seen) {
|
|
644
|
+
if (!meta || !Array.isArray(meta.baselineIds) || meta.baselineIds.length === 0) return null;
|
|
645
|
+
const ids = meta.baselineIds;
|
|
646
|
+
const hasBegin = meta.begin !== null && meta.begin !== undefined;
|
|
647
|
+
const hasEnd = meta.end !== null && meta.end !== undefined;
|
|
648
|
+
const from = hasBegin ? Math.max(0, Math.min(ids.length, Number(meta.begin))) : 0;
|
|
649
|
+
const to = hasEnd ? Math.max(0, Math.min(ids.length, Number(meta.end) + 1)) : ids.length;
|
|
650
|
+
const out = [];
|
|
651
|
+
for (let i = from; i < Math.max(from, to); i += 1) {
|
|
652
|
+
if (!seen.has(ids[i])) out.push(i);
|
|
653
|
+
}
|
|
654
|
+
return out;
|
|
655
|
+
}
|
|
656
|
+
|
|
613
657
|
async function readTextFileAsync(path) {
|
|
614
658
|
const fsPromises = await import("node:fs/promises");
|
|
615
659
|
try {
|
|
@@ -651,11 +695,21 @@ async function computeResults(dataDir) {
|
|
|
651
695
|
const text = await readTextFileAsync(join(answerDir, file));
|
|
652
696
|
if (text === null) continue;
|
|
653
697
|
const key = `${model}\u0000${task}`;
|
|
654
|
-
const stat = answers.get(key) ?? { answered: 0, errors: 0 };
|
|
698
|
+
const stat = answers.get(key) ?? { answered: 0, errors: 0, empty: 0, seen: null };
|
|
699
|
+
// baseline 评测要回答"少做了哪几个题",所以需要收集实际出现的 question_id。
|
|
700
|
+
// 只用正则抽 id(不 JSON.parse),对几百 KB~几 MB 的答案文件也够快。
|
|
701
|
+
const runMetaEntry = runMeta.get(model);
|
|
702
|
+
const trackIds = runMetaEntry && Array.isArray(runMetaEntry.baselineIds) && runMetaEntry.baselineIds.length > 0;
|
|
703
|
+
if (trackIds && stat.seen === null) stat.seen = new Set();
|
|
655
704
|
for (const line of text.split("\n")) {
|
|
656
705
|
if (line.trim().length === 0) continue;
|
|
657
706
|
stat.answered += 1;
|
|
658
707
|
if (line.includes('"$ERROR$"')) stat.errors += 1;
|
|
708
|
+
else if (isEmptyAnswerLine(line)) stat.empty += 1;
|
|
709
|
+
if (trackIds) {
|
|
710
|
+
const idMatch = line.match(QUESTION_ID_RE);
|
|
711
|
+
if (idMatch !== null) stat.seen.add(idMatch[1]);
|
|
712
|
+
}
|
|
659
713
|
}
|
|
660
714
|
// 空文件(尚未产出答案,或成绩已被删除只留下 0 字节壳)不计入结果:
|
|
661
715
|
// 否则删掉的记录会在下一次扫描时凭“文件名仍在”重新冒出来。
|
|
@@ -691,8 +745,13 @@ async function computeResults(dataDir) {
|
|
|
691
745
|
const category = entry?.category ?? catByTask.get(task) ?? "";
|
|
692
746
|
const total = totals.get(task) ?? 0;
|
|
693
747
|
const judgedCount = entry?.n ?? 0;
|
|
694
|
-
//
|
|
695
|
-
|
|
748
|
+
// 「做出来」的题 = 尝试数 - API 失败 - 空答案。
|
|
749
|
+
// 关键:思考吃满 max_tokens、正文为空的题属于"没做出来",不是"做错了",
|
|
750
|
+
// 必须从正确率分母里剔除,否则一个模型 8 题里只答出 1 题(0.9167 分)
|
|
751
|
+
// 会被算成 11.5% 而不是 91.7%。
|
|
752
|
+
const empty = stat.empty ?? 0;
|
|
753
|
+
const notProduced = stat.errors + empty;
|
|
754
|
+
const done = Math.max(0, stat.answered - notProduced);
|
|
696
755
|
// 选择题数 = 用户在「题目序号范围」里选择的题数(end 含端点)。
|
|
697
756
|
// 有元数据就直接用范围大小;没有元数据(历史数据)时用本次实际写入的答案行数兜底:
|
|
698
757
|
// LiveBench 会为范围内每一题写一行(访问失败也写 $ERROR$),所以跑完后
|
|
@@ -727,9 +786,12 @@ async function computeResults(dataDir) {
|
|
|
727
786
|
// 实际尝试数比设定还多(断点重跑等)时,以实际为准
|
|
728
787
|
if (configured < stat.answered) configured = stat.answered;
|
|
729
788
|
const notDone = Math.max(0, configured - done);
|
|
730
|
-
// 正确率 = 做对的题 /
|
|
731
|
-
const judgedDone = Math.max(0, judgedCount -
|
|
789
|
+
// 正确率 = 做对的题 / **做出来的题**(API 失败、思考吃满 token 没产出答案的都不进分母)
|
|
790
|
+
const judgedDone = Math.max(0, judgedCount - notProduced);
|
|
732
791
|
const score = entry && judgedDone > 0 ? (entry.sum / judgedDone) * 100 : null;
|
|
792
|
+
// Baseline 评测:算出**具体哪几个题号没做出来**(题号 = 该题库内的 0 起下标,
|
|
793
|
+
// 与「题目序号范围」的编号一致,方便直接复制去重跑)。
|
|
794
|
+
const missingIndexes = missingBaselineIndexes(meta, stat.seen ?? new Set());
|
|
733
795
|
return {
|
|
734
796
|
model,
|
|
735
797
|
category,
|
|
@@ -740,9 +802,13 @@ async function computeResults(dataDir) {
|
|
|
740
802
|
time: entry && entry.time > 0 ? entry.time : null,
|
|
741
803
|
answered: stat.answered,
|
|
742
804
|
errors: stat.errors,
|
|
805
|
+
empty,
|
|
806
|
+
notProduced,
|
|
743
807
|
done,
|
|
744
808
|
configured,
|
|
745
809
|
notDone,
|
|
810
|
+
// baseline 专有:没做出来的题号(0 起,相对该题库)
|
|
811
|
+
missingIndexes,
|
|
746
812
|
// 本次运行的开始时间(ISO);旧数据没有元数据时为 null,前端退回解析 displayName 里的运行戳
|
|
747
813
|
runStartedAt: meta !== undefined && typeof meta.startedAt === "string" ? meta.startedAt : null,
|
|
748
814
|
// 非 null 表示这一行跑的是 Baseline 题库子集,不是该任务全量
|
|
@@ -974,7 +1040,10 @@ function apply(ctx) {
|
|
|
974
1040
|
"--bench-name",
|
|
975
1041
|
...(benchNames !== null ? benchNames : [benchParts.join("/")]),
|
|
976
1042
|
"--livebench-release-option", release,
|
|
977
|
-
|
|
1043
|
+
// 上限放到 200k:推理模型在难题上会把 max_tokens 全烧在思考里,正文为空
|
|
1044
|
+
// (eval_status=token_exhaustion),此时"做不出来"其实是预算不够而不是能力不够。
|
|
1045
|
+
// 旧上限 32768 在 olympiad 这类题上会直接把参考模型卡死。
|
|
1046
|
+
"--max-tokens", String(asInt(body.maxTokens, 256, 200000, 32000)),
|
|
978
1047
|
"--parallel-requests", String(asInt(body.parallel, 1, 8, 1)),
|
|
979
1048
|
// 流式:中转网关(Cloudflare 等)对非流式请求有 ~100s 超时(524),
|
|
980
1049
|
// 高推理强度模型思考数分钟必然超时。流式保持字节流动可规避。
|
|
@@ -1065,6 +1134,8 @@ function apply(ctx) {
|
|
|
1065
1134
|
baselineTask: baselinePick !== null ? baselinePick.task : null,
|
|
1066
1135
|
baselinePicks: baselinePick !== null ? baselinePick.picks : null,
|
|
1067
1136
|
baselineSize: baselinePick !== null ? baselinePick.ids.length : null,
|
|
1137
|
+
// 实际使用的有序 id 列表:/results 靠它算出"少做了哪几题"
|
|
1138
|
+
baselineIds: baselinePick !== null ? baselinePick.ids : null,
|
|
1068
1139
|
startedAt: new Date().toISOString(),
|
|
1069
1140
|
}), "utf8");
|
|
1070
1141
|
} catch { /* 元数据写入失败不影响评测 */ }
|
|
@@ -1406,4 +1477,4 @@ function apply(ctx) {
|
|
|
1406
1477
|
}), `${name}: delete route`);
|
|
1407
1478
|
}
|
|
1408
1479
|
|
|
1409
|
-
export { name, inject, apply, writeGeneratedModelConfig, readProviders, validateBenchName, clampedRangeLength, releaseQuestionCount };
|
|
1480
|
+
export { name, inject, apply, writeGeneratedModelConfig, readProviders, validateBenchName, clampedRangeLength, releaseQuestionCount, isEmptyAnswerLine, missingBaselineIndexes };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-livebench-panel",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.22",
|
|
4
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)",
|