dsh-livebench-panel 0.2.11 → 0.2.12

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.
Files changed (3) hide show
  1. package/lib/client.js +22 -15
  2. package/lib/index.js +76 -10
  3. package/package.json +1 -1
package/lib/client.js CHANGED
@@ -587,21 +587,28 @@ window.__ModuleLoader__.load({
587
587
  h("td", null, model),
588
588
  matrix.tasks.map((taskKeyCol) => {
589
589
  const row = matrix.cells.get(`${model}__@__${taskKeyCol}`);
590
- let cellText = "×";
591
- let title = "该模型未执行此任务";
592
- if (row) {
593
- if (row.answered > 0 && row.errors >= row.answered) {
594
- cellText = `E (${row.errors})`;
595
- title = "该任务全部回答均 API 失败($ERROR$),计 0 ";
596
- } else if (row.judged === 0) {
597
- cellText = "未判分";
598
- title = "有答案但尚未判分";
599
- } else {
600
- cellText = `${row.score.toFixed(1)} (${row.judged}/${row.total})`;
601
- if (row.errors > 0) title += ` · 其中 ${row.errors} 题 API 失败计 0 分`;
602
- }
590
+ // 单元格:上方正确率,下方 (-没做/设定/总)。
591
+ // 没做/没做完(访问失败、网络中断)不计入正确率分母。
592
+ const subStyle = { fontSize: "10.5px", color: "var(--dsw-alias-label-tertiary)", fontWeight: "400" };
593
+ const sub = `(-${row ? (row.notDone ?? 0) : 0}/${row ? (row.configured ?? 0) : 0}/${row ? row.total : 0})`;
594
+ if (!row) {
595
+ return h("td", { key: taskKeyCol, className: c("score"), title: "该模型未执行此任务(无任何记录)" },
596
+ h("div", null, "×"),
597
+ h("div", { style: subStyle }, sub));
603
598
  }
604
- return h("td", { key: taskKeyCol, className: c("score"), title }, cellText);
599
+ const allFailed = row.answered > 0 && row.errors >= row.answered;
600
+ const title = allFailed
601
+ ? `该任务 ${row.errors} 题全部访问失败(未做完),不计入正确率`
602
+ : `做完 ${row.done ?? 0} 题,没做/失败 ${row.notDone ?? 0} 题` +
603
+ (row.errors > 0 ? `(其中 ${row.errors} 题 API 失败)` : "");
604
+ if (allFailed || row.judged === 0) {
605
+ return h("td", { key: taskKeyCol, className: c("score"), title },
606
+ h("div", null, "—"),
607
+ h("div", { style: subStyle }, sub));
608
+ }
609
+ return h("td", { key: taskKeyCol, className: c("score"), title },
610
+ h("div", null, row.score.toFixed(1)),
611
+ h("div", { style: subStyle }, sub));
605
612
  }),
606
613
  h("td", null, h("button", {
607
614
  className: c("btnGhost") + " " + c("btn"), title: "删除该模型的所有评测成绩",
@@ -658,7 +665,7 @@ window.__ModuleLoader__.load({
658
665
  "提示:回答为 $ERROR$ 表示该题 API 调用失败(网络/鉴权/参数问题)计 0 分,可在上方日志区查看具体错误;zebra_puzzle(逻辑谜题)是公认最难的任务,低分属正常现象。"),
659
666
  ),
660
667
  h("p", { className: c("hint") },
661
- "评分读取 LiveBench ground_truth_judgment.jsonl;正式榜单可用 release 2024-11-25。多选题集请在「分类/任务」中缩小范围,避免长时间运行。"),
668
+ "格式说明:单元格上方为正确率(= 做对 / 做完),下方括号 (-没做/设定/总):没做或没做完的题(访问失败、网络中断、未跑到)不计入正确率。× 表示该模型完全没跑过该任务。正式榜单可用 release 2024-11-25"),
662
669
  );
663
670
  }
664
671
  /**
package/lib/index.js CHANGED
@@ -307,6 +307,22 @@ function readDirSafe(dir) {
307
307
  }
308
308
  }
309
309
 
310
+ /** 列出目录下的【文件】(可按扩展名过滤)——注意 readDirSafe 只列目录。 */
311
+ function readFilesSafe(dir, ext) {
312
+ try {
313
+ return readdirSync(dir).filter((f) => {
314
+ try {
315
+ if (!statSync(join(dir, f)).isFile()) return false;
316
+ return ext === undefined || f.endsWith(ext);
317
+ } catch {
318
+ return false;
319
+ }
320
+ });
321
+ } catch {
322
+ return [];
323
+ }
324
+ }
325
+
310
326
  function readdirNames(dir) {
311
327
  try {
312
328
  return readdirSync(dir).filter((entry) => {
@@ -506,11 +522,22 @@ async function readTextFileAsync(path) {
506
522
  async function computeResults(dataDir) {
507
523
  const now = Date.now();
508
524
  if (resultsCache.data !== null && now - resultsCache.at < 5000) return resultsCache.data;
509
- const judged = new Map(); // `${model}${task}` -> {model, category, task, sum, n, time}
525
+ const judged = new Map(); // `${model}\u0000${task}` -> {model, category, task, sum, n, time}
510
526
  const totals = new Map(); // task -> question count
511
- const answers = new Map(); // `${model}${task}` -> {answered, errors}
527
+ const answers = new Map(); // `${model}\u0000${task}` -> {answered, errors}
512
528
  const catByTask = new Map();
513
529
  if (!existsSync(dataDir)) return { rows: [], taskTotals: {} };
530
+ // 读取每次评测的配置范围(displayName -> {benchNames, begin, end})
531
+ const runMeta = new Map();
532
+ try {
533
+ const metaDir = join(dataDir, ".dsh_runs");
534
+ for (const f of readdirSync(metaDir)) {
535
+ if (!f.endsWith(".json")) continue;
536
+ try {
537
+ runMeta.set(f.slice(0, -5), JSON.parse(readFileSync(join(metaDir, f), "utf8")));
538
+ } catch { /* 单个元数据损坏忽略 */ }
539
+ }
540
+ } catch { /* 无元数据目录(历史数据) */ }
514
541
  for (const category of readDirSafe(dataDir)) {
515
542
  const catDir = join(dataDir, category);
516
543
  for (const task of readDirSafe(catDir)) {
@@ -519,12 +546,11 @@ async function computeResults(dataDir) {
519
546
  const qText = await readTextFileAsync(join(taskDir, "question.jsonl"));
520
547
  totals.set(task, qText === null ? 0 : qText.split("\n").reduce((n, l) => n + (l.trim().length > 0 ? 1 : 0), 0));
521
548
  const answerDir = join(taskDir, "model_answer");
522
- for (const file of readDirSafe(answerDir)) {
523
- if (!file.endsWith(".jsonl")) continue;
549
+ for (const file of readFilesSafe(answerDir, ".jsonl")) {
524
550
  const model = file.slice(0, -".jsonl".length);
525
551
  const text = await readTextFileAsync(join(answerDir, file));
526
552
  if (text === null) continue;
527
- const key = `${model}${task}`;
553
+ const key = `${model}\u0000${task}`;
528
554
  const stat = answers.get(key) ?? { answered: 0, errors: 0 };
529
555
  for (const line of text.split("\n")) {
530
556
  if (line.trim().length === 0) continue;
@@ -542,7 +568,7 @@ async function computeResults(dataDir) {
542
568
  const model = typeof row.model === "string" ? row.model : null;
543
569
  const score = typeof row.score === "number" ? row.score : Number(row.score);
544
570
  if (model === null || !Number.isFinite(score) || score < 0) continue;
545
- const key = `${model}${task}`;
571
+ const key = `${model}\u0000${task}`;
546
572
  const entry = judged.get(key) ?? { model, category, task, sum: 0, n: 0, time: 0 };
547
573
  entry.sum += score;
548
574
  entry.n += 1;
@@ -559,18 +585,45 @@ async function computeResults(dataDir) {
559
585
  const rows = [...keys].map((key) => {
560
586
  const entry = judged.get(key);
561
587
  const stat = answers.get(key) ?? { answered: 0, errors: 0 };
562
- const [model, task] = key.split("");
588
+ const [model, task] = key.split("\u0000");
563
589
  const category = entry?.category ?? catByTask.get(task) ?? "";
590
+ const total = totals.get(task) ?? 0;
591
+ const judgedCount = entry?.n ?? 0;
592
+ // 做完的题 = 实际产出的有效答案(尝试数 - 访问失败数)
593
+ const done = Math.max(0, stat.answered - stat.errors);
594
+ // 设定题数:优先用评测元数据的范围(end 含端点),否则用尝试/判分中的较大值
595
+ let configured = Math.max(stat.answered, judgedCount);
596
+ const meta = runMeta.get(model);
597
+ if (meta !== undefined) {
598
+ const inScope = (meta.benchNames || []).some((bn) => {
599
+ const parts = String(bn).split("/").filter((s) => s.length > 0);
600
+ if (parts.length >= 3) return parts[1] === category && parts[2] === task;
601
+ if (parts.length === 2) return parts[1] === category;
602
+ return true;
603
+ });
604
+ if (inScope) {
605
+ const hasRange = meta.begin !== null && meta.begin !== undefined && meta.end !== null && meta.end !== undefined;
606
+ configured = hasRange ? Math.min(total, Number(meta.end) - Number(meta.begin) + 1) : total;
607
+ }
608
+ }
609
+ if (configured < stat.answered) configured = stat.answered;
610
+ const notDone = Math.max(0, configured - done);
611
+ // 正确率 = 做对的题 / 已判分的做完题(访问失败/未做的题不计入分母)
612
+ const judgedDone = Math.max(0, judgedCount - stat.errors);
613
+ const score = entry && judgedDone > 0 ? (entry.sum / judgedDone) * 100 : null;
564
614
  return {
565
615
  model,
566
616
  category,
567
617
  task,
568
- score: entry && entry.n > 0 ? (entry.sum / entry.n) * 100 : null,
618
+ score,
569
619
  judged: entry?.n ?? 0,
570
- total: totals.get(task) ?? 0,
620
+ total,
571
621
  time: entry && entry.time > 0 ? entry.time : null,
572
622
  answered: stat.answered,
573
623
  errors: stat.errors,
624
+ done,
625
+ configured,
626
+ notDone,
574
627
  };
575
628
  });
576
629
  const result = { rows, taskTotals: Object.fromEntries(totals) };
@@ -808,6 +861,19 @@ function apply(ctx) {
808
861
  }
809
862
  }
810
863
 
864
+ // 记录本次评测的配置范围:/results 用它计算每个任务的"设定题数"
865
+ // (未做/失败的题数 = 设定题数 - 实际产出的有效答案数)。
866
+ try {
867
+ const metaDir = join(layout.dataDir, ".dsh_runs");
868
+ if (!existsSync(metaDir)) mkdirSync(metaDir, { recursive: true });
869
+ writeFileSync(join(metaDir, displayName + ".json"), JSON.stringify({
870
+ benchNames: benchNames !== null ? benchNames : [benchParts.join("/")],
871
+ begin: body.begin ?? null,
872
+ end: body.end ?? null,
873
+ startedAt: new Date().toISOString(),
874
+ }), "utf8");
875
+ } catch { /* 元数据写入失败不影响评测 */ }
876
+
811
877
  const child = spawn(layout.pythonExe, args, {
812
878
  cwd: layout.livebenchDir,
813
879
  env,
@@ -865,7 +931,7 @@ function apply(ctx) {
865
931
  startRun(retryBody, record.autoRetries + 1).catch((e) => {
866
932
  appendLog(record, `[自动补跑启动失败] ${e.message}`);
867
933
  });
868
- }, 15000);
934
+ }, [15000, 60000, 180000][Math.min(record.autoRetries, 2)]);
869
935
  }
870
936
  } catch (e) {
871
937
  record.log.push(`[自动补跑检查失败] ${e.message}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-livebench-panel",
3
- "version": "0.2.11",
3
+ "version": "0.2.12",
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.",
5
5
  "license": "MIT",
6
6
  "type": "module",