dsh-livebench-panel 0.1.5 → 0.1.6

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 +156 -41
  2. package/lib/index.js +95 -7
  3. package/package.json +1 -1
package/lib/client.js CHANGED
@@ -45,7 +45,12 @@ window.__ModuleLoader__.load({
45
45
  .dlb_helpTable th,.dlb_helpTable td{text-align:left;vertical-align:top;padding:5px 8px;border-bottom:1px solid var(--dsw-alias-border-l2)}
46
46
  .dlb_helpTable th{color:var(--dsw-alias-label-tertiary);font-weight:500;white-space:nowrap}
47
47
  .dlb_helpTable td{color:var(--dsw-alias-label-secondary);line-height:1.55}
48
- .dlb_helpP{margin:0 0 8px;font-size:12px;color:var(--dsw-alias-label-secondary);line-height:1.6}`;
48
+ .dlb_helpP{margin:0 0 8px;font-size:12px;color:var(--dsw-alias-label-secondary);line-height:1.6}
49
+ .dlb_chips{display:flex;flex-wrap:wrap;gap:6px}
50
+ .dlb_chip{appearance:none;font:inherit;cursor:pointer;color:var(--dsw-alias-label-secondary);background:transparent;border:1px solid var(--dsw-alias-border-l2);border-radius:999px;padding:3px 12px;font-size:12px;line-height:18px}
51
+ .dlb_chip:disabled{opacity:.4;cursor:not-allowed}
52
+ .dlb_chipOn{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-state-business-primary);background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 14%,transparent)}
53
+ .dlb_drag{cursor:grab;color:var(--dsw-alias-label-tertiary);text-align:center;user-select:none}`;
49
54
  const tagId = "dsh-livebench-panel/panel.css";
50
55
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
51
56
  const tag = document.createElement("style");
@@ -87,13 +92,18 @@ window.__ModuleLoader__.load({
87
92
  function LiveBenchView() {
88
93
  const [config, setConfig] = useState(null);
89
94
  const [configError, setConfigError] = useState(null);
90
- const [sel, setSel] = useState({ provider: "", model: "", reasoning: "default", category: "", task: "", release: "2024-11-25", begin: "", end: "", maxTokens: "32000" });
95
+ const [sel, setSel] = useState({ provider: "", model: "", reasoning: "default", cats: [], tasks: [], release: "2024-11-25", begin: "", end: "", maxTokens: "32000" });
91
96
  const [busy, setBusy] = useState(false);
92
97
  const [running, setRunning] = useState(false);
93
98
  const [log, setLog] = useState("");
94
99
  const [exitCode, setExitCode] = useState(null);
95
100
  const [startError, setStartError] = useState(null);
96
101
  const [results, setResults] = useState(null);
102
+ const [pickedRows, setPickedRows] = useState(() => new Set());
103
+ const [order, setOrder] = useState(() => {
104
+ try { return JSON.parse(localStorage.getItem("dlb_result_order_v1")) ?? []; } catch { return []; }
105
+ });
106
+ const dragKey = useRef(null);
97
107
  const logRef = useRef(null);
98
108
 
99
109
  const loadConfig = useCallback(async () => {
@@ -158,10 +168,6 @@ window.__ModuleLoader__.load({
158
168
  }, [log]);
159
169
 
160
170
  const categories = useMemo(() => (config ? Object.keys(config.tasks) : []), [config]);
161
- const taskList = useMemo(() => {
162
- if (!config || sel.category.length === 0) return [];
163
- return Object.keys(config.tasks[sel.category] ?? {});
164
- }, [config, sel.category]);
165
171
  // 有效题数:LiveBench 会丢弃「发布晚于所选 release」和「在所选 release
166
172
  // 前已退役」的题目,这里按题目桶 (发布日, 移除日) 精确复算。
167
173
  const countFor = useCallback((category, task) => {
@@ -175,6 +181,20 @@ window.__ModuleLoader__.load({
175
181
  return sum + (releasedOk && notRemoved ? b.n : 0);
176
182
  }, 0);
177
183
  }, [config, sel.release]);
184
+ const catCount = useCallback((category) => {
185
+ if (!config) return 0;
186
+ return Object.keys(config.tasks[category] ?? {}).reduce((sum, task) => sum + (countFor(category, task) ?? 0), 0);
187
+ }, [config, countFor]);
188
+ // 多选任务 chips:未选分类时展示全部分类的任务
189
+ const taskChips = useMemo(() => {
190
+ const cats = sel.cats.length > 0 ? sel.cats : categories;
191
+ return cats.flatMap((cat) => Object.keys(config?.tasks[cat] ?? {}).map((task) => `${cat}/${task}`));
192
+ }, [sel.cats, categories, config]);
193
+ const toggleList = (key, value) => setSel((prev) => {
194
+ const list = prev[key];
195
+ const next = list.includes(value) ? list.filter((v) => v !== value) : [...list, value];
196
+ return { ...prev, [key]: next };
197
+ });
178
198
  const selectedProvider = useMemo(
179
199
  () => (config ? config.providers.find((p) => p.id === sel.provider) ?? null : null),
180
200
  [config, sel.provider],
@@ -190,8 +210,17 @@ window.__ModuleLoader__.load({
190
210
 
191
211
  const onStart = async () => {
192
212
  setStartError(null);
193
- if (sel.category !== "" && sel.task !== "" && countFor(sel.category, sel.task) === 0) {
194
- setStartError(`任务 ${sel.task} release ${sel.release} 下没有可用题目(该批题目已退役)。请换一个 release 或任务。`);
213
+ const benchNames = [];
214
+ if (sel.cats.length === 0) {
215
+ benchNames.push("live_bench");
216
+ } else if (sel.tasks.length === 0) {
217
+ for (const cat of sel.cats) benchNames.push(`live_bench/${cat}`);
218
+ } else {
219
+ for (const t of sel.tasks) benchNames.push(`live_bench/${t}`);
220
+ }
221
+ const zeroTask = sel.tasks.find((t) => countFor(t.split("/")[0], t.split("/")[1]) === 0);
222
+ if (zeroTask) {
223
+ setStartError(`任务 ${zeroTask.split("/")[1]} 在 release ${sel.release} 下没有可用题目(该批题目已退役),请取消勾选或换 release。`);
195
224
  return;
196
225
  }
197
226
  setBusy(true);
@@ -203,8 +232,7 @@ window.__ModuleLoader__.load({
203
232
  provider: sel.provider,
204
233
  model: sel.model,
205
234
  reasoningEffort: sel.reasoning,
206
- category: sel.category,
207
- task: sel.task,
235
+ benchNames,
208
236
  release: sel.release,
209
237
  begin: sel.begin,
210
238
  end: sel.end,
@@ -233,22 +261,65 @@ window.__ModuleLoader__.load({
233
261
  const provider = config?.providers.find((p) => p.id === value) ?? null;
234
262
  next.model = provider?.models[0]?.id ?? "";
235
263
  }
236
- if (key === "category") next.task = "";
237
264
  return next;
238
265
  });
239
266
  };
240
267
 
241
- // results table: pivot rows -> model x task
242
- const resultTable = useMemo(() => {
243
- if (!results || results.rows.length === 0) return null;
244
- const models = [...new Set(results.rows.map((r) => r.model))].sort();
245
- const tasks = [...new Set(results.rows.map((r) => r.task))].sort();
246
- const cell = (model, task) => {
247
- const row = results.rows.find((r) => r.model === model && r.task === task);
248
- return row ? `${row.score.toFixed(1)} (${row.judged}/${row.total})` : "—";
249
- };
250
- return { models, tasks, cell };
251
- }, [results]);
268
+ // ---- 成绩行:排序(拖拽,localStorage 持久化)/ 选择 / 删除 ----
269
+ const rowKey = (row) => `${row.model}|${row.category}|${row.task}`;
270
+ const sortedRows = useMemo(() => {
271
+ if (!results) return [];
272
+ const indexOf = new Map(order.map((key, index) => [key, index]));
273
+ return [...results.rows].sort((a, b) => {
274
+ const ia = indexOf.has(rowKey(a)) ? indexOf.get(rowKey(a)) : Number.MAX_SAFE_INTEGER;
275
+ const ib = indexOf.has(rowKey(b)) ? indexOf.get(rowKey(b)) : Number.MAX_SAFE_INTEGER;
276
+ if (ia !== ib) return ia - ib;
277
+ return (b.time ?? 0) - (a.time ?? 0);
278
+ });
279
+ }, [results, order]);
280
+
281
+ const persistOrder = (keys) => {
282
+ setOrder(keys);
283
+ try { localStorage.setItem("dlb_result_order_v1", JSON.stringify(keys)); } catch { /* private mode */ }
284
+ };
285
+
286
+ const onRowDrop = (targetKey) => {
287
+ const sourceKey = dragKey.current;
288
+ dragKey.current = null;
289
+ if (!sourceKey || sourceKey === targetKey) return;
290
+ const keys = sortedRows.map(rowKey);
291
+ const from = keys.indexOf(sourceKey);
292
+ const to = keys.indexOf(targetKey);
293
+ if (from < 0 || to < 0) return;
294
+ keys.splice(to, 0, keys.splice(from, 1)[0]);
295
+ persistOrder(keys);
296
+ };
297
+
298
+ const togglePicked = (key) => setPickedRows((prev) => {
299
+ const next = new Set(prev);
300
+ if (next.has(key)) next.delete(key); else next.add(key);
301
+ return next;
302
+ });
303
+
304
+ const deleteRows = async (rows) => {
305
+ if (rows.length === 0) return;
306
+ if (!window.confirm(`确认删除 ${rows.length} 条评测成绩?将同时删除对应的模型答案记录,不可恢复。`)) return;
307
+ await api("/delete", {
308
+ method: "POST",
309
+ headers: { "content-type": "application/json" },
310
+ body: JSON.stringify({ rows: rows.map((r) => ({ model: r.model, category: r.category, task: r.task })) }),
311
+ });
312
+ setPickedRows(new Set());
313
+ persistOrder([...order].filter((key) => !rows.some((r) => rowKey(r) === key)));
314
+ await loadResults();
315
+ };
316
+
317
+ const fmtTime = (t) => {
318
+ if (!t) return "—";
319
+ const dt = new Date(t * 1000);
320
+ const pad = (n) => String(n).padStart(2, "0");
321
+ return `${dt.getFullYear()}-${pad(dt.getMonth() + 1)}-${pad(dt.getDate())} ${pad(dt.getHours())}:${pad(dt.getMinutes())}`;
322
+ };
252
323
 
253
324
  return h("div", { className: c("root") },
254
325
  h("div", { className: c("head") },
@@ -279,20 +350,30 @@ window.__ModuleLoader__.load({
279
350
  h("select", { className: c("select"), value: sel.release, onChange: setField("release") },
280
351
  (config?.releases ?? ["2024-11-25"]).map((r) => h("option", { key: r, value: r }, r))),
281
352
  ),
282
- h("div", { className: c("field") },
283
- h("label", { className: c("label") }, "分类"),
284
- h("select", { className: c("select"), value: sel.category, onChange: setField("category") },
285
- h("option", { value: "" }, "全部分类"),
286
- categories.map((cat) => h("option", { key: cat, value: cat }, cat))),
353
+ h("div", { className: c("field"), style: { gridColumn: "1 / -1" } },
354
+ h("label", { className: c("label") }, "分类(可多选,不选 = 全部分类;括号内为该 release 下有效题数)"),
355
+ h("div", { className: c("chips") },
356
+ categories.map((cat) => {
357
+ const n = catCount(cat);
358
+ const active = sel.cats.includes(cat);
359
+ return h("button", {
360
+ key: cat, type: "button", className: c("chip") + (active ? " " + c("chipOn") : ""),
361
+ onClick: () => toggleList("cats", cat), disabled: n === 0,
362
+ }, `${cat}(${n})`);
363
+ })),
287
364
  ),
288
- h("div", { className: c("field") },
289
- h("label", { className: c("label") }, "任务"),
290
- h("select", { className: c("select"), value: sel.task, onChange: setField("task") },
291
- h("option", { value: "" }, sel.category === "" ? "(先选分类)" : "全部任务"),
292
- taskList.map((task) => {
293
- const n = countFor(sel.category, task);
294
- const label = n === null ? task : `${task}(${n} 题)`;
295
- return h("option", { key: task, value: task, disabled: n === 0 }, n === 0 ? `${task}(此release无题)` : label);
365
+ sel.cats.length > 0 && h("div", { className: c("field"), style: { gridColumn: "1 / -1" } },
366
+ h("label", { className: c("label") }, "任务(可多选,不选 = 所选分类的全部任务)"),
367
+ h("div", { className: c("chips") },
368
+ taskChips.map((key) => {
369
+ const [cat, task] = key.split("/");
370
+ const n = countFor(cat, task) ?? 0;
371
+ const active = sel.tasks.includes(key);
372
+ return h("button", {
373
+ key, type: "button", className: c("chip") + (active ? " " + c("chipOn") : ""),
374
+ onClick: () => toggleList("tasks", key), disabled: n === 0,
375
+ title: n === 0 ? "该任务在此 release 下已无可用题目" : `${n} 题有效`,
376
+ }, `${task}(${n})`);
296
377
  })),
297
378
  ),
298
379
  h("div", { className: c("field") },
@@ -324,18 +405,52 @@ window.__ModuleLoader__.load({
324
405
  ),
325
406
  h("pre", { className: c("log"), ref: logRef }, log || "(暂无输出)"),
326
407
  ),
327
- resultTable !== null && h("div", { className: c("card") },
408
+ sortedRows.length > 0 && h("div", { className: c("card") },
328
409
  h("div", { className: c("row") },
329
- h("span", { className: c("label") }, "评测成绩(分数 = 平均分 ×100,括号内为 已判/总题数)"),
410
+ h("span", { className: c("label") }, "评测成绩(分数 = 平均分 ×100;行首可拖动排序,顺序保存在本机)"),
411
+ h("button", {
412
+ className: c("btn") + " " + c("btnDanger"),
413
+ disabled: pickedRows.size === 0,
414
+ onClick: () => deleteRows(sortedRows.filter((r) => pickedRows.has(rowKey(r)))),
415
+ }, `删除所选(${pickedRows.size})`),
330
416
  ),
331
417
  h("table", { className: c("table") },
332
418
  h("thead", null, h("tr", null,
419
+ h("th", { style: { width: "30px" } }, "⠿"),
420
+ h("th", { style: { width: "30px" } },
421
+ h("input", { type: "checkbox", checked: pickedRows.size > 0 && pickedRows.size === sortedRows.length,
422
+ onChange: (event) => setPickedRows(event.target.checked ? new Set(sortedRows.map(rowKey)) : new Set()) })),
423
+ h("th", null, "time"),
333
424
  h("th", null, "model"),
334
- resultTable.tasks.map((task) => h("th", { key: task }, task)))),
425
+ h("th", null, "category"),
426
+ h("th", null, "task"),
427
+ h("th", null, "score"),
428
+ h("th", null, "操作"),
429
+ )),
335
430
  h("tbody", null,
336
- resultTable.models.map((model) => h("tr", { key: model },
337
- h("td", null, model),
338
- resultTable.tasks.map((task) => h("td", { key: task, className: c("score") }, resultTable.cell(model, task)))))),
431
+ sortedRows.map((row) => {
432
+ const key = rowKey(row);
433
+ const checked = pickedRows.has(key);
434
+ return h("tr", {
435
+ key,
436
+ draggable: true,
437
+ onDragStart: () => { dragKey.current = key; },
438
+ onDragOver: (event) => event.preventDefault(),
439
+ onDrop: () => onRowDrop(key),
440
+ },
441
+ h("td", { className: c("drag"), title: "拖动排序" }, "⠿"),
442
+ h("td", null, h("input", { type: "checkbox", checked, onChange: () => togglePicked(key) })),
443
+ h("td", null, fmtTime(row.time)),
444
+ h("td", null, row.model),
445
+ h("td", null, row.category),
446
+ h("td", null, row.task),
447
+ h("td", { className: c("score") }, `${row.score.toFixed(1)} (${row.judged}/${row.total})`),
448
+ h("td", null, h("button", {
449
+ className: c("btnGhost") + " " + c("btn"), title: "删除此条成绩",
450
+ onClick: () => deleteRows([row]),
451
+ }, "删除")),
452
+ );
453
+ })),
339
454
  ),
340
455
  ),
341
456
  config !== null && config.available === false && h("div", { className: c("card") + " " + c("notice") },
package/lib/index.js CHANGED
@@ -365,7 +365,7 @@ function readJsonl(path) {
365
365
  * files, plus the judged/total question counts per task.
366
366
  */
367
367
  function computeResults(dataDir) {
368
- const judged = new Map(); // `${model}\u0000${task}` -> {model, category, task, sum, n}
368
+ const judged = new Map(); // `${model}\u0000${task}` -> {model, category, task, sum, n, time}
369
369
  const totals = new Map(); // task -> question count
370
370
  if (!existsSync(dataDir)) return { rows: [], taskTotals: {} };
371
371
  for (const category of readDirSafe(dataDir)) {
@@ -380,9 +380,11 @@ function computeResults(dataDir) {
380
380
  const score = typeof row.score === "number" ? row.score : Number(row.score);
381
381
  if (model === null || !Number.isFinite(score) || score < 0) continue;
382
382
  const key = `${model}\u0000${task}`;
383
- const entry = judged.get(key) ?? { model, category, task, sum: 0, n: 0 };
383
+ const entry = judged.get(key) ?? { model, category, task, sum: 0, n: 0, time: 0 };
384
384
  entry.sum += score;
385
385
  entry.n += 1;
386
+ const tstamp = Number(row.tstamp);
387
+ if (Number.isFinite(tstamp) && tstamp > entry.time) entry.time = tstamp;
386
388
  judged.set(key, entry);
387
389
  }
388
390
  }
@@ -394,11 +396,38 @@ function computeResults(dataDir) {
394
396
  score: entry.n > 0 ? (entry.sum / entry.n) * 100 : 0,
395
397
  judged: entry.n,
396
398
  total: totals.get(entry.task) ?? 0,
399
+ time: entry.time > 0 ? entry.time : null,
397
400
  }));
398
401
  const taskTotals = Object.fromEntries(totals);
399
402
  return { rows, taskTotals };
400
403
  }
401
404
 
405
+ /**
406
+ * Remove every JSONL line of one model from a file (matched on the given
407
+ * field), rewriting the file only when something changed.
408
+ * @returns {number} removed line count.
409
+ */
410
+ function filterJsonlByModel(path, field, model) {
411
+ if (!existsSync(path)) return 0;
412
+ const lines = readFileSync(path, "utf8").split(/\r?\n/);
413
+ const kept = [];
414
+ let removed = 0;
415
+ for (const line of lines) {
416
+ if (line.trim().length === 0) continue;
417
+ let match = false;
418
+ try {
419
+ const parsed = JSON.parse(line);
420
+ match = parsed[field] === model;
421
+ } catch {
422
+ match = false;
423
+ }
424
+ if (match) removed += 1;
425
+ else kept.push(line);
426
+ }
427
+ if (removed > 0) writeFileSync(path, kept.length > 0 ? kept.join("\n") + "\n" : "", "utf8");
428
+ return removed;
429
+ }
430
+
402
431
  /** Clamp helper for query params. */
403
432
  function asInt(value, min, max, fallback) {
404
433
  const n = Number(value);
@@ -438,10 +467,28 @@ function apply(ctx) {
438
467
  const providers = readProviders(profileDir);
439
468
  const provider = providers.find((p) => p.id === providerId) ?? null;
440
469
 
441
- // A selected reasoning effort is passed through LiveBench's model-config
442
- // api_kwargs (chat_completion_openai merges them into the SDK call). The
443
- // effort is encoded in the display name so different efforts land in
444
- // separate result rows.
470
+ // Multi-select support: the client resolves its category/task chips into
471
+ // an explicit bench-path list ("live_bench", "live_bench/<cat>",
472
+ // "live_bench/<cat>/<task>"); gen_api_answer accepts nargs="+".
473
+ let benchNames = null;
474
+ if (Array.isArray(body.benchNames) && body.benchNames.length > 0) {
475
+ if (body.benchNames.length > 24) {
476
+ return { status: 400, payload: { ok: false, error: "too many bench names (max 24)" } };
477
+ }
478
+ benchNames = [];
479
+ for (const bn of body.benchNames) {
480
+ if (typeof bn !== "string") return { status: 400, payload: { ok: false, error: "invalid bench name" } };
481
+ const parts = bn.split("/").filter((s) => s.length > 0);
482
+ if (parts[0] !== "live_bench" || parts.length > 3) {
483
+ return { status: 400, payload: { ok: false, error: `invalid bench path: ${bn}` } };
484
+ }
485
+ for (const seg of parts) {
486
+ if (!/^[a-z0-9_]+$/.test(seg)) return { status: 400, payload: { ok: false, error: `invalid bench path: ${bn}` } };
487
+ }
488
+ benchNames.push(parts.join("/"));
489
+ }
490
+ }
491
+
445
492
  const hasEffortSuffix = reasoningEffort !== null && reasoningEffort !== "off";
446
493
  const displayName = displayModelName(providerId || "direct", modelId) + (hasEffortSuffix ? "@" + reasoningEffort : "");
447
494
  let cliModel = modelId;
@@ -456,7 +503,8 @@ function apply(ctx) {
456
503
  "run_livebench.py",
457
504
  "--model", cliModel,
458
505
  "--model-display-name", displayName,
459
- "--bench-name", benchParts.join("/"),
506
+ "--bench-name",
507
+ ...(benchNames !== null ? benchNames : [benchParts.join("/")]),
460
508
  "--livebench-release-option", release,
461
509
  "--max-tokens", String(asInt(body.maxTokens, 256, 32768, 32000)),
462
510
  "--parallel-requests", String(asInt(body.parallel, 1, 8, 2)),
@@ -669,6 +717,46 @@ function apply(ctx) {
669
717
  sendJson(res, 200, { ok: true, rows, taskTotals });
670
718
  },
671
719
  }), `${name}: results route`);
720
+
721
+ ctx.effect(() => ctx.webServer.register({
722
+ kind: "exact",
723
+ path: `${API}/delete`,
724
+ handler: async (req, res) => {
725
+ if (req.method !== "POST") {
726
+ sendJson(res, 405, { ok: false, error: "method not allowed" });
727
+ return;
728
+ }
729
+ if (!originAllowed(req)) {
730
+ sendJson(res, 403, { ok: false, error: "forbidden origin" });
731
+ return;
732
+ }
733
+ let body;
734
+ try {
735
+ body = JSON.parse(await readBody(req));
736
+ } catch {
737
+ sendJson(res, 400, { ok: false, error: "invalid request body" });
738
+ return;
739
+ }
740
+ const rows = Array.isArray(body.rows) ? body.rows.slice(0, 200) : [];
741
+ const layout = livebenchLayout();
742
+ const tasks = scanCategoryTasks(layout.dataDir);
743
+ let removed = 0;
744
+ for (const row of rows) {
745
+ if (!row || typeof row !== "object") continue;
746
+ const model = typeof row.model === "string" ? row.model : "";
747
+ const category = typeof row.category === "string" ? row.category : "";
748
+ const task = typeof row.task === "string" ? row.task : "";
749
+ // path-safety: membership in the scanned task list + charset guards
750
+ if (!/^[A-Za-z0-9._@-]{1,160}$/.test(model)) continue;
751
+ if (!/^[a-z0-9_]{1,60}$/.test(category) || !/^[a-z0-9_]{1,60}$/.test(task)) continue;
752
+ if (!(tasks[category] && tasks[category][task])) continue;
753
+ const taskDir = join(layout.dataDir, category, task);
754
+ removed += filterJsonlByModel(join(taskDir, "model_answer", `${model}.jsonl`), "model_id", model);
755
+ removed += filterJsonlByModel(join(taskDir, "model_judgment", "ground_truth_judgment.jsonl"), "model", model);
756
+ }
757
+ sendJson(res, 200, { ok: true, removed });
758
+ },
759
+ }), `${name}: delete route`);
672
760
  }
673
761
 
674
762
  /** Count alive runs (at most one today, kept for future parallel lanes). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-livebench-panel",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
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",