general-eval-card / lib /clean-hierarchy.ts
j-chim's picture
Fix validated evaluator displays
4bce289
Raw
History Blame
71 kB
import type {
BenchmarkIndexAppearance,
BenchmarkIndexEntry,
EvalHierarchy,
HierarchyBenchmark,
HierarchyComposite,
HierarchyFamily,
} from "@/lib/backend-artifacts"
import { decorateHierarchyDerivedTags } from "@/lib/benchmark-tags"
const CLEANED_MARKER = "_evalCardCleaned" as const
type CleanableHierarchy = EvalHierarchy & { [CLEANED_MARKER]?: boolean }
// Families where the warehouse splits ONE underlying benchmark into N
// parallel children. Two collapse strategies:
//
// mode: "composite" β€” the children stay as distinct benchmarks but get
// wrapped in one synthetic composite. Used for Fibble Arena (1-/2-/
// 3-/…-lies are genuinely different game variants) and CapArena-Auto
// (vs-cogvlm / vs-gpt-4o / … are different reference comparators).
//
// mode: "slices" β€” the children collapse into a single standalone
// benchmark whose `slices[]` are the former children. Used for
// AgentHarm where each child is a category score (Harassment, Fraud,
// Disinformation, …) of one underlying benchmark. The synthetic
// standalone owns the union of constituent_evaluation_ids; each slice carries
// its source child's metrics verbatim.
//
// Keyed by `family.key`.
type SplitFamilyRule =
| { mode: "composite"; syntheticKey: string; syntheticDisplayName: string }
| { mode: "slices"; syntheticKey: string; syntheticDisplayName: string }
// Group sibling benchmarks by stripping a trailing "(...)" suffix from
// their display_name; each prefix becomes its own standalone benchmark
// with language/variant splits underneath. Used for SWE-PolyBench
// (8 benches β†’ 2 standalones Γ— 4 language splits) and Multi-SWE-Bench.
| { mode: "paren-suffix-splits" }
// Hoist all composite children up to family.benchmarks. Used for
// reward-bench whose composites group siblings that should sit at the
// family level (RewardBench, RewardBench 2, RewardBench Safety = 3
// distinct benchmarks, not nested inside two composite wrappers).
| { mode: "flatten-composites" }
// Group siblings by display_name prefix and fold the parenthetical
// suffix INTO a metric label rather than into a split. Used when the
// suffix denotes a metric ("Humanity's Last Exam (accuracy)" /
// "(calibration error)" β†’ one benchmark with two metrics).
| { mode: "paren-suffix-metrics" }
const SPLIT_FAMILIES: Record<string, SplitFamilyRule> = {
// Fibble Arena: the warehouse already ships the canonical "fibble-arena"
// benchmark with 6 internal slices (fibble_arena_*lie, 3 metrics each).
// The fibble1-arena .. fibble5-arena composites are warehouse-duplicates
// of those slices. "slices" mode auto-detects this (parent already has
// slices β†’ drop siblings) and we end up with one standalone benchmark.
"fibble-arena": {
mode: "slices",
syntheticKey: "fibble-arena",
syntheticDisplayName: "Fibble Arena",
},
// CapArena-Auto: 5 sibling benchmarks (Caparena AUTO AVG, Caption
// Length, vs-cogvlm-19b, vs-gpt-4o, vs-minicpm-8b) that should fold
// into a single CapArena-Auto benchmark with 5 splits.
caparena: {
mode: "slices",
syntheticKey: "caparena-auto",
syntheticDisplayName: "CapArena-Auto",
},
// AgentHarm: the warehouse ships an `agentharm` benchmark with no
// slices alongside ~8 sibling category-scored benchmarks (Copyright,
// Cybercrime, Drugs, Hate, Sexual, air-bench-2024-*, disinformation).
// Those siblings are category-level scores of the same benchmark and
// belong as slices. "slices" mode folds them into the parent.
agentharm: {
mode: "slices",
syntheticKey: "agentharm",
syntheticDisplayName: "AgentHarm",
},
// MATH-MC: 5 sibling Level-1..Level-5 benchmarks should fold into one
// math-mc benchmark with 5 splits.
"math-mc": {
mode: "slices",
syntheticKey: "math-mc",
syntheticDisplayName: "MATH-MC",
},
// GSM-MC: lone GSM-MC sibling under a same-named family β€” collapse to
// a single standalone (the family wrapper is redundant).
"gsm-mc": {
mode: "slices",
syntheticKey: "gsm-mc",
syntheticDisplayName: "GSM-MC",
},
// MT-Bench: overall / turn 1 / turn 2 are splits of the same benchmark.
"mt-bench": {
mode: "slices",
syntheticKey: "mt-bench",
syntheticDisplayName: "MT-Bench",
},
// SWE-PolyBench: 8 sibling benches β†’ 2 standalones (PolyBench /
// PolyBench Verified) Γ— 4 language splits.
"swe-polybench-leaderboard": { mode: "paren-suffix-splits" },
// Multi-SWE-Bench: 6 language siblings β†’ 1 standalone Γ— 6 language splits.
"multi-swe-bench-leaderboard": { mode: "paren-suffix-splits" },
// RewardBench: composites currently wrap 3 benchmarks (rewardbench,
// rewardbench-2, rewardbench-safety) that should sit at family level.
"reward-bench": { mode: "flatten-composites" },
// CySE2: 3 composite-wrapped sibling benchmarks (interpreter-abuse,
// prompt-injection, vulnerability-exploit) that are category-level
// scores of one CySE2 benchmark. Fold into one standalone with 3
// splits.
cyse2: {
mode: "slices",
syntheticKey: "cyse2",
syntheticDisplayName: "CySE2",
},
// HLE: "Humanity's Last Exam (accuracy)" / "(calibration error)" are
// the same benchmark with two different metrics. Fold into one
// benchmark whose metrics are accuracy + calibration error.
hle: { mode: "paren-suffix-metrics" },
// BFCL: 7 sibling category benchmarks (Live, Multi Turn, Non Live,
// Web Search, Format Sensitivity, Memory, Relevance) are all category
// splits of one BFCL benchmark.
bfcl: {
mode: "slices",
syntheticKey: "bfcl",
syntheticDisplayName: "BFCL",
},
// HF Open LLM v2: 6 curated benchmarks (BBH, GPQA, IFEval, MATH-Lvl 5,
// MMLU-Pro, MuSR) that together define the leaderboard. Wrap them in a
// synthetic composite so the family card surfaces it as one composite
// benchmark with 6 children. The members are also protected from the
// `isPoorerDuplicate` filter (see PROTECTED_LEADERBOARD_FAMILIES) since
// richer copies elsewhere (mmlu-pro-leaderboard, ifeval) would otherwise
// strip them out of this family.
"hf-open-llm-v2": {
mode: "composite",
syntheticKey: "hf-open-llm-v2",
syntheticDisplayName: "HF Open LLM v2",
},
}
/**
* Families whose member benchmarks are integral to a curated leaderboard
* and must survive `consolidateDedicatedHomeBenchmarks` even when richer
* copies of the same benchmark exist elsewhere. Without this guard the
* dedup filter strips e.g. MMLU-Pro and IFEval from the HF Open LLM v2
* family because mmlu-pro-leaderboard / ifeval families publish them
* with more slices.
*/
const PROTECTED_LEADERBOARD_FAMILIES = new Set<string>(["hf-open-llm-v2"])
/**
* NOTE: this hierarchy cleaning belongs in the producer pipeline; it lives
* here as a stopgap until that backend refactor lands.
*
* One-shot post-processor that turns the warehouse's raw hierarchy into a
* frontend-ready artefact:
* 1. Sanitises display names that the upstream pipeline accidentally
* leaks across families (e.g. WASP's name landing on `math-mc` /
* `gsm-mc`). Empty / missing names fall back to a humanised slug.
* 2. Decorates every node with `derivedTags` from
* data/benchmarks/categories.json β€” top-down inheritance + a
* bottom-up union so families inherit their children's tags. Powers
* the `/evals` category chips and the model-view re-bucketing.
* 3. Filters `benchmark_index[]` to drop family-rollup entries (the
* producer occasionally emits one entry per family enumerating
* every sibling benchmark β€” `key="artificial analysis"` listing 11
* of them, `key="llm stats"` listing 34). Real cross-family
* duplicates collapse to ≀2 distinct `benchmark_key` values; rollups
* span every benchmark in a family. Also dedupes `(family_key,
* eval_summary_id)` pairs since the producer occasionally lists the
* same eval row under multiple "families" (math-500 appears under
* both family=math and family=artificial-analysis pointing at the
* same `artificial-analysis-llms%2Fmath-500` row).
*
* Mutates in place and tags the returned object with `_evalCardCleaned:
* true` so re-applying the cleaner is a no-op. Designed to run server-
* side once per snapshot fetch and persist via the sidecar disk cache;
* client-side `decorateHierarchyDerivedTags` calls become no-ops on
* already-cleaned data.
*/
/**
* Optional comparison-index payload β€” when supplied, the cleaner can
* dedupe aggregator (llm-stats) appearances by checking whether their
* scores literally match a non-aggregator family's scores for the same
* canonical benchmark. Doing this at hierarchy-build time means the
* frontend never has to think about aggregator dedup again.
*/
type ComparisonIndexLike = {
evals: Record<
string,
{
metrics: Array<{
metric_summary_id: string
metric_name?: string | null
scores: Array<{
model_route_id?: string | null
model_group_id?: string | null
score?: number | null
}>
}>
}
>
}
export function cleanHierarchy(
raw: EvalHierarchy,
comparisonIndex?: ComparisonIndexLike | null,
): EvalHierarchy {
const h = raw as CleanableHierarchy
if (h[CLEANED_MARKER]) return h
consolidateAirBench(h)
consolidateDedicatedHomeBenchmarks(h)
collapseValsAiSetupVariants(h)
dedupValsAiAliasedBenches(h)
flattenSplitFamilies(h)
dropGroupingLeaderboardRollups(h)
if (comparisonIndex) {
dedupAggregatorBenchesByScore(h, comparisonIndex)
}
decorateHierarchyDerivedTags(h)
// Run AFTER the sanitizer so it can't clobber our suffixed bench
// display_names (`MMLU-Pro Β· Arcadia Impact` etc.) β€” the merged-in
// bench keys deliberately use a non-shareToken-matching form to
// keep them distinct, which would otherwise trip the humanizeKey
// fallback in benchmark-tags.sanitizeName.
groupSameBenchAcrossSources(h)
if (h.benchmark_index) {
const survivingFamilyKeys = new Set<string>(
(h.families ?? []).map((f) => f.key),
)
h.benchmark_index = filterBenchmarkIndex(
h.benchmark_index,
survivingFamilyKeys,
)
}
recomputeStats(h)
if (comparisonIndex) {
h._modelCoverageMap = buildModelCoverageMap(h, comparisonIndex)
}
h[CLEANED_MARKER] = true
return h
}
/**
* Build a { model_id β†’ distinct_benchmark_count } map from the cleaned
* hierarchy and the comparison-index scores. Emitted under both the
* comparison-index's `model_route_id` (dunder form, e.g. `openai__gpt-5`)
* AND its slash-form equivalent (`openai/gpt-5`) so lookups from the
* model-card layer β€” which exposes the slash-form `route_id` β€” resolve.
*
* Steps:
* 1. Walk every surviving benchmark's constituent_evaluation_ids to build
* eval_summary_id β†’ benchmark_key.
* 2. Walk comparison-index scores to collect, per model, the set of
* constituent_evaluation_ids it has a finite score for.
* 3. For each model, count the distinct benchmark_keys reachable
* from its covered eval ids; emit under both id surfaces.
*/
function buildModelCoverageMap(
h: CleanableHierarchy,
comparisonIndex: ComparisonIndexLike,
): Record<string, number> {
// Step 1: eval_summary_id β†’ benchmark_key
const evalToBenchmark = new Map<string, string>()
const visitBench = (b: HierarchyBenchmark) => {
for (const id of b.constituent_evaluation_ids ?? []) {
if (!evalToBenchmark.has(id)) evalToBenchmark.set(id, b.key)
}
}
for (const fam of h.families ?? []) {
for (const b of fam.benchmarks ?? []) visitBench(b)
for (const b of fam.standalone_benchmarks ?? []) visitBench(b)
for (const c of fam.composites ?? []) {
for (const b of c.benchmarks ?? []) visitBench(b)
}
}
// Step 2: model_route_id β†’ Set<eval_summary_id with a finite score>
const modelEvals = new Map<string, Set<string>>()
for (const [evalId, entry] of Object.entries(comparisonIndex.evals ?? {})) {
for (const metric of entry.metrics ?? []) {
for (const row of metric.scores ?? []) {
const modelId = row.model_route_id || row.model_group_id
if (!modelId || row.score == null || !Number.isFinite(row.score as number)) continue
const set = modelEvals.get(modelId) ?? new Set<string>()
set.add(evalId)
modelEvals.set(modelId, set)
}
}
}
// Step 3: count distinct benchmark keys per model; emit under both
// the dunder form (matches comparison-index keys) and the slash form
// (matches the model card's `route_id`). Without the slash alias the
// data-backend lookup misses every row.
const coverage: Record<string, number> = {}
for (const [modelId, evalIds] of modelEvals) {
const benchKeys = new Set<string>()
for (const id of evalIds) {
const bKey = evalToBenchmark.get(id)
if (bKey) benchKeys.add(bKey)
}
if (benchKeys.size === 0) continue
coverage[modelId] = benchKeys.size
if (modelId.includes("__")) {
coverage[modelId.replace(/__/g, "/")] = benchKeys.size
}
}
return coverage
}
/**
* Recompute the headline counts on `stats` so the home / evals pages
* reflect the post-consolidation hierarchy. Upstream's `stats` block is
* derived from the raw warehouse output, but the cleaner drops
* aggregator duplicates, leaderboard wrappers, and folds split families
* β€” so families / composites / benchmarks / slices / metrics all
* shrink. `metric_rows_scanned` measures the raw producer's input
* volume and stays as-is.
*/
function recomputeStats(h: CleanableHierarchy) {
let familyCount = 0
let compositeCount = 0
let benchmarkCount = 0
let sliceCount = 0
let metricCount = 0
for (const fam of h.families ?? []) {
familyCount++
const visit = (b: HierarchyBenchmark) => {
benchmarkCount++
const slices = (b.slices ?? []) as Array<{ metrics?: unknown[] }>
sliceCount += slices.length
for (const s of slices) {
metricCount += s.metrics?.length ?? 0
}
}
for (const b of fam.benchmarks ?? []) visit(b)
for (const b of fam.standalone_benchmarks ?? []) visit(b)
for (const c of fam.composites ?? []) {
compositeCount++
for (const b of c.benchmarks ?? []) visit(b)
}
}
const stats = (h as { stats?: Record<string, number> }).stats ?? {}
stats.family_count = familyCount
stats.composite_count = compositeCount
stats.benchmark_count = benchmarkCount
stats.slice_count = sliceCount
stats.metric_count = metricCount
;(h as { stats?: Record<string, number> }).stats = stats
}
const AGGREGATOR_FAMILY_KEYS = new Set<string>(["llm-stats"])
/**
* Drop an aggregator family's benchmark when its scores match a
* non-aggregator family's benchmark with the same key, for every model
* they share. Same scores across the entire shared model set is the
* "literally the same data" signal: aggregators republish numbers from
* canonical sources, so when the numbers line up exactly they're not
* an independent report. We compare with tight tolerance (1e-9) so true
* floating-point equality counts but rounding differences (3-decimal
* vs 4-decimal precision in the source) leave the appearances alone.
*/
function dedupAggregatorBenchesByScore(
h: CleanableHierarchy,
comparisonIndex: ComparisonIndexLike,
) {
type BenchHandle = {
fam: HierarchyFamily
bench: HierarchyBenchmark
container: HierarchyBenchmark[]
}
const allHandles: BenchHandle[] = []
for (const fam of h.families ?? []) {
if (fam.benchmarks) {
for (const b of fam.benchmarks)
allHandles.push({ fam, bench: b, container: fam.benchmarks })
}
if (fam.standalone_benchmarks) {
for (const b of fam.standalone_benchmarks)
allHandles.push({ fam, bench: b, container: fam.standalone_benchmarks })
}
for (const c of fam.composites ?? []) {
if (c.benchmarks) {
for (const b of c.benchmarks)
allHandles.push({ fam, bench: b, container: c.benchmarks })
}
}
}
// Map model_route_id β†’ score for a given eval id, picking the metric
// whose summary id local part most closely matches `metricHint` (so
// accuracy compares to accuracy, not accuracy vs stderr). When no
// hint is provided, use the eval's primary metric (first non-stderr).
const STDERR_PATTERN = /_(stderr|std_err|standard_error)$/i
const isStderr = (id: string) => STDERR_PATTERN.test(id)
const metricLocal = (id: string) =>
id.split("%3A").pop()?.toLowerCase().trim() ?? ""
const buildScoreMap = (
evalId: string,
metricHint: string | null,
): Map<string, number> | null => {
const evalEntry = comparisonIndex.evals[evalId]
if (!evalEntry || !evalEntry.metrics?.length) return null
const usableMetrics = evalEntry.metrics.filter(
(m) => !isStderr(m.metric_summary_id ?? ""),
)
if (usableMetrics.length === 0) return null
const target =
(metricHint &&
usableMetrics.find(
(m) => metricLocal(m.metric_summary_id) === metricHint,
)) ||
usableMetrics[0]
const map = new Map<string, number>()
for (const row of target.scores ?? []) {
const id = row.model_route_id || row.model_group_id
if (!id || row.score == null || !Number.isFinite(row.score)) continue
map.set(id, row.score as number)
}
return map
}
const scoresEqual = (a: number, b: number) => Math.abs(a - b) <= 1e-9
// Group handles by benchmark key (the canonical identity). For each
// key with both aggregator and non-aggregator copies, compare their
// scores; if every shared model agrees exactly, drop the aggregator
// bench from its family.
const byKey = new Map<string, BenchHandle[]>()
for (const h of allHandles) {
const list = byKey.get(h.bench.key) ?? []
list.push(h)
byKey.set(h.bench.key, list)
}
const drops = new Set<HierarchyBenchmark>()
for (const handles of byKey.values()) {
if (handles.length < 2) continue
const aggHandles = handles.filter((h) =>
AGGREGATOR_FAMILY_KEYS.has(h.fam.key),
)
const nonAgg = handles.filter((h) => !AGGREGATOR_FAMILY_KEYS.has(h.fam.key))
if (aggHandles.length === 0 || nonAgg.length === 0) continue
for (const agg of aggHandles) {
const aggIds = agg.bench.constituent_evaluation_ids ?? []
if (aggIds.length === 0) continue
for (const peer of nonAgg) {
const peerIds = peer.bench.constituent_evaluation_ids ?? []
if (peerIds.length === 0) continue
// Try to match scores between aggregator's eval and peer's
// eval. Compare against the first eval id pair where both
// sides have a score map.
let matched = false
outer: for (const aId of aggIds) {
const aMap = buildScoreMap(aId, null)
if (!aMap || aMap.size === 0) continue
for (const pId of peerIds) {
const pMap = buildScoreMap(pId, null)
if (!pMap || pMap.size === 0) continue
// Need at least 3 shared models for a confident match;
// sub-3-model overlaps are easy to coincide by chance.
const shared: Array<[number, number]> = []
for (const [model, aScore] of aMap) {
const pScore = pMap.get(model)
if (pScore != null) shared.push([aScore, pScore])
}
if (shared.length < 3) continue
const allEqual = shared.every(([x, y]) => scoresEqual(x, y))
if (allEqual) {
matched = true
break outer
}
}
}
if (matched) {
drops.add(agg.bench)
break
}
}
}
}
if (drops.size === 0) return
for (const fam of h.families ?? []) {
if (fam.benchmarks)
fam.benchmarks = fam.benchmarks.filter((b) => !drops.has(b))
if (fam.standalone_benchmarks)
fam.standalone_benchmarks = fam.standalone_benchmarks.filter(
(b) => !drops.has(b),
)
for (const c of fam.composites ?? []) {
if (c.benchmarks)
c.benchmarks = c.benchmarks.filter((b) => !drops.has(b))
}
if (fam.composites) {
fam.composites = fam.composites.filter(
(c) => (c.benchmarks ?? []).length > 0,
)
}
}
// Drop emptied aggregator families.
h.families = (h.families ?? []).filter((fam) => {
const total =
(fam.benchmarks ?? []).length +
(fam.standalone_benchmarks ?? []).length +
(fam.composites ?? []).reduce(
(n, c) => n + (c.benchmarks ?? []).length,
0,
)
return total > 0
})
}
/**
* Drop strictly-poorer duplicate appearances of the same benchmark
* across families. Two snapshots in the wild:
*
* APEX v1 β€” appears under `apex-v1/apex-v1` with 5 slices AND under
* `apex-agents/apex-v1` with 1 slice. The 1-slice copy is a partial
* duplicate that just clutters apex-agents.
*
* MMLU-Pro β€” appears under `mmlu-pro/mmlu-pro` with 1 slice/metric
* AND under `mmlu-pro-leaderboard/mmlu-pro` with 15 slices/15
* metrics. The 1-slice copy is the impoverished one.
*
* Rule: if benchmark.key appears in multiple families, score each copy
* by `slice_count + metric_count` and drop strictly poorer copies. Ties
* stay (e.g. `big-bench` and `big-bench-hard` both list the same
* 1-slice BBH β€” without a richness gap we can't pick a winner, so we
* leave the warehouse's intended grouping in place). Families that end
* up with zero benchmarks after the drop are removed too.
*/
function consolidateDedicatedHomeBenchmarks(h: CleanableHierarchy) {
const richness = (b: HierarchyBenchmark): number => {
const slices = b.slices ?? []
const sliceCount = slices.length
const metricCount = slices.reduce(
(n, s) => n + ((s as { metrics?: unknown[] }).metrics?.length ?? 0),
0,
)
return sliceCount + metricCount
}
// Collect every appearance of every benchmark, indexed by key, so the
// duplicate check can compare each instance against its peers.
const instancesByKey = new Map<string, HierarchyBenchmark[]>()
const visit = (b: HierarchyBenchmark) => {
const list = instancesByKey.get(b.key)
if (list) list.push(b)
else instancesByKey.set(b.key, [b])
}
for (const fam of h.families ?? []) {
for (const b of fam.benchmarks ?? []) visit(b)
for (const b of fam.standalone_benchmarks ?? []) visit(b)
for (const c of fam.composites ?? []) {
for (const b of c.benchmarks ?? []) visit(b)
}
}
const sharesAnyEvalId = (a: HierarchyBenchmark, b: HierarchyBenchmark) => {
const aIds = new Set(a.constituent_evaluation_ids ?? [])
if (aIds.size === 0) return false
for (const id of b.constituent_evaluation_ids ?? []) if (aIds.has(id)) return true
return false
}
// Drop strictly-poorer copies, BUT only when the poor copy shares an
// eval_summary_id with a richer instance. Without that gate, a curated
// family (e.g. `gaia` reporting GAIA at richness 2 with eval_id
// `gaia%2Fgaia`) gets nuked just because some unrelated source family
// (`hal` reporting GAIA at richness 8 with eval_id `hal%2Fgaia`)
// happens to use the same bench key β€” they're different physical
// rows and both deserve to surface. With the gate, livebench's
// structurally-poorer rows still lose to live-bench since they share
// eval_ids (`live-bench%2Flivebench-coding` lives in both).
const isPoorerDuplicate = (b: HierarchyBenchmark): boolean => {
const peers = instancesByKey.get(b.key) ?? []
const r = richness(b)
return peers.some(
(peer) => peer !== b && richness(peer) > r && sharesAnyEvalId(peer, b),
)
}
for (const fam of h.families ?? []) {
// Skip dedup for curated-leaderboard families whose constituent
// benchmarks define the leaderboard's identity (HF Open LLM v2 etc.).
// Without this, richer copies elsewhere strip the leaderboard down.
if (PROTECTED_LEADERBOARD_FAMILIES.has(fam.key)) continue
if (fam.benchmarks) {
fam.benchmarks = fam.benchmarks.filter((b) => !isPoorerDuplicate(b))
}
if (fam.standalone_benchmarks) {
fam.standalone_benchmarks = fam.standalone_benchmarks.filter(
(b) => !isPoorerDuplicate(b),
)
}
for (const c of fam.composites ?? []) {
if (c.benchmarks) {
c.benchmarks = c.benchmarks.filter((b) => !isPoorerDuplicate(b))
}
}
if (fam.composites) {
fam.composites = fam.composites.filter(
(c) => (c.benchmarks ?? []).length > 0,
)
}
}
// Drop emptied families.
h.families = (h.families ?? []).filter((fam) => {
const total =
(fam.benchmarks ?? []).length +
(fam.standalone_benchmarks ?? []).length +
(fam.composites ?? []).reduce(
(n, c) => n + (c.benchmarks ?? []).length,
0,
)
return total > 0
})
// Final pass: drop redundant wrapper families. Two cases:
//
// (a) Strict-subset wrapper: family A's benchmark set is a STRICT
// subset of family B's, with each shared bench having equal-or-
// better richness in B. Drop A, keep B. Catches the
// `livebench` family (3 benches) being a subset of `live-bench`
// (4 benches). Apex-v1 is safe because its copy is unique.
//
// (b) Self-wrapper tie: family.key == its sole benchmark.key AND
// another family also carries that benchmark at equal richness.
// Drop the self-wrapper, keep the other (parent) family.
// Catches `big-bench-hard` family being redundant when
// `big-bench` also lists it; same for `global-mmlu-lite`.
//
// Order matters: apply (a) first since it can leave a single
// benchmark behind that triggers (b).
type BenchHandle = { fam: HierarchyFamily; bench: HierarchyBenchmark }
const allFamilies = [...h.families]
const benchesByFam = new Map<HierarchyFamily, BenchHandle[]>()
for (const fam of allFamilies) {
const list: BenchHandle[] = []
for (const b of fam.benchmarks ?? []) list.push({ fam, bench: b })
for (const b of fam.standalone_benchmarks ?? []) list.push({ fam, bench: b })
for (const c of fam.composites ?? []) {
for (const b of c.benchmarks ?? []) list.push({ fam, bench: b })
}
benchesByFam.set(fam, list)
}
const dropped = new Set<HierarchyFamily>()
// Two appearances of the same benchmark key are "physically the
// same" only when they share at least one eval_summary_id. Different
// constituent_evaluation_ids mean different sources independently reporting on
// the same canonical benchmark (e.g. llm-stats and openai-humaneval
// both list HumanEval but report it from their own data). Don't
// collapse those.
const evalIds = (b: HierarchyBenchmark) =>
new Set(b.constituent_evaluation_ids ?? [])
const sharesEvalId = (
a: HierarchyBenchmark,
b: HierarchyBenchmark,
): boolean => {
const aIds = evalIds(a)
for (const id of evalIds(b)) if (aIds.has(id)) return true
return false
}
// (a) strict-subset.
//
// Only fires when A's family key is textually related to B's β€” they
// slugify the same, or one's slug contains the other. Without that
// gate the rule was eating curated benchmark families like
// `tau2-bench` whenever its rows happened to all be republished by a
// single source family (`exgentic-open-agent`). Those aren't redundant
// wrappers β€” they're separate curated leaderboards that the upstream
// pipeline intentionally surfaces. The intended target is wrappers
// like `livebench` (slug `livebench`) being a strict subset of
// `live-bench` (slug `livebench`), where the slugs match exactly.
const slugForKey = (key: string) =>
key.toLowerCase().replace(/[^a-z0-9]+/g, "")
const familyKeysRelated = (aKey: string, bKey: string) => {
const aSlug = slugForKey(aKey)
const bSlug = slugForKey(bKey)
if (!aSlug || !bSlug) return false
return aSlug === bSlug || aSlug.includes(bSlug) || bSlug.includes(aSlug)
}
for (const a of allFamilies) {
if (dropped.has(a)) continue
if (PROTECTED_LEADERBOARD_FAMILIES.has(a.key)) continue
const aBenches = benchesByFam.get(a) ?? []
if (aBenches.length === 0) continue
for (const b of allFamilies) {
if (a === b || dropped.has(b)) continue
if (!familyKeysRelated(a.key, b.key)) continue
const bBenches = benchesByFam.get(b) ?? []
if (bBenches.length <= aBenches.length) continue
const bByKey = new Map(bBenches.map((h) => [h.bench.key, h.bench]))
const aSubset = aBenches.every((h) => {
const peer = bByKey.get(h.bench.key)
if (peer == null) return false
if (!sharesEvalId(peer, h.bench)) return false
return richness(peer) >= richness(h.bench)
})
if (aSubset) {
dropped.add(a)
break
}
}
}
// (b) self-wrapper tie
for (const a of allFamilies) {
if (dropped.has(a)) continue
if (PROTECTED_LEADERBOARD_FAMILIES.has(a.key)) continue
const aBenches = benchesByFam.get(a) ?? []
if (aBenches.length !== 1) continue
const sole = aBenches[0].bench
if (sole.key !== a.key) continue
// Look for another (kept) family that carries this bench at >=
// richness AND with a shared eval_summary_id (= physical duplicate).
const elsewhere = allFamilies.some((b) => {
if (b === a || dropped.has(b)) return false
const peers = benchesByFam.get(b) ?? []
return peers.some(
(h) =>
h.bench.key === sole.key &&
richness(h.bench) >= richness(sole) &&
sharesEvalId(h.bench, sole),
)
})
if (elsewhere) dropped.add(a)
}
// (b2) leaderboard-wrapper merge. A family whose key ends in
// "-leaderboard" and contains exactly one benchmark whose key matches
// the family key minus that suffix is a thin wrapper around a
// benchmark that's already covered by another family. Merge the
// wrapper's constituent_evaluation_ids into the canonical bench so we don't
// lose the leaderboard's source row, then drop the wrapper family.
// Example: `swe-bench-verified-leaderboard` family with sole bench
// `swe-bench-verified` β€” merge its `swe-bench-verified-leaderboard%
// 2Fswe-bench-verified` eval id into swe-bench's `swe-bench-verified`
// bench.
// Aggregator families (e.g. llm-stats) republish numbers from other
// sources, but at hierarchy-build time we keep them as their own
// family. Score-equality-based aggregator dedup (per the user's
// earlier guidance) lives in the Overlaps view, not here. We use this
// set only as a tie-breaker when deciding which family inherits a
// wrapper's constituent_evaluation_ids.
const AGGREGATOR_KEYS = new Set<string>(["llm-stats"])
const isAggregator = (fam: HierarchyFamily) => AGGREGATOR_KEYS.has(fam.key)
// (b2) leaderboard-wrapper merge. A `*-leaderboard` family with one
// benchmark whose key matches its parent name is a thin wrapper
// around that benchmark. Merge its constituent_evaluation_ids into a sibling
// family that already carries the same bench, then drop the wrapper.
// Prefer a non-aggregator merge target so the leaderboard's row lands
// in the canonical paper home rather than llm-stats.
for (const a of allFamilies) {
if (dropped.has(a)) continue
if (!a.key.endsWith("-leaderboard")) continue
const aBenches = benchesByFam.get(a) ?? []
if (aBenches.length !== 1) continue
const sole = aBenches[0].bench
const stripped = a.key.replace(/-leaderboard$/, "")
if (sole.key !== stripped) continue
// Only merge the wrapper when a candidate carries the bench at
// equal-or-better richness. Without this, a thin one-slice copy of
// the bench in another family (e.g. hf-open-llm-v2's 1-slice
// mmlu-pro) would absorb a much richer wrapper (mmlu-pro-leaderboard
// with 15 slices) and silently drop the slice data.
const soleRichness = richness(sole)
const candidates = allFamilies.filter((b) => {
if (a === b || dropped.has(b)) return false
const peers = benchesByFam.get(b) ?? []
return peers.some(
(h) => h.bench.key === sole.key && richness(h.bench) >= soleRichness,
)
})
candidates.sort((x, y) => {
const xAgg = isAggregator(x) ? 1 : 0
const yAgg = isAggregator(y) ? 1 : 0
return xAgg - yAgg
})
const target = candidates[0]
if (!target) continue
const peer = (benchesByFam.get(target) ?? []).find(
(h) => h.bench.key === sole.key,
)
if (!peer) continue
const merged = new Set<string>()
for (const id of peer.bench.constituent_evaluation_ids ?? []) merged.add(id)
for (const id of sole.constituent_evaluation_ids ?? []) merged.add(id)
peer.bench.constituent_evaluation_ids = [...merged]
dropped.add(a)
}
// (c) sole-bench / shared-eval-id tie. Two families that each carry a
// single benchmark with the SAME constituent_evaluation_ids are surfacing the
// same physical data under different family keys β€” keys can differ
// (livecodebench vs livecodebenchpro both wrap the
// `livecodebenchpro%2Flivecodebench-pro` row). Drop one, preferring
// the family whose key best matches the benchmark's key (after
// stripping dashes / underscores), tie-broken alphabetically.
const flat = (s: string) =>
String(s ?? "").toLowerCase().replace(/[_\s-]+/g, "")
for (const a of allFamilies) {
if (dropped.has(a)) continue
const aBenches = benchesByFam.get(a) ?? []
if (aBenches.length !== 1) continue
const aSole = aBenches[0].bench
const aIds = new Set(aSole.constituent_evaluation_ids ?? [])
if (aIds.size === 0) continue
for (const b of allFamilies) {
if (a === b || dropped.has(b)) continue
const bBenches = benchesByFam.get(b) ?? []
if (bBenches.length !== 1) continue
const bSole = bBenches[0].bench
const bIds = new Set(bSole.constituent_evaluation_ids ?? [])
if (bIds.size !== aIds.size) continue
let same = true
for (const id of aIds) if (!bIds.has(id)) { same = false; break }
if (!same) continue
// Tie-break: pick the loser. The family whose flattened key
// doesn't match the bench's flattened key loses; if both match or
// neither matches, alphabetically-later loses.
const aMatch = flat(a.key) === flat(aSole.key)
const bMatch = flat(b.key) === flat(bSole.key)
let loser: HierarchyFamily | null = null
if (aMatch && !bMatch) loser = b
else if (bMatch && !aMatch) loser = a
else loser = a.key < b.key ? b : a
dropped.add(loser)
if (loser === a) break
}
}
// (d) Drop single-bench families that are pure aliases of bench
// rows already published under another family.
//
// The upstream registry occasionally lists the same physical eval
// row under two family names β€” the canonical example is the `mmlu`
// family, whose sole bench `mmlu-pro` carries eval_id
// `artificial-analysis-llms%2Fmmlu-pro`. That id is ALREADY covered
// by the `artificial-analysis` family's mmlu-pro bench, so the
// `mmlu` family is just a duplicate breadcrumb that doesn't add any
// new data path.
//
// A family qualifies for drop only when (1) it has exactly one
// bench, and (2) every one of that bench's constituent_evaluation_ids is
// already carried by some other surviving family. That keeps
// independent sources alive β€” `mmlu-pro` (eval_id
// `mmlu-pro%2Fmmlu-pro`) and `mmlu-pro-leaderboard` (eval_id
// `mmlu-pro-leaderboard%2Fmmlu-pro`) each have a unique source row,
// so neither is a pure alias and both stay.
const idsCoveredByFamily = new Map<HierarchyFamily, Set<string>>()
for (const fam of allFamilies) {
if (dropped.has(fam)) continue
const ids = new Set<string>()
for (const handle of benchesByFam.get(fam) ?? []) {
for (const id of handle.bench.constituent_evaluation_ids ?? []) ids.add(id)
}
idsCoveredByFamily.set(fam, ids)
}
// Pre-compute the set of surviving family keys so we can also gate
// on "the bench key has its own canonical family". Without this gate
// we'd incorrectly drop curated leaderboards (`tau-bench` whose sole
// bench `tau-bench-airline` is only covered by the `hal` source
// family) just because some other family already publishes the row.
const familyKeysAlive = new Set<string>()
for (const fam of allFamilies) {
if (!dropped.has(fam)) familyKeysAlive.add(fam.key)
}
for (const fam of allFamilies) {
if (dropped.has(fam)) continue
const benches = benchesByFam.get(fam) ?? []
if (benches.length !== 1) continue
const soleBench = benches[0].bench
// Bench key must already belong to another family β€” that family is
// the canonical home; this single-bench family is just a stray
// breadcrumb. (Skipping when the bench key equals the family key
// itself, since that's the family BEING the canonical home.)
if (soleBench.key === fam.key) continue
if (!familyKeysAlive.has(soleBench.key)) continue
const soleIds = soleBench.constituent_evaluation_ids ?? []
if (soleIds.length === 0) continue
const allCoveredElsewhere = soleIds.every((id) => {
for (const [otherFam, otherIds] of idsCoveredByFamily) {
if (otherFam === fam || dropped.has(otherFam)) continue
if (otherIds.has(id)) return true
}
return false
})
if (allCoveredElsewhere) dropped.add(fam)
}
h.families = allFamilies.filter((fam) => !dropped.has(fam))
}
/**
* Group single-bench families that publish the same conceptual benchmark
* from different upstream sources into one merged family card.
*
* Triggered when β‰₯2 single-bench families share a bench key but their
* bench rows have non-overlapping constituent_evaluation_ids β€” i.e. independent
* sources publishing the same benchmark. The richest family (most
* models) keeps its slot; other families contribute their bench under
* the survivor as siblings, with each bench's display_name suffixed
* with " Β· <Source>" so the user can tell which run a row came from.
*
* Runs AFTER decorateHierarchyDerivedTags so the sanitizer's
* shareToken / humanizeKey passes don't clobber the suffixed names
* (the merged-in bench keys like `mmlu-pro__arcadia` deliberately
* don't share tokens with "MMLU-Pro Β· Arcadia Impact").
*/
function groupSameBenchAcrossSources(h: CleanableHierarchy) {
const sourceLabel = (
bench: HierarchyBenchmark,
fallback: string,
): string => {
const sources = (bench.metrics ?? []).flatMap((m) => m.sources ?? [])
for (const s of sources) {
const trimmed = String(s ?? "").trim()
if (trimmed) return trimmed
}
return fallback
}
const slugifyShort = (s: string) =>
s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "")
type Handle = { fam: HierarchyFamily; bench: HierarchyBenchmark }
const collectBenches = (fam: HierarchyFamily): HierarchyBenchmark[] => [
...(fam.benchmarks ?? []),
...(fam.standalone_benchmarks ?? []),
...(fam.composites ?? []).flatMap((c) => c.benchmarks ?? []),
]
const candidatesByKey = new Map<string, Handle[]>()
for (const fam of h.families ?? []) {
const benches = collectBenches(fam)
if (benches.length !== 1) continue
const sole = benches[0]
const list = candidatesByKey.get(sole.key) ?? []
list.push({ fam, bench: sole })
candidatesByKey.set(sole.key, list)
}
const dropped = new Set<HierarchyFamily>()
for (const [, group] of candidatesByKey) {
if (group.length < 2) continue
// eval_ids must be disjoint (otherwise an earlier rule should have
// caught them as aliases of the same row).
const seenIds = new Set<string>()
let disjoint = true
for (const entry of group) {
for (const id of entry.bench.constituent_evaluation_ids ?? []) {
if (seenIds.has(id)) { disjoint = false; break }
seenIds.add(id)
}
if (!disjoint) break
}
if (!disjoint) continue
const sortedGroup = [...group].sort((x, y) => {
const xModels = x.bench.metrics?.[0]?.models_count ?? 0
const yModels = y.bench.metrics?.[0]?.models_count ?? 0
if (xModels !== yModels) return yModels - xModels
return x.fam.key.localeCompare(y.fam.key)
})
const survivor = sortedGroup[0]
const baseDisplay =
survivor.bench.display_name?.trim() ||
survivor.fam.display_name?.trim() ||
survivor.bench.key
for (const entry of sortedGroup) {
const src = sourceLabel(
entry.bench,
entry.fam.display_name || entry.fam.key,
)
entry.bench.display_name = `${baseDisplay} Β· ${src}`
if (entry !== survivor) {
entry.bench.key = `${survivor.bench.key}__${slugifyShort(src) || slugifyShort(entry.fam.key)}`
survivor.fam.benchmarks = survivor.fam.benchmarks ?? []
survivor.fam.benchmarks.push(entry.bench)
dropped.add(entry.fam)
}
}
survivor.fam.display_name = baseDisplay
}
if (dropped.size === 0) return
h.families = (h.families ?? []).filter((fam) => !dropped.has(fam))
}
/**
* AIR-Bench 2024 is a single safety benchmark with a 4-tier taxonomy
* (314 leaf risk categories per the spec, ~30 of which the HELM
* leaderboard exposes). The warehouse currently surfaces it in three
* places:
* 1. `helm` family > `helm-air-bench` composite (the canonical HELM
* shape, with one `air-bench-2024` benchmark whose `slices[]`
* enumerate the categories β€” 60 leaf eval rows).
* 2. `agentharm` family β€” 2 cherry-picked rows
* (`agentharm%2Fair-bench-2024-13-harassment`,
* `…32-fraud`) sitting alongside unrelated AgentHarm scores.
* 3. A standalone `air-bench-2024` family carrying the same 2 rows
* from #2.
*
* Consolidate everything under HELM AIR-Bench: drop the standalone
* family, strip AIR-Bench rows out of agentharm, and plant the union
* of every AIR-Bench eval id under helm's `constituent_evaluation_ids` and the
* helm-air-bench composite's benchmark `constituent_evaluation_ids`. The
* benchmark-id heuristic β€” `air-bench-2024` prefix β€” is narrow enough
* to be safe and broad enough to catch alternate sources.
*/
/**
* Drop "vals ai X" duplicates inside the vals-ai family.
*
* The upstream feed publishes some benchmarks twice under the same family
* β€” once with a canonical key (`mgsm`, `gpqa-overall`) and once with a
* `"vals ai <suffix>"` alias (`vals ai mgsm`, `vals ai gpqa`). The aliases
* are pure surface duplicates: same family, same models_count, same metric
* config, scores within rounding of each other. Keeping both makes the
* family card render the same benchmark twice. We drop the alias when a
* non-aliased sibling already carries the suffix.
*
* Aliases without a non-aliased sibling (`vals ai finance agent`) are
* preserved, and `vals_ai.swebench.<bucket>` time-buckets are untouched
* because they use `vals_ai.` (dot/underscore) instead of the `"vals ai "`
* (space) alias prefix.
*/
/**
* vals_ai records ship eval_names shaped "vals_ai.<benchmark>.<setup-variant>"
* β€” e.g. "vals_ai.swebench.>4 hours", "vals_ai.swebench.<15 min fix". The
* pre-patch producer treats those as separate benchmarks. They're really
* setup variants (time budgets) of a single benchmark; we collapse each
* leaked sibling into a new slice on the canonical bench.
*
* Once the producer (build_hierarchy_v2.py:EVAL_NAME_SHAPE) is re-run, this
* cleaner is a no-op β€” the leaked keys never appear.
*/
function collapseValsAiSetupVariants(h: CleanableHierarchy) {
const PATTERN = /^vals_ai\.([a-z0-9_-]+)\.(.+)$/i
// Tokenise for fuzzy "swebench" ↔ "swe-bench" matching.
const compact = (s: string) => s.toLowerCase().replace(/[^a-z0-9]/g, "")
for (const fam of h.families ?? []) {
if (fam.key !== "vals-ai") continue
const benches: HierarchyBenchmark[] = [
...(fam.benchmarks ?? []),
...(fam.standalone_benchmarks ?? []),
]
// Group leaked benches by bench label (e.g. "swebench") so we know
// which canonical sibling to merge each set into.
const leakedByBench = new Map<string, HierarchyBenchmark[]>()
for (const b of benches) {
const m = b.key.match(PATTERN)
if (!m) continue
const label = compact(m[1])
const arr = leakedByBench.get(label) ?? []
arr.push(b)
leakedByBench.set(label, arr)
}
if (leakedByBench.size === 0) continue
const removeKeys = new Set<string>()
for (const [benchLabel, leaked] of leakedByBench) {
const canonical = benches.find((b) => {
if (PATTERN.test(b.key)) return false
return compact(b.key) === benchLabel
})
if (!canonical) continue
const existingSliceKeys = new Set((canonical.slices ?? []).map((s) => s.key))
const mergedEvalIds = new Set(canonical.constituent_evaluation_ids ?? [])
for (const l of leaked) {
const m = l.key.match(PATTERN)
if (!m) continue
const variant = m[2].trim()
const sliceKey = `vals-ai-${variant
.toLowerCase()
.replace(/[^a-z0-9<>]+/g, "-")
.replace(/^-|-$/g, "") || "variant"}`
if (!existingSliceKeys.has(sliceKey)) {
canonical.slices = canonical.slices ?? []
canonical.slices.push({
key: sliceKey,
display_name: `Vals.ai Β· ${variant}`,
metrics: l.metrics ?? [],
})
existingSliceKeys.add(sliceKey)
}
for (const id of l.constituent_evaluation_ids ?? []) mergedEvalIds.add(id)
removeKeys.add(l.key)
}
canonical.constituent_evaluation_ids = Array.from(mergedEvalIds)
}
if (removeKeys.size > 0) {
if (fam.benchmarks) fam.benchmarks = fam.benchmarks.filter((b) => !removeKeys.has(b.key))
if (fam.standalone_benchmarks) {
fam.standalone_benchmarks = fam.standalone_benchmarks.filter((b) => !removeKeys.has(b.key))
}
for (const c of fam.composites ?? []) {
if (c.benchmarks) c.benchmarks = c.benchmarks.filter((b) => !removeKeys.has(b.key))
}
}
}
}
function dedupValsAiAliasedBenches(h: CleanableHierarchy) {
const ALIAS_PREFIX = "vals ai "
// Normalise to a set of word tokens so suffix "gpqa" matches sibling
// "gpqa-overall" but suffix "finance agent" doesn't match unrelated
// siblings.
const tokens = (key: string): Set<string> =>
new Set(
key
.toLowerCase()
.split(/[^a-z0-9]+/)
.filter(Boolean),
)
for (const fam of h.families ?? []) {
if (fam.key !== "vals-ai") continue
const benches = [
...(fam.benchmarks ?? []),
...(fam.standalone_benchmarks ?? []),
]
const siblingTokens = benches
.filter((b) => !b.key.startsWith(ALIAS_PREFIX))
.map((b) => ({ key: b.key, tokens: tokens(b.key) }))
const isAliasedDuplicate = (b: HierarchyBenchmark): boolean => {
if (!b.key.startsWith(ALIAS_PREFIX)) return false
const suffixTokens = tokens(b.key.slice(ALIAS_PREFIX.length))
if (suffixTokens.size === 0) return false
return siblingTokens.some(({ tokens: tks }) => {
for (const t of suffixTokens) if (!tks.has(t)) return false
return true
})
}
if (fam.benchmarks) {
fam.benchmarks = fam.benchmarks.filter((b) => !isAliasedDuplicate(b))
}
if (fam.standalone_benchmarks) {
fam.standalone_benchmarks = fam.standalone_benchmarks.filter(
(b) => !isAliasedDuplicate(b),
)
}
for (const c of fam.composites ?? []) {
if (c.benchmarks) {
c.benchmarks = c.benchmarks.filter((b) => !isAliasedDuplicate(b))
}
}
}
}
function consolidateAirBench(h: CleanableHierarchy) {
const isAirBenchEvalId = (id: string) =>
/(?:^|%2F)air-bench-2024(?:[-%]|$)/i.test(id)
const isAirBenchBenchmarkKey = (key: string) =>
/^air-bench-2024(?:[-_]|$)/i.test(key)
// Collect every AIR-Bench eval id surfaced anywhere in the hierarchy.
const airBenchEvalIds = new Set<string>()
for (const fam of h.families ?? []) {
for (const id of fam.constituent_evaluation_ids ?? []) {
if (isAirBenchEvalId(id)) airBenchEvalIds.add(id)
}
// The HELM AIR-Bench composite ships its 60+ leaf categories as
// entries on the rollup benchmark's `slices[]`, NOT in
// `family.constituent_evaluation_ids` (which only carries the rollup itself).
// Reconstruct the slice eval ids by combining the source prefix with
// each slice key so the consolidation step can plant them all under
// helm. Without this the leaves orphan to evalEntry.family_id and
// either land under their own ad-hoc section or vanish entirely
// when the standalone `air-bench-2024` family is dropped below.
for (const composite of fam.composites ?? []) {
for (const bench of composite.benchmarks ?? []) {
if (!isAirBenchBenchmarkKey(bench.key)) continue
const sourcePrefixes = new Set<string>()
for (const id of bench.constituent_evaluation_ids ?? []) {
if (id.includes("%2F")) sourcePrefixes.add(id.split("%2F")[0])
}
if (sourcePrefixes.size === 0) sourcePrefixes.add(composite.key)
for (const slice of bench.slices ?? []) {
// Only synthesise ids for real category slices (clean slugs). The
// raw fine-subtask keys ("airbench 2024 - #1.1: ...") aren't real
// evals, so they'd produce phantom constituents that 404. Stopgap β€”
// the proper fix is resolving those names to slugs upstream in the data.
if (!/^[a-z0-9][a-z0-9._-]*$/.test(slice.key)) continue
for (const prefix of sourcePrefixes) {
airBenchEvalIds.add(`${prefix}%2F${slice.key}`)
}
}
}
}
}
// 1. Drop the standalone `air-bench-2024` family.
h.families = (h.families ?? []).filter((f) => f.key !== "air-bench-2024")
// 2. Strip AIR-Bench from non-HELM families (agentharm in practice).
for (const fam of h.families) {
if (fam.key === "helm") continue
fam.constituent_evaluation_ids = (fam.constituent_evaluation_ids ?? []).filter(
(id) => !airBenchEvalIds.has(id),
)
if (fam.benchmarks) {
fam.benchmarks = fam.benchmarks.filter(
(b) => !isAirBenchBenchmarkKey(b.key),
)
}
if (fam.standalone_benchmarks) {
fam.standalone_benchmarks = fam.standalone_benchmarks.filter(
(b) => !isAirBenchBenchmarkKey(b.key),
)
}
for (const c of fam.composites ?? []) {
c.benchmarks = (c.benchmarks ?? []).filter(
(b) => !isAirBenchBenchmarkKey(b.key),
)
}
}
// 3. Plant every AIR-Bench eval id under helm > helm-air-bench, and
// extend the composite's benchmark `constituent_evaluation_ids` so the
// hierarchy lookup routes them all to the same composite.
const helm = h.families.find((f) => f.key === "helm")
if (helm) {
const helmIds = new Set(helm.constituent_evaluation_ids ?? [])
for (const id of airBenchEvalIds) helmIds.add(id)
helm.constituent_evaluation_ids = [...helmIds]
const composite = (helm.composites ?? []).find(
(c) => c.key === "helm-air-bench",
)
if (composite) {
const bench =
(composite.benchmarks ?? []).find((b) => b.key === "air-bench-2024") ??
(composite.benchmarks ?? [])[0]
if (bench) {
const benchIds = new Set(bench.constituent_evaluation_ids ?? [])
for (const id of airBenchEvalIds) benchIds.add(id)
bench.constituent_evaluation_ids = [...benchIds]
}
}
}
}
function flattenSplitFamilies(h: CleanableHierarchy) {
for (const family of h.families ?? []) {
const rule = SPLIT_FAMILIES[family.key]
if (!rule) continue
const fam = family as HierarchyFamily
const collected: HierarchyBenchmark[] = [
...(fam.benchmarks ?? []),
...(fam.standalone_benchmarks ?? []),
...(fam.composites ?? []).flatMap((c) => c.benchmarks ?? []),
]
if (collected.length === 0) continue
const seenKeys = new Set<string>()
const benchmarks = collected.filter((b) => {
if (seenKeys.has(b.key)) return false
seenKeys.add(b.key)
return true
})
if (rule.mode === "composite") {
const synthetic: HierarchyComposite = {
key: rule.syntheticKey,
display_name: rule.syntheticDisplayName,
category: fam.category,
tags: { domains: [], languages: [], tasks: [] },
benchmarks,
}
fam.composites = [synthetic]
fam.benchmarks = []
fam.standalone_benchmarks = []
fam.display_name = rule.syntheticDisplayName
continue
}
if (rule.mode === "flatten-composites") {
// Hoist every composite's children up to family.benchmarks so the
// composites disappear and their children sit at the family level.
// Drops empty composites entirely. Used for reward-bench (3
// benchmarks artificially split across 2 composite wrappers).
const seen = new Set<string>()
const flat: HierarchyBenchmark[] = []
for (const b of benchmarks) {
if (seen.has(b.key)) continue
seen.add(b.key)
flat.push(b)
}
fam.benchmarks = flat
fam.standalone_benchmarks = []
fam.composites = []
continue
}
if (rule.mode === "paren-suffix-metrics") {
// Group siblings by display_name prefix; the parenthetical suffix
// is treated as a metric label, not a split. HLE example:
// "Humanity's Last Exam (accuracy)" + "(calibration error)"
// β†’ one benchmark "Humanity's Last Exam" with two metrics:
// "Accuracy", "Calibration Error".
type Group = {
prefix: string
metrics: any[]
summaryIds: Set<string>
}
const slugify = (s: string) =>
s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")
const titleize = (s: string) =>
s
.split(/\s+/)
.map((w) => (w.length > 0 ? w[0].toUpperCase() + w.slice(1) : w))
.join(" ")
const groups = new Map<string, Group>()
for (const child of benchmarks) {
const display = String(child.display_name ?? child.key ?? "")
const m = display.match(/^(.*?)\s*\(([^)]+)\)\s*$/u)
const prefix = m ? m[1].trim() : display.trim()
const metricLabel = m ? m[2].trim() : "Score"
const group = groups.get(prefix) ?? {
prefix,
metrics: [],
summaryIds: new Set<string>(),
}
// Collect this child's existing metric(s); rename their display
// name to the parenthetical so the merged benchmark surfaces
// "Accuracy" and "Calibration Error" rather than the upstream's
// "Accuracy" / "Score".
const childRoot =
(child.slices ?? []).find((s: any) => s?.is_bare_stem === true) ??
(child.slices ?? []).find((s: any) => (s?.metrics ?? []).length > 0) ??
null
const childMetrics = childRoot?.metrics ?? (child as any).metrics ?? []
if (childMetrics.length > 0) {
for (const metric of childMetrics) {
group.metrics.push({
...metric,
key: slugify(metricLabel) || metric.key,
display_name: titleize(metricLabel),
})
}
} else {
group.metrics.push({
key: slugify(metricLabel),
display_name: titleize(metricLabel),
})
}
for (const id of child.constituent_evaluation_ids ?? []) group.summaryIds.add(id)
groups.set(prefix, group)
}
const standalones: HierarchyBenchmark[] = []
for (const group of groups.values()) {
const rootSlice = {
key: slugify(group.prefix),
display_name: group.prefix,
slice_key: null,
is_bare_stem: true,
metrics: group.metrics,
}
standalones.push({
key: slugify(group.prefix),
display_name: group.prefix,
tags: { domains: [], languages: [], tasks: [] },
constituent_evaluation_ids: [...group.summaryIds],
slices: [rootSlice],
} as unknown as HierarchyBenchmark)
}
// Re-route family-level eval lookup to the merged benchmarks.
const famIdsM = new Set<string>(fam.constituent_evaluation_ids ?? [])
for (const s of standalones)
for (const id of s.constituent_evaluation_ids ?? []) famIdsM.add(id)
fam.constituent_evaluation_ids = [...famIdsM]
fam.standalone_benchmarks = standalones
fam.benchmarks = []
fam.composites = []
continue
}
if (rule.mode === "paren-suffix-splits") {
// Group benchmarks by display_name prefix (everything before the
// trailing "(...)" parenthetical). Each prefix becomes its own
// standalone benchmark; the parenthetical content becomes the
// split label. SWE-PolyBench Verified (Java) / (Python) / ... fold
// into ONE standalone "SWE-PolyBench Verified" with 4 splits.
type Group = {
prefix: string
slices: any[]
summaryIds: Set<string>
}
const slugify = (s: string) =>
s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")
const groups = new Map<string, Group>()
for (const child of benchmarks) {
const display = String(child.display_name ?? child.key ?? "")
const m = display.match(/^(.*?)\s*\(([^)]+)\)\s*$/u)
const prefix = m ? m[1].trim() : display.trim()
const splitLabel = m ? m[2].trim() : "Overall"
const group = groups.get(prefix) ?? {
prefix,
slices: [],
summaryIds: new Set<string>(),
}
const childRoot =
(child.slices ?? []).find((s: any) => s?.is_bare_stem === true) ??
(child.slices ?? []).find((s: any) => (s?.metrics ?? []).length > 0) ??
null
const childMetrics = childRoot?.metrics ?? (child as any).metrics ?? []
for (const id of child.constituent_evaluation_ids ?? []) group.summaryIds.add(id)
group.slices.push({
key: slugify(splitLabel),
display_name: splitLabel,
slice_key: slugify(splitLabel),
is_bare_stem: false,
metrics: childMetrics,
})
groups.set(prefix, group)
}
const standalones: HierarchyBenchmark[] = []
for (const group of groups.values()) {
standalones.push({
key: slugify(group.prefix),
display_name: group.prefix,
tags: { domains: [], languages: [], tasks: [] },
constituent_evaluation_ids: [...group.summaryIds],
slices: group.slices,
} as unknown as HierarchyBenchmark)
}
// Re-route family-level eval lookup so split eval ids resolve to
// the surviving benchmarks (frontend's plotbox builder otherwise
// splits them back out into separate grids).
const famIdsS = new Set<string>(fam.constituent_evaluation_ids ?? [])
for (const s of standalones)
for (const id of s.constituent_evaluation_ids ?? []) famIdsS.add(id)
fam.constituent_evaluation_ids = [...famIdsS]
fam.standalone_benchmarks = standalones
fam.benchmarks = []
fam.composites = []
continue
}
// mode === "slices": collapse to a single standalone benchmark.
// Two sub-cases:
// (a) one of the children already IS the canonical parent (key ==
// syntheticKey) AND carries its own slices β€” Fibble Arena's
// "fibble-arena" benchmark already ships the per-N-lies slices.
// Promote it as-is and drop the sibling duplicates.
// (b) parent has no slices (or no parent exists) β€” fold every child
// in as a slice (AgentHarm: parent benchmark + 8 category
// siblings, none with their own slices).
const parent =
benchmarks.find((b) => b.key === rule.syntheticKey) ?? null
const parentSlices = (parent?.slices as any[] | undefined) ?? []
if (parent && parentSlices.length > 0) {
// Case (a): keep parent verbatim. Merge each dropped sibling's
// `constituent_evaluation_ids` into the parent so the hierarchy lookup
// routes orphaned eval rows (e.g. `fibble1-arena%2F…`) back to
// the surviving Fibble Arena benchmark β€” without this the
// model-detail plotbox builder rebuilds the splits as separate
// grids.
const mergedIds = new Set<string>(parent.constituent_evaluation_ids ?? [])
for (const child of benchmarks) {
if (child.key === parent.key) continue
for (const id of child.constituent_evaluation_ids ?? []) mergedIds.add(id)
}
const standalone = {
...parent,
display_name: rule.syntheticDisplayName,
constituent_evaluation_ids: [...mergedIds],
} as HierarchyBenchmark
// Same fix at family level β€” `family.constituent_evaluation_ids` drives
// `buildHierarchyEvalIndex`, so missing sibling ids would orphan
// the lookup.
const famIds = new Set<string>(fam.constituent_evaluation_ids ?? [])
for (const id of mergedIds) famIds.add(id)
fam.constituent_evaluation_ids = [...famIds]
fam.standalone_benchmarks = [standalone]
fam.benchmarks = []
fam.composites = []
fam.display_name = rule.syntheticDisplayName
continue
}
// Case (b): synthesise slices from children.
const allSummaryIds = new Set<string>()
const slices: any[] = []
if (parent) {
for (const id of parent.constituent_evaluation_ids ?? []) allSummaryIds.add(id)
const parentMetrics = (parent as any).metrics ?? []
// Synthesise a root slice carrying the parent's own metrics so the
// overall benchmark scope is preserved (AgentHarm rollup metrics).
slices.push({
key: rule.syntheticKey,
display_name: rule.syntheticDisplayName,
slice_key: null,
is_bare_stem: true,
metrics: parentMetrics,
})
} else {
slices.push({
key: rule.syntheticKey,
display_name: rule.syntheticDisplayName,
slice_key: null,
is_bare_stem: true,
metrics: [],
})
}
for (const child of benchmarks) {
if (child.key === rule.syntheticKey) continue
for (const id of child.constituent_evaluation_ids ?? []) allSummaryIds.add(id)
const childRoot =
(child.slices ?? []).find((s: any) => s?.is_bare_stem === true) ??
(child.slices ?? []).find((s: any) => (s?.metrics ?? []).length > 0) ??
null
const childMetrics = childRoot?.metrics ?? (child as any).metrics ?? []
slices.push({
key: child.key,
display_name: child.display_name ?? child.key,
slice_key: child.key,
is_bare_stem: false,
metrics: childMetrics,
})
}
const standalone = {
...(parent ?? {}),
key: rule.syntheticKey,
display_name: rule.syntheticDisplayName,
tags: parent?.tags ?? { domains: [], languages: [], tasks: [] },
constituent_evaluation_ids: [...allSummaryIds],
slices,
} as unknown as HierarchyBenchmark
// Make sure family-level lookup routes these eval ids back to the
// surviving benchmark too (otherwise the model-detail plotbox
// builder rebuilds the splits as separate grids).
const famIds = new Set<string>(fam.constituent_evaluation_ids ?? [])
for (const id of allSummaryIds) famIds.add(id)
fam.constituent_evaluation_ids = [...famIds]
fam.standalone_benchmarks = [standalone]
fam.benchmarks = []
fam.composites = []
fam.display_name = (rule as { syntheticDisplayName: string }).syntheticDisplayName
}
}
/**
* Drop a grouping's own aggregate "leaderboard" rollup benchmark.
*
* Some sources ship, inside a composite (or a multi-benchmark family), an
* extra benchmark that is just the aggregate score for the whole group β€”
* e.g. HELM's `helm-safety` composite ("HELM Safety") carries a
* `helm-safety-leaderboard` benchmark ("HELM-Safety-Leaderboard") that is
* the composite's own rollup. Listing it as a sibling benchmark makes the
* grouping show up as BOTH a family/group AND a benchmark β€” a semantic
* duplicate ("HELM Safety" is only a family, not a benchmark). We strip the
* rollup leaf so the group is only ever a group; the real member benchmarks
* (BBQ, HarmBench, …) stay.
*
* Detection is deliberately narrow: a leaf qualifies only when its key is
* the parent grouping's key plus a `-leaderboard` suffix
* (`${parentKey}-leaderboard`). That suffix is an unambiguous rollup signal
* β€” it catches all six HELM composites without touching real sibling
* benchmarks whose slug merely coincides with the family (e.g.
* `reward-bench`'s genuine `rewardbench` benchmark sitting beside
* RewardBench 2 / Safety / Reasoning). We only strip within groups that
* keep at least one other benchmark, so single-benchmark families β€” where
* the lone bench legitimately IS the family β€” are never touched.
*
* We drop only the benchmark leaf, NOT the rollup's eval ids from
* `family.constituent_evaluation_ids`. Those ids stay so the rollup eval row
* still resolves to its family/composite in the hierarchy lookup (it just
* no longer carries a benchmark-leaf label) β€” the row is an aggregate, not
* a distinct benchmark, which is exactly the outcome we want.
*/
function dropGroupingLeaderboardRollups(h: CleanableHierarchy) {
const slug = (s: string) => (s ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "")
const isSelfRollup = (b: HierarchyBenchmark, parentKey: string): boolean =>
Boolean(b.key) &&
b.key.endsWith("-leaderboard") &&
slug(b.key.replace(/-leaderboard$/, "")) === slug(parentKey)
const strip = (
benches: HierarchyBenchmark[],
parentKey: string,
): HierarchyBenchmark[] => {
if (benches.length < 2) return benches
const kept = benches.filter((b) => !isSelfRollup(b, parentKey))
// Never empty a group; only apply when something actually dropped.
return kept.length > 0 && kept.length < benches.length ? kept : benches
}
for (const fam of h.families ?? []) {
// Composites are always groupings β€” strip their self-rollup leaf.
for (const c of fam.composites ?? []) {
if (c.benchmarks) c.benchmarks = strip(c.benchmarks, c.key)
}
// Family-level rollup (a `${family.key}-leaderboard` bench sitting
// directly under a multi-benchmark family). None in the current
// snapshot, but keep the hierarchy consistent if one appears.
if (fam.benchmarks) fam.benchmarks = strip(fam.benchmarks, fam.key)
if (fam.standalone_benchmarks) {
fam.standalone_benchmarks = strip(fam.standalone_benchmarks, fam.key)
}
}
}
export function isHierarchyCleaned(h: EvalHierarchy | null | undefined): boolean {
return Boolean((h as CleanableHierarchy | null | undefined)?.[CLEANED_MARKER])
}
function filterBenchmarkIndex(
entries: BenchmarkIndexEntry[],
survivingFamilyKeys: Set<string>,
): BenchmarkIndexEntry[] {
const out: BenchmarkIndexEntry[] = []
for (const entry of entries) {
const distinctBenchKeys = new Set<string>()
for (const app of entry.appearances ?? []) {
if (app.benchmark_key) distinctBenchKeys.add(app.benchmark_key)
}
// True cross-family duplicates carry one canonical benchmark and so
// collapse to ≀2 distinct keys (104/121 entries in a recent snapshot
// are 1-key, 15 are 2-key; the bad rollups are 11 and 34).
if (distinctBenchKeys.size > 2) continue
const seenPair = new Set<string>()
// First pass: drop appearances under families that no longer exist
// post-consolidation, then dedupe (family, eval_summary_id) pairs.
const cleanedApps: BenchmarkIndexAppearance[] = []
for (const app of entry.appearances ?? []) {
if (!survivingFamilyKeys.has(app.family_key)) continue
const newIds: string[] = []
for (const id of app.constituent_evaluation_ids ?? []) {
const pair = `${app.family_key}::${id}`
if (seenPair.has(pair)) continue
seenPair.add(pair)
newIds.push(id)
}
if (newIds.length === 0) continue
cleanedApps.push({ ...app, constituent_evaluation_ids: newIds })
}
// Second pass: drop appearances whose eval_summary_id set is fully
// contained in another appearance's set. Two appearances sharing the
// same eval_summary_id are physically the same data plumbed under
// different family keys (BBH was listed under both `big-bench` and
// `big-bench-hard` families with the same `big-bench-hard%2Fbig-
// bench-hard` id). Keep one β€” preferring an appearance whose
// family.key matches the entry's canonical key, then by family-key
// sort order so the choice is stable.
const dedupedApps: BenchmarkIndexAppearance[] = []
const dropped = new Set<number>()
const idSet = (a: BenchmarkIndexAppearance) =>
new Set(a.constituent_evaluation_ids ?? [])
const isStrictlyContainedOrEqualWithLossTie = (
iA: number,
iB: number,
): boolean => {
const a = cleanedApps[iA]
const b = cleanedApps[iB]
const aIds = idSet(a)
const bIds = idSet(b)
// a's ids βŠ† b's ids?
for (const id of aIds) if (!bIds.has(id)) return false
// strict subset OR tie where b is the canonical-home wrapper
if (aIds.size < bIds.size) return true
// tie β€” drop a if b's family key matches the entry's canonical key
if (b.family_key === entry.key) return true
return false
}
for (let i = 0; i < cleanedApps.length; i++) {
if (dropped.has(i)) continue
let drop = false
for (let j = 0; j < cleanedApps.length; j++) {
if (i === j || dropped.has(j)) continue
if (isStrictlyContainedOrEqualWithLossTie(i, j)) {
drop = true
break
}
}
if (drop) dropped.add(i)
}
for (let i = 0; i < cleanedApps.length; i++) {
if (!dropped.has(i)) dedupedApps.push(cleanedApps[i])
}
// Skip degenerate entries: need β‰₯2 families AND β‰₯2 distinct
// constituent_evaluation_ids for there to be something to cross-reference.
const distinctFamilies = new Set(dedupedApps.map((a) => a.family_key))
const distinctIds = new Set<string>()
for (const a of dedupedApps) for (const id of a.constituent_evaluation_ids) distinctIds.add(id)
if (distinctFamilies.size <= 1 || distinctIds.size <= 1) continue
out.push({ ...entry, appearances: dedupedApps })
}
return out
}