Spaces:
Running
Merge cross-source benchmark families; tidy leaderboard panel + table chrome
Browse filesclean-hierarchy:
* New `groupSameBenchAcrossSources` pass collapses N≥2 single-bench
families that share a bench key but ship disjoint eval_summary_ids
(i.e. independent sources publishing the same conceptual benchmark)
into a single family card. Survivor = most models; other benches
are moved in as siblings with display_name " · <Source>" suffixed
from metrics[0].sources, and a unique key suffix
(mmlu-pro__arcadia-impact) so siblings don't collide. Runs AFTER
decorateHierarchyDerivedTags so the sanitizer's shareToken /
humanizeKey passes don't clobber the suffixed names. Net effect:
one MMLU-Pro card with two source-tagged benches.
eval-detail:
* Drop the standalone bar column (header repeated the eval name, the
bar repeated the score). Inline a thin perf bar under the score
cell with the setup / dataset caption right-aligned beneath.
* Drop the duplicate ApplesToApplesBanner from inside
MultiMetricLeaderboard — the parent already renders it.
* Render the multi-metric SplitPicker INSIDE MultiMetricLeaderboard
(after the section heading + description) so it matches the
position of the single-metric branch's Split picker. Both fibble
and global-mmlu now render the dropdown in the same spot.
* Restore the subtask Split picker for evals like Global MMLU Lite
(1 root metric + N subtasks) but keep it gated by !splitConfig so
fibble (with page-level Split) doesn't double up.
* Wording: rename remaining "Slice" UI strings to "Split" (Slice
breakdown → Split breakdown, "metric spec · ... · slices" →
"splits") to match the rest of the app.
* getMetricChipLabel walks display_name → metric_name → metric_id →
column_key tail and humanises underscores so AgentHarm's null-
display-name metrics surface as "avg full score" etc. instead of
four chips literally labelled "Metric".
family-table:
* "Reported results" column now divides total_results by the family's
bench-level metric count, giving a unique-models-tested
approximation. AgentHarm's 72 metric-rows / 4 metrics → 18 unique
models surfaced instead of the misleading raw 72.
* Drop the dead totalModels prop and unused visibleLeaves local.
score-distribution:
* View toggle (Distribution / Frontier) reverts to ec-pill chips —
same chip style as the metric row below; row labels (VIEW vs
METRIC) carry the differentiation.
models page:
* Default sort changes from Released ↓ to Coverage ↓ (and the Reset
Filters button resets to Coverage ↓).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- app/evals/page.tsx +0 -1
- app/models/page.tsx +2 -2
- components/eval-detail.tsx +74 -79
- components/family-table.tsx +26 -9
- components/score-distribution.tsx +26 -16
- lib/clean-hierarchy.ts +101 -0
|
@@ -312,7 +312,6 @@ function EvalsPageInner() {
|
|
| 312 |
) : (
|
| 313 |
<FamilyTable
|
| 314 |
families={visibleFamilies}
|
| 315 |
-
totalModels={totalModels}
|
| 316 |
evalItems={evalItems}
|
| 317 |
benchmarkCards={benchmarkCards}
|
| 318 |
categoryFilter={new Set(selectedCategories)}
|
|
|
|
| 312 |
) : (
|
| 313 |
<FamilyTable
|
| 314 |
families={visibleFamilies}
|
|
|
|
| 315 |
evalItems={evalItems}
|
| 316 |
benchmarkCards={benchmarkCards}
|
| 317 |
categoryFilter={new Set(selectedCategories)}
|
|
@@ -58,7 +58,7 @@ export default function ModelsPage() {
|
|
| 58 |
const [loadingDevelopers, setLoadingDevelopers] = useState(false)
|
| 59 |
const [developersReady, setDevelopersReady] = useState(false)
|
| 60 |
const [groupByDeveloper, setGroupByDeveloper] = useState(false)
|
| 61 |
-
const [modelSortBy, setModelSortBy] = useState<ModelSort>("
|
| 62 |
const [modelSortDir, setModelSortDir] = useState<SortDir>("desc")
|
| 63 |
const [developerSortBy, setDeveloperSortBy] = useState<DevSort>("models")
|
| 64 |
const [developerSortDir, setDeveloperSortDir] = useState<SortDir>("desc")
|
|
@@ -383,7 +383,7 @@ export default function ModelsPage() {
|
|
| 383 |
className="btn-ec outline"
|
| 384 |
onClick={() => {
|
| 385 |
setSearchQuery("")
|
| 386 |
-
setModelSortBy("
|
| 387 |
setModelSortDir("desc")
|
| 388 |
setDeveloperSortBy("models")
|
| 389 |
setDeveloperSortDir("desc")
|
|
|
|
| 58 |
const [loadingDevelopers, setLoadingDevelopers] = useState(false)
|
| 59 |
const [developersReady, setDevelopersReady] = useState(false)
|
| 60 |
const [groupByDeveloper, setGroupByDeveloper] = useState(false)
|
| 61 |
+
const [modelSortBy, setModelSortBy] = useState<ModelSort>("coverage")
|
| 62 |
const [modelSortDir, setModelSortDir] = useState<SortDir>("desc")
|
| 63 |
const [developerSortBy, setDeveloperSortBy] = useState<DevSort>("models")
|
| 64 |
const [developerSortDir, setDeveloperSortDir] = useState<SortDir>("desc")
|
|
|
|
| 383 |
className="btn-ec outline"
|
| 384 |
onClick={() => {
|
| 385 |
setSearchQuery("")
|
| 386 |
+
setModelSortBy("coverage")
|
| 387 |
setModelSortDir("desc")
|
| 388 |
setDeveloperSortBy("models")
|
| 389 |
setDeveloperSortDir("desc")
|
|
@@ -624,7 +624,14 @@ export function EvalDetail({
|
|
| 624 |
return Array.from(seen, ([key, name]) => ({ key, label: name }))
|
| 625 |
}, [lb.leaderboard_metrics])
|
| 626 |
|
| 627 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 628 |
|
| 629 |
const ALL_SLICE_KEY = "__all__"
|
| 630 |
const [activeSlice, setActiveSlice] = useState<string>(ALL_SLICE_KEY)
|
|
@@ -870,7 +877,7 @@ export function EvalDetail({
|
|
| 870 |
className="font-mono text-[10px] uppercase tracking-[0.12em]"
|
| 871 |
style={{ color: "var(--fg-subtle)" }}
|
| 872 |
>
|
| 873 |
-
metric spec · completeness · comparability{summary.subtasks?.length ? " ·
|
| 874 |
</span>
|
| 875 |
</div>
|
| 876 |
{overviewOpen ? (
|
|
@@ -992,7 +999,7 @@ export function EvalDetail({
|
|
| 992 |
className="font-mono uppercase mb-1"
|
| 993 |
style={{ fontSize: 10, letterSpacing: "0.14em", color: "var(--fg-subtle)" }}
|
| 994 |
>
|
| 995 |
-
|
| 996 |
</div>
|
| 997 |
<ul
|
| 998 |
className="flex flex-col"
|
|
@@ -1050,10 +1057,11 @@ export function EvalDetail({
|
|
| 1050 |
summary={lb.comparability_summary}
|
| 1051 |
detailsAnchorId="comparability-panel"
|
| 1052 |
/>
|
| 1053 |
-
|
| 1054 |
-
|
| 1055 |
-
|
| 1056 |
-
|
|
|
|
| 1057 |
</section>
|
| 1058 |
) : (
|
| 1059 |
<section>
|
|
@@ -1090,11 +1098,16 @@ export function EvalDetail({
|
|
| 1090 |
<SplitPicker config={splitConfig} className="mb-4" />
|
| 1091 |
)}
|
| 1092 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1093 |
{hasSlicePicker && (
|
| 1094 |
<SplitPicker
|
| 1095 |
className="mb-4"
|
| 1096 |
config={{
|
| 1097 |
-
label: "
|
| 1098 |
activeId: activeSlice,
|
| 1099 |
onChange: setActiveSlice,
|
| 1100 |
options: [
|
|
@@ -1156,12 +1169,7 @@ export function EvalDetail({
|
|
| 1156 |
<th className="hidden lg:table-cell" style={{ minWidth: 160 }}>
|
| 1157 |
{isResearchView ? "Developer" : "Provider"}
|
| 1158 |
</th>
|
| 1159 |
-
<th className="
|
| 1160 |
-
{lb.composite_benchmark_name && lb.composite_benchmark_name !== lb.evaluation_name
|
| 1161 |
-
? `${lb.composite_benchmark_name} · ${lb.evaluation_name}`
|
| 1162 |
-
: lb.evaluation_name}
|
| 1163 |
-
</th>
|
| 1164 |
-
<th className="num" style={{ width: 130 }}>
|
| 1165 |
{lb.metric_config.unit ?? "Score"}
|
| 1166 |
</th>
|
| 1167 |
<th className="hidden lg:table-cell" style={{ width: 110 }}>Evaluator</th>
|
|
@@ -1289,51 +1297,54 @@ export function EvalDetail({
|
|
| 1289 |
</div>
|
| 1290 |
</td>
|
| 1291 |
|
| 1292 |
-
<td className="
|
| 1293 |
-
{/*
|
| 1294 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1295 |
<div
|
| 1296 |
style={{
|
| 1297 |
-
position: "
|
| 1298 |
-
|
| 1299 |
-
|
| 1300 |
-
|
|
|
|
| 1301 |
}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1302 |
>
|
| 1303 |
-
|
| 1304 |
-
style={{
|
| 1305 |
-
position: "absolute",
|
| 1306 |
-
inset: 0,
|
| 1307 |
-
width: `${Math.max(2, normalizedScore * 100)}%`,
|
| 1308 |
-
background: isTopRank ? "var(--accent)" : "var(--fg-muted)",
|
| 1309 |
-
opacity: isTopRank ? 1 : 0.55,
|
| 1310 |
-
}}
|
| 1311 |
-
/>
|
| 1312 |
</div>
|
| 1313 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1314 |
<div
|
| 1315 |
-
className="mt-1 font-mono
|
| 1316 |
-
style={{ fontSize: 10,
|
| 1317 |
>
|
| 1318 |
-
{
|
| 1319 |
</div>
|
| 1320 |
)}
|
| 1321 |
-
{!setupLabel &&
|
| 1322 |
-
datasetName &&
|
| 1323 |
-
!isResearchView &&
|
| 1324 |
-
datasetName !== lb.evaluation_name && (
|
| 1325 |
-
<div
|
| 1326 |
-
className="mt-1 font-mono truncate"
|
| 1327 |
-
style={{ fontSize: 10, color: "var(--fg-subtle)" }}
|
| 1328 |
-
>
|
| 1329 |
-
{datasetName}
|
| 1330 |
-
</div>
|
| 1331 |
-
)}
|
| 1332 |
-
</div>
|
| 1333 |
-
</td>
|
| 1334 |
-
|
| 1335 |
-
<td className="num align-top tabular-nums" style={{ fontSize: 15, fontWeight: 600 }}>
|
| 1336 |
-
{formatRawScore(modelResult.score, undefined)}
|
| 1337 |
</td>
|
| 1338 |
|
| 1339 |
<td className="hidden lg:table-cell align-top">
|
|
@@ -1387,7 +1398,7 @@ export function EvalDetail({
|
|
| 1387 |
|
| 1388 |
{isExpanded && (
|
| 1389 |
<tr>
|
| 1390 |
-
<td colSpan={hasAnyUpdatedTimestamp ?
|
| 1391 |
<div className="space-y-5 px-4 py-5 sm:px-6">
|
| 1392 |
<div className="grid gap-4 xl:grid-cols-3">
|
| 1393 |
<DetailPanel
|
|
@@ -1530,13 +1541,13 @@ export function EvalDetail({
|
|
| 1530 |
className="font-mono uppercase"
|
| 1531 |
style={{ fontSize: 10, letterSpacing: "0.14em", color: "var(--fg-subtle)" }}
|
| 1532 |
>
|
| 1533 |
-
|
| 1534 |
</div>
|
| 1535 |
<div className="overflow-x-auto" style={{ border: "1px solid var(--border-soft)" }}>
|
| 1536 |
<table className="ec-htable">
|
| 1537 |
<thead>
|
| 1538 |
<tr>
|
| 1539 |
-
<th>
|
| 1540 |
<th className="num">Raw</th>
|
| 1541 |
</tr>
|
| 1542 |
</thead>
|
|
@@ -1669,7 +1680,7 @@ export function EvalDetail({
|
|
| 1669 |
})}
|
| 1670 |
{leaderboardRows.length === 0 && (
|
| 1671 |
<tr>
|
| 1672 |
-
<td colSpan={hasAnyUpdatedTimestamp ?
|
| 1673 |
No leaderboard entries match the selected parameter range.
|
| 1674 |
</td>
|
| 1675 |
</tr>
|
|
@@ -1706,9 +1717,11 @@ export function EvalDetail({
|
|
| 1706 |
function MultiMetricLeaderboard({
|
| 1707 |
summary,
|
| 1708 |
isResearchView,
|
|
|
|
| 1709 |
}: {
|
| 1710 |
summary: BenchmarkEvalSummary
|
| 1711 |
isResearchView: boolean
|
|
|
|
| 1712 |
}) {
|
| 1713 |
const [page, setPage] = useState(1)
|
| 1714 |
// Default sort: the first root-scope metric (the benchmark's overall
|
|
@@ -1984,10 +1997,9 @@ function MultiMetricLeaderboard({
|
|
| 1984 |
|
| 1985 |
return (
|
| 1986 |
<section>
|
| 1987 |
-
|
| 1988 |
-
|
| 1989 |
-
|
| 1990 |
-
/>
|
| 1991 |
<div className="section-head">
|
| 1992 |
<h2>{isResearchView ? "Leaderboard" : "Reporting Comparison"}</h2>
|
| 1993 |
<span
|
|
@@ -2049,6 +2061,10 @@ function MultiMetricLeaderboard({
|
|
| 2049 |
</DropdownMenu>
|
| 2050 |
</div>
|
| 2051 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2052 |
{/* Distribution panel — one curve, dropdown swaps between metrics */}
|
| 2053 |
{(() => {
|
| 2054 |
const distSeries = visibleMetrics
|
|
@@ -2086,27 +2102,6 @@ function MultiMetricLeaderboard({
|
|
| 2086 |
})()}
|
| 2087 |
|
| 2088 |
<div className="ec-card" style={{ padding: 0, overflow: "hidden" }}>
|
| 2089 |
-
{hasSliceTabs && (
|
| 2090 |
-
<div className="border-b bg-background px-5 py-3 sm:px-6 flex items-center gap-3">
|
| 2091 |
-
<span
|
| 2092 |
-
className="font-mono uppercase tracking-[0.14em] shrink-0"
|
| 2093 |
-
style={{ fontSize: 10, color: "var(--fg-subtle)" }}
|
| 2094 |
-
>
|
| 2095 |
-
Slice
|
| 2096 |
-
</span>
|
| 2097 |
-
<select
|
| 2098 |
-
className="ec-select"
|
| 2099 |
-
value={activeSliceTab}
|
| 2100 |
-
onChange={(e) => setActiveSliceTab(e.target.value)}
|
| 2101 |
-
>
|
| 2102 |
-
<option value="all">Overall</option>
|
| 2103 |
-
{sliceTabs.map((tab) => (
|
| 2104 |
-
<option key={tab.key} value={tab.key}>{tab.label}</option>
|
| 2105 |
-
))}
|
| 2106 |
-
</select>
|
| 2107 |
-
</div>
|
| 2108 |
-
)}
|
| 2109 |
-
|
| 2110 |
{hasParameterData && (
|
| 2111 |
<div className="border-b bg-background px-5 py-4 sm:px-6">
|
| 2112 |
<ParamRangePicker
|
|
|
|
| 624 |
return Array.from(seen, ([key, name]) => ({ key, label: name }))
|
| 625 |
}, [lb.leaderboard_metrics])
|
| 626 |
|
| 627 |
+
// Suppress the slice picker when a split picker is already in play.
|
| 628 |
+
// For evals like Fibble Arena both pickers partition the same axis
|
| 629 |
+
// (each split is one of the per-lie variants; each slice is the
|
| 630 |
+
// matrix-backfilled subtask for the same per-lie variant), so showing
|
| 631 |
+
// both reads as a redundant control. The page-level split is more
|
| 632 |
+
// authoritative — it loads richer per-eval data — so it wins.
|
| 633 |
+
const hasSlicePicker =
|
| 634 |
+
!hasMultiMetricLeaderboard && subtaskSlices.length > 1 && !splitConfig
|
| 635 |
|
| 636 |
const ALL_SLICE_KEY = "__all__"
|
| 637 |
const [activeSlice, setActiveSlice] = useState<string>(ALL_SLICE_KEY)
|
|
|
|
| 877 |
className="font-mono text-[10px] uppercase tracking-[0.12em]"
|
| 878 |
style={{ color: "var(--fg-subtle)" }}
|
| 879 |
>
|
| 880 |
+
metric spec · completeness · comparability{summary.subtasks?.length ? " · splits" : ""}
|
| 881 |
</span>
|
| 882 |
</div>
|
| 883 |
{overviewOpen ? (
|
|
|
|
| 999 |
className="font-mono uppercase mb-1"
|
| 1000 |
style={{ fontSize: 10, letterSpacing: "0.14em", color: "var(--fg-subtle)" }}
|
| 1001 |
>
|
| 1002 |
+
Split breakdown · {summary.subtasks.length}
|
| 1003 |
</div>
|
| 1004 |
<ul
|
| 1005 |
className="flex flex-col"
|
|
|
|
| 1057 |
summary={lb.comparability_summary}
|
| 1058 |
detailsAnchorId="comparability-panel"
|
| 1059 |
/>
|
| 1060 |
+
<MultiMetricLeaderboard
|
| 1061 |
+
summary={lb}
|
| 1062 |
+
isResearchView={isResearchView}
|
| 1063 |
+
splitConfig={splitConfig}
|
| 1064 |
+
/>
|
| 1065 |
</section>
|
| 1066 |
) : (
|
| 1067 |
<section>
|
|
|
|
| 1098 |
<SplitPicker config={splitConfig} className="mb-4" />
|
| 1099 |
)}
|
| 1100 |
|
| 1101 |
+
{/* Subtask split picker for evals like Global MMLU Lite where
|
| 1102 |
+
the splits live as subtasks of a single eval (not as
|
| 1103 |
+
separate eval IDs the page-level SplitPicker can swap to).
|
| 1104 |
+
Suppressed when a page-level split is already in play —
|
| 1105 |
+
two pickers would partition the same axis (see fibble). */}
|
| 1106 |
{hasSlicePicker && (
|
| 1107 |
<SplitPicker
|
| 1108 |
className="mb-4"
|
| 1109 |
config={{
|
| 1110 |
+
label: "Split",
|
| 1111 |
activeId: activeSlice,
|
| 1112 |
onChange: setActiveSlice,
|
| 1113 |
options: [
|
|
|
|
| 1169 |
<th className="hidden lg:table-cell" style={{ minWidth: 160 }}>
|
| 1170 |
{isResearchView ? "Developer" : "Provider"}
|
| 1171 |
</th>
|
| 1172 |
+
<th className="num" style={{ minWidth: 200 }}>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1173 |
{lb.metric_config.unit ?? "Score"}
|
| 1174 |
</th>
|
| 1175 |
<th className="hidden lg:table-cell" style={{ width: 110 }}>Evaluator</th>
|
|
|
|
| 1297 |
</div>
|
| 1298 |
</td>
|
| 1299 |
|
| 1300 |
+
<td className="num align-top">
|
| 1301 |
+
{/* Score with inline performance bar so the
|
| 1302 |
+
previously-dedicated bar column can be
|
| 1303 |
+
dropped — its only purpose was visualising
|
| 1304 |
+
this same number. Caption shows shot/CoT
|
| 1305 |
+
setup or a differing dataset name when
|
| 1306 |
+
available; otherwise it's omitted. */}
|
| 1307 |
+
<div className="flex items-baseline justify-end gap-2 tabular-nums" style={{ fontSize: 15, fontWeight: 600 }}>
|
| 1308 |
+
<span>{formatRawScore(modelResult.score, undefined)}</span>
|
| 1309 |
+
</div>
|
| 1310 |
+
<div
|
| 1311 |
+
className="mt-1 hidden md:block"
|
| 1312 |
+
style={{
|
| 1313 |
+
position: "relative",
|
| 1314 |
+
height: 4,
|
| 1315 |
+
background: "var(--bg-surface)",
|
| 1316 |
+
overflow: "hidden",
|
| 1317 |
+
}}
|
| 1318 |
+
>
|
| 1319 |
<div
|
| 1320 |
style={{
|
| 1321 |
+
position: "absolute",
|
| 1322 |
+
inset: 0,
|
| 1323 |
+
width: `${Math.max(2, normalizedScore * 100)}%`,
|
| 1324 |
+
background: isTopRank ? "var(--accent)" : "var(--fg-muted)",
|
| 1325 |
+
opacity: isTopRank ? 1 : 0.55,
|
| 1326 |
}}
|
| 1327 |
+
/>
|
| 1328 |
+
</div>
|
| 1329 |
+
{setupLabel && (
|
| 1330 |
+
<div
|
| 1331 |
+
className="mt-1 font-mono uppercase truncate text-right"
|
| 1332 |
+
style={{ fontSize: 10, letterSpacing: "0.06em", color: "var(--fg-subtle)" }}
|
| 1333 |
>
|
| 1334 |
+
{setupLabel}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1335 |
</div>
|
| 1336 |
+
)}
|
| 1337 |
+
{!setupLabel &&
|
| 1338 |
+
datasetName &&
|
| 1339 |
+
!isResearchView &&
|
| 1340 |
+
datasetName !== lb.evaluation_name && (
|
| 1341 |
<div
|
| 1342 |
+
className="mt-1 font-mono truncate text-right"
|
| 1343 |
+
style={{ fontSize: 10, color: "var(--fg-subtle)" }}
|
| 1344 |
>
|
| 1345 |
+
{datasetName}
|
| 1346 |
</div>
|
| 1347 |
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1348 |
</td>
|
| 1349 |
|
| 1350 |
<td className="hidden lg:table-cell align-top">
|
|
|
|
| 1398 |
|
| 1399 |
{isExpanded && (
|
| 1400 |
<tr>
|
| 1401 |
+
<td colSpan={hasAnyUpdatedTimestamp ? 7 : 6} style={{ background: "var(--bg-warm)", padding: 0 }}>
|
| 1402 |
<div className="space-y-5 px-4 py-5 sm:px-6">
|
| 1403 |
<div className="grid gap-4 xl:grid-cols-3">
|
| 1404 |
<DetailPanel
|
|
|
|
| 1541 |
className="font-mono uppercase"
|
| 1542 |
style={{ fontSize: 10, letterSpacing: "0.14em", color: "var(--fg-subtle)" }}
|
| 1543 |
>
|
| 1544 |
+
Split breakdown
|
| 1545 |
</div>
|
| 1546 |
<div className="overflow-x-auto" style={{ border: "1px solid var(--border-soft)" }}>
|
| 1547 |
<table className="ec-htable">
|
| 1548 |
<thead>
|
| 1549 |
<tr>
|
| 1550 |
+
<th>Split</th>
|
| 1551 |
<th className="num">Raw</th>
|
| 1552 |
</tr>
|
| 1553 |
</thead>
|
|
|
|
| 1680 |
})}
|
| 1681 |
{leaderboardRows.length === 0 && (
|
| 1682 |
<tr>
|
| 1683 |
+
<td colSpan={hasAnyUpdatedTimestamp ? 7 : 6} style={{ padding: "32px 16px", textAlign: "center", color: "var(--fg-muted)" }}>
|
| 1684 |
No leaderboard entries match the selected parameter range.
|
| 1685 |
</td>
|
| 1686 |
</tr>
|
|
|
|
| 1717 |
function MultiMetricLeaderboard({
|
| 1718 |
summary,
|
| 1719 |
isResearchView,
|
| 1720 |
+
splitConfig,
|
| 1721 |
}: {
|
| 1722 |
summary: BenchmarkEvalSummary
|
| 1723 |
isResearchView: boolean
|
| 1724 |
+
splitConfig?: SplitConfig
|
| 1725 |
}) {
|
| 1726 |
const [page, setPage] = useState(1)
|
| 1727 |
// Default sort: the first root-scope metric (the benchmark's overall
|
|
|
|
| 1997 |
|
| 1998 |
return (
|
| 1999 |
<section>
|
| 2000 |
+
{/* The parent EvalDetail already renders the apples-to-apples
|
| 2001 |
+
banner before this leaderboard section — duplicating it here
|
| 2002 |
+
made the box appear twice on multi-metric evals like fibble. */}
|
|
|
|
| 2003 |
<div className="section-head">
|
| 2004 |
<h2>{isResearchView ? "Leaderboard" : "Reporting Comparison"}</h2>
|
| 2005 |
<span
|
|
|
|
| 2061 |
</DropdownMenu>
|
| 2062 |
</div>
|
| 2063 |
|
| 2064 |
+
{splitConfig && (
|
| 2065 |
+
<SplitPicker config={splitConfig} className="mb-4" />
|
| 2066 |
+
)}
|
| 2067 |
+
|
| 2068 |
{/* Distribution panel — one curve, dropdown swaps between metrics */}
|
| 2069 |
{(() => {
|
| 2070 |
const distSeries = visibleMetrics
|
|
|
|
| 2102 |
})()}
|
| 2103 |
|
| 2104 |
<div className="ec-card" style={{ padding: 0, overflow: "hidden" }}>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2105 |
{hasParameterData && (
|
| 2106 |
<div className="border-b bg-background px-5 py-4 sm:px-6">
|
| 2107 |
<ParamRangePicker
|
|
@@ -16,7 +16,6 @@ export type FamilySortCol = "name" | "benchmarks" | "results"
|
|
| 16 |
|
| 17 |
interface FamilyTableProps {
|
| 18 |
families: HierarchyFamily[]
|
| 19 |
-
totalModels: number
|
| 20 |
evalItems?: Map<string, BenchmarkEvalListItem>
|
| 21 |
benchmarkCards?: Record<string, BenchmarkCard>
|
| 22 |
domainFilter?: Set<string> | null
|
|
@@ -197,7 +196,6 @@ interface RowData {
|
|
| 197 |
|
| 198 |
export function FamilyTable({
|
| 199 |
families,
|
| 200 |
-
totalModels,
|
| 201 |
evalItems,
|
| 202 |
benchmarkCards,
|
| 203 |
domainFilter,
|
|
@@ -299,13 +297,38 @@ export function FamilyTable({
|
|
| 299 |
if (overview) description = overview.length > 140 ? overview.slice(0, 137) + "…" : overview
|
| 300 |
}
|
| 301 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 302 |
out.push({
|
| 303 |
key: fam.key,
|
| 304 |
name: displayName,
|
| 305 |
keySlug: fam.key,
|
| 306 |
tags: fam.derivedTags ?? [],
|
| 307 |
benchmarks: benchmarkCount,
|
| 308 |
-
evalsCount:
|
| 309 |
leaves: visibleLeafEntries,
|
| 310 |
sections,
|
| 311 |
description,
|
|
@@ -372,9 +395,6 @@ export function FamilyTable({
|
|
| 372 |
? expanded[row.key] ?? true
|
| 373 |
: expanded[row.key] ?? false
|
| 374 |
const allLeaves = row.sections.flatMap((s) => s.leaves)
|
| 375 |
-
const visibleLeaves = isExpanded
|
| 376 |
-
? allLeaves.slice(0, LEAVES_INLINE_MAX)
|
| 377 |
-
: []
|
| 378 |
const hiddenLeafCount = isExpanded
|
| 379 |
? Math.max(allLeaves.length - LEAVES_INLINE_MAX, 0)
|
| 380 |
: 0
|
|
@@ -461,9 +481,6 @@ export function FamilyTable({
|
|
| 461 |
<td className="num font-mono text-[13px]">{row.benchmarks.toLocaleString()}</td>
|
| 462 |
<td className="num font-mono text-[13px]">
|
| 463 |
{row.evalsCount.toLocaleString()}
|
| 464 |
-
{totalModels > 0 && (
|
| 465 |
-
<span className="text-[color:var(--fg-subtle)]"> / {totalModels.toLocaleString()}</span>
|
| 466 |
-
)}
|
| 467 |
</td>
|
| 468 |
<td>
|
| 469 |
<span className="font-mono text-[10px] tracking-[0.12em] uppercase text-[color:var(--accent)] inline-flex items-center gap-1">
|
|
|
|
| 16 |
|
| 17 |
interface FamilyTableProps {
|
| 18 |
families: HierarchyFamily[]
|
|
|
|
| 19 |
evalItems?: Map<string, BenchmarkEvalListItem>
|
| 20 |
benchmarkCards?: Record<string, BenchmarkCard>
|
| 21 |
domainFilter?: Set<string> | null
|
|
|
|
| 196 |
|
| 197 |
export function FamilyTable({
|
| 198 |
families,
|
|
|
|
| 199 |
evalItems,
|
| 200 |
benchmarkCards,
|
| 201 |
domainFilter,
|
|
|
|
| 297 |
if (overview) description = overview.length > 140 ? overview.slice(0, 137) + "…" : overview
|
| 298 |
}
|
| 299 |
|
| 300 |
+
// "Reported results" approximates the unique models tested in the
|
| 301 |
+
// family. provenance_summary.total_results counts every (model,
|
| 302 |
+
// metric) report row, so families that publish many metrics inflate
|
| 303 |
+
// the number — agentharm's 4 declared metrics turned 18 unique
|
| 304 |
+
// models into 72 result rows. Normalise by the family's metric
|
| 305 |
+
// count (sum of bench-level metric entries; clamp to 1 to avoid
|
| 306 |
+
// div-by-zero on families whose metrics array is empty).
|
| 307 |
+
const totalResults =
|
| 308 |
+
fam.provenance_summary?.total_results ??
|
| 309 |
+
fam.reproducibility_summary?.results_total ??
|
| 310 |
+
fam.evals_count ??
|
| 311 |
+
metricCount
|
| 312 |
+
const allBenchesForMetricCount: HierarchyBenchmark[] = [
|
| 313 |
+
...(fam.benchmarks ?? []),
|
| 314 |
+
...(fam.standalone_benchmarks ?? []),
|
| 315 |
+
...(fam.composites ?? []).flatMap((c) => c.benchmarks ?? []),
|
| 316 |
+
]
|
| 317 |
+
const familyMetricCount = allBenchesForMetricCount.reduce(
|
| 318 |
+
(n, b) => n + Math.max((b.metrics ?? []).length, 1),
|
| 319 |
+
0,
|
| 320 |
+
)
|
| 321 |
+
const reportedResults =
|
| 322 |
+
familyMetricCount > 0
|
| 323 |
+
? Math.round(totalResults / familyMetricCount)
|
| 324 |
+
: totalResults
|
| 325 |
out.push({
|
| 326 |
key: fam.key,
|
| 327 |
name: displayName,
|
| 328 |
keySlug: fam.key,
|
| 329 |
tags: fam.derivedTags ?? [],
|
| 330 |
benchmarks: benchmarkCount,
|
| 331 |
+
evalsCount: reportedResults,
|
| 332 |
leaves: visibleLeafEntries,
|
| 333 |
sections,
|
| 334 |
description,
|
|
|
|
| 395 |
? expanded[row.key] ?? true
|
| 396 |
: expanded[row.key] ?? false
|
| 397 |
const allLeaves = row.sections.flatMap((s) => s.leaves)
|
|
|
|
|
|
|
|
|
|
| 398 |
const hiddenLeafCount = isExpanded
|
| 399 |
? Math.max(allLeaves.length - LEAVES_INLINE_MAX, 0)
|
| 400 |
: 0
|
|
|
|
| 481 |
<td className="num font-mono text-[13px]">{row.benchmarks.toLocaleString()}</td>
|
| 482 |
<td className="num font-mono text-[13px]">
|
| 483 |
{row.evalsCount.toLocaleString()}
|
|
|
|
|
|
|
|
|
|
| 484 |
</td>
|
| 485 |
<td>
|
| 486 |
<span className="font-mono text-[10px] tracking-[0.12em] uppercase text-[color:var(--accent)] inline-flex items-center gap-1">
|
|
@@ -284,22 +284,32 @@ export function ScoreDistribution({
|
|
| 284 |
{canShowFrontier ? "View" : "Score distribution"}
|
| 285 |
</span>
|
| 286 |
{canShowFrontier && (
|
| 287 |
-
<div
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
>
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 303 |
</div>
|
| 304 |
)}
|
| 305 |
</div>
|
|
|
|
| 284 |
{canShowFrontier ? "View" : "Score distribution"}
|
| 285 |
</span>
|
| 286 |
{canShowFrontier && (
|
| 287 |
+
<div
|
| 288 |
+
role="tablist"
|
| 289 |
+
aria-label="Chart view"
|
| 290 |
+
className="inline-flex items-center gap-1"
|
| 291 |
+
>
|
| 292 |
+
{(["distribution", "frontier"] as const).map((view) => {
|
| 293 |
+
const on = effectiveView === view
|
| 294 |
+
const label = view === "distribution" ? "Distribution" : "Frontier"
|
| 295 |
+
return (
|
| 296 |
+
<button
|
| 297 |
+
key={view}
|
| 298 |
+
type="button"
|
| 299 |
+
role="tab"
|
| 300 |
+
aria-selected={on}
|
| 301 |
+
onClick={() => setView(view)}
|
| 302 |
+
title={
|
| 303 |
+
view === "frontier"
|
| 304 |
+
? "Frontier score over model release dates (cumulative best)."
|
| 305 |
+
: "Kernel-density distribution of model scores."
|
| 306 |
+
}
|
| 307 |
+
className={`ec-pill${on ? " on" : ""}`}
|
| 308 |
+
>
|
| 309 |
+
{label}
|
| 310 |
+
</button>
|
| 311 |
+
)
|
| 312 |
+
})}
|
| 313 |
</div>
|
| 314 |
)}
|
| 315 |
</div>
|
|
@@ -214,6 +214,12 @@ export function cleanHierarchy(
|
|
| 214 |
dedupAggregatorBenchesByScore(h, comparisonIndex)
|
| 215 |
}
|
| 216 |
decorateHierarchyDerivedTags(h)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
if (h.benchmark_index) {
|
| 218 |
const survivingFamilyKeys = new Set<string>(
|
| 219 |
(h.families ?? []).map((f) => f.key),
|
|
@@ -891,6 +897,101 @@ function consolidateDedicatedHomeBenchmarks(h: CleanableHierarchy) {
|
|
| 891 |
h.families = allFamilies.filter((fam) => !dropped.has(fam))
|
| 892 |
}
|
| 893 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 894 |
/**
|
| 895 |
* AIR-Bench 2024 is a single safety benchmark with a 4-tier taxonomy
|
| 896 |
* (314 leaf risk categories per the spec, ~30 of which the HELM
|
|
|
|
| 214 |
dedupAggregatorBenchesByScore(h, comparisonIndex)
|
| 215 |
}
|
| 216 |
decorateHierarchyDerivedTags(h)
|
| 217 |
+
// Run AFTER the sanitizer so it can't clobber our suffixed bench
|
| 218 |
+
// display_names (`MMLU-Pro · Arcadia Impact` etc.) — the merged-in
|
| 219 |
+
// bench keys deliberately use a non-shareToken-matching form to
|
| 220 |
+
// keep them distinct, which would otherwise trip the humanizeKey
|
| 221 |
+
// fallback in benchmark-tags.sanitizeName.
|
| 222 |
+
groupSameBenchAcrossSources(h)
|
| 223 |
if (h.benchmark_index) {
|
| 224 |
const survivingFamilyKeys = new Set<string>(
|
| 225 |
(h.families ?? []).map((f) => f.key),
|
|
|
|
| 897 |
h.families = allFamilies.filter((fam) => !dropped.has(fam))
|
| 898 |
}
|
| 899 |
|
| 900 |
+
/**
|
| 901 |
+
* Group single-bench families that publish the same conceptual benchmark
|
| 902 |
+
* from different upstream sources into one merged family card.
|
| 903 |
+
*
|
| 904 |
+
* Triggered when ≥2 single-bench families share a bench key but their
|
| 905 |
+
* bench rows have non-overlapping eval_summary_ids — i.e. independent
|
| 906 |
+
* sources publishing the same benchmark. The richest family (most
|
| 907 |
+
* models) keeps its slot; other families contribute their bench under
|
| 908 |
+
* the survivor as siblings, with each bench's display_name suffixed
|
| 909 |
+
* with " · <Source>" so the user can tell which run a row came from.
|
| 910 |
+
*
|
| 911 |
+
* Runs AFTER decorateHierarchyDerivedTags so the sanitizer's
|
| 912 |
+
* shareToken / humanizeKey passes don't clobber the suffixed names
|
| 913 |
+
* (the merged-in bench keys like `mmlu-pro__arcadia` deliberately
|
| 914 |
+
* don't share tokens with "MMLU-Pro · Arcadia Impact").
|
| 915 |
+
*/
|
| 916 |
+
function groupSameBenchAcrossSources(h: CleanableHierarchy) {
|
| 917 |
+
const sourceLabel = (
|
| 918 |
+
bench: HierarchyBenchmark,
|
| 919 |
+
fallback: string,
|
| 920 |
+
): string => {
|
| 921 |
+
const sources = (bench.metrics ?? []).flatMap((m) => m.sources ?? [])
|
| 922 |
+
for (const s of sources) {
|
| 923 |
+
const trimmed = String(s ?? "").trim()
|
| 924 |
+
if (trimmed) return trimmed
|
| 925 |
+
}
|
| 926 |
+
return fallback
|
| 927 |
+
}
|
| 928 |
+
const slugifyShort = (s: string) =>
|
| 929 |
+
s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "")
|
| 930 |
+
|
| 931 |
+
type Handle = { fam: HierarchyFamily; bench: HierarchyBenchmark }
|
| 932 |
+
const collectBenches = (fam: HierarchyFamily): HierarchyBenchmark[] => [
|
| 933 |
+
...(fam.benchmarks ?? []),
|
| 934 |
+
...(fam.standalone_benchmarks ?? []),
|
| 935 |
+
...(fam.composites ?? []).flatMap((c) => c.benchmarks ?? []),
|
| 936 |
+
]
|
| 937 |
+
const candidatesByKey = new Map<string, Handle[]>()
|
| 938 |
+
for (const fam of h.families ?? []) {
|
| 939 |
+
const benches = collectBenches(fam)
|
| 940 |
+
if (benches.length !== 1) continue
|
| 941 |
+
const sole = benches[0]
|
| 942 |
+
const list = candidatesByKey.get(sole.key) ?? []
|
| 943 |
+
list.push({ fam, bench: sole })
|
| 944 |
+
candidatesByKey.set(sole.key, list)
|
| 945 |
+
}
|
| 946 |
+
|
| 947 |
+
const dropped = new Set<HierarchyFamily>()
|
| 948 |
+
for (const [, group] of candidatesByKey) {
|
| 949 |
+
if (group.length < 2) continue
|
| 950 |
+
// eval_ids must be disjoint (otherwise an earlier rule should have
|
| 951 |
+
// caught them as aliases of the same row).
|
| 952 |
+
const seenIds = new Set<string>()
|
| 953 |
+
let disjoint = true
|
| 954 |
+
for (const entry of group) {
|
| 955 |
+
for (const id of entry.bench.summary_eval_ids ?? []) {
|
| 956 |
+
if (seenIds.has(id)) { disjoint = false; break }
|
| 957 |
+
seenIds.add(id)
|
| 958 |
+
}
|
| 959 |
+
if (!disjoint) break
|
| 960 |
+
}
|
| 961 |
+
if (!disjoint) continue
|
| 962 |
+
|
| 963 |
+
const sortedGroup = [...group].sort((x, y) => {
|
| 964 |
+
const xModels = x.bench.metrics?.[0]?.models_count ?? 0
|
| 965 |
+
const yModels = y.bench.metrics?.[0]?.models_count ?? 0
|
| 966 |
+
if (xModels !== yModels) return yModels - xModels
|
| 967 |
+
return x.fam.key.localeCompare(y.fam.key)
|
| 968 |
+
})
|
| 969 |
+
const survivor = sortedGroup[0]
|
| 970 |
+
const baseDisplay =
|
| 971 |
+
survivor.bench.display_name?.trim() ||
|
| 972 |
+
survivor.fam.display_name?.trim() ||
|
| 973 |
+
survivor.bench.key
|
| 974 |
+
|
| 975 |
+
for (const entry of sortedGroup) {
|
| 976 |
+
const src = sourceLabel(
|
| 977 |
+
entry.bench,
|
| 978 |
+
entry.fam.display_name || entry.fam.key,
|
| 979 |
+
)
|
| 980 |
+
entry.bench.display_name = `${baseDisplay} · ${src}`
|
| 981 |
+
if (entry !== survivor) {
|
| 982 |
+
entry.bench.key = `${survivor.bench.key}__${slugifyShort(src) || slugifyShort(entry.fam.key)}`
|
| 983 |
+
survivor.fam.benchmarks = survivor.fam.benchmarks ?? []
|
| 984 |
+
survivor.fam.benchmarks.push(entry.bench)
|
| 985 |
+
dropped.add(entry.fam)
|
| 986 |
+
}
|
| 987 |
+
}
|
| 988 |
+
survivor.fam.display_name = baseDisplay
|
| 989 |
+
}
|
| 990 |
+
|
| 991 |
+
if (dropped.size === 0) return
|
| 992 |
+
h.families = (h.families ?? []).filter((fam) => !dropped.has(fam))
|
| 993 |
+
}
|
| 994 |
+
|
| 995 |
/**
|
| 996 |
* AIR-Bench 2024 is a single safety benchmark with a 4-tier taxonomy
|
| 997 |
* (314 leaf risk categories per the spec, ~30 of which the HELM
|