j-chim commited on
Commit
4bce289
·
1 Parent(s): c40bb86

Fix validated evaluator displays

Browse files
app/evals/page.tsx CHANGED
@@ -66,6 +66,12 @@ function EvalsPageInner() {
66
  )
67
  const [evaluatorSortCol, setEvaluatorSortCol] = useState<EvaluatorTableSortCol>("evals")
68
  const [evaluatorSortDir, setEvaluatorSortDir] = useState<"asc" | "desc">("desc")
 
 
 
 
 
 
69
  const deferredSearchQuery = useDeferredValue(searchQuery)
70
 
71
  const handleEvaluatorSort = useCallback((col: EvaluatorTableSortCol) => {
@@ -178,13 +184,13 @@ function EvalsPageInner() {
178
  const dirMul = evaluatorSortDir === "asc" ? 1 : -1
179
  return list.slice().sort((a, b) => {
180
  let cmp = 0
181
- if (evaluatorSortCol === "name") cmp = a.name.localeCompare(b.name)
182
- else if (evaluatorSortCol === "verified") cmp = a.verifiedCount - b.verifiedCount
183
  else cmp = a.evalCount - b.evalCount
184
  if (cmp === 0) cmp = a.name.localeCompare(b.name)
185
  return cmp * dirMul
186
  })
187
- }, [evaluatorGroups, deferredSearchQuery, evaluatorSortCol, evaluatorSortDir])
188
 
189
  const families = hierarchy?.families ?? []
190
 
@@ -502,7 +508,7 @@ function EvalsPageInner() {
502
  ) : groupBy === "evaluator" ? (
503
  <EvaluatorTable
504
  rows={visibleEvaluators}
505
- sortCol={evaluatorSortCol}
506
  sortDir={evaluatorSortDir}
507
  onSort={handleEvaluatorSort}
508
  verifiedOnly={verifiedOnly}
 
66
  )
67
  const [evaluatorSortCol, setEvaluatorSortCol] = useState<EvaluatorTableSortCol>("evals")
68
  const [evaluatorSortDir, setEvaluatorSortDir] = useState<"asc" | "desc">("desc")
69
+ // In verified-only mode the Verified column is hidden (it equals
70
+ // Evaluations reported), so a stale "verified" sort would otherwise sit
71
+ // active on a column the user can no longer see or toggle. Clamp it to
72
+ // "evals" — same ordering, but on a visible, interactive header.
73
+ const effectiveEvaluatorSortCol: EvaluatorTableSortCol =
74
+ verifiedOnly && evaluatorSortCol === "verified" ? "evals" : evaluatorSortCol
75
  const deferredSearchQuery = useDeferredValue(searchQuery)
76
 
77
  const handleEvaluatorSort = useCallback((col: EvaluatorTableSortCol) => {
 
184
  const dirMul = evaluatorSortDir === "asc" ? 1 : -1
185
  return list.slice().sort((a, b) => {
186
  let cmp = 0
187
+ if (effectiveEvaluatorSortCol === "name") cmp = a.name.localeCompare(b.name)
188
+ else if (effectiveEvaluatorSortCol === "verified") cmp = a.verifiedCount - b.verifiedCount
189
  else cmp = a.evalCount - b.evalCount
190
  if (cmp === 0) cmp = a.name.localeCompare(b.name)
191
  return cmp * dirMul
192
  })
193
+ }, [evaluatorGroups, deferredSearchQuery, effectiveEvaluatorSortCol, evaluatorSortDir])
194
 
195
  const families = hierarchy?.families ?? []
196
 
 
508
  ) : groupBy === "evaluator" ? (
509
  <EvaluatorTable
510
  rows={visibleEvaluators}
511
+ sortCol={effectiveEvaluatorSortCol}
512
  sortDir={evaluatorSortDir}
513
  onSort={handleEvaluatorSort}
514
  verifiedOnly={verifiedOnly}
components/evaluator-table.tsx CHANGED
@@ -67,7 +67,11 @@ export function EvaluatorTable({ rows, sortCol, sortDir, onSort, verifiedOnly }:
67
  <tr>
68
  <SortTh col="name" style={{ width: "55%" }}>Evaluator</SortTh>
69
  <SortTh col="evals" className="num">Evaluations reported</SortTh>
70
- <SortTh col="verified" className="num">Verified</SortTh>
 
 
 
 
71
  <th style={{ width: 90 }} />
72
  </tr>
73
  </thead>
@@ -85,18 +89,27 @@ export function EvaluatorTable({ rows, sortCol, sortDir, onSort, verifiedOnly }:
85
  </Link>
86
  </td>
87
  <td className="num font-mono text-[13px]">
88
- {row.evalCount.toLocaleString()}
89
- </td>
90
- <td className="num font-mono text-[13px]">
91
- {row.verifiedCount > 0 ? (
92
  <span className="inline-flex items-center gap-1 text-[color:var(--accent)]">
93
- {row.verifiedCount.toLocaleString()}
94
  <VerifiedBadge verified size="sm" withTooltip={false} />
95
  </span>
96
  ) : (
97
- <span className="text-[color:var(--fg-subtle)]">—</span>
98
  )}
99
  </td>
 
 
 
 
 
 
 
 
 
 
 
 
100
  <td>
101
  <Link
102
  href={hrefFor(row.slug)}
 
67
  <tr>
68
  <SortTh col="name" style={{ width: "55%" }}>Evaluator</SortTh>
69
  <SortTh col="evals" className="num">Evaluations reported</SortTh>
70
+ {/* In verified-only mode every (eval, org) membership is already
71
+ verified, so verifiedCount === evalCount for every row — the
72
+ two columns are identical. Drop the redundant Verified column
73
+ there and keep it only when the counts can differ. */}
74
+ {!verifiedOnly && <SortTh col="verified" className="num">Verified</SortTh>}
75
  <th style={{ width: 90 }} />
76
  </tr>
77
  </thead>
 
89
  </Link>
90
  </td>
91
  <td className="num font-mono text-[13px]">
92
+ {verifiedOnly ? (
 
 
 
93
  <span className="inline-flex items-center gap-1 text-[color:var(--accent)]">
94
+ {row.evalCount.toLocaleString()}
95
  <VerifiedBadge verified size="sm" withTooltip={false} />
96
  </span>
97
  ) : (
98
+ row.evalCount.toLocaleString()
99
  )}
100
  </td>
101
+ {!verifiedOnly && (
102
+ <td className="num font-mono text-[13px]">
103
+ {row.verifiedCount > 0 ? (
104
+ <span className="inline-flex items-center gap-1 text-[color:var(--accent)]">
105
+ {row.verifiedCount.toLocaleString()}
106
+ <VerifiedBadge verified size="sm" withTooltip={false} />
107
+ </span>
108
+ ) : (
109
+ <span className="text-[color:var(--fg-subtle)]">—</span>
110
+ )}
111
+ </td>
112
+ )}
113
  <td>
114
  <Link
115
  href={hrefFor(row.slug)}
lib/clean-hierarchy.ts CHANGED
@@ -214,6 +214,7 @@ export function cleanHierarchy(
214
  collapseValsAiSetupVariants(h)
215
  dedupValsAiAliasedBenches(h)
216
  flattenSplitFamilies(h)
 
217
  if (comparisonIndex) {
218
  dedupAggregatorBenchesByScore(h, comparisonIndex)
219
  }
@@ -1556,6 +1557,67 @@ function flattenSplitFamilies(h: CleanableHierarchy) {
1556
  }
1557
  }
1558
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1559
  export function isHierarchyCleaned(h: EvalHierarchy | null | undefined): boolean {
1560
  return Boolean((h as CleanableHierarchy | null | undefined)?.[CLEANED_MARKER])
1561
  }
 
214
  collapseValsAiSetupVariants(h)
215
  dedupValsAiAliasedBenches(h)
216
  flattenSplitFamilies(h)
217
+ dropGroupingLeaderboardRollups(h)
218
  if (comparisonIndex) {
219
  dedupAggregatorBenchesByScore(h, comparisonIndex)
220
  }
 
1557
  }
1558
  }
1559
 
1560
+ /**
1561
+ * Drop a grouping's own aggregate "leaderboard" rollup benchmark.
1562
+ *
1563
+ * Some sources ship, inside a composite (or a multi-benchmark family), an
1564
+ * extra benchmark that is just the aggregate score for the whole group —
1565
+ * e.g. HELM's `helm-safety` composite ("HELM Safety") carries a
1566
+ * `helm-safety-leaderboard` benchmark ("HELM-Safety-Leaderboard") that is
1567
+ * the composite's own rollup. Listing it as a sibling benchmark makes the
1568
+ * grouping show up as BOTH a family/group AND a benchmark — a semantic
1569
+ * duplicate ("HELM Safety" is only a family, not a benchmark). We strip the
1570
+ * rollup leaf so the group is only ever a group; the real member benchmarks
1571
+ * (BBQ, HarmBench, …) stay.
1572
+ *
1573
+ * Detection is deliberately narrow: a leaf qualifies only when its key is
1574
+ * the parent grouping's key plus a `-leaderboard` suffix
1575
+ * (`${parentKey}-leaderboard`). That suffix is an unambiguous rollup signal
1576
+ * — it catches all six HELM composites without touching real sibling
1577
+ * benchmarks whose slug merely coincides with the family (e.g.
1578
+ * `reward-bench`'s genuine `rewardbench` benchmark sitting beside
1579
+ * RewardBench 2 / Safety / Reasoning). We only strip within groups that
1580
+ * keep at least one other benchmark, so single-benchmark families — where
1581
+ * the lone bench legitimately IS the family — are never touched.
1582
+ *
1583
+ * We drop only the benchmark leaf, NOT the rollup's eval ids from
1584
+ * `family.constituent_evaluation_ids`. Those ids stay so the rollup eval row
1585
+ * still resolves to its family/composite in the hierarchy lookup (it just
1586
+ * no longer carries a benchmark-leaf label) — the row is an aggregate, not
1587
+ * a distinct benchmark, which is exactly the outcome we want.
1588
+ */
1589
+ function dropGroupingLeaderboardRollups(h: CleanableHierarchy) {
1590
+ const slug = (s: string) => (s ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "")
1591
+ const isSelfRollup = (b: HierarchyBenchmark, parentKey: string): boolean =>
1592
+ Boolean(b.key) &&
1593
+ b.key.endsWith("-leaderboard") &&
1594
+ slug(b.key.replace(/-leaderboard$/, "")) === slug(parentKey)
1595
+
1596
+ const strip = (
1597
+ benches: HierarchyBenchmark[],
1598
+ parentKey: string,
1599
+ ): HierarchyBenchmark[] => {
1600
+ if (benches.length < 2) return benches
1601
+ const kept = benches.filter((b) => !isSelfRollup(b, parentKey))
1602
+ // Never empty a group; only apply when something actually dropped.
1603
+ return kept.length > 0 && kept.length < benches.length ? kept : benches
1604
+ }
1605
+
1606
+ for (const fam of h.families ?? []) {
1607
+ // Composites are always groupings — strip their self-rollup leaf.
1608
+ for (const c of fam.composites ?? []) {
1609
+ if (c.benchmarks) c.benchmarks = strip(c.benchmarks, c.key)
1610
+ }
1611
+ // Family-level rollup (a `${family.key}-leaderboard` bench sitting
1612
+ // directly under a multi-benchmark family). None in the current
1613
+ // snapshot, but keep the hierarchy consistent if one appears.
1614
+ if (fam.benchmarks) fam.benchmarks = strip(fam.benchmarks, fam.key)
1615
+ if (fam.standalone_benchmarks) {
1616
+ fam.standalone_benchmarks = strip(fam.standalone_benchmarks, fam.key)
1617
+ }
1618
+ }
1619
+ }
1620
+
1621
  export function isHierarchyCleaned(h: EvalHierarchy | null | undefined): boolean {
1622
  return Boolean((h as CleanableHierarchy | null | undefined)?.[CLEANED_MARKER])
1623
  }
tests/clean-hierarchy.test.ts CHANGED
@@ -457,6 +457,109 @@ describe("cleanHierarchy", () => {
457
  )
458
  })
459
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
460
  it("is idempotent: re-applying produces identical output", () => {
461
  const raw: EvalHierarchy = {
462
  families: [family("aime", "AIME")],
 
457
  )
458
  })
459
 
460
+ it("drops a grouping's own *-leaderboard rollup but keeps real members", () => {
461
+ // HELM's `helm-safety` composite ("HELM Safety", a grouping) ships a
462
+ // `helm-safety-leaderboard` benchmark that is the composite's own
463
+ // aggregate — it makes the group show up as both a family and a
464
+ // benchmark. The rollup leaf is dropped; the real members survive.
465
+ const raw: EvalHierarchy = {
466
+ families: [
467
+ family("helm", "HELM", {
468
+ composites: [
469
+ {
470
+ key: "helm-safety",
471
+ display_name: "HELM Safety",
472
+ category: "Safety",
473
+ tags: { domains: [], languages: [], tasks: [] },
474
+ benchmarks: [
475
+ bench("bbq", "BBQ"),
476
+ bench("harmbench", "HarmBench"),
477
+ bench("helm-safety-leaderboard", "HELM-Safety-Leaderboard"),
478
+ ],
479
+ },
480
+ ],
481
+ }),
482
+ ],
483
+ }
484
+
485
+ const cleaned = cleanHierarchy(raw)
486
+ const helm = cleaned.families.find((f) => f.key === "helm")!
487
+ const safety = helm.composites!.find((c) => c.key === "helm-safety")!
488
+ const keys = (safety.benchmarks ?? []).map((b) => b.key)
489
+ expect(keys).not.toContain("helm-safety-leaderboard")
490
+ expect(keys).toEqual(expect.arrayContaining(["bbq", "harmbench"]))
491
+ })
492
+
493
+ it("keeps a real sibling whose slug matches the family but lacks the -leaderboard suffix", () => {
494
+ // reward-bench's genuine `rewardbench` benchmark slugifies the same as
495
+ // the `reward-bench` family but is a real member, not a rollup. The
496
+ // -leaderboard-only rule must leave it (and its siblings) alone.
497
+ const raw: EvalHierarchy = {
498
+ families: [
499
+ family("foo-bench", "Foo Bench", {
500
+ benchmarks: [bench("foobench", "Foo Bench"), bench("foobench-2", "Foo Bench 2")],
501
+ }),
502
+ ],
503
+ }
504
+
505
+ const cleaned = cleanHierarchy(raw)
506
+ const fam = cleaned.families.find((f) => f.key === "foo-bench")!
507
+ const keys = [...(fam.benchmarks ?? []), ...(fam.standalone_benchmarks ?? [])].map((b) => b.key)
508
+ expect(keys).toEqual(expect.arrayContaining(["foobench", "foobench-2"]))
509
+ })
510
+
511
+ it("drops a family-level rollup and preserves its eval ids on the family", () => {
512
+ const raw: EvalHierarchy = {
513
+ families: [
514
+ family("widget-bench", "Widget Bench", {
515
+ constituent_evaluation_ids: [
516
+ "widget-bench%2Fwidget-bench-leaderboard",
517
+ "widget-bench%2Fwidget-a",
518
+ ],
519
+ standalone_benchmarks: [
520
+ bench("widget-bench-leaderboard", "Widget Bench Leaderboard"),
521
+ bench("widget-a", "Widget A"),
522
+ ],
523
+ }),
524
+ ],
525
+ }
526
+
527
+ const cleaned = cleanHierarchy(raw)
528
+ const fam = cleaned.families.find((f) => f.key === "widget-bench")!
529
+ const keys = [...(fam.benchmarks ?? []), ...(fam.standalone_benchmarks ?? [])].map((b) => b.key)
530
+ // Rollup leaf gone, real member kept.
531
+ expect(keys).not.toContain("widget-bench-leaderboard")
532
+ expect(keys).toContain("widget-a")
533
+ // …but the rollup's eval id stays on the family so it still resolves.
534
+ expect(fam.constituent_evaluation_ids).toContain(
535
+ "widget-bench%2Fwidget-bench-leaderboard",
536
+ )
537
+ })
538
+
539
+ it("never empties a group: a lone *-leaderboard bench is kept", () => {
540
+ const raw: EvalHierarchy = {
541
+ families: [
542
+ family("solo", "Solo", {
543
+ composites: [
544
+ {
545
+ key: "solo-grp",
546
+ display_name: "Solo Grp",
547
+ category: "General",
548
+ tags: { domains: [], languages: [], tasks: [] },
549
+ benchmarks: [bench("solo-grp-leaderboard", "Solo Grp Leaderboard")],
550
+ },
551
+ ],
552
+ }),
553
+ ],
554
+ }
555
+
556
+ const cleaned = cleanHierarchy(raw)
557
+ const grp = cleaned.families
558
+ .find((f) => f.key === "solo")
559
+ ?.composites?.find((c) => c.key === "solo-grp")
560
+ expect(grp?.benchmarks?.map((b) => b.key)).toEqual(["solo-grp-leaderboard"])
561
+ })
562
+
563
  it("is idempotent: re-applying produces identical output", () => {
564
  const raw: EvalHierarchy = {
565
  families: [family("aime", "AIME")],