dsh-livebench-panel 0.2.22 → 0.2.23
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 +71 -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,
|
|
@@ -1121,8 +1169,9 @@ function apply(ctx) {
|
|
|
1121
1169
|
if (!existsSync(metaDir)) mkdirSync(metaDir, { recursive: true });
|
|
1122
1170
|
writeFileSync(join(metaDir, displayName + ".json"), JSON.stringify({
|
|
1123
1171
|
benchNames: benchNames !== null ? benchNames : [benchParts.join("/")],
|
|
1124
|
-
|
|
1125
|
-
|
|
1172
|
+
// 空串要存成 null:否则读取端会把「没填」当成"范围 0..0"
|
|
1173
|
+
begin: normalizeRangeBound(body.begin),
|
|
1174
|
+
end: normalizeRangeBound(body.end),
|
|
1126
1175
|
// 算"本次设定题数"要用 release 过滤后的有效题数来裁剪边界,所以必须记下来
|
|
1127
1176
|
release,
|
|
1128
1177
|
// baseline 评测标出来:成绩表里同一列会混着"全量"和"题库子集"两种分数,
|
|
@@ -1477,4 +1526,4 @@ function apply(ctx) {
|
|
|
1477
1526
|
}), `${name}: delete route`);
|
|
1478
1527
|
}
|
|
1479
1528
|
|
|
1480
|
-
export { name, inject, apply, writeGeneratedModelConfig, readProviders, validateBenchName, clampedRangeLength, releaseQuestionCount, isEmptyAnswerLine, missingBaselineIndexes };
|
|
1529
|
+
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.23",
|
|
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)",
|