dsh-livebench-panel 0.2.21 → 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 CHANGED
@@ -805,10 +805,14 @@ window.__ModuleLoader__.load({
805
805
  row.errors > 0 ? `${row.errors} 题 API 失败` : null,
806
806
  emptyCount > 0 ? `${emptyCount} 题思考占满 max-tokens、未产出答案` : null,
807
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
+ : "";
808
812
  const title = basePrefix + (allFailed
809
813
  ? `本次选择 ${row.configured ?? 0} 题,全部没做出来(${reasons}),不计入正确率 ${sub}`
810
814
  : `本次选择 ${row.configured ?? 0} 题,做出来 ${row.done ?? 0} 题,没做出来 ${row.notDone ?? 0} 题 ${sub}`
811
- + (reasons ? `(其中 ${reasons})` : "")); if (allFailed || row.judged === 0) {
815
+ + (reasons ? `(其中 ${reasons})` : "")) + missingText; if (allFailed || row.judged === 0) {
812
816
  return h("td", { key: taskKeyCol, className: cls, title },
813
817
  h("div", { className: c("dim") }, "—"),
814
818
  h("div", { className: c("cellSub") }, sub));
package/lib/index.js CHANGED
@@ -627,6 +627,33 @@ function isEmptyAnswerLine(line) {
627
627
  return EMPTY_ANSWER_RE.test(line);
628
628
  }
629
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
+
630
657
  async function readTextFileAsync(path) {
631
658
  const fsPromises = await import("node:fs/promises");
632
659
  try {
@@ -668,12 +695,21 @@ async function computeResults(dataDir) {
668
695
  const text = await readTextFileAsync(join(answerDir, file));
669
696
  if (text === null) continue;
670
697
  const key = `${model}\u0000${task}`;
671
- const stat = answers.get(key) ?? { answered: 0, errors: 0, empty: 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();
672
704
  for (const line of text.split("\n")) {
673
705
  if (line.trim().length === 0) continue;
674
706
  stat.answered += 1;
675
707
  if (line.includes('"$ERROR$"')) stat.errors += 1;
676
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
+ }
677
713
  }
678
714
  // 空文件(尚未产出答案,或成绩已被删除只留下 0 字节壳)不计入结果:
679
715
  // 否则删掉的记录会在下一次扫描时凭“文件名仍在”重新冒出来。
@@ -753,6 +789,9 @@ async function computeResults(dataDir) {
753
789
  // 正确率 = 做对的题 / **做出来的题**(API 失败、思考吃满 token 没产出答案的都不进分母)
754
790
  const judgedDone = Math.max(0, judgedCount - notProduced);
755
791
  const score = entry && judgedDone > 0 ? (entry.sum / judgedDone) * 100 : null;
792
+ // Baseline 评测:算出**具体哪几个题号没做出来**(题号 = 该题库内的 0 起下标,
793
+ // 与「题目序号范围」的编号一致,方便直接复制去重跑)。
794
+ const missingIndexes = missingBaselineIndexes(meta, stat.seen ?? new Set());
756
795
  return {
757
796
  model,
758
797
  category,
@@ -768,6 +807,8 @@ async function computeResults(dataDir) {
768
807
  done,
769
808
  configured,
770
809
  notDone,
810
+ // baseline 专有:没做出来的题号(0 起,相对该题库)
811
+ missingIndexes,
771
812
  // 本次运行的开始时间(ISO);旧数据没有元数据时为 null,前端退回解析 displayName 里的运行戳
772
813
  runStartedAt: meta !== undefined && typeof meta.startedAt === "string" ? meta.startedAt : null,
773
814
  // 非 null 表示这一行跑的是 Baseline 题库子集,不是该任务全量
@@ -1093,6 +1134,8 @@ function apply(ctx) {
1093
1134
  baselineTask: baselinePick !== null ? baselinePick.task : null,
1094
1135
  baselinePicks: baselinePick !== null ? baselinePick.picks : null,
1095
1136
  baselineSize: baselinePick !== null ? baselinePick.ids.length : null,
1137
+ // 实际使用的有序 id 列表:/results 靠它算出"少做了哪几题"
1138
+ baselineIds: baselinePick !== null ? baselinePick.ids : null,
1096
1139
  startedAt: new Date().toISOString(),
1097
1140
  }), "utf8");
1098
1141
  } catch { /* 元数据写入失败不影响评测 */ }
@@ -1434,4 +1477,4 @@ function apply(ctx) {
1434
1477
  }), `${name}: delete route`);
1435
1478
  }
1436
1479
 
1437
- export { name, inject, apply, writeGeneratedModelConfig, readProviders, validateBenchName, clampedRangeLength, releaseQuestionCount, isEmptyAnswerLine };
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.21",
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)",