dsh-livebench-panel 0.2.22 → 0.2.24
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/index.js +84 -22
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -630,26 +630,66 @@ function isEmptyAnswerLine(line) {
|
|
|
630
630
|
/** 从答案行里抽 question_id(只做正则,不 JSON.parse)。 */
|
|
631
631
|
const QUESTION_ID_RE = /"question_id"\s*:\s*"([A-Za-z0-9_.:-]{8,120})"/;
|
|
632
632
|
|
|
633
|
+
/**
|
|
634
|
+
* 「题目序号范围」的边界是否真的设了。
|
|
635
|
+
*
|
|
636
|
+
* 注意:面板表单里没填时提交的是**空字符串**,不是 null。旧代码只判 `!== null/undefined`,
|
|
637
|
+
* 于是 `""` 被当成"设了范围",`Number("") === 0` → 范围变成 0..0,只检查第 0 题。
|
|
638
|
+
*/
|
|
639
|
+
function isRangeBoundSet(value) {
|
|
640
|
+
return value !== null && value !== undefined && String(value).trim().length > 0;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/** 把范围边界规范成 number 或 null(空串一律当没设)。 */
|
|
644
|
+
function normalizeRangeBound(value) {
|
|
645
|
+
return isRangeBoundSet(value) ? Number(value) : null;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* 取某次运行的 baseline 有序 id 列表。
|
|
650
|
+
* 新元数据直接读 baselineIds;老元数据(加字段之前启动的运行)从
|
|
651
|
+
* baseline / baselineTask / baselinePicks 反查 BASELINE_SETS 还原。
|
|
652
|
+
* @returns {string[]|null}
|
|
653
|
+
*/
|
|
654
|
+
function baselineIdsFor(meta) {
|
|
655
|
+
if (!meta) return null;
|
|
656
|
+
if (Array.isArray(meta.baselineIds) && meta.baselineIds.length > 0) return meta.baselineIds;
|
|
657
|
+
const set = typeof meta.baseline === "string" ? BASELINE_SETS[meta.baseline] : null;
|
|
658
|
+
const taskDef = set && typeof meta.baselineTask === "string"
|
|
659
|
+
? (set.tasks ?? []).find((t) => t.task === meta.baselineTask) ?? null
|
|
660
|
+
: null;
|
|
661
|
+
if (!taskDef || !Array.isArray(meta.baselinePicks) || meta.baselinePicks.length === 0) return null;
|
|
662
|
+
const merged = [];
|
|
663
|
+
for (const pick of meta.baselinePicks) {
|
|
664
|
+
if (pick !== "passed" && pick !== "failed") continue;
|
|
665
|
+
for (const qid of taskDef[pick]) if (!merged.includes(qid)) merged.push(qid);
|
|
666
|
+
}
|
|
667
|
+
return merged.length > 0 ? merged : null;
|
|
668
|
+
}
|
|
669
|
+
|
|
633
670
|
/**
|
|
634
671
|
* Baseline 评测里"没做出来的题号"。
|
|
635
672
|
*
|
|
636
|
-
*
|
|
673
|
+
* 「没做出来」= 压根没跑 + 跑了但空答案(思考吃满 max-tokens)+ 跑了但 API 失败,
|
|
674
|
+
* 因为它们都该从正确率分母里排除,也都是用户想补跑的题。
|
|
675
|
+
*
|
|
676
|
+
* 编号是 **该题库内的 0 起下标**,与「题目序号范围」的编号一致,可以直接复制去重跑。
|
|
637
677
|
* 范围先按题库实际题数裁剪 —— 「0-1000」这种越界输入不会把编号算歪(基准永远是实际题数)。
|
|
638
678
|
*
|
|
639
|
-
* @param {object|undefined} meta
|
|
640
|
-
* @param {Set<string>}
|
|
679
|
+
* @param {object|undefined} meta 运行元数据(baselineIds,或可由 baseline 反查)
|
|
680
|
+
* @param {Set<string>} okIds 答案文件里有**有效答案**的 question_id
|
|
641
681
|
* @returns {number[]|null} 不是 baseline 运行时返回 null
|
|
642
682
|
*/
|
|
643
|
-
function missingBaselineIndexes(meta,
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
const hasBegin = meta.begin
|
|
647
|
-
const hasEnd = meta.end
|
|
683
|
+
function missingBaselineIndexes(meta, okIds) {
|
|
684
|
+
const ids = baselineIdsFor(meta);
|
|
685
|
+
if (ids === null) return null;
|
|
686
|
+
const hasBegin = isRangeBoundSet(meta.begin);
|
|
687
|
+
const hasEnd = isRangeBoundSet(meta.end);
|
|
648
688
|
const from = hasBegin ? Math.max(0, Math.min(ids.length, Number(meta.begin))) : 0;
|
|
649
689
|
const to = hasEnd ? Math.max(0, Math.min(ids.length, Number(meta.end) + 1)) : ids.length;
|
|
650
690
|
const out = [];
|
|
651
691
|
for (let i = from; i < Math.max(from, to); i += 1) {
|
|
652
|
-
if (!
|
|
692
|
+
if (!okIds.has(ids[i])) out.push(i);
|
|
653
693
|
}
|
|
654
694
|
return out;
|
|
655
695
|
}
|
|
@@ -695,20 +735,24 @@ async function computeResults(dataDir) {
|
|
|
695
735
|
const text = await readTextFileAsync(join(answerDir, file));
|
|
696
736
|
if (text === null) continue;
|
|
697
737
|
const key = `${model}\u0000${task}`;
|
|
698
|
-
const stat = answers.get(key) ?? { answered: 0, errors: 0, empty: 0,
|
|
699
|
-
// baseline 评测要回答"少做了哪几个题"
|
|
738
|
+
const stat = answers.get(key) ?? { answered: 0, errors: 0, empty: 0, okIds: null };
|
|
739
|
+
// baseline 评测要回答"少做了哪几个题",所以需要收集**有有效答案**的 question_id。
|
|
740
|
+
// 「没做出来」= 压根没跑 + 跑了但空答案 + 跑了但 API 失败,三者都算,
|
|
741
|
+
// 因为它们都该从正确率分母里排除,也都是用户想补跑的题。
|
|
700
742
|
// 只用正则抽 id(不 JSON.parse),对几百 KB~几 MB 的答案文件也够快。
|
|
701
743
|
const runMetaEntry = runMeta.get(model);
|
|
702
|
-
const trackIds =
|
|
703
|
-
if (trackIds && stat.
|
|
744
|
+
const trackIds = baselineIdsFor(runMetaEntry) !== null;
|
|
745
|
+
if (trackIds && stat.okIds === null) stat.okIds = new Set();
|
|
704
746
|
for (const line of text.split("\n")) {
|
|
705
747
|
if (line.trim().length === 0) continue;
|
|
706
748
|
stat.answered += 1;
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
if (
|
|
749
|
+
const isError = line.includes('"$ERROR$"');
|
|
750
|
+
const isEmpty = !isError && isEmptyAnswerLine(line);
|
|
751
|
+
if (isError) stat.errors += 1;
|
|
752
|
+
else if (isEmpty) stat.empty += 1;
|
|
753
|
+
if (trackIds && !isError && !isEmpty) {
|
|
710
754
|
const idMatch = line.match(QUESTION_ID_RE);
|
|
711
|
-
if (idMatch !== null) stat.
|
|
755
|
+
if (idMatch !== null) stat.okIds.add(idMatch[1]);
|
|
712
756
|
}
|
|
713
757
|
}
|
|
714
758
|
// 空文件(尚未产出答案,或成绩已被删除只留下 0 字节壳)不计入结果:
|
|
@@ -776,10 +820,14 @@ async function computeResults(dataDir) {
|
|
|
776
820
|
? Number(meta.baselineSize)
|
|
777
821
|
: null;
|
|
778
822
|
const scopeTotal = baselineTotal ?? byRelease ?? total;
|
|
779
|
-
const hasRange = meta.begin
|
|
823
|
+
const hasRange = isRangeBoundSet(meta.begin) && isRangeBoundSet(meta.end);
|
|
780
824
|
if (hasRange) {
|
|
781
825
|
const len = clampedRangeLength(scopeTotal, Number(meta.begin), Number(meta.end));
|
|
782
826
|
if (len !== null) configured = len;
|
|
827
|
+
} else if (baselineTotal !== null) {
|
|
828
|
+
// baseline 且没设范围 = 整个题库,所以"选择题数"恒等于题库大小,
|
|
829
|
+
// 而不是当前已答题数(否则跑到一半会显示"选择 6 题")
|
|
830
|
+
configured = baselineTotal;
|
|
783
831
|
}
|
|
784
832
|
}
|
|
785
833
|
}
|
|
@@ -791,7 +839,7 @@ async function computeResults(dataDir) {
|
|
|
791
839
|
const score = entry && judgedDone > 0 ? (entry.sum / judgedDone) * 100 : null;
|
|
792
840
|
// Baseline 评测:算出**具体哪几个题号没做出来**(题号 = 该题库内的 0 起下标,
|
|
793
841
|
// 与「题目序号范围」的编号一致,方便直接复制去重跑)。
|
|
794
|
-
const missingIndexes = missingBaselineIndexes(meta, stat.
|
|
842
|
+
const missingIndexes = missingBaselineIndexes(meta, stat.okIds ?? new Set());
|
|
795
843
|
return {
|
|
796
844
|
model,
|
|
797
845
|
category,
|
|
@@ -1084,6 +1132,19 @@ function apply(ctx) {
|
|
|
1084
1132
|
// livebench import (shortuuid etc.).
|
|
1085
1133
|
PATH: `${join(layout.root, ".venv", "Scripts")}${delimiter}${process.env.PATH ?? ""}`,
|
|
1086
1134
|
};
|
|
1135
|
+
// 丢掉从 dsh web 进程继承来的虚拟环境标记。
|
|
1136
|
+
//
|
|
1137
|
+
// run_livebench.py 会读 VIRTUAL_ENV 并执行 `source <VIRTUAL_ENV>/bin/activate`:
|
|
1138
|
+
// 那是 Unix 路径 + Unix 语法,Windows 上必然失败;更糟的是它接着用写死的 ':'
|
|
1139
|
+
// 去拼 PATH,把上面刚前置好的 venv Scripts 条目粘成无效路径,于是后面裸调的
|
|
1140
|
+
// `python` 回退到系统/conda 解释器,报 `No module named 'shortuuid'`。
|
|
1141
|
+
//
|
|
1142
|
+
// 本机常见触发场景:dsh web 是从一个 conda 已激活的终端启动的(例如
|
|
1143
|
+
// VIRTUAL_ENV=D:\...\Anaconda3\envs\mineru)。面板自己已经把正确的 venv 放在
|
|
1144
|
+
// PATH 最前,不需要任何 activate,所以这里直接删掉这两个标记。
|
|
1145
|
+
delete env.VIRTUAL_ENV;
|
|
1146
|
+
delete env.CONDA_PREFIX;
|
|
1147
|
+
delete env.CONDA_DEFAULT_ENV;
|
|
1087
1148
|
// Provider routing: keys are resolved through the harness credential seam
|
|
1088
1149
|
// when available (values may live encrypted in .credentials.yaml rather
|
|
1089
1150
|
// than in the process env), falling back to the plain environment
|
|
@@ -1121,8 +1182,9 @@ function apply(ctx) {
|
|
|
1121
1182
|
if (!existsSync(metaDir)) mkdirSync(metaDir, { recursive: true });
|
|
1122
1183
|
writeFileSync(join(metaDir, displayName + ".json"), JSON.stringify({
|
|
1123
1184
|
benchNames: benchNames !== null ? benchNames : [benchParts.join("/")],
|
|
1124
|
-
|
|
1125
|
-
|
|
1185
|
+
// 空串要存成 null:否则读取端会把「没填」当成"范围 0..0"
|
|
1186
|
+
begin: normalizeRangeBound(body.begin),
|
|
1187
|
+
end: normalizeRangeBound(body.end),
|
|
1126
1188
|
// 算"本次设定题数"要用 release 过滤后的有效题数来裁剪边界,所以必须记下来
|
|
1127
1189
|
release,
|
|
1128
1190
|
// baseline 评测标出来:成绩表里同一列会混着"全量"和"题库子集"两种分数,
|
|
@@ -1477,4 +1539,4 @@ function apply(ctx) {
|
|
|
1477
1539
|
}), `${name}: delete route`);
|
|
1478
1540
|
}
|
|
1479
1541
|
|
|
1480
|
-
export { name, inject, apply, writeGeneratedModelConfig, readProviders, validateBenchName, clampedRangeLength, releaseQuestionCount, isEmptyAnswerLine, missingBaselineIndexes };
|
|
1542
|
+
export { name, inject, apply, writeGeneratedModelConfig, readProviders, validateBenchName, clampedRangeLength, releaseQuestionCount, isEmptyAnswerLine, missingBaselineIndexes, baselineIdsFor };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-livebench-panel",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.24",
|
|
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)",
|