dsh-livebench-panel 0.1.2 → 0.1.4

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
@@ -151,8 +151,21 @@ window.__ModuleLoader__.load({
151
151
  const categories = useMemo(() => (config ? Object.keys(config.tasks) : []), [config]);
152
152
  const taskList = useMemo(() => {
153
153
  if (!config || sel.category.length === 0) return [];
154
- return config.tasks[sel.category] ?? [];
154
+ return Object.keys(config.tasks[sel.category] ?? {});
155
155
  }, [config, sel.category]);
156
+ // 有效题数:LiveBench 会丢弃「发布晚于所选 release」和「在所选 release
157
+ // 前已退役」的题目,这里按题目桶 (发布日, 移除日) 精确复算。
158
+ const countFor = useCallback((category, task) => {
159
+ if (!config || category.length === 0) return null;
160
+ const meta = (config.tasks[category] ?? {})[task];
161
+ if (!meta || !Array.isArray(meta.buckets)) return null;
162
+ const option = sel.release;
163
+ return meta.buckets.reduce((sum, b) => {
164
+ const releasedOk = b.r !== "" && b.r <= option;
165
+ const notRemoved = b.rm === "" || b.rm > option;
166
+ return sum + (releasedOk && notRemoved ? b.n : 0);
167
+ }, 0);
168
+ }, [config, sel.release]);
156
169
  const selectedProvider = useMemo(
157
170
  () => (config ? config.providers.find((p) => p.id === sel.provider) ?? null : null),
158
171
  [config, sel.provider],
@@ -168,6 +181,10 @@ window.__ModuleLoader__.load({
168
181
 
169
182
  const onStart = async () => {
170
183
  setStartError(null);
184
+ if (sel.category !== "" && sel.task !== "" && countFor(sel.category, sel.task) === 0) {
185
+ setStartError(`任务 ${sel.task} 在 release ${sel.release} 下没有可用题目(该批题目已退役)。请换一个 release 或任务。`);
186
+ return;
187
+ }
171
188
  setBusy(true);
172
189
  try {
173
190
  const payload = await api("/start", {
@@ -263,7 +280,11 @@ window.__ModuleLoader__.load({
263
280
  h("label", { className: c("label") }, "任务"),
264
281
  h("select", { className: c("select"), value: sel.task, onChange: setField("task") },
265
282
  h("option", { value: "" }, sel.category === "" ? "(先选分类)" : "全部任务"),
266
- taskList.map((task) => h("option", { key: task, value: task }, task))),
283
+ taskList.map((task) => {
284
+ const n = countFor(sel.category, task);
285
+ const label = n === null ? task : `${task}(${n} 题)`;
286
+ return h("option", { key: task, value: task, disabled: n === 0 }, n === 0 ? `${task}(此release无题)` : label);
287
+ })),
267
288
  ),
268
289
  h("div", { className: c("field") },
269
290
  h("label", { className: c("label") }, "题目序号范围(可选)"),
package/lib/index.js CHANGED
@@ -227,18 +227,25 @@ function livebenchLayout() {
227
227
  function scanCategoryTasks(dataDir) {
228
228
  const tasks = {};
229
229
  if (!existsSync(dataDir)) return tasks;
230
- let categoryDirs;
231
- try {
232
- categoryDirs = readDirSafe(dataDir);
233
- } catch {
234
- return tasks;
235
- }
230
+ const categoryDirs = readDirSafe(dataDir);
236
231
  for (const category of categoryDirs) {
237
232
  const catDir = join(dataDir, category);
238
233
  for (const task of readDirSafe(catDir)) {
239
- if (existsSync(join(catDir, task, "question.jsonl"))) {
240
- (tasks[category] ??= []).push(task);
234
+ const questionFile = join(catDir, task, "question.jsonl");
235
+ if (!existsSync(questionFile)) continue;
236
+ // Bucket questions by (release date, removal date) so the UI can
237
+ // compute how many questions survive a chosen release option:
238
+ // LiveBench drops questions released after the option and questions
239
+ // removed at or before the option.
240
+ const buckets = [];
241
+ for (const q of readJsonl(questionFile)) {
242
+ const r = typeof q.livebench_release_date === "string" ? q.livebench_release_date : "";
243
+ const rm = typeof q.livebench_removal_date === "string" ? q.livebench_removal_date : "";
244
+ const bucket = buckets.find((b) => b.r === r && b.rm === rm);
245
+ if (bucket) bucket.n += 1;
246
+ else buckets.push({ r, rm, n: 1 });
241
247
  }
248
+ ((tasks[category] ??= {})[task] = { buckets, total: buckets.reduce((sum, b) => sum + b.n, 0) });
242
249
  }
243
250
  }
244
251
  return tasks;
@@ -403,7 +410,7 @@ function apply(ctx) {
403
410
  /** The single run slot. */
404
411
  let run = null;
405
412
 
406
- const startRun = (body) => {
413
+ const startRun = async (body) => {
407
414
  const layout = livebenchLayout();
408
415
  if (!layout.available) {
409
416
  return { status: 409, payload: { ok: false, error: `LiveBench venv not found under ${layout.root}` } };
@@ -473,11 +480,23 @@ function apply(ctx) {
473
480
  PATH: `${join(layout.root, ".venv", "Scripts")}${delimiter}${process.env.PATH ?? ""}`,
474
481
  };
475
482
  // Only OpenAI-compatible providers can be routed with --api-base; for
476
- // those, hand the key over via env (never the command line).
483
+ // those, hand the key over via env (never the command line). The key is
484
+ // resolved through the harness credential seam when available (values may
485
+ // live encrypted in .credentials.yaml rather than in the process env),
486
+ // falling back to the plain environment variable.
477
487
  if (provider && provider.api === "openai-completions" && provider.baseURL) {
478
488
  args.push("--api-base", provider.baseURL);
479
- if (provider.keyEnv && process.env[provider.keyEnv]) {
480
- env.LIVEBENCH_API_KEY = process.env[provider.keyEnv];
489
+ if (provider.keyEnv) {
490
+ let key;
491
+ try {
492
+ const credentials = ctx.get ? ctx.get("credentials") : undefined;
493
+ if (credentials && typeof credentials.resolve === "function") {
494
+ const resolved = await credentials.resolve(provider.keyEnv);
495
+ if (resolved && typeof resolved.value === "string" && resolved.value.length > 0) key = resolved.value;
496
+ }
497
+ } catch { /* credential seam unavailable — fall through to env */ }
498
+ if (!key && process.env[provider.keyEnv]) key = process.env[provider.keyEnv];
499
+ if (key) env.LIVEBENCH_API_KEY = key;
481
500
  }
482
501
  }
483
502
 
@@ -581,7 +600,7 @@ function apply(ctx) {
581
600
  sendJson(res, 409, { ok: false, error: "another evaluation is already running" });
582
601
  return;
583
602
  }
584
- const result = startRun(body);
603
+ const result = await startRun(body);
585
604
  sendJson(res, result.status, result.payload);
586
605
  },
587
606
  }), `${name}: start route`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-livebench-panel",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
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",