dsh-livebench-panel 0.2.20 → 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/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: 32768, value: sel.maxTokens, onChange: setField("maxTokens") }),
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,20 @@ window.__ModuleLoader__.load({
795
795
  h("div", { className: c("dim") }, "×"));
796
796
  }
797
797
  const sub = `(-${row.notDone ?? 0}/${row.configured ?? 0})`;
798
- const allFailed = row.answered > 0 && row.errors >= row.answered;
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(",");
800
808
  const title = basePrefix + (allFailed
801
- ? `本次选择 ${row.configured ?? 0} 题,其中 ${row.errors} 题全部访问失败(未做完),不计入正确率 ${sub}`
802
- : `本次选择 ${row.configured ?? 0} 题,做完 ${row.done ?? 0} 题,没做或没做完 ${row.notDone ?? 0} 题 ${sub}` +
803
- (row.errors > 0 ? `(其中 ${row.errors} 题 API 失败)` : "")); if (allFailed || row.judged === 0) {
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) {
804
812
  return h("td", { key: taskKeyCol, className: cls, title },
805
813
  h("div", { className: c("dim") }, "—"),
806
814
  h("div", { className: c("cellSub") }, sub));
package/lib/index.js CHANGED
@@ -610,6 +610,23 @@ 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
+
613
630
  async function readTextFileAsync(path) {
614
631
  const fsPromises = await import("node:fs/promises");
615
632
  try {
@@ -651,11 +668,12 @@ async function computeResults(dataDir) {
651
668
  const text = await readTextFileAsync(join(answerDir, file));
652
669
  if (text === null) continue;
653
670
  const key = `${model}\u0000${task}`;
654
- const stat = answers.get(key) ?? { answered: 0, errors: 0 };
671
+ const stat = answers.get(key) ?? { answered: 0, errors: 0, empty: 0 };
655
672
  for (const line of text.split("\n")) {
656
673
  if (line.trim().length === 0) continue;
657
674
  stat.answered += 1;
658
675
  if (line.includes('"$ERROR$"')) stat.errors += 1;
676
+ else if (isEmptyAnswerLine(line)) stat.empty += 1;
659
677
  }
660
678
  // 空文件(尚未产出答案,或成绩已被删除只留下 0 字节壳)不计入结果:
661
679
  // 否则删掉的记录会在下一次扫描时凭“文件名仍在”重新冒出来。
@@ -691,8 +709,13 @@ async function computeResults(dataDir) {
691
709
  const category = entry?.category ?? catByTask.get(task) ?? "";
692
710
  const total = totals.get(task) ?? 0;
693
711
  const judgedCount = entry?.n ?? 0;
694
- // 做完的题 = 实际产出的有效答案(尝试数 - 访问失败数)
695
- const done = Math.max(0, stat.answered - stat.errors);
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);
696
719
  // 选择题数 = 用户在「题目序号范围」里选择的题数(end 含端点)。
697
720
  // 有元数据就直接用范围大小;没有元数据(历史数据)时用本次实际写入的答案行数兜底:
698
721
  // LiveBench 会为范围内每一题写一行(访问失败也写 $ERROR$),所以跑完后
@@ -727,8 +750,8 @@ async function computeResults(dataDir) {
727
750
  // 实际尝试数比设定还多(断点重跑等)时,以实际为准
728
751
  if (configured < stat.answered) configured = stat.answered;
729
752
  const notDone = Math.max(0, configured - done);
730
- // 正确率 = 做对的题 / 已判分的做完题(访问失败/未做的题不计入分母)
731
- const judgedDone = Math.max(0, judgedCount - stat.errors);
753
+ // 正确率 = 做对的题 / **做出来的题**(API 失败、思考吃满 token 没产出答案的都不进分母)
754
+ const judgedDone = Math.max(0, judgedCount - notProduced);
732
755
  const score = entry && judgedDone > 0 ? (entry.sum / judgedDone) * 100 : null;
733
756
  return {
734
757
  model,
@@ -740,6 +763,8 @@ async function computeResults(dataDir) {
740
763
  time: entry && entry.time > 0 ? entry.time : null,
741
764
  answered: stat.answered,
742
765
  errors: stat.errors,
766
+ empty,
767
+ notProduced,
743
768
  done,
744
769
  configured,
745
770
  notDone,
@@ -974,7 +999,10 @@ function apply(ctx) {
974
999
  "--bench-name",
975
1000
  ...(benchNames !== null ? benchNames : [benchParts.join("/")]),
976
1001
  "--livebench-release-option", release,
977
- "--max-tokens", String(asInt(body.maxTokens, 256, 32768, 32000)),
1002
+ // 上限放到 200k:推理模型在难题上会把 max_tokens 全烧在思考里,正文为空
1003
+ // (eval_status=token_exhaustion),此时"做不出来"其实是预算不够而不是能力不够。
1004
+ // 旧上限 32768 在 olympiad 这类题上会直接把参考模型卡死。
1005
+ "--max-tokens", String(asInt(body.maxTokens, 256, 200000, 32000)),
978
1006
  "--parallel-requests", String(asInt(body.parallel, 1, 8, 1)),
979
1007
  // 流式:中转网关(Cloudflare 等)对非流式请求有 ~100s 超时(524),
980
1008
  // 高推理强度模型思考数分钟必然超时。流式保持字节流动可规避。
@@ -1406,4 +1434,4 @@ function apply(ctx) {
1406
1434
  }), `${name}: delete route`);
1407
1435
  }
1408
1436
 
1409
- export { name, inject, apply, writeGeneratedModelConfig, readProviders, validateBenchName, clampedRangeLength, releaseQuestionCount };
1437
+ export { name, inject, apply, writeGeneratedModelConfig, readProviders, validateBenchName, clampedRangeLength, releaseQuestionCount, isEmptyAnswerLine };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-livebench-panel",
3
- "version": "0.2.20",
3
+ "version": "0.2.21",
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)",