Spaces:
Running
Separate policy and researcher views
Browse files- Foundations: glossary + Term + ModeSwitch helpers
- Policy: plain-language summary, scope chips, provenance row,
collapsible subtasks, technical details pushed into expander
- Researcher: per-row Reproducibility card with adaptive compact mode
for limited-disclosure rows; agent setup group for agentic evals;
lazy-fetched generation_config + score_details from model files
- Replace boilerplate IBM risk field with curated known-issues registry
- Apples-to-apples banner above leaderboards, anchored to
Comparability panel
- Methodology split: surface task setup, scoring, validation
- Compact row-signals indicator replacing repetitive coloured badges
- Cleaner divergence chips in Comparability panel; full-width when
only one divergence type fires
- Flag-this-score button that links to the dataset record on HF and
pre-fills a discussion title plus copyable context
- Defensive guard so an embedded ancestor card no longer leaks its
text onto a leaf-benchmark page
- app/api/eval-row-config/route.ts +65 -0
- components/apples-to-apples-banner.tsx +98 -0
- components/eval-detail.tsx +288 -94
- components/flag-score-button.tsx +267 -0
- components/known-issues-panel.tsx +108 -0
- components/mode-switch.tsx +28 -0
- components/policy-overview.tsx +350 -0
- components/researcher-reproducibility-card.tsx +483 -0
- components/signals/comparability-panel.tsx +162 -52
- components/signals/reproducibility-panel.tsx +1 -1
- components/signals/row-signals-compact.tsx +98 -0
- components/term.tsx +47 -0
- lib/eval-processing.ts +2 -0
- lib/glossary.ts +136 -0
- lib/known-issues.ts +47 -0
- lib/model-data.ts +1 -0
- metadata/benchmark_known_issues.json +84 -0
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { NextResponse } from "next/server"
|
| 2 |
+
|
| 3 |
+
import { fetchModelDetail } from "@/lib/hf-data"
|
| 4 |
+
|
| 5 |
+
/**
|
| 6 |
+
* Lightweight lookup that returns the per-(model, benchmark) reproducibility
|
| 7 |
+
* payload — generation_config + sample_size/standard_error/confidence_interval
|
| 8 |
+
* — extracted from the model's full record. Used by the leaderboard's
|
| 9 |
+
* Reproducibility card on row expand so we avoid joining N model files into
|
| 10 |
+
* the eval-detail page on every load.
|
| 11 |
+
*/
|
| 12 |
+
export async function GET(request: Request) {
|
| 13 |
+
const { searchParams } = new URL(request.url)
|
| 14 |
+
const modelId = searchParams.get("model_id")
|
| 15 |
+
const benchmarkKey = (searchParams.get("benchmark_key") || "").toLowerCase()
|
| 16 |
+
const evalName = (searchParams.get("eval_name") || "").toLowerCase()
|
| 17 |
+
|
| 18 |
+
if (!modelId) {
|
| 19 |
+
return NextResponse.json({ error: "Missing model_id" }, { status: 400 })
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
const slug = modelId.replace(/[\/]/g, "__")
|
| 23 |
+
const detail = await fetchModelDetail(slug)
|
| 24 |
+
if (!detail) {
|
| 25 |
+
return NextResponse.json({ generation_config: null, score_details: null }, { status: 200 })
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
let bestGenerationConfig: unknown = null
|
| 29 |
+
let bestScoreDetails: unknown = null
|
| 30 |
+
|
| 31 |
+
const candidates = Object.values(detail.evaluations_by_category ?? {}).flat()
|
| 32 |
+
for (const ev of candidates) {
|
| 33 |
+
const evBench = ((ev.benchmark as string | undefined) ?? "").toLowerCase()
|
| 34 |
+
for (const r of ev.evaluation_results ?? []) {
|
| 35 |
+
const rName = (r.evaluation_name ?? "").toLowerCase()
|
| 36 |
+
const rDisplay = (r.display_name ?? "").toLowerCase()
|
| 37 |
+
const matches =
|
| 38 |
+
(benchmarkKey && (evBench === benchmarkKey || evBench.includes(benchmarkKey) || rName.includes(benchmarkKey))) ||
|
| 39 |
+
(evalName && (rName === evalName || rDisplay === evalName))
|
| 40 |
+
if (!matches) continue
|
| 41 |
+
|
| 42 |
+
const candidateGen = ev.generation_config ?? r.generation_config
|
| 43 |
+
const argsCount =
|
| 44 |
+
candidateGen && typeof candidateGen === "object" && "generation_args" in candidateGen
|
| 45 |
+
? Object.keys((candidateGen as { generation_args?: Record<string, unknown> }).generation_args ?? {}).length
|
| 46 |
+
: 0
|
| 47 |
+
const currentCount =
|
| 48 |
+
bestGenerationConfig && typeof bestGenerationConfig === "object" && "generation_args" in bestGenerationConfig
|
| 49 |
+
? Object.keys((bestGenerationConfig as { generation_args?: Record<string, unknown> }).generation_args ?? {}).length
|
| 50 |
+
: 0
|
| 51 |
+
if (candidateGen && argsCount > currentCount) {
|
| 52 |
+
bestGenerationConfig = candidateGen
|
| 53 |
+
}
|
| 54 |
+
if (!bestScoreDetails && r.score_details) {
|
| 55 |
+
bestScoreDetails = r.score_details
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
if (bestGenerationConfig && bestScoreDetails) break
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
return NextResponse.json({
|
| 62 |
+
generation_config: bestGenerationConfig,
|
| 63 |
+
score_details: bestScoreDetails,
|
| 64 |
+
})
|
| 65 |
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client"
|
| 2 |
+
|
| 3 |
+
import { AlertTriangle, GitCompareArrows, UsersRound } from "lucide-react"
|
| 4 |
+
import { useAudienceMode } from "@/components/audience-mode-provider"
|
| 5 |
+
import { SignalTooltip } from "@/components/signals/signal-tooltip"
|
| 6 |
+
import type { ComparabilitySummary } from "@/lib/backend-artifacts"
|
| 7 |
+
|
| 8 |
+
interface ApplesToApplesBannerProps {
|
| 9 |
+
summary?: ComparabilitySummary | null
|
| 10 |
+
/**
|
| 11 |
+
* Optional anchor id so the "see details" link can scroll to the full
|
| 12 |
+
* Comparability panel further down the page.
|
| 13 |
+
*/
|
| 14 |
+
detailsAnchorId?: string
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
/**
|
| 18 |
+
* High-visibility warning shown above the leaderboard when the row-level
|
| 19 |
+
* comparability summary indicates that some scores were collected under
|
| 20 |
+
* different setups (variant divergence) or only by the model developer
|
| 21 |
+
* (cross-party divergence). Designed to interrupt naive ranking comparisons
|
| 22 |
+
* before the reader scrolls to the per-row signals.
|
| 23 |
+
*/
|
| 24 |
+
export function ApplesToApplesBanner({ summary, detailsAnchorId }: ApplesToApplesBannerProps) {
|
| 25 |
+
const { mode } = useAudienceMode()
|
| 26 |
+
|
| 27 |
+
if (!summary) return null
|
| 28 |
+
|
| 29 |
+
const variantCount = summary.variant_divergent_count ?? 0
|
| 30 |
+
const crossPartyCount = summary.cross_party_divergent_count ?? 0
|
| 31 |
+
const totalConcerns = variantCount + crossPartyCount
|
| 32 |
+
if (totalConcerns === 0) return null
|
| 33 |
+
|
| 34 |
+
const variantsChecked = summary.groups_with_variant_check ?? 0
|
| 35 |
+
const crossPartyChecked = summary.groups_with_cross_party_check ?? 0
|
| 36 |
+
|
| 37 |
+
const concernPhrases: string[] = []
|
| 38 |
+
if (variantCount > 0) {
|
| 39 |
+
concernPhrases.push(
|
| 40 |
+
`${variantCount} group${variantCount === 1 ? "" : "s"} where models used different setups`,
|
| 41 |
+
)
|
| 42 |
+
}
|
| 43 |
+
if (crossPartyCount > 0) {
|
| 44 |
+
concernPhrases.push(
|
| 45 |
+
`${crossPartyCount} group${crossPartyCount === 1 ? "" : "s"} where reports come only from the model developer`,
|
| 46 |
+
)
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
const headline =
|
| 50 |
+
mode === "policy"
|
| 51 |
+
? "Heads up: not every score here is directly comparable."
|
| 52 |
+
: "Apples-to-apples warning"
|
| 53 |
+
|
| 54 |
+
const body =
|
| 55 |
+
mode === "policy"
|
| 56 |
+
? `${concernPhrases.join(" and ")}. Direct ranking comparisons may be misleading.`
|
| 57 |
+
: `${concernPhrases.join("; ")}. See the Comparability panel below for which models and which fields differ.`
|
| 58 |
+
|
| 59 |
+
return (
|
| 60 |
+
<div className="flex items-start gap-3 rounded-2xl border border-amber-300/70 bg-amber-50/70 px-4 py-3 text-sm text-amber-900 dark:border-amber-900/50 dark:bg-amber-950/25 dark:text-amber-100">
|
| 61 |
+
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0" />
|
| 62 |
+
<div className="min-w-0 flex-1 space-y-1">
|
| 63 |
+
<div className="font-semibold leading-tight">{headline}</div>
|
| 64 |
+
<p className="text-[13px] leading-5">{body}</p>
|
| 65 |
+
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-[11px] opacity-90">
|
| 66 |
+
{variantCount > 0 && (
|
| 67 |
+
<SignalTooltip
|
| 68 |
+
content={`${variantCount} of ${variantsChecked} groups checked for setup divergence flagged a problem (e.g. different shots, prompts, or scoring).`}
|
| 69 |
+
>
|
| 70 |
+
<span className="inline-flex items-center gap-1 cursor-help">
|
| 71 |
+
<GitCompareArrows className="h-3 w-3" />
|
| 72 |
+
Setup divergence: {variantCount}/{variantsChecked || variantCount}
|
| 73 |
+
</span>
|
| 74 |
+
</SignalTooltip>
|
| 75 |
+
)}
|
| 76 |
+
{crossPartyCount > 0 && (
|
| 77 |
+
<SignalTooltip
|
| 78 |
+
content={`${crossPartyCount} of ${crossPartyChecked} groups checked for source divergence flagged a problem (only the model developer reported a number).`}
|
| 79 |
+
>
|
| 80 |
+
<span className="inline-flex items-center gap-1 cursor-help">
|
| 81 |
+
<UsersRound className="h-3 w-3" />
|
| 82 |
+
Source divergence: {crossPartyCount}/{crossPartyChecked || crossPartyCount}
|
| 83 |
+
</span>
|
| 84 |
+
</SignalTooltip>
|
| 85 |
+
)}
|
| 86 |
+
{detailsAnchorId && (
|
| 87 |
+
<a
|
| 88 |
+
href={`#${detailsAnchorId}`}
|
| 89 |
+
className="ml-auto font-medium underline-offset-4 hover:underline"
|
| 90 |
+
>
|
| 91 |
+
See details ↓
|
| 92 |
+
</a>
|
| 93 |
+
)}
|
| 94 |
+
</div>
|
| 95 |
+
</div>
|
| 96 |
+
</div>
|
| 97 |
+
)
|
| 98 |
+
}
|
|
@@ -9,6 +9,7 @@ import { CompletenessPanel } from "@/components/signals/completeness-panel"
|
|
| 9 |
import { ComparabilityPanel } from "@/components/signals/comparability-panel"
|
| 10 |
import { ReproducibilityPanel } from "@/components/signals/reproducibility-panel"
|
| 11 |
import { SignalsRowBadges } from "@/components/signals/signals-row-badges"
|
|
|
|
| 12 |
import { SignalTooltip } from "@/components/signals/signal-tooltip"
|
| 13 |
import { getCompletenessPopulatedCount } from "@/components/signals/signal-utils"
|
| 14 |
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
|
@@ -54,6 +55,12 @@ import {
|
|
| 54 |
} from "lucide-react"
|
| 55 |
import type { BenchmarkCard } from "@/lib/benchmark-schema"
|
| 56 |
import type { BenchmarkEvalSummary, ModelResultForBenchmark } from "@/lib/eval-processing"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
|
| 58 |
interface EvalDetailProps {
|
| 59 |
summary: BenchmarkEvalSummary
|
|
@@ -523,6 +530,11 @@ export function EvalDetail({ summary }: EvalDetailProps) {
|
|
| 523 |
(summary.leaderboard_metrics?.length ?? 0) > 1 &&
|
| 524 |
(summary.leaderboard_rows?.length ?? 0) > 0
|
| 525 |
const [overviewOpen, setOverviewOpen] = useState(true)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 526 |
const [expandedRows, setExpandedRows] = useState<Record<string, boolean>>({})
|
| 527 |
const [leaderboardPage, setLeaderboardPage] = useState(1)
|
| 528 |
const [minParamStep, setMinParamStep] = useState(0)
|
|
@@ -636,6 +648,7 @@ export function EvalDetail({ summary }: EvalDetailProps) {
|
|
| 636 |
|
| 637 |
return (
|
| 638 |
<div className="space-y-6">
|
|
|
|
| 639 |
<Card className="overflow-hidden">
|
| 640 |
<Collapsible open={overviewOpen} onOpenChange={setOverviewOpen}>
|
| 641 |
<CollapsibleTrigger asChild>
|
|
@@ -645,7 +658,7 @@ export function EvalDetail({ summary }: EvalDetailProps) {
|
|
| 645 |
>
|
| 646 |
<div className="min-w-0 space-y-1">
|
| 647 |
<div className="text-[11px] font-semibold uppercase tracking-[0.2em] text-muted-foreground">
|
| 648 |
-
{isResearchView ? "Benchmark overview" : "
|
| 649 |
</div>
|
| 650 |
<div className="flex flex-wrap items-center gap-2">
|
| 651 |
<span className="text-base font-semibold tracking-tight sm:text-lg">{summary.evaluation_name}</span>
|
|
@@ -710,11 +723,6 @@ export function EvalDetail({ summary }: EvalDetailProps) {
|
|
| 710 |
{summary.metric_config.evaluation_description}
|
| 711 |
</p>
|
| 712 |
|
| 713 |
-
{!isResearchView && (
|
| 714 |
-
<p className="max-w-3xl text-sm leading-6 text-muted-foreground">
|
| 715 |
-
{`${summary.benchmark_card?.purpose_and_intended_users?.goal ?? "This benchmark reports a capability result."} Scores should be read alongside benchmark scope, metric definitions, and the source dataset context.`}
|
| 716 |
-
</p>
|
| 717 |
-
)}
|
| 718 |
</div>
|
| 719 |
|
| 720 |
<div className="grid w-full grid-cols-2 gap-2 xl:grid-cols-4">
|
|
@@ -890,6 +898,14 @@ export function EvalDetail({ summary }: EvalDetailProps) {
|
|
| 890 |
isResearchView={isResearchView}
|
| 891 |
defaultOpen
|
| 892 |
defaultRisksOpen={!isResearchView}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 893 |
/>
|
| 894 |
)}
|
| 895 |
</CardContent>
|
|
@@ -901,7 +917,11 @@ export function EvalDetail({ summary }: EvalDetailProps) {
|
|
| 901 |
<MultiMetricLeaderboard summary={summary} isResearchView={isResearchView} />
|
| 902 |
) : (
|
| 903 |
<Card className="overflow-hidden">
|
| 904 |
-
<CardHeader className="border-b bg-muted/10">
|
|
|
|
|
|
|
|
|
|
|
|
|
| 905 |
<div className="flex flex-col gap-3 lg:flex-row lg:items-end lg:justify-between">
|
| 906 |
<div className="space-y-2">
|
| 907 |
<div className="flex items-center gap-2">
|
|
@@ -1034,7 +1054,6 @@ export function EvalDetail({ summary }: EvalDetailProps) {
|
|
| 1034 |
{isResearchView ? "Evaluator" : "Reporting Org"}
|
| 1035 |
</TableHead>
|
| 1036 |
<TableHead className="hidden min-w-[120px] lg:table-cell">Updated</TableHead>
|
| 1037 |
-
<TableHead className="w-16 px-4 text-right">Details</TableHead>
|
| 1038 |
</TableRow>
|
| 1039 |
</TableHeader>
|
| 1040 |
<TableBody>
|
|
@@ -1044,6 +1063,7 @@ export function EvalDetail({ summary }: EvalDetailProps) {
|
|
| 1044 |
? Object.entries(modelResult.score_details.details).filter(([, value]) => typeof value === "number")
|
| 1045 |
: []
|
| 1046 |
const hasExpandableDetails =
|
|
|
|
| 1047 |
(modelResult.aggregate_components && modelResult.aggregate_components.length > 1) ||
|
| 1048 |
subtasks.length > 1
|
| 1049 |
|
|
@@ -1075,7 +1095,18 @@ export function EvalDetail({ summary }: EvalDetailProps) {
|
|
| 1075 |
|
| 1076 |
<TableCell className="whitespace-normal">
|
| 1077 |
<div className="space-y-1">
|
| 1078 |
-
<div className="font-semibold leading-tight">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1079 |
<Link
|
| 1080 |
href={`/models/${getModelFamilyRouteId(modelResult.model_info)}`}
|
| 1081 |
className="underline decoration-dotted underline-offset-4 hover:text-primary"
|
|
@@ -1113,8 +1144,10 @@ export function EvalDetail({ summary }: EvalDetailProps) {
|
|
| 1113 |
</TableCell>
|
| 1114 |
|
| 1115 |
<TableCell className="text-right">
|
| 1116 |
-
<div className="
|
| 1117 |
-
|
|
|
|
|
|
|
| 1118 |
</TableCell>
|
| 1119 |
|
| 1120 |
{isResearchView ? (
|
|
@@ -1160,24 +1193,11 @@ export function EvalDetail({ summary }: EvalDetailProps) {
|
|
| 1160 |
<TableCell className="hidden lg:table-cell">
|
| 1161 |
<div className="text-sm text-muted-foreground">{formatDate(modelResult.evaluation_timestamp)}</div>
|
| 1162 |
</TableCell>
|
| 1163 |
-
|
| 1164 |
-
<TableCell className="px-4 text-right">
|
| 1165 |
-
{hasExpandableDetails && (
|
| 1166 |
-
<Button
|
| 1167 |
-
variant="ghost"
|
| 1168 |
-
size="icon"
|
| 1169 |
-
aria-label={isExpanded ? "Collapse details" : "Expand details"}
|
| 1170 |
-
onClick={() => toggleRow(key)}
|
| 1171 |
-
>
|
| 1172 |
-
{isExpanded ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
|
| 1173 |
-
</Button>
|
| 1174 |
-
)}
|
| 1175 |
-
</TableCell>
|
| 1176 |
</TableRow>
|
| 1177 |
|
| 1178 |
{isExpanded && (
|
| 1179 |
<TableRow className="hover:bg-transparent">
|
| 1180 |
-
<TableCell colSpan={
|
| 1181 |
<div className="space-y-5 px-4 py-5 sm:px-6">
|
| 1182 |
<div className="grid gap-4 xl:grid-cols-3">
|
| 1183 |
<DetailPanel
|
|
@@ -1244,13 +1264,15 @@ export function EvalDetail({ summary }: EvalDetailProps) {
|
|
| 1244 |
)}
|
| 1245 |
</DetailPanel>
|
| 1246 |
|
| 1247 |
-
|
|
|
|
|
|
|
| 1248 |
|
| 1249 |
<DetailPanel
|
| 1250 |
title={isResearchView ? "Score Breakdown" : "Metric Summary"}
|
| 1251 |
subtitle={
|
| 1252 |
isResearchView
|
| 1253 |
-
? "Raw
|
| 1254 |
: "Raw performance plus uncertainty and sample details."
|
| 1255 |
}
|
| 1256 |
>
|
|
@@ -1260,19 +1282,23 @@ export function EvalDetail({ summary }: EvalDetailProps) {
|
|
| 1260 |
/>
|
| 1261 |
<MetaRow label="Score Type" value={modelResult.result.metric_config.score_type} />
|
| 1262 |
<MetaRow label="Range" value={`${minScore} - ${maxScore}`} />
|
| 1263 |
-
|
| 1264 |
-
|
| 1265 |
-
|
| 1266 |
-
|
| 1267 |
-
|
| 1268 |
-
|
| 1269 |
-
|
| 1270 |
-
|
| 1271 |
-
|
| 1272 |
-
|
| 1273 |
-
|
| 1274 |
-
|
| 1275 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1276 |
)}
|
| 1277 |
</DetailPanel>
|
| 1278 |
</div>
|
|
@@ -1334,53 +1360,74 @@ export function EvalDetail({ summary }: EvalDetailProps) {
|
|
| 1334 |
</div>
|
| 1335 |
)}
|
| 1336 |
|
| 1337 |
-
{
|
| 1338 |
<div className="space-y-3">
|
| 1339 |
-
<
|
| 1340 |
-
|
| 1341 |
-
|
| 1342 |
-
|
| 1343 |
-
|
| 1344 |
-
|
| 1345 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1346 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1347 |
|
| 1348 |
-
|
| 1349 |
-
|
| 1350 |
-
Object.entries(modelResult.result.generation_config.generation_args).map(([key, value]) => (
|
| 1351 |
-
<div key={key} className="rounded-xl border bg-background/70 p-4">
|
| 1352 |
<div className="text-xs uppercase tracking-[0.16em] text-muted-foreground">
|
| 1353 |
-
|
| 1354 |
</div>
|
| 1355 |
-
<div className="mt-2 text-sm font-medium">
|
| 1356 |
-
{formatMetadataValue(
|
| 1357 |
</div>
|
| 1358 |
</div>
|
| 1359 |
-
)
|
| 1360 |
|
| 1361 |
-
|
| 1362 |
-
|
| 1363 |
-
|
| 1364 |
-
|
| 1365 |
-
|
| 1366 |
-
|
| 1367 |
-
|
| 1368 |
-
|
| 1369 |
-
</div>
|
| 1370 |
-
)}
|
| 1371 |
-
|
| 1372 |
-
{modelResult.result.generation_config.prompt_template && (
|
| 1373 |
-
<div className="rounded-xl border bg-background/70 p-4 md:col-span-2 xl:col-span-3">
|
| 1374 |
-
<div className="text-xs uppercase tracking-[0.16em] text-muted-foreground">
|
| 1375 |
-
Prompt Template
|
| 1376 |
-
</div>
|
| 1377 |
-
<div className="mt-2 text-sm font-medium whitespace-pre-wrap">
|
| 1378 |
-
{formatMetadataValue(modelResult.result.generation_config.prompt_template)}
|
| 1379 |
</div>
|
| 1380 |
-
|
| 1381 |
-
|
| 1382 |
</div>
|
| 1383 |
-
|
| 1384 |
)}
|
| 1385 |
</div>
|
| 1386 |
</TableCell>
|
|
@@ -1391,7 +1438,7 @@ export function EvalDetail({ summary }: EvalDetailProps) {
|
|
| 1391 |
})}
|
| 1392 |
{leaderboardRows.length === 0 && (
|
| 1393 |
<TableRow>
|
| 1394 |
-
<TableCell colSpan={
|
| 1395 |
No leaderboard entries match the selected parameter range.
|
| 1396 |
</TableCell>
|
| 1397 |
</TableRow>
|
|
@@ -1429,6 +1476,32 @@ function MultiMetricLeaderboard({
|
|
| 1429 |
const [activeSubtaskTab, setActiveSubtaskTab] = useState<string>("all")
|
| 1430 |
const [minParamStep, setMinParamStep] = useState(0)
|
| 1431 |
const [maxParamStep, setMaxParamStep] = useState(PARAM_RANGE_VALUES.length - 1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1432 |
const leaderboardMetrics = summary.leaderboard_metrics ?? []
|
| 1433 |
const leaderboardRows = summary.leaderboard_rows ?? []
|
| 1434 |
const allMetricKeys = useMemo(() => leaderboardMetrics.map((metric) => metric.column_key), [leaderboardMetrics])
|
|
@@ -1703,7 +1776,11 @@ function MultiMetricLeaderboard({
|
|
| 1703 |
|
| 1704 |
return (
|
| 1705 |
<Card className="overflow-hidden">
|
| 1706 |
-
<CardHeader className="border-b bg-muted/10">
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1707 |
<div className="flex flex-col gap-3 lg:flex-row lg:items-end lg:justify-between">
|
| 1708 |
<div className="space-y-2">
|
| 1709 |
<div className="flex items-center gap-2">
|
|
@@ -1940,9 +2017,13 @@ function MultiMetricLeaderboard({
|
|
| 1940 |
<TableBody>
|
| 1941 |
{pagedRows.map((row) => {
|
| 1942 |
const rank = rankByModelId.get(row.model_info.id) ?? 0
|
|
|
|
|
|
|
|
|
|
| 1943 |
|
| 1944 |
return (
|
| 1945 |
-
<
|
|
|
|
| 1946 |
<TableCell className="px-4">
|
| 1947 |
<div
|
| 1948 |
className={cn(
|
|
@@ -1955,13 +2036,28 @@ function MultiMetricLeaderboard({
|
|
| 1955 |
</TableCell>
|
| 1956 |
<TableCell className="px-4 whitespace-normal">
|
| 1957 |
<div className="space-y-1">
|
| 1958 |
-
<div className="font-semibold leading-tight">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1959 |
<Link
|
| 1960 |
href={`/models/${getModelFamilyRouteId(row.model_info)}`}
|
| 1961 |
className="underline decoration-dotted underline-offset-4 hover:text-primary"
|
| 1962 |
>
|
| 1963 |
{row.model_info.name}
|
| 1964 |
</Link>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1965 |
</div>
|
| 1966 |
<div className="flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
|
| 1967 |
{row.model_info.parameter_count && (
|
|
@@ -1976,12 +2072,6 @@ function MultiMetricLeaderboard({
|
|
| 1976 |
)}
|
| 1977 |
<span className="lg:hidden">{row.model_info.developer ?? "Unknown developer"}</span>
|
| 1978 |
</div>
|
| 1979 |
-
<SignalsRowBadges
|
| 1980 |
-
annotations={getRowLevelAnnotations(row, visibleMetrics)}
|
| 1981 |
-
variant="row"
|
| 1982 |
-
className="mt-1 justify-start"
|
| 1983 |
-
hideOnMobile={false}
|
| 1984 |
-
/>
|
| 1985 |
</div>
|
| 1986 |
</TableCell>
|
| 1987 |
|
|
@@ -2016,6 +2106,34 @@ function MultiMetricLeaderboard({
|
|
| 2016 |
{formatDate(row.evaluation_timestamp)}
|
| 2017 |
</TableCell>
|
| 2018 |
</TableRow>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2019 |
)})}
|
| 2020 |
|
| 2021 |
{filteredRows.length === 0 && (
|
|
@@ -2046,11 +2164,13 @@ function BenchmarkCardCollapsible({
|
|
| 2046 |
isResearchView,
|
| 2047 |
defaultOpen = true,
|
| 2048 |
defaultRisksOpen = false,
|
|
|
|
| 2049 |
}: {
|
| 2050 |
card: BenchmarkCard
|
| 2051 |
isResearchView: boolean
|
| 2052 |
defaultOpen?: boolean
|
| 2053 |
defaultRisksOpen?: boolean
|
|
|
|
| 2054 |
}) {
|
| 2055 |
const [open, setOpen] = useState(defaultOpen)
|
| 2056 |
return (
|
|
@@ -2079,6 +2199,7 @@ function BenchmarkCardCollapsible({
|
|
| 2079 |
card={card}
|
| 2080 |
isResearchView={isResearchView}
|
| 2081 |
defaultRisksOpen={defaultRisksOpen}
|
|
|
|
| 2082 |
/>
|
| 2083 |
</CollapsibleContent>
|
| 2084 |
</Collapsible>
|
|
@@ -2095,12 +2216,12 @@ function DetailPanel({
|
|
| 2095 |
children: React.ReactNode
|
| 2096 |
}) {
|
| 2097 |
return (
|
| 2098 |
-
<div className="rounded-2xl border bg-background/70 p-4">
|
| 2099 |
<div className="mb-4">
|
| 2100 |
<div className="font-semibold">{title}</div>
|
| 2101 |
<div className="text-sm text-muted-foreground">{subtitle}</div>
|
| 2102 |
</div>
|
| 2103 |
-
<div className="space-y-2.5">{children}</div>
|
| 2104 |
</div>
|
| 2105 |
)
|
| 2106 |
}
|
|
@@ -2129,9 +2250,28 @@ function MetaRow({
|
|
| 2129 |
}
|
| 2130 |
}
|
| 2131 |
return (
|
| 2132 |
-
<div
|
| 2133 |
-
|
| 2134 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2135 |
</div>
|
| 2136 |
)
|
| 2137 |
}
|
|
@@ -2183,10 +2323,12 @@ function BenchmarkCardPanel({
|
|
| 2183 |
card,
|
| 2184 |
isResearchView,
|
| 2185 |
defaultRisksOpen = false,
|
|
|
|
| 2186 |
}: {
|
| 2187 |
card: BenchmarkCard
|
| 2188 |
isResearchView: boolean
|
| 2189 |
defaultRisksOpen?: boolean
|
|
|
|
| 2190 |
}) {
|
| 2191 |
const [risksOpen, setRisksOpen] = useState(defaultRisksOpen)
|
| 2192 |
const details = card.benchmark_details
|
|
@@ -2236,6 +2378,8 @@ function BenchmarkCardPanel({
|
|
| 2236 |
</CardHeader>
|
| 2237 |
|
| 2238 |
<CardContent className="space-y-6 p-5 sm:p-6">
|
|
|
|
|
|
|
| 2239 |
{/* Overview + domains */}
|
| 2240 |
<div className="space-y-3">
|
| 2241 |
<p className="text-sm leading-6 text-muted-foreground">{details.overview}</p>
|
|
@@ -2288,6 +2432,52 @@ function BenchmarkCardPanel({
|
|
| 2288 |
</div>
|
| 2289 |
</div>
|
| 2290 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2291 |
{/* Research-only: methodology + dataset details */}
|
| 2292 |
{isResearchView && (
|
| 2293 |
<div className="grid gap-4 sm:grid-cols-2">
|
|
@@ -2339,8 +2529,12 @@ function BenchmarkCardPanel({
|
|
| 2339 |
</div>
|
| 2340 |
)}
|
| 2341 |
|
| 2342 |
-
{/*
|
| 2343 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2344 |
<Collapsible open={risksOpen} onOpenChange={setRisksOpen}>
|
| 2345 |
<CollapsibleTrigger asChild>
|
| 2346 |
<button
|
|
|
|
| 9 |
import { ComparabilityPanel } from "@/components/signals/comparability-panel"
|
| 10 |
import { ReproducibilityPanel } from "@/components/signals/reproducibility-panel"
|
| 11 |
import { SignalsRowBadges } from "@/components/signals/signals-row-badges"
|
| 12 |
+
import { RowSignalsCompact } from "@/components/signals/row-signals-compact"
|
| 13 |
import { SignalTooltip } from "@/components/signals/signal-tooltip"
|
| 14 |
import { getCompletenessPopulatedCount } from "@/components/signals/signal-utils"
|
| 15 |
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
|
|
|
| 55 |
} from "lucide-react"
|
| 56 |
import type { BenchmarkCard } from "@/lib/benchmark-schema"
|
| 57 |
import type { BenchmarkEvalSummary, ModelResultForBenchmark } from "@/lib/eval-processing"
|
| 58 |
+
import { PolicyOverview } from "@/components/policy-overview"
|
| 59 |
+
import { ResearcherReproducibilityCard } from "@/components/researcher-reproducibility-card"
|
| 60 |
+
import { KnownIssuesPanel } from "@/components/known-issues-panel"
|
| 61 |
+
import { getKnownIssues, type KnownIssue } from "@/lib/known-issues"
|
| 62 |
+
import { ApplesToApplesBanner } from "@/components/apples-to-apples-banner"
|
| 63 |
+
import { FlagScoreButton } from "@/components/flag-score-button"
|
| 64 |
|
| 65 |
interface EvalDetailProps {
|
| 66 |
summary: BenchmarkEvalSummary
|
|
|
|
| 530 |
(summary.leaderboard_metrics?.length ?? 0) > 1 &&
|
| 531 |
(summary.leaderboard_rows?.length ?? 0) > 0
|
| 532 |
const [overviewOpen, setOverviewOpen] = useState(true)
|
| 533 |
+
// Collapse the dense technical overview by default in policy mode; expand
|
| 534 |
+
// for researchers. Reset whenever the user switches modes.
|
| 535 |
+
useEffect(() => {
|
| 536 |
+
setOverviewOpen(isResearchView)
|
| 537 |
+
}, [isResearchView])
|
| 538 |
const [expandedRows, setExpandedRows] = useState<Record<string, boolean>>({})
|
| 539 |
const [leaderboardPage, setLeaderboardPage] = useState(1)
|
| 540 |
const [minParamStep, setMinParamStep] = useState(0)
|
|
|
|
| 648 |
|
| 649 |
return (
|
| 650 |
<div className="space-y-6">
|
| 651 |
+
{!isResearchView && <PolicyOverview summary={summary} />}
|
| 652 |
<Card className="overflow-hidden">
|
| 653 |
<Collapsible open={overviewOpen} onOpenChange={setOverviewOpen}>
|
| 654 |
<CollapsibleTrigger asChild>
|
|
|
|
| 658 |
>
|
| 659 |
<div className="min-w-0 space-y-1">
|
| 660 |
<div className="text-[11px] font-semibold uppercase tracking-[0.2em] text-muted-foreground">
|
| 661 |
+
{isResearchView ? "Benchmark overview" : "Technical details"}
|
| 662 |
</div>
|
| 663 |
<div className="flex flex-wrap items-center gap-2">
|
| 664 |
<span className="text-base font-semibold tracking-tight sm:text-lg">{summary.evaluation_name}</span>
|
|
|
|
| 723 |
{summary.metric_config.evaluation_description}
|
| 724 |
</p>
|
| 725 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 726 |
</div>
|
| 727 |
|
| 728 |
<div className="grid w-full grid-cols-2 gap-2 xl:grid-cols-4">
|
|
|
|
| 898 |
isResearchView={isResearchView}
|
| 899 |
defaultOpen
|
| 900 |
defaultRisksOpen={!isResearchView}
|
| 901 |
+
knownIssues={getKnownIssues(
|
| 902 |
+
summary.evaluation_name,
|
| 903 |
+
summary.composite_benchmark_name,
|
| 904 |
+
summary.composite_benchmark_key,
|
| 905 |
+
summary.benchmark_family_key,
|
| 906 |
+
summary.benchmark_leaf_key,
|
| 907 |
+
summary.benchmark_card.benchmark_details?.name,
|
| 908 |
+
)}
|
| 909 |
/>
|
| 910 |
)}
|
| 911 |
</CardContent>
|
|
|
|
| 917 |
<MultiMetricLeaderboard summary={summary} isResearchView={isResearchView} />
|
| 918 |
) : (
|
| 919 |
<Card className="overflow-hidden">
|
| 920 |
+
<CardHeader className="border-b bg-muted/10 space-y-3">
|
| 921 |
+
<ApplesToApplesBanner
|
| 922 |
+
summary={summary.comparability_summary}
|
| 923 |
+
detailsAnchorId="comparability-panel"
|
| 924 |
+
/>
|
| 925 |
<div className="flex flex-col gap-3 lg:flex-row lg:items-end lg:justify-between">
|
| 926 |
<div className="space-y-2">
|
| 927 |
<div className="flex items-center gap-2">
|
|
|
|
| 1054 |
{isResearchView ? "Evaluator" : "Reporting Org"}
|
| 1055 |
</TableHead>
|
| 1056 |
<TableHead className="hidden min-w-[120px] lg:table-cell">Updated</TableHead>
|
|
|
|
| 1057 |
</TableRow>
|
| 1058 |
</TableHeader>
|
| 1059 |
<TableBody>
|
|
|
|
| 1063 |
? Object.entries(modelResult.score_details.details).filter(([, value]) => typeof value === "number")
|
| 1064 |
: []
|
| 1065 |
const hasExpandableDetails =
|
| 1066 |
+
isResearchView ||
|
| 1067 |
(modelResult.aggregate_components && modelResult.aggregate_components.length > 1) ||
|
| 1068 |
subtasks.length > 1
|
| 1069 |
|
|
|
|
| 1095 |
|
| 1096 |
<TableCell className="whitespace-normal">
|
| 1097 |
<div className="space-y-1">
|
| 1098 |
+
<div className="flex items-center gap-1.5 font-semibold leading-tight">
|
| 1099 |
+
{hasExpandableDetails && (
|
| 1100 |
+
<button
|
| 1101 |
+
type="button"
|
| 1102 |
+
onClick={() => toggleRow(key)}
|
| 1103 |
+
aria-label={isExpanded ? "Collapse details" : "Expand details"}
|
| 1104 |
+
aria-expanded={isExpanded}
|
| 1105 |
+
className="-ml-1 inline-flex h-5 w-5 items-center justify-center rounded text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
|
| 1106 |
+
>
|
| 1107 |
+
{isExpanded ? <ChevronUp className="h-3.5 w-3.5" /> : <ChevronDown className="h-3.5 w-3.5" />}
|
| 1108 |
+
</button>
|
| 1109 |
+
)}
|
| 1110 |
<Link
|
| 1111 |
href={`/models/${getModelFamilyRouteId(modelResult.model_info)}`}
|
| 1112 |
className="underline decoration-dotted underline-offset-4 hover:text-primary"
|
|
|
|
| 1144 |
</TableCell>
|
| 1145 |
|
| 1146 |
<TableCell className="text-right">
|
| 1147 |
+
<div className="flex items-center justify-end gap-1.5">
|
| 1148 |
+
<div className="text-xl font-semibold tabular-nums">{formatRawScore(modelResult.score, summary.metric_config.unit)}</div>
|
| 1149 |
+
<RowSignalsCompact annotations={rowAnnotations} />
|
| 1150 |
+
</div>
|
| 1151 |
</TableCell>
|
| 1152 |
|
| 1153 |
{isResearchView ? (
|
|
|
|
| 1193 |
<TableCell className="hidden lg:table-cell">
|
| 1194 |
<div className="text-sm text-muted-foreground">{formatDate(modelResult.evaluation_timestamp)}</div>
|
| 1195 |
</TableCell>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1196 |
</TableRow>
|
| 1197 |
|
| 1198 |
{isExpanded && (
|
| 1199 |
<TableRow className="hover:bg-transparent">
|
| 1200 |
+
<TableCell colSpan={7} className="bg-muted/10 px-0 py-0">
|
| 1201 |
<div className="space-y-5 px-4 py-5 sm:px-6">
|
| 1202 |
<div className="grid gap-4 xl:grid-cols-3">
|
| 1203 |
<DetailPanel
|
|
|
|
| 1264 |
)}
|
| 1265 |
</DetailPanel>
|
| 1266 |
|
| 1267 |
+
{!isResearchView && (
|
| 1268 |
+
<ReproducibilityPanel gap={rowAnnotations?.reproducibility_gap} />
|
| 1269 |
+
)}
|
| 1270 |
|
| 1271 |
<DetailPanel
|
| 1272 |
title={isResearchView ? "Score Breakdown" : "Metric Summary"}
|
| 1273 |
subtitle={
|
| 1274 |
isResearchView
|
| 1275 |
+
? "Raw score and scale."
|
| 1276 |
: "Raw performance plus uncertainty and sample details."
|
| 1277 |
}
|
| 1278 |
>
|
|
|
|
| 1282 |
/>
|
| 1283 |
<MetaRow label="Score Type" value={modelResult.result.metric_config.score_type} />
|
| 1284 |
<MetaRow label="Range" value={`${minScore} - ${maxScore}`} />
|
| 1285 |
+
{!isResearchView && (
|
| 1286 |
+
<>
|
| 1287 |
+
<MetaRow
|
| 1288 |
+
label="Sample Size"
|
| 1289 |
+
value={modelResult.score_details.sample_size ?? "Unknown"}
|
| 1290 |
+
/>
|
| 1291 |
+
<MetaRow
|
| 1292 |
+
label="Standard Error"
|
| 1293 |
+
value={modelResult.score_details.standard_error ?? "Unknown"}
|
| 1294 |
+
/>
|
| 1295 |
+
{modelResult.score_details.confidence_interval && (
|
| 1296 |
+
<MetaRow
|
| 1297 |
+
label="Confidence Interval"
|
| 1298 |
+
value={`${modelResult.score_details.confidence_interval.lower} - ${modelResult.score_details.confidence_interval.upper} (${modelResult.score_details.confidence_interval.confidence_level}%)`}
|
| 1299 |
+
/>
|
| 1300 |
+
)}
|
| 1301 |
+
</>
|
| 1302 |
)}
|
| 1303 |
</DetailPanel>
|
| 1304 |
</div>
|
|
|
|
| 1360 |
</div>
|
| 1361 |
)}
|
| 1362 |
|
| 1363 |
+
{isResearchView ? (
|
| 1364 |
<div className="space-y-3">
|
| 1365 |
+
<ResearcherReproducibilityCard
|
| 1366 |
+
modelResult={modelResult}
|
| 1367 |
+
benchmarkKey={summary.benchmark_leaf_key ?? summary.composite_benchmark_key}
|
| 1368 |
+
evalName={summary.evaluation_name}
|
| 1369 |
+
/>
|
| 1370 |
+
<div className="flex justify-end">
|
| 1371 |
+
<FlagScoreButton
|
| 1372 |
+
modelName={modelResult.model_info.name}
|
| 1373 |
+
modelId={modelResult.model_info.id}
|
| 1374 |
+
benchmarkName={summary.evaluation_name}
|
| 1375 |
+
benchmarkId={summary.evaluation_id}
|
| 1376 |
+
score={formatRawScore(modelResult.score, summary.metric_config.unit)}
|
| 1377 |
+
sourceUrl={modelResult.source_metadata.source_url}
|
| 1378 |
+
sourceRecordUrl={modelResult.source_record_url}
|
| 1379 |
+
/>
|
| 1380 |
</div>
|
| 1381 |
+
</div>
|
| 1382 |
+
) : (
|
| 1383 |
+
modelResult.result.generation_config && (
|
| 1384 |
+
<div className="space-y-3">
|
| 1385 |
+
<div>
|
| 1386 |
+
<div className="text-xs font-semibold uppercase tracking-[0.2em] text-muted-foreground">
|
| 1387 |
+
Generation Config
|
| 1388 |
+
</div>
|
| 1389 |
+
<div className="text-sm text-muted-foreground">
|
| 1390 |
+
Evaluation-time generation parameters.
|
| 1391 |
+
</div>
|
| 1392 |
+
</div>
|
| 1393 |
+
|
| 1394 |
+
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-3">
|
| 1395 |
+
{modelResult.result.generation_config.generation_args &&
|
| 1396 |
+
Object.entries(modelResult.result.generation_config.generation_args).map(([key, value]) => (
|
| 1397 |
+
<div key={key} className="rounded-xl border bg-background/70 p-4">
|
| 1398 |
+
<div className="text-xs uppercase tracking-[0.16em] text-muted-foreground">
|
| 1399 |
+
{key.replace(/_/g, " ")}
|
| 1400 |
+
</div>
|
| 1401 |
+
<div className="mt-2 text-sm font-medium">
|
| 1402 |
+
{formatMetadataValue(value)}
|
| 1403 |
+
</div>
|
| 1404 |
+
</div>
|
| 1405 |
+
))}
|
| 1406 |
|
| 1407 |
+
{modelResult.result.generation_config.additional_details && (
|
| 1408 |
+
<div className="rounded-xl border bg-background/70 p-4 md:col-span-2 xl:col-span-3">
|
|
|
|
|
|
|
| 1409 |
<div className="text-xs uppercase tracking-[0.16em] text-muted-foreground">
|
| 1410 |
+
Additional Details
|
| 1411 |
</div>
|
| 1412 |
+
<div className="mt-2 text-sm font-medium whitespace-pre-wrap">
|
| 1413 |
+
{formatMetadataValue(modelResult.result.generation_config.additional_details)}
|
| 1414 |
</div>
|
| 1415 |
</div>
|
| 1416 |
+
)}
|
| 1417 |
|
| 1418 |
+
{modelResult.result.generation_config.prompt_template && (
|
| 1419 |
+
<div className="rounded-xl border bg-background/70 p-4 md:col-span-2 xl:col-span-3">
|
| 1420 |
+
<div className="text-xs uppercase tracking-[0.16em] text-muted-foreground">
|
| 1421 |
+
Prompt Template
|
| 1422 |
+
</div>
|
| 1423 |
+
<div className="mt-2 text-sm font-medium whitespace-pre-wrap">
|
| 1424 |
+
{formatMetadataValue(modelResult.result.generation_config.prompt_template)}
|
| 1425 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1426 |
</div>
|
| 1427 |
+
)}
|
| 1428 |
+
</div>
|
| 1429 |
</div>
|
| 1430 |
+
)
|
| 1431 |
)}
|
| 1432 |
</div>
|
| 1433 |
</TableCell>
|
|
|
|
| 1438 |
})}
|
| 1439 |
{leaderboardRows.length === 0 && (
|
| 1440 |
<TableRow>
|
| 1441 |
+
<TableCell colSpan={7} className="px-6 py-12 text-center text-sm text-muted-foreground">
|
| 1442 |
No leaderboard entries match the selected parameter range.
|
| 1443 |
</TableCell>
|
| 1444 |
</TableRow>
|
|
|
|
| 1476 |
const [activeSubtaskTab, setActiveSubtaskTab] = useState<string>("all")
|
| 1477 |
const [minParamStep, setMinParamStep] = useState(0)
|
| 1478 |
const [maxParamStep, setMaxParamStep] = useState(PARAM_RANGE_VALUES.length - 1)
|
| 1479 |
+
const [expandedRows, setExpandedRows] = useState<Record<string, boolean>>({})
|
| 1480 |
+
|
| 1481 |
+
// Index ModelResultForBenchmark entries by model_info.id so we can power the
|
| 1482 |
+
// research-mode reproducibility card from a multi-metric row. There may be
|
| 1483 |
+
// several entries per model (one per metric); we prefer one with a recorded
|
| 1484 |
+
// generation_config so the card has the most data to show.
|
| 1485 |
+
const modelResultByModelId = useMemo(() => {
|
| 1486 |
+
const map = new Map<string, ModelResultForBenchmark>()
|
| 1487 |
+
for (const result of summary.model_results) {
|
| 1488 |
+
const id = result.model_info.id
|
| 1489 |
+
const existing = map.get(id)
|
| 1490 |
+
if (!existing) {
|
| 1491 |
+
map.set(id, result)
|
| 1492 |
+
continue
|
| 1493 |
+
}
|
| 1494 |
+
const existingHasGen = existing.result.generation_config != null
|
| 1495 |
+
const candidateHasGen = result.result.generation_config != null
|
| 1496 |
+
if (!existingHasGen && candidateHasGen) {
|
| 1497 |
+
map.set(id, result)
|
| 1498 |
+
}
|
| 1499 |
+
}
|
| 1500 |
+
return map
|
| 1501 |
+
}, [summary.model_results])
|
| 1502 |
+
|
| 1503 |
+
const toggleExpandedRow = (key: string) =>
|
| 1504 |
+
setExpandedRows((current) => ({ ...current, [key]: !current[key] }))
|
| 1505 |
const leaderboardMetrics = summary.leaderboard_metrics ?? []
|
| 1506 |
const leaderboardRows = summary.leaderboard_rows ?? []
|
| 1507 |
const allMetricKeys = useMemo(() => leaderboardMetrics.map((metric) => metric.column_key), [leaderboardMetrics])
|
|
|
|
| 1776 |
|
| 1777 |
return (
|
| 1778 |
<Card className="overflow-hidden">
|
| 1779 |
+
<CardHeader className="border-b bg-muted/10 space-y-3">
|
| 1780 |
+
<ApplesToApplesBanner
|
| 1781 |
+
summary={summary.comparability_summary}
|
| 1782 |
+
detailsAnchorId="comparability-panel"
|
| 1783 |
+
/>
|
| 1784 |
<div className="flex flex-col gap-3 lg:flex-row lg:items-end lg:justify-between">
|
| 1785 |
<div className="space-y-2">
|
| 1786 |
<div className="flex items-center gap-2">
|
|
|
|
| 2017 |
<TableBody>
|
| 2018 |
{pagedRows.map((row) => {
|
| 2019 |
const rank = rankByModelId.get(row.model_info.id) ?? 0
|
| 2020 |
+
const expandKey = row.model_info.id
|
| 2021 |
+
const isExpanded = expandedRows[expandKey] ?? false
|
| 2022 |
+
const matchingResult = modelResultByModelId.get(row.model_info.id)
|
| 2023 |
|
| 2024 |
return (
|
| 2025 |
+
<Fragment key={row.model_info.id}>
|
| 2026 |
+
<TableRow className={cn("hover:bg-muted/10", isExpanded && "bg-muted/15")}>
|
| 2027 |
<TableCell className="px-4">
|
| 2028 |
<div
|
| 2029 |
className={cn(
|
|
|
|
| 2036 |
</TableCell>
|
| 2037 |
<TableCell className="px-4 whitespace-normal">
|
| 2038 |
<div className="space-y-1">
|
| 2039 |
+
<div className="flex items-center gap-1.5 font-semibold leading-tight">
|
| 2040 |
+
{isResearchView && matchingResult && (
|
| 2041 |
+
<button
|
| 2042 |
+
type="button"
|
| 2043 |
+
onClick={() => toggleExpandedRow(expandKey)}
|
| 2044 |
+
aria-label={isExpanded ? "Hide reproducibility" : "Show reproducibility"}
|
| 2045 |
+
aria-expanded={isExpanded}
|
| 2046 |
+
className="-ml-1 inline-flex h-5 w-5 items-center justify-center rounded text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
|
| 2047 |
+
>
|
| 2048 |
+
{isExpanded ? <ChevronUp className="h-3.5 w-3.5" /> : <ChevronDown className="h-3.5 w-3.5" />}
|
| 2049 |
+
</button>
|
| 2050 |
+
)}
|
| 2051 |
<Link
|
| 2052 |
href={`/models/${getModelFamilyRouteId(row.model_info)}`}
|
| 2053 |
className="underline decoration-dotted underline-offset-4 hover:text-primary"
|
| 2054 |
>
|
| 2055 |
{row.model_info.name}
|
| 2056 |
</Link>
|
| 2057 |
+
<RowSignalsCompact
|
| 2058 |
+
annotations={getRowLevelAnnotations(row, visibleMetrics)}
|
| 2059 |
+
className="ml-1"
|
| 2060 |
+
/>
|
| 2061 |
</div>
|
| 2062 |
<div className="flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
|
| 2063 |
{row.model_info.parameter_count && (
|
|
|
|
| 2072 |
)}
|
| 2073 |
<span className="lg:hidden">{row.model_info.developer ?? "Unknown developer"}</span>
|
| 2074 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2075 |
</div>
|
| 2076 |
</TableCell>
|
| 2077 |
|
|
|
|
| 2106 |
{formatDate(row.evaluation_timestamp)}
|
| 2107 |
</TableCell>
|
| 2108 |
</TableRow>
|
| 2109 |
+
{isResearchView && isExpanded && matchingResult && (
|
| 2110 |
+
<TableRow className="hover:bg-transparent">
|
| 2111 |
+
<TableCell
|
| 2112 |
+
colSpan={visibleMetrics.length + 5}
|
| 2113 |
+
className="bg-muted/10 px-4 py-5 sm:px-6"
|
| 2114 |
+
>
|
| 2115 |
+
<div className="space-y-3">
|
| 2116 |
+
<ResearcherReproducibilityCard
|
| 2117 |
+
modelResult={matchingResult}
|
| 2118 |
+
benchmarkKey={summary.benchmark_leaf_key ?? summary.composite_benchmark_key}
|
| 2119 |
+
evalName={summary.evaluation_name}
|
| 2120 |
+
/>
|
| 2121 |
+
<div className="flex justify-end">
|
| 2122 |
+
<FlagScoreButton
|
| 2123 |
+
modelName={matchingResult.model_info.name}
|
| 2124 |
+
modelId={matchingResult.model_info.id}
|
| 2125 |
+
benchmarkName={summary.evaluation_name}
|
| 2126 |
+
benchmarkId={summary.evaluation_id}
|
| 2127 |
+
score={formatRawScore(matchingResult.score, summary.metric_config.unit)}
|
| 2128 |
+
sourceUrl={matchingResult.source_metadata.source_url}
|
| 2129 |
+
sourceRecordUrl={matchingResult.source_record_url}
|
| 2130 |
+
/>
|
| 2131 |
+
</div>
|
| 2132 |
+
</div>
|
| 2133 |
+
</TableCell>
|
| 2134 |
+
</TableRow>
|
| 2135 |
+
)}
|
| 2136 |
+
</Fragment>
|
| 2137 |
)})}
|
| 2138 |
|
| 2139 |
{filteredRows.length === 0 && (
|
|
|
|
| 2164 |
isResearchView,
|
| 2165 |
defaultOpen = true,
|
| 2166 |
defaultRisksOpen = false,
|
| 2167 |
+
knownIssues = [],
|
| 2168 |
}: {
|
| 2169 |
card: BenchmarkCard
|
| 2170 |
isResearchView: boolean
|
| 2171 |
defaultOpen?: boolean
|
| 2172 |
defaultRisksOpen?: boolean
|
| 2173 |
+
knownIssues?: KnownIssue[]
|
| 2174 |
}) {
|
| 2175 |
const [open, setOpen] = useState(defaultOpen)
|
| 2176 |
return (
|
|
|
|
| 2199 |
card={card}
|
| 2200 |
isResearchView={isResearchView}
|
| 2201 |
defaultRisksOpen={defaultRisksOpen}
|
| 2202 |
+
knownIssues={knownIssues}
|
| 2203 |
/>
|
| 2204 |
</CollapsibleContent>
|
| 2205 |
</Collapsible>
|
|
|
|
| 2216 |
children: React.ReactNode
|
| 2217 |
}) {
|
| 2218 |
return (
|
| 2219 |
+
<div className="min-w-0 rounded-2xl border bg-background/70 p-4">
|
| 2220 |
<div className="mb-4">
|
| 2221 |
<div className="font-semibold">{title}</div>
|
| 2222 |
<div className="text-sm text-muted-foreground">{subtitle}</div>
|
| 2223 |
</div>
|
| 2224 |
+
<div className="min-w-0 space-y-2.5">{children}</div>
|
| 2225 |
</div>
|
| 2226 |
)
|
| 2227 |
}
|
|
|
|
| 2250 |
}
|
| 2251 |
}
|
| 2252 |
return (
|
| 2253 |
+
<div
|
| 2254 |
+
className="text-sm"
|
| 2255 |
+
style={{
|
| 2256 |
+
display: "grid",
|
| 2257 |
+
gridTemplateColumns: "8rem minmax(0, 1fr)",
|
| 2258 |
+
columnGap: "0.75rem",
|
| 2259 |
+
width: "100%",
|
| 2260 |
+
minWidth: 0,
|
| 2261 |
+
}}
|
| 2262 |
+
>
|
| 2263 |
+
<div className="text-muted-foreground">{label}</div>
|
| 2264 |
+
<div
|
| 2265 |
+
className="font-medium"
|
| 2266 |
+
style={{
|
| 2267 |
+
minWidth: 0,
|
| 2268 |
+
maxWidth: "100%",
|
| 2269 |
+
overflowWrap: "anywhere",
|
| 2270 |
+
wordBreak: "break-word",
|
| 2271 |
+
}}
|
| 2272 |
+
>
|
| 2273 |
+
{value}
|
| 2274 |
+
</div>
|
| 2275 |
</div>
|
| 2276 |
)
|
| 2277 |
}
|
|
|
|
| 2323 |
card,
|
| 2324 |
isResearchView,
|
| 2325 |
defaultRisksOpen = false,
|
| 2326 |
+
knownIssues = [],
|
| 2327 |
}: {
|
| 2328 |
card: BenchmarkCard
|
| 2329 |
isResearchView: boolean
|
| 2330 |
defaultRisksOpen?: boolean
|
| 2331 |
+
knownIssues?: KnownIssue[]
|
| 2332 |
}) {
|
| 2333 |
const [risksOpen, setRisksOpen] = useState(defaultRisksOpen)
|
| 2334 |
const details = card.benchmark_details
|
|
|
|
| 2378 |
</CardHeader>
|
| 2379 |
|
| 2380 |
<CardContent className="space-y-6 p-5 sm:p-6">
|
| 2381 |
+
{knownIssues.length > 0 && <KnownIssuesPanel issues={knownIssues} variant="full" />}
|
| 2382 |
+
|
| 2383 |
{/* Overview + domains */}
|
| 2384 |
<div className="space-y-3">
|
| 2385 |
<p className="text-sm leading-6 text-muted-foreground">{details.overview}</p>
|
|
|
|
| 2432 |
</div>
|
| 2433 |
</div>
|
| 2434 |
|
| 2435 |
+
{(methodology.methods?.length > 0 ||
|
| 2436 |
+
(methodology.calculation && methodology.calculation !== "Not specified") ||
|
| 2437 |
+
(methodology.validation && methodology.validation !== "Not specified")) && (
|
| 2438 |
+
<div className="rounded-[1.25rem] border border-border/70 bg-muted/10 p-4">
|
| 2439 |
+
<div className="mb-3 text-[11px] font-semibold uppercase tracking-[0.2em] text-muted-foreground">
|
| 2440 |
+
How tasks were sourced and scored
|
| 2441 |
+
</div>
|
| 2442 |
+
<div className="grid gap-4 lg:grid-cols-2">
|
| 2443 |
+
{methodology.methods?.length > 0 && (
|
| 2444 |
+
<div>
|
| 2445 |
+
<div className="mb-1.5 text-xs font-semibold text-foreground/80">
|
| 2446 |
+
Task setup
|
| 2447 |
+
</div>
|
| 2448 |
+
<ol className="list-decimal space-y-1.5 pl-4 text-sm leading-5 text-muted-foreground">
|
| 2449 |
+
{methodology.methods.map((m, i) => (
|
| 2450 |
+
<li key={i}>{m}</li>
|
| 2451 |
+
))}
|
| 2452 |
+
</ol>
|
| 2453 |
+
</div>
|
| 2454 |
+
)}
|
| 2455 |
+
<div className="space-y-3">
|
| 2456 |
+
{methodology.calculation && methodology.calculation !== "Not specified" && (
|
| 2457 |
+
<div>
|
| 2458 |
+
<div className="mb-1.5 text-xs font-semibold text-foreground/80">
|
| 2459 |
+
Score calculation
|
| 2460 |
+
</div>
|
| 2461 |
+
<p className="text-sm leading-5 text-muted-foreground">
|
| 2462 |
+
{methodology.calculation}
|
| 2463 |
+
</p>
|
| 2464 |
+
</div>
|
| 2465 |
+
)}
|
| 2466 |
+
{methodology.validation && methodology.validation !== "Not specified" && (
|
| 2467 |
+
<div>
|
| 2468 |
+
<div className="mb-1.5 text-xs font-semibold text-foreground/80">
|
| 2469 |
+
Validation
|
| 2470 |
+
</div>
|
| 2471 |
+
<p className="text-sm leading-5 text-muted-foreground">
|
| 2472 |
+
{methodology.validation}
|
| 2473 |
+
</p>
|
| 2474 |
+
</div>
|
| 2475 |
+
)}
|
| 2476 |
+
</div>
|
| 2477 |
+
</div>
|
| 2478 |
+
</div>
|
| 2479 |
+
)}
|
| 2480 |
+
|
| 2481 |
{/* Research-only: methodology + dataset details */}
|
| 2482 |
{isResearchView && (
|
| 2483 |
<div className="grid gap-4 sm:grid-cols-2">
|
|
|
|
| 2529 |
</div>
|
| 2530 |
)}
|
| 2531 |
|
| 2532 |
+
{/* Generic IBM-style AI risks. These are boilerplate (per audit
|
| 2533 |
+
feedback: "least useful feature for policy users"), so in policy
|
| 2534 |
+
mode we hide them entirely — the curated known-issues panel above
|
| 2535 |
+
carries the benchmark-specific concerns. Researchers still get the
|
| 2536 |
+
full collapsible list. */}
|
| 2537 |
+
{risks.length > 0 && isResearchView && (
|
| 2538 |
<Collapsible open={risksOpen} onOpenChange={setRisksOpen}>
|
| 2539 |
<CollapsibleTrigger asChild>
|
| 2540 |
<button
|
|
@@ -0,0 +1,267 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client"
|
| 2 |
+
|
| 3 |
+
import { useMemo, useState } from "react"
|
| 4 |
+
import {
|
| 5 |
+
Copy,
|
| 6 |
+
ExternalLink,
|
| 7 |
+
FileSearch,
|
| 8 |
+
Flag,
|
| 9 |
+
GitPullRequestArrow,
|
| 10 |
+
MessageSquare,
|
| 11 |
+
} from "lucide-react"
|
| 12 |
+
import {
|
| 13 |
+
Dialog,
|
| 14 |
+
DialogContent,
|
| 15 |
+
DialogDescription,
|
| 16 |
+
DialogHeader,
|
| 17 |
+
DialogTitle,
|
| 18 |
+
} from "@/components/ui/dialog"
|
| 19 |
+
import { Button } from "@/components/ui/button"
|
| 20 |
+
|
| 21 |
+
interface FlagScoreButtonProps {
|
| 22 |
+
modelName: string
|
| 23 |
+
modelId: string
|
| 24 |
+
benchmarkName: string
|
| 25 |
+
benchmarkId?: string
|
| 26 |
+
score: number | string
|
| 27 |
+
/** URL to the published source the score was extracted from (paper, blog, leaderboard). */
|
| 28 |
+
sourceUrl?: string
|
| 29 |
+
/** URL to the processed record JSON in the card_backend HF dataset. */
|
| 30 |
+
sourceRecordUrl?: string
|
| 31 |
+
/** Optional explicit upstream record URL in evaleval/EEE_datastore (raw source of truth). */
|
| 32 |
+
eeeRecordUrl?: string
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
interface DatasetLinks {
|
| 36 |
+
repoSlug: string
|
| 37 |
+
recordViewUrl: string | null
|
| 38 |
+
recordRawUrl: string | null
|
| 39 |
+
discussionsUrl: string
|
| 40 |
+
newDiscussionUrl: string
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
/**
|
| 44 |
+
* Given a /resolve/main/... HF dataset URL, derive the helpful sibling URLs
|
| 45 |
+
* for the dataset (file viewer, discussions list, new-discussion form). Returns
|
| 46 |
+
* null when the input doesn't look like a HF dataset URL.
|
| 47 |
+
*/
|
| 48 |
+
function deriveDatasetLinks(recordUrl: string | undefined, prefilledTitle: string): DatasetLinks | null {
|
| 49 |
+
if (!recordUrl) return null
|
| 50 |
+
const m = recordUrl.match(
|
| 51 |
+
/^https:\/\/huggingface\.co\/datasets\/([^/]+\/[^/]+)\/(?:resolve|raw|blob)\/[^/]+\/(.*)$/,
|
| 52 |
+
)
|
| 53 |
+
if (!m) return null
|
| 54 |
+
const repoSlug = m[1]
|
| 55 |
+
const path = m[2]
|
| 56 |
+
const datasetBase = `https://huggingface.co/datasets/${repoSlug}`
|
| 57 |
+
const recordViewUrl = `${datasetBase}/blob/main/${path}`
|
| 58 |
+
const recordRawUrl = `${datasetBase}/resolve/main/${path}`
|
| 59 |
+
const discussionsUrl = `${datasetBase}/discussions`
|
| 60 |
+
const newDiscussionUrl = `${datasetBase}/discussions/new?title=${encodeURIComponent(prefilledTitle)}`
|
| 61 |
+
return { repoSlug, recordViewUrl, recordRawUrl, discussionsUrl, newDiscussionUrl }
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
/**
|
| 65 |
+
* "Flag this score" — researcher affordance that, instead of capturing the
|
| 66 |
+
* report ourselves, sends the user directly to the upstream HF dataset where
|
| 67 |
+
* the data lives. They can then file a discussion or submit a correction PR
|
| 68 |
+
* against the actual record. We also offer a copyable context snippet so the
|
| 69 |
+
* issue body has all the relevant identifiers without needing to retype them.
|
| 70 |
+
*/
|
| 71 |
+
export function FlagScoreButton({
|
| 72 |
+
modelName,
|
| 73 |
+
modelId,
|
| 74 |
+
benchmarkName,
|
| 75 |
+
benchmarkId,
|
| 76 |
+
score,
|
| 77 |
+
sourceUrl,
|
| 78 |
+
sourceRecordUrl,
|
| 79 |
+
eeeRecordUrl,
|
| 80 |
+
}: FlagScoreButtonProps) {
|
| 81 |
+
const [open, setOpen] = useState(false)
|
| 82 |
+
const [copied, setCopied] = useState<"context" | null>(null)
|
| 83 |
+
|
| 84 |
+
const prefilledTitle = `Possible issue: ${modelName} on ${benchmarkName} (score ${score})`
|
| 85 |
+
|
| 86 |
+
const cardBackendLinks = useMemo(
|
| 87 |
+
() => deriveDatasetLinks(sourceRecordUrl, prefilledTitle),
|
| 88 |
+
[sourceRecordUrl, prefilledTitle],
|
| 89 |
+
)
|
| 90 |
+
const eeeLinks = useMemo(
|
| 91 |
+
() => deriveDatasetLinks(eeeRecordUrl, prefilledTitle),
|
| 92 |
+
[eeeRecordUrl, prefilledTitle],
|
| 93 |
+
)
|
| 94 |
+
|
| 95 |
+
// Prefer the EEE upstream as the "correction venue" when known — that's
|
| 96 |
+
// where raw evaluation records live. Fall back to card_backend (the
|
| 97 |
+
// pipeline output) when EEE isn't directly addressable for this row.
|
| 98 |
+
const correctionLinks = eeeLinks ?? cardBackendLinks
|
| 99 |
+
|
| 100 |
+
const contextSnippet = [
|
| 101 |
+
`Model: ${modelName} (${modelId})`,
|
| 102 |
+
`Benchmark: ${benchmarkName}${benchmarkId ? ` (${benchmarkId})` : ""}`,
|
| 103 |
+
`Reported score: ${score}`,
|
| 104 |
+
sourceUrl ? `Original source URL: ${sourceUrl}` : null,
|
| 105 |
+
sourceRecordUrl ? `Pipeline record: ${sourceRecordUrl}` : null,
|
| 106 |
+
eeeRecordUrl ? `EEE upstream record: ${eeeRecordUrl}` : null,
|
| 107 |
+
]
|
| 108 |
+
.filter(Boolean)
|
| 109 |
+
.join("\n")
|
| 110 |
+
|
| 111 |
+
const handleCopyContext = async () => {
|
| 112 |
+
try {
|
| 113 |
+
await navigator.clipboard.writeText(contextSnippet)
|
| 114 |
+
setCopied("context")
|
| 115 |
+
setTimeout(() => setCopied(null), 1800)
|
| 116 |
+
} catch {
|
| 117 |
+
// Clipboard might be blocked; ignore — the user can still select text manually.
|
| 118 |
+
}
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
return (
|
| 122 |
+
<>
|
| 123 |
+
<Button
|
| 124 |
+
type="button"
|
| 125 |
+
size="sm"
|
| 126 |
+
variant="outline"
|
| 127 |
+
className="h-7 gap-1.5 text-xs"
|
| 128 |
+
onClick={() => setOpen(true)}
|
| 129 |
+
>
|
| 130 |
+
<Flag className="h-3 w-3" />
|
| 131 |
+
Flag this score
|
| 132 |
+
</Button>
|
| 133 |
+
|
| 134 |
+
<Dialog open={open} onOpenChange={setOpen}>
|
| 135 |
+
<DialogContent className="sm:max-w-lg">
|
| 136 |
+
<DialogHeader>
|
| 137 |
+
<DialogTitle>Flag this score</DialogTitle>
|
| 138 |
+
<DialogDescription>
|
| 139 |
+
Take this report directly to the dataset where the record lives. You can file a
|
| 140 |
+
discussion or open a correction PR against the actual file.
|
| 141 |
+
</DialogDescription>
|
| 142 |
+
</DialogHeader>
|
| 143 |
+
|
| 144 |
+
<div className="space-y-4">
|
| 145 |
+
<div className="rounded-xl border bg-muted/20 p-3 text-xs">
|
| 146 |
+
<div className="text-[10px] font-semibold uppercase tracking-[0.16em] text-muted-foreground">
|
| 147 |
+
Flagging
|
| 148 |
+
</div>
|
| 149 |
+
<div className="mt-1">
|
| 150 |
+
<span className="font-medium">{modelName}</span>{" "}
|
| 151 |
+
<span className="text-muted-foreground">on</span>{" "}
|
| 152 |
+
<span className="font-medium">{benchmarkName}</span>
|
| 153 |
+
</div>
|
| 154 |
+
<div className="text-muted-foreground tabular-nums">Score: {score}</div>
|
| 155 |
+
</div>
|
| 156 |
+
|
| 157 |
+
<div className="space-y-2">
|
| 158 |
+
{correctionLinks?.recordViewUrl && (
|
| 159 |
+
<a
|
| 160 |
+
href={correctionLinks.recordViewUrl}
|
| 161 |
+
target="_blank"
|
| 162 |
+
rel="noreferrer"
|
| 163 |
+
className="flex items-center justify-between rounded-xl border border-border/70 bg-background px-3 py-2.5 text-sm transition-colors hover:border-primary/40 hover:bg-muted/20"
|
| 164 |
+
>
|
| 165 |
+
<div className="flex items-center gap-2">
|
| 166 |
+
<FileSearch className="h-4 w-4 text-muted-foreground" />
|
| 167 |
+
<div>
|
| 168 |
+
<div className="font-semibold">View the underlying record</div>
|
| 169 |
+
<div className="text-xs text-muted-foreground">
|
| 170 |
+
Opens the JSON file on{" "}
|
| 171 |
+
<span className="font-mono">{correctionLinks.repoSlug}</span>
|
| 172 |
+
</div>
|
| 173 |
+
</div>
|
| 174 |
+
</div>
|
| 175 |
+
<ExternalLink className="h-3.5 w-3.5 text-muted-foreground" />
|
| 176 |
+
</a>
|
| 177 |
+
)}
|
| 178 |
+
|
| 179 |
+
{correctionLinks && (
|
| 180 |
+
<a
|
| 181 |
+
href={correctionLinks.newDiscussionUrl}
|
| 182 |
+
target="_blank"
|
| 183 |
+
rel="noreferrer"
|
| 184 |
+
className="flex items-center justify-between rounded-xl border border-border/70 bg-background px-3 py-2.5 text-sm transition-colors hover:border-primary/40 hover:bg-muted/20"
|
| 185 |
+
>
|
| 186 |
+
<div className="flex items-center gap-2">
|
| 187 |
+
<MessageSquare className="h-4 w-4 text-muted-foreground" />
|
| 188 |
+
<div>
|
| 189 |
+
<div className="font-semibold">Open a discussion</div>
|
| 190 |
+
<div className="text-xs text-muted-foreground">
|
| 191 |
+
Pre-filled title; paste the context snippet into the body.
|
| 192 |
+
</div>
|
| 193 |
+
</div>
|
| 194 |
+
</div>
|
| 195 |
+
<ExternalLink className="h-3.5 w-3.5 text-muted-foreground" />
|
| 196 |
+
</a>
|
| 197 |
+
)}
|
| 198 |
+
|
| 199 |
+
{correctionLinks && (
|
| 200 |
+
<a
|
| 201 |
+
href={correctionLinks.discussionsUrl}
|
| 202 |
+
target="_blank"
|
| 203 |
+
rel="noreferrer"
|
| 204 |
+
className="flex items-center justify-between rounded-xl border border-border/70 bg-background px-3 py-2.5 text-sm transition-colors hover:border-primary/40 hover:bg-muted/20"
|
| 205 |
+
>
|
| 206 |
+
<div className="flex items-center gap-2">
|
| 207 |
+
<GitPullRequestArrow className="h-4 w-4 text-muted-foreground" />
|
| 208 |
+
<div>
|
| 209 |
+
<div className="font-semibold">Browse existing discussions</div>
|
| 210 |
+
<div className="text-xs text-muted-foreground">
|
| 211 |
+
Check whether someone already filed a similar correction.
|
| 212 |
+
</div>
|
| 213 |
+
</div>
|
| 214 |
+
</div>
|
| 215 |
+
<ExternalLink className="h-3.5 w-3.5 text-muted-foreground" />
|
| 216 |
+
</a>
|
| 217 |
+
)}
|
| 218 |
+
|
| 219 |
+
{!correctionLinks && (
|
| 220 |
+
<div className="rounded-xl border border-dashed border-amber-300/60 bg-amber-50/40 px-3 py-2.5 text-xs text-amber-800 dark:border-amber-900/60 dark:bg-amber-950/20 dark:text-amber-200">
|
| 221 |
+
No upstream record URL is recorded for this row, so we can't link directly to
|
| 222 |
+
the dataset. Copy the context below and file an issue at{" "}
|
| 223 |
+
<a
|
| 224 |
+
className="underline-offset-4 hover:underline"
|
| 225 |
+
href="https://huggingface.co/datasets/evaleval/EEE_datastore/discussions"
|
| 226 |
+
target="_blank"
|
| 227 |
+
rel="noreferrer"
|
| 228 |
+
>
|
| 229 |
+
evaleval/EEE_datastore/discussions
|
| 230 |
+
</a>
|
| 231 |
+
.
|
| 232 |
+
</div>
|
| 233 |
+
)}
|
| 234 |
+
</div>
|
| 235 |
+
|
| 236 |
+
<div className="space-y-1.5">
|
| 237 |
+
<div className="flex items-center justify-between">
|
| 238 |
+
<label className="text-xs font-semibold text-muted-foreground">
|
| 239 |
+
Context to paste into the issue
|
| 240 |
+
</label>
|
| 241 |
+
<Button
|
| 242 |
+
type="button"
|
| 243 |
+
size="sm"
|
| 244 |
+
variant="ghost"
|
| 245 |
+
onClick={handleCopyContext}
|
| 246 |
+
className="h-7 gap-1.5 text-xs"
|
| 247 |
+
>
|
| 248 |
+
<Copy className="h-3 w-3" />
|
| 249 |
+
{copied === "context" ? "Copied" : "Copy"}
|
| 250 |
+
</Button>
|
| 251 |
+
</div>
|
| 252 |
+
<pre className="max-h-40 overflow-auto whitespace-pre-wrap break-all rounded-xl border bg-muted/10 p-3 text-[11px] leading-5 text-muted-foreground">
|
| 253 |
+
{contextSnippet}
|
| 254 |
+
</pre>
|
| 255 |
+
</div>
|
| 256 |
+
|
| 257 |
+
<div className="flex justify-end pt-1">
|
| 258 |
+
<Button type="button" variant="ghost" onClick={() => setOpen(false)}>
|
| 259 |
+
Close
|
| 260 |
+
</Button>
|
| 261 |
+
</div>
|
| 262 |
+
</div>
|
| 263 |
+
</DialogContent>
|
| 264 |
+
</Dialog>
|
| 265 |
+
</>
|
| 266 |
+
)
|
| 267 |
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client"
|
| 2 |
+
|
| 3 |
+
import { AlertOctagon, AlertTriangle, ExternalLink, Info } from "lucide-react"
|
| 4 |
+
import type { KnownIssue } from "@/lib/known-issues"
|
| 5 |
+
|
| 6 |
+
interface KnownIssuesPanelProps {
|
| 7 |
+
issues: KnownIssue[]
|
| 8 |
+
/**
|
| 9 |
+
* "compact" — single-line summary chip suitable for surfacing at the top of
|
| 10 |
+
* the policy overview. "full" — full bordered list with descriptions.
|
| 11 |
+
*/
|
| 12 |
+
variant?: "compact" | "full"
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
const SEVERITY_STYLE: Record<KnownIssue["severity"], { wrap: string; icon: React.ComponentType<{ className?: string }>; label: string }> = {
|
| 16 |
+
info: {
|
| 17 |
+
wrap: "border-sky-300/60 bg-sky-50/60 text-sky-900 dark:border-sky-900/50 dark:bg-sky-950/20 dark:text-sky-100",
|
| 18 |
+
icon: Info,
|
| 19 |
+
label: "Note",
|
| 20 |
+
},
|
| 21 |
+
warning: {
|
| 22 |
+
wrap: "border-amber-300/60 bg-amber-50/60 text-amber-900 dark:border-amber-900/50 dark:bg-amber-950/20 dark:text-amber-100",
|
| 23 |
+
icon: AlertTriangle,
|
| 24 |
+
label: "Known issue",
|
| 25 |
+
},
|
| 26 |
+
critical: {
|
| 27 |
+
wrap: "border-rose-300/60 bg-rose-50/60 text-rose-900 dark:border-rose-900/50 dark:bg-rose-950/20 dark:text-rose-100",
|
| 28 |
+
icon: AlertOctagon,
|
| 29 |
+
label: "Critical issue",
|
| 30 |
+
},
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
export function KnownIssuesPanel({ issues, variant = "full" }: KnownIssuesPanelProps) {
|
| 34 |
+
if (issues.length === 0) return null
|
| 35 |
+
|
| 36 |
+
const sorted = [...issues].sort((a, b) => severityRank(b.severity) - severityRank(a.severity))
|
| 37 |
+
const headlineSeverity = sorted[0].severity
|
| 38 |
+
const Style = SEVERITY_STYLE[headlineSeverity]
|
| 39 |
+
const Icon = Style.icon
|
| 40 |
+
|
| 41 |
+
if (variant === "compact") {
|
| 42 |
+
return (
|
| 43 |
+
<div className={`flex items-start gap-2 rounded-2xl border px-3 py-2 text-sm ${Style.wrap}`}>
|
| 44 |
+
<Icon className="mt-0.5 h-4 w-4 shrink-0" />
|
| 45 |
+
<div className="min-w-0">
|
| 46 |
+
<span className="font-semibold">
|
| 47 |
+
{issues.length} known issue{issues.length === 1 ? "" : "s"} documented
|
| 48 |
+
</span>
|
| 49 |
+
<span className="ml-1 text-muted-foreground">— see below for detail.</span>
|
| 50 |
+
</div>
|
| 51 |
+
</div>
|
| 52 |
+
)
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
return (
|
| 56 |
+
<section className="space-y-2">
|
| 57 |
+
<div className="text-[11px] font-semibold uppercase tracking-[0.2em] text-muted-foreground">
|
| 58 |
+
Known issues with this benchmark
|
| 59 |
+
</div>
|
| 60 |
+
<ul className="space-y-2">
|
| 61 |
+
{sorted.map((issue, idx) => {
|
| 62 |
+
const S = SEVERITY_STYLE[issue.severity]
|
| 63 |
+
const I = S.icon
|
| 64 |
+
return (
|
| 65 |
+
<li
|
| 66 |
+
key={`${issue.title}-${idx}`}
|
| 67 |
+
className={`rounded-2xl border px-3.5 py-3 ${S.wrap}`}
|
| 68 |
+
>
|
| 69 |
+
<div className="flex items-start gap-2">
|
| 70 |
+
<I className="mt-0.5 h-4 w-4 shrink-0" />
|
| 71 |
+
<div className="min-w-0 flex-1">
|
| 72 |
+
<div className="flex flex-wrap items-baseline gap-2">
|
| 73 |
+
<span className="text-[10px] font-semibold uppercase tracking-[0.18em] opacity-80">
|
| 74 |
+
{S.label}
|
| 75 |
+
</span>
|
| 76 |
+
<span className="font-semibold leading-tight">{issue.title}</span>
|
| 77 |
+
</div>
|
| 78 |
+
<p className="mt-1 text-sm leading-5 opacity-90">{issue.summary}</p>
|
| 79 |
+
{(issue.source_url || issue.published) && (
|
| 80 |
+
<div className="mt-2 flex flex-wrap items-center gap-3 text-xs opacity-80">
|
| 81 |
+
{issue.source_url && (
|
| 82 |
+
<a
|
| 83 |
+
href={issue.source_url}
|
| 84 |
+
target="_blank"
|
| 85 |
+
rel="noreferrer"
|
| 86 |
+
className="inline-flex items-center gap-1 underline-offset-4 hover:underline"
|
| 87 |
+
>
|
| 88 |
+
Source <ExternalLink className="h-3 w-3" />
|
| 89 |
+
</a>
|
| 90 |
+
)}
|
| 91 |
+
{issue.published && <span>Published {issue.published}</span>}
|
| 92 |
+
</div>
|
| 93 |
+
)}
|
| 94 |
+
</div>
|
| 95 |
+
</div>
|
| 96 |
+
</li>
|
| 97 |
+
)
|
| 98 |
+
})}
|
| 99 |
+
</ul>
|
| 100 |
+
</section>
|
| 101 |
+
)
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
function severityRank(s: KnownIssue["severity"]): number {
|
| 105 |
+
if (s === "critical") return 3
|
| 106 |
+
if (s === "warning") return 2
|
| 107 |
+
return 1
|
| 108 |
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client"
|
| 2 |
+
|
| 3 |
+
import type { ReactNode } from "react"
|
| 4 |
+
import { useAudienceMode } from "@/components/audience-mode-provider"
|
| 5 |
+
|
| 6 |
+
interface ModeSwitchProps {
|
| 7 |
+
research?: ReactNode
|
| 8 |
+
policy?: ReactNode
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
/**
|
| 12 |
+
* Renders different content per audience mode. Use this instead of inline
|
| 13 |
+
* `mode === "research"` checks so that call sites stay readable and the
|
| 14 |
+
* two branches stay obviously parallel.
|
| 15 |
+
*/
|
| 16 |
+
export function ModeSwitch({ research, policy }: ModeSwitchProps) {
|
| 17 |
+
const { mode } = useAudienceMode()
|
| 18 |
+
return <>{mode === "research" ? research ?? null : policy ?? null}</>
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
/**
|
| 22 |
+
* Hook variant for cases where ModeSwitch can't be used (e.g. choosing
|
| 23 |
+
* between two non-JSX values like a className or a number).
|
| 24 |
+
*/
|
| 25 |
+
export function useModeValue<T>(values: { research: T; policy: T }): T {
|
| 26 |
+
const { mode } = useAudienceMode()
|
| 27 |
+
return mode === "research" ? values.research : values.policy
|
| 28 |
+
}
|
|
@@ -0,0 +1,350 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client"
|
| 2 |
+
|
| 3 |
+
import { useMemo, useState } from "react"
|
| 4 |
+
import { BookOpen, ChevronDown, ChevronUp, ExternalLink, FileText, Globe, Layers, ScrollText, Tag, Users } from "lucide-react"
|
| 5 |
+
import { SignalTooltip } from "@/components/signals/signal-tooltip"
|
| 6 |
+
import type { BenchmarkEvalSummary } from "@/lib/eval-processing"
|
| 7 |
+
import { getKnownIssues } from "@/lib/known-issues"
|
| 8 |
+
import { KnownIssuesPanel } from "@/components/known-issues-panel"
|
| 9 |
+
|
| 10 |
+
interface PolicyOverviewProps {
|
| 11 |
+
summary: BenchmarkEvalSummary
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
const SUMMARY_PREVIEW_CHARS = 280
|
| 15 |
+
|
| 16 |
+
function classifyResource(url: string): { kind: "paper" | "dataset" | "leaderboard" | "site"; label: string } {
|
| 17 |
+
const lower = url.toLowerCase()
|
| 18 |
+
if (lower.includes("arxiv.org") || lower.endsWith(".pdf") || lower.includes("/papers/")) {
|
| 19 |
+
return { kind: "paper", label: "Paper" }
|
| 20 |
+
}
|
| 21 |
+
if (lower.includes("huggingface.co/datasets") || lower.includes("/dataset")) {
|
| 22 |
+
return { kind: "dataset", label: "Dataset" }
|
| 23 |
+
}
|
| 24 |
+
if (lower.includes("leaderboard")) {
|
| 25 |
+
return { kind: "leaderboard", label: "Leaderboard" }
|
| 26 |
+
}
|
| 27 |
+
return { kind: "site", label: "Source" }
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
function shortHost(url: string) {
|
| 31 |
+
return url.replace(/^https?:\/\//, "").replace(/\/.*$/, "")
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
/**
|
| 35 |
+
* Plain-language summary surface shown at the top of the benchmark page in
|
| 36 |
+
* policy mode. Designed to answer three questions a non-technical reader has:
|
| 37 |
+
* what does this measure, who built it, and where does it come from?
|
| 38 |
+
*
|
| 39 |
+
* Technical detail (variants, metric specifications, score scales) lives in
|
| 40 |
+
* the existing overview card below this and is collapsed by default.
|
| 41 |
+
*/
|
| 42 |
+
function normalizeId(value: string | undefined | null): string {
|
| 43 |
+
if (!value) return ""
|
| 44 |
+
return value
|
| 45 |
+
.toLowerCase()
|
| 46 |
+
.trim()
|
| 47 |
+
.replace(/[\s_\-/]+/g, "")
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
export function PolicyOverview({ summary }: PolicyOverviewProps) {
|
| 51 |
+
const card = summary.benchmark_card
|
| 52 |
+
|
| 53 |
+
// Defensive check: pipelines older than the "ancestor card leak" fix
|
| 54 |
+
// sometimes attach the parent suite's card to a leaf benchmark (e.g.
|
| 55 |
+
// helm_classic's card embedded under XSUM). Detect when the card's own
|
| 56 |
+
// name is clearly not this benchmark and ignore its narrative text — the
|
| 57 |
+
// synthesized fallback below produces something accurate instead.
|
| 58 |
+
const cardName = card?.benchmark_details?.name
|
| 59 |
+
const cardNameNorm = normalizeId(cardName)
|
| 60 |
+
const evalIdentifiers = [
|
| 61 |
+
summary.evaluation_name,
|
| 62 |
+
summary.benchmark_leaf_key,
|
| 63 |
+
summary.composite_benchmark_key,
|
| 64 |
+
summary.composite_benchmark_name,
|
| 65 |
+
summary.canonical_display_name,
|
| 66 |
+
summary.evaluation_id,
|
| 67 |
+
].map(normalizeId)
|
| 68 |
+
// Treat the card as belonging to this eval when its name fuzzily appears in
|
| 69 |
+
// any of the eval's identifiers (or vice versa). Otherwise the card is from
|
| 70 |
+
// a different (typically ancestor) benchmark.
|
| 71 |
+
const cardMatchesEval =
|
| 72 |
+
!cardName ||
|
| 73 |
+
evalIdentifiers.some(
|
| 74 |
+
(id) =>
|
| 75 |
+
id.length > 0 &&
|
| 76 |
+
cardNameNorm.length > 0 &&
|
| 77 |
+
(id.includes(cardNameNorm) || cardNameNorm.includes(id)),
|
| 78 |
+
)
|
| 79 |
+
|
| 80 |
+
const overview = cardMatchesEval ? card?.benchmark_details?.overview?.trim() || "" : ""
|
| 81 |
+
const goal = cardMatchesEval ? card?.purpose_and_intended_users?.goal?.trim() || "" : ""
|
| 82 |
+
|
| 83 |
+
// Detect "parent" benchmark pages — either an aggregated composite or a
|
| 84 |
+
// multi-metric matrix where each column is a subtask. In both cases the
|
| 85 |
+
// per-evaluation `metric_config.evaluation_description` belongs to whichever
|
| 86 |
+
// component was processed first (e.g. just the "airline" subset of Tau
|
| 87 |
+
// Bench 2) and would mislead a policy reader. Synthesize parent framing
|
| 88 |
+
// instead and surface the subtasks separately.
|
| 89 |
+
const isAggregated = summary.is_aggregated === true
|
| 90 |
+
const aggregateNames = (summary.aggregate_sources ?? [])
|
| 91 |
+
.map((s) => s.composite_benchmark_name)
|
| 92 |
+
.filter((s): s is string => typeof s === "string" && s.length > 0)
|
| 93 |
+
|
| 94 |
+
const subtaskLabels = useMemo(() => {
|
| 95 |
+
const seen = new Set<string>()
|
| 96 |
+
const labels: string[] = []
|
| 97 |
+
const add = (raw: string | undefined | null) => {
|
| 98 |
+
if (!raw) return
|
| 99 |
+
const trimmed = raw.trim()
|
| 100 |
+
if (!trimmed) return
|
| 101 |
+
const key = trimmed.toLowerCase()
|
| 102 |
+
if (seen.has(key)) return
|
| 103 |
+
seen.add(key)
|
| 104 |
+
labels.push(trimmed)
|
| 105 |
+
}
|
| 106 |
+
for (const subtask of summary.subtasks ?? []) {
|
| 107 |
+
add(subtask.display_name || subtask.subtask_name)
|
| 108 |
+
}
|
| 109 |
+
for (const metric of summary.leaderboard_metrics ?? []) {
|
| 110 |
+
if (metric.scope === "subtask") {
|
| 111 |
+
add(metric.subtask_name || metric.display_name)
|
| 112 |
+
}
|
| 113 |
+
}
|
| 114 |
+
for (const name of aggregateNames) add(name)
|
| 115 |
+
return labels
|
| 116 |
+
}, [summary.subtasks, summary.leaderboard_metrics, aggregateNames])
|
| 117 |
+
|
| 118 |
+
const isMatrix = (summary.leaderboard_metrics?.length ?? 0) > 1
|
| 119 |
+
const isParentPage = isAggregated || (isMatrix && subtaskLabels.length > 1)
|
| 120 |
+
const useComponentDescription = !isParentPage
|
| 121 |
+
|
| 122 |
+
const parentFallback = isParentPage && subtaskLabels.length > 1
|
| 123 |
+
? `${summary.evaluation_name} reports results across ${subtaskLabels.length} ${
|
| 124 |
+
isAggregated ? "component benchmarks" : "subtasks"
|
| 125 |
+
}. Each is evaluated separately; the score shown is the ${
|
| 126 |
+
isAggregated ? "average" : "per-subtask result"
|
| 127 |
+
}.`
|
| 128 |
+
: null
|
| 129 |
+
|
| 130 |
+
const summaryText =
|
| 131 |
+
overview ||
|
| 132 |
+
goal ||
|
| 133 |
+
parentFallback ||
|
| 134 |
+
(useComponentDescription ? summary.metric_config.evaluation_description : summary.evaluation_name)
|
| 135 |
+
|
| 136 |
+
const [expanded, setExpanded] = useState(false)
|
| 137 |
+
const [subtasksOpen, setSubtasksOpen] = useState(false)
|
| 138 |
+
const isLong = summaryText.length > SUMMARY_PREVIEW_CHARS
|
| 139 |
+
const visibleText = expanded || !isLong
|
| 140 |
+
? summaryText
|
| 141 |
+
: summaryText.slice(0, SUMMARY_PREVIEW_CHARS).replace(/\s+\S*$/, "") + "…"
|
| 142 |
+
|
| 143 |
+
const domains = useMemo(() => {
|
| 144 |
+
const fromTags = summary.tags?.domains ?? []
|
| 145 |
+
const fromCard = cardMatchesEval ? card?.benchmark_details?.domains ?? [] : []
|
| 146 |
+
return Array.from(new Set([...fromTags, ...fromCard].map((d) => d.trim()).filter(Boolean))).slice(0, 6)
|
| 147 |
+
}, [summary.tags?.domains, card?.benchmark_details?.domains, cardMatchesEval])
|
| 148 |
+
|
| 149 |
+
const languages = useMemo(() => {
|
| 150 |
+
const fromTags = summary.tags?.languages ?? []
|
| 151 |
+
const fromCard = cardMatchesEval ? card?.benchmark_details?.languages ?? [] : []
|
| 152 |
+
return Array.from(new Set([...fromTags, ...fromCard].map((d) => d.trim()).filter(Boolean))).slice(0, 4)
|
| 153 |
+
}, [summary.tags?.languages, card?.benchmark_details?.languages, cardMatchesEval])
|
| 154 |
+
|
| 155 |
+
const license = card?.ethical_and_legal_considerations?.data_licensing
|
| 156 |
+
const showLicense = license && license !== "Not specified"
|
| 157 |
+
|
| 158 |
+
const resources = useMemo(() => {
|
| 159 |
+
if (!cardMatchesEval) return []
|
| 160 |
+
const urls = (card?.benchmark_details?.resources ?? []).filter((r) => typeof r === "string" && r.startsWith("http"))
|
| 161 |
+
const seen = new Map<string, { kind: ReturnType<typeof classifyResource>["kind"]; label: string; url: string }>()
|
| 162 |
+
for (const url of urls) {
|
| 163 |
+
const c = classifyResource(url)
|
| 164 |
+
if (!seen.has(c.kind)) {
|
| 165 |
+
seen.set(c.kind, { ...c, url })
|
| 166 |
+
}
|
| 167 |
+
}
|
| 168 |
+
return Array.from(seen.values())
|
| 169 |
+
}, [card?.benchmark_details?.resources, cardMatchesEval])
|
| 170 |
+
|
| 171 |
+
const evaluators = (summary.evaluator_names ?? []).slice(0, 3)
|
| 172 |
+
const hasMoreEvaluators = (summary.evaluator_names?.length ?? 0) > evaluators.length
|
| 173 |
+
|
| 174 |
+
const knownIssues = useMemo(
|
| 175 |
+
() =>
|
| 176 |
+
getKnownIssues(
|
| 177 |
+
summary.evaluation_name,
|
| 178 |
+
summary.composite_benchmark_name,
|
| 179 |
+
summary.composite_benchmark_key,
|
| 180 |
+
summary.benchmark_family_key,
|
| 181 |
+
summary.benchmark_leaf_key,
|
| 182 |
+
card?.benchmark_details?.name,
|
| 183 |
+
),
|
| 184 |
+
[
|
| 185 |
+
summary.evaluation_name,
|
| 186 |
+
summary.composite_benchmark_name,
|
| 187 |
+
summary.composite_benchmark_key,
|
| 188 |
+
summary.benchmark_family_key,
|
| 189 |
+
summary.benchmark_leaf_key,
|
| 190 |
+
card?.benchmark_details?.name,
|
| 191 |
+
],
|
| 192 |
+
)
|
| 193 |
+
|
| 194 |
+
const directionLabel = summary.metric_config.lower_is_better
|
| 195 |
+
? "Lower scores are better"
|
| 196 |
+
: "Higher scores are better"
|
| 197 |
+
|
| 198 |
+
return (
|
| 199 |
+
<section className="rounded-3xl border bg-card p-5 sm:p-6">
|
| 200 |
+
<header className="mb-4 flex flex-wrap items-center gap-2">
|
| 201 |
+
<BookOpen className="h-5 w-5 text-primary" />
|
| 202 |
+
<h2 className="text-lg font-semibold tracking-tight">{summary.evaluation_name}</h2>
|
| 203 |
+
<span className="text-xs text-muted-foreground">In plain language</span>
|
| 204 |
+
</header>
|
| 205 |
+
|
| 206 |
+
{knownIssues.length > 0 && (
|
| 207 |
+
<div className="mb-3">
|
| 208 |
+
<KnownIssuesPanel issues={knownIssues} variant="compact" />
|
| 209 |
+
</div>
|
| 210 |
+
)}
|
| 211 |
+
|
| 212 |
+
<p className="text-base leading-7 text-foreground/90">
|
| 213 |
+
{visibleText}
|
| 214 |
+
{isLong && (
|
| 215 |
+
<button
|
| 216 |
+
type="button"
|
| 217 |
+
onClick={() => setExpanded((v) => !v)}
|
| 218 |
+
className="ml-1 text-sm font-medium text-primary underline-offset-4 hover:underline"
|
| 219 |
+
>
|
| 220 |
+
{expanded ? "Show less" : "Read more"}
|
| 221 |
+
</button>
|
| 222 |
+
)}
|
| 223 |
+
</p>
|
| 224 |
+
|
| 225 |
+
<p className="mt-3 text-sm text-muted-foreground">
|
| 226 |
+
<SignalTooltip
|
| 227 |
+
content={
|
| 228 |
+
summary.metric_config.lower_is_better
|
| 229 |
+
? "On this benchmark, a lower number means the model did better."
|
| 230 |
+
: "On this benchmark, a higher number means the model did better."
|
| 231 |
+
}
|
| 232 |
+
>
|
| 233 |
+
<span className="underline decoration-dotted decoration-muted-foreground/60 underline-offset-4 cursor-help">
|
| 234 |
+
{directionLabel}.
|
| 235 |
+
</span>
|
| 236 |
+
</SignalTooltip>{" "}
|
| 237 |
+
Compared across {summary.models_count} model{summary.models_count === 1 ? "" : "s"}.
|
| 238 |
+
</p>
|
| 239 |
+
|
| 240 |
+
{isParentPage && subtaskLabels.length > 1 && (
|
| 241 |
+
<div className="mt-4 rounded-2xl border border-border/60 bg-muted/10">
|
| 242 |
+
<button
|
| 243 |
+
type="button"
|
| 244 |
+
onClick={() => setSubtasksOpen((v) => !v)}
|
| 245 |
+
aria-expanded={subtasksOpen}
|
| 246 |
+
className="flex w-full items-center justify-between gap-3 px-3.5 py-2.5 text-left transition-colors hover:bg-muted/20 rounded-2xl"
|
| 247 |
+
>
|
| 248 |
+
<span className="flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-[0.18em] text-muted-foreground">
|
| 249 |
+
<Layers className="h-3.5 w-3.5" />
|
| 250 |
+
{isAggregated ? `Component benchmarks (${subtaskLabels.length})` : `Subtasks (${subtaskLabels.length})`}
|
| 251 |
+
</span>
|
| 252 |
+
{subtasksOpen ? (
|
| 253 |
+
<ChevronUp className="h-4 w-4 text-muted-foreground" />
|
| 254 |
+
) : (
|
| 255 |
+
<ChevronDown className="h-4 w-4 text-muted-foreground" />
|
| 256 |
+
)}
|
| 257 |
+
</button>
|
| 258 |
+
{subtasksOpen && (
|
| 259 |
+
<ul className="grid list-disc gap-x-6 gap-y-1 px-3.5 pb-3.5 pl-9 text-sm sm:grid-cols-2 lg:grid-cols-3">
|
| 260 |
+
{subtaskLabels.map((name) => (
|
| 261 |
+
<li key={name} className="capitalize">
|
| 262 |
+
{name}
|
| 263 |
+
</li>
|
| 264 |
+
))}
|
| 265 |
+
</ul>
|
| 266 |
+
)}
|
| 267 |
+
</div>
|
| 268 |
+
)}
|
| 269 |
+
|
| 270 |
+
{(domains.length > 0 || languages.length > 0 || showLicense) && (
|
| 271 |
+
<div className="mt-4 flex flex-wrap gap-2">
|
| 272 |
+
{domains.map((d) => (
|
| 273 |
+
<span
|
| 274 |
+
key={`d-${d}`}
|
| 275 |
+
className="inline-flex items-center gap-1 rounded-full border border-border/60 bg-muted/30 px-2.5 py-1 text-xs font-medium capitalize"
|
| 276 |
+
>
|
| 277 |
+
<Tag className="h-3 w-3 shrink-0 text-muted-foreground" />
|
| 278 |
+
{d}
|
| 279 |
+
</span>
|
| 280 |
+
))}
|
| 281 |
+
{languages.map((l) => (
|
| 282 |
+
<span
|
| 283 |
+
key={`l-${l}`}
|
| 284 |
+
className="inline-flex items-center gap-1 rounded-full border border-sky-200/70 bg-sky-50/60 px-2.5 py-1 text-xs font-medium dark:border-sky-900/40 dark:bg-sky-950/20"
|
| 285 |
+
>
|
| 286 |
+
<Globe className="h-3 w-3 shrink-0 text-sky-600" />
|
| 287 |
+
{l}
|
| 288 |
+
</span>
|
| 289 |
+
))}
|
| 290 |
+
{showLicense && (
|
| 291 |
+
<SignalTooltip content="The license under which the benchmark dataset is released.">
|
| 292 |
+
<span className="inline-flex items-center gap-1 rounded-full border border-border/60 bg-background px-2.5 py-1 text-xs font-medium cursor-help">
|
| 293 |
+
<ScrollText className="h-3 w-3 shrink-0 text-muted-foreground" />
|
| 294 |
+
{license}
|
| 295 |
+
</span>
|
| 296 |
+
</SignalTooltip>
|
| 297 |
+
)}
|
| 298 |
+
</div>
|
| 299 |
+
)}
|
| 300 |
+
|
| 301 |
+
{(resources.length > 0 || evaluators.length > 0) && (
|
| 302 |
+
<div className="mt-5 border-t pt-4">
|
| 303 |
+
<div className="mb-2 text-[11px] font-semibold uppercase tracking-[0.18em] text-muted-foreground">
|
| 304 |
+
Where this comes from
|
| 305 |
+
</div>
|
| 306 |
+
<div className="flex flex-wrap items-center gap-3">
|
| 307 |
+
{resources.map((r) => (
|
| 308 |
+
<a
|
| 309 |
+
key={r.url}
|
| 310 |
+
href={r.url}
|
| 311 |
+
target="_blank"
|
| 312 |
+
rel="noreferrer"
|
| 313 |
+
className="inline-flex items-center gap-1.5 rounded-full border border-border/60 bg-background px-3 py-1.5 text-xs font-medium text-muted-foreground transition-colors hover:border-primary/40 hover:text-primary"
|
| 314 |
+
>
|
| 315 |
+
<FileText className="h-3 w-3 shrink-0" />
|
| 316 |
+
<span className="font-semibold text-foreground">{r.label}</span>
|
| 317 |
+
<span className="text-muted-foreground">{shortHost(r.url)}</span>
|
| 318 |
+
<ExternalLink className="h-3 w-3 shrink-0" />
|
| 319 |
+
</a>
|
| 320 |
+
))}
|
| 321 |
+
{evaluators.length > 0 && (
|
| 322 |
+
<SignalTooltip
|
| 323 |
+
content={
|
| 324 |
+
<span className="block space-y-1">
|
| 325 |
+
<span className="block font-semibold">Who reported these scores</span>
|
| 326 |
+
<span className="block">{summary.evaluator_names?.join(", ")}</span>
|
| 327 |
+
{summary.third_party_ratio > 0 && (
|
| 328 |
+
<span className="block text-muted-foreground">
|
| 329 |
+
{Math.round(summary.third_party_ratio * 100)}% of results come from independent evaluators (not the model's own developer).
|
| 330 |
+
</span>
|
| 331 |
+
)}
|
| 332 |
+
</span>
|
| 333 |
+
}
|
| 334 |
+
>
|
| 335 |
+
<span className="inline-flex items-center gap-1.5 rounded-full border border-border/60 bg-background px-3 py-1.5 text-xs font-medium cursor-help">
|
| 336 |
+
<Users className="h-3 w-3 shrink-0 text-muted-foreground" />
|
| 337 |
+
<span className="font-semibold text-foreground">Reported by</span>
|
| 338 |
+
<span className="text-muted-foreground">
|
| 339 |
+
{evaluators.join(", ")}
|
| 340 |
+
{hasMoreEvaluators ? ` +${(summary.evaluator_names?.length ?? 0) - evaluators.length} more` : ""}
|
| 341 |
+
</span>
|
| 342 |
+
</span>
|
| 343 |
+
</SignalTooltip>
|
| 344 |
+
)}
|
| 345 |
+
</div>
|
| 346 |
+
</div>
|
| 347 |
+
)}
|
| 348 |
+
</section>
|
| 349 |
+
)
|
| 350 |
+
}
|
|
@@ -0,0 +1,483 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client"
|
| 2 |
+
|
| 3 |
+
import { useEffect, useState, type ReactNode } from "react"
|
| 4 |
+
import { AlertTriangle, ExternalLink, FlaskConical } from "lucide-react"
|
| 5 |
+
import { Term } from "@/components/term"
|
| 6 |
+
import { SignalTooltip } from "@/components/signals/signal-tooltip"
|
| 7 |
+
import type { ModelResultForBenchmark } from "@/lib/eval-processing"
|
| 8 |
+
import type { GenerationConfig, ScoreDetails } from "@/lib/benchmark-schema"
|
| 9 |
+
|
| 10 |
+
interface ResearcherReproducibilityCardProps {
|
| 11 |
+
modelResult: ModelResultForBenchmark
|
| 12 |
+
/**
|
| 13 |
+
* Benchmark identifier used to pick the right per-eval row when fetching
|
| 14 |
+
* enrichment from the model's full record. The eval-detail endpoint
|
| 15 |
+
* synthesizes leaderboard rows without `generation_config`, so we top up
|
| 16 |
+
* lazily on row expand from /api/eval-row-config.
|
| 17 |
+
*/
|
| 18 |
+
benchmarkKey?: string
|
| 19 |
+
evalName?: string
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
const KNOWN_DECODING_KEYS = ["temperature", "top_p", "top_k", "max_tokens", "seed", "reasoning"] as const
|
| 23 |
+
|
| 24 |
+
// Keys that belong to agentic eval setups, surfaced in their own group rather
|
| 25 |
+
// than dumped under "decoding" extras as raw JSON.
|
| 26 |
+
const KNOWN_AGENT_KEYS = [
|
| 27 |
+
"agentic_eval_config",
|
| 28 |
+
"max_attempts",
|
| 29 |
+
"eval_limits",
|
| 30 |
+
"eval_plan",
|
| 31 |
+
"sandbox",
|
| 32 |
+
"max_turns",
|
| 33 |
+
"message_limit",
|
| 34 |
+
] as const
|
| 35 |
+
|
| 36 |
+
const KEY_LABEL: Record<string, string> = {
|
| 37 |
+
temperature: "temperature",
|
| 38 |
+
top_p: "top-p",
|
| 39 |
+
top_k: "top-k",
|
| 40 |
+
max_tokens: "max tokens",
|
| 41 |
+
seed: "seed",
|
| 42 |
+
reasoning: "reasoning mode",
|
| 43 |
+
n: "samples per prompt",
|
| 44 |
+
best_of: "best-of-N",
|
| 45 |
+
num_samples: "samples per prompt",
|
| 46 |
+
num_runs: "runs",
|
| 47 |
+
n_shot: "n-shot",
|
| 48 |
+
num_fewshot: "few-shot examples",
|
| 49 |
+
fewshot: "few-shot examples",
|
| 50 |
+
agentic_eval_config: "tools available",
|
| 51 |
+
max_attempts: "max attempts",
|
| 52 |
+
eval_limits: "eval limits",
|
| 53 |
+
eval_plan: "eval plan",
|
| 54 |
+
sandbox: "sandbox",
|
| 55 |
+
max_turns: "max turns",
|
| 56 |
+
message_limit: "message limit",
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
/**
|
| 60 |
+
* Try to render a structured agentic config object as a short, readable
|
| 61 |
+
* string. Falls back to null so the caller can use the generic formatter.
|
| 62 |
+
*/
|
| 63 |
+
function formatAgentValue(key: string, value: unknown): string | null {
|
| 64 |
+
if (value == null) return null
|
| 65 |
+
if (typeof value !== "object") return null
|
| 66 |
+
|
| 67 |
+
if (key === "agentic_eval_config") {
|
| 68 |
+
const tools = (value as { available_tools?: unknown }).available_tools
|
| 69 |
+
if (Array.isArray(tools)) {
|
| 70 |
+
const names = tools
|
| 71 |
+
.map((t) => (t && typeof t === "object" ? (t as { name?: unknown }).name : null))
|
| 72 |
+
.filter((n): n is string => typeof n === "string" && n.length > 0)
|
| 73 |
+
if (names.length === 0) return "no tools"
|
| 74 |
+
if (names.length <= 4) return `${names.length} tools: ${names.join(", ")}`
|
| 75 |
+
return `${names.length} tools: ${names.slice(0, 4).join(", ")} +${names.length - 4}`
|
| 76 |
+
}
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
if (key === "eval_limits") {
|
| 80 |
+
const obj = value as Record<string, unknown>
|
| 81 |
+
const parts: string[] = []
|
| 82 |
+
for (const k of ["message_limit", "max_messages", "token_limit", "max_tokens"]) {
|
| 83 |
+
if (typeof obj[k] === "number") parts.push(`${k.replace(/_/g, " ")}: ${obj[k]}`)
|
| 84 |
+
}
|
| 85 |
+
if (parts.length > 0) return parts.join(", ")
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
if (key === "eval_plan") {
|
| 89 |
+
const name = (value as { name?: unknown }).name
|
| 90 |
+
const steps = (value as { steps?: unknown }).steps
|
| 91 |
+
if (typeof name === "string" && Array.isArray(steps)) return `${name} (${steps.length} step${steps.length === 1 ? "" : "s"})`
|
| 92 |
+
if (typeof name === "string") return name
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
if (key === "sandbox") {
|
| 96 |
+
const keys = Object.keys(value as Record<string, unknown>)
|
| 97 |
+
if (keys.length === 0) return "default"
|
| 98 |
+
return keys.join(", ")
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
return null
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
function pickFromAdditional(value: unknown, keys: string[]): unknown | undefined {
|
| 105 |
+
if (!value || typeof value !== "object") return undefined
|
| 106 |
+
const obj = value as Record<string, unknown>
|
| 107 |
+
for (const k of keys) {
|
| 108 |
+
if (obj[k] !== undefined && obj[k] !== null) return obj[k]
|
| 109 |
+
}
|
| 110 |
+
return undefined
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
/**
|
| 114 |
+
* Renders any value as a short string for the parameter cards. Returns null
|
| 115 |
+
* when the value is empty so the caller can render "Not disclosed" instead.
|
| 116 |
+
*/
|
| 117 |
+
function formatValue(value: unknown): string | null {
|
| 118 |
+
if (value === undefined || value === null) return null
|
| 119 |
+
if (typeof value === "boolean") return value ? "yes" : "no"
|
| 120 |
+
if (typeof value === "number") return Number.isInteger(value) ? value.toString() : value.toFixed(3)
|
| 121 |
+
if (typeof value === "string") {
|
| 122 |
+
const trimmed = value.trim()
|
| 123 |
+
return trimmed.length === 0 ? null : trimmed
|
| 124 |
+
}
|
| 125 |
+
try {
|
| 126 |
+
return JSON.stringify(value)
|
| 127 |
+
} catch {
|
| 128 |
+
return null
|
| 129 |
+
}
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
function ParamRow({
|
| 133 |
+
label,
|
| 134 |
+
value,
|
| 135 |
+
termKey,
|
| 136 |
+
hint,
|
| 137 |
+
}: {
|
| 138 |
+
label: string
|
| 139 |
+
value: ReactNode | null
|
| 140 |
+
termKey?: string
|
| 141 |
+
hint?: string
|
| 142 |
+
}) {
|
| 143 |
+
const isMissing = value === null || value === undefined
|
| 144 |
+
return (
|
| 145 |
+
<div className="flex items-baseline justify-between gap-3 border-b border-dashed border-border/50 py-1.5 text-sm last:border-0">
|
| 146 |
+
<span className="text-muted-foreground">
|
| 147 |
+
{termKey ? <Term term={termKey}>{label}</Term> : label}
|
| 148 |
+
</span>
|
| 149 |
+
{isMissing ? (
|
| 150 |
+
<SignalTooltip
|
| 151 |
+
content={
|
| 152 |
+
hint ??
|
| 153 |
+
"This parameter wasn't reported by the source. Without it, the result may not be exactly reproducible."
|
| 154 |
+
}
|
| 155 |
+
>
|
| 156 |
+
<span className="inline-flex items-center gap-1 text-xs font-medium text-amber-700 dark:text-amber-300 cursor-help">
|
| 157 |
+
<AlertTriangle className="h-3 w-3" /> Not disclosed
|
| 158 |
+
</span>
|
| 159 |
+
</SignalTooltip>
|
| 160 |
+
) : (
|
| 161 |
+
<span className="font-medium tabular-nums">{value}</span>
|
| 162 |
+
)}
|
| 163 |
+
</div>
|
| 164 |
+
)
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
interface FieldSpec {
|
| 168 |
+
label: string
|
| 169 |
+
value: string | null
|
| 170 |
+
termKey?: string
|
| 171 |
+
hint?: string
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
interface FieldGroup {
|
| 175 |
+
title: string
|
| 176 |
+
fields: FieldSpec[]
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
/**
|
| 180 |
+
* Detailed reproducibility surface for researcher mode. Shown inside a
|
| 181 |
+
* leaderboard row's expanded panel. Adaptive: when most fields aren't disclosed
|
| 182 |
+
* (the common case today), it collapses to a compact "Limited disclosure"
|
| 183 |
+
* summary showing only fields that *are* present, with a button to reveal the
|
| 184 |
+
* full audit grid.
|
| 185 |
+
*/
|
| 186 |
+
export function ResearcherReproducibilityCard({
|
| 187 |
+
modelResult,
|
| 188 |
+
benchmarkKey,
|
| 189 |
+
evalName,
|
| 190 |
+
}: ResearcherReproducibilityCardProps) {
|
| 191 |
+
const [enrichedGen, setEnrichedGen] = useState<GenerationConfig | null>(null)
|
| 192 |
+
const [enrichedScore, setEnrichedScore] = useState<ScoreDetails | null>(null)
|
| 193 |
+
const [loading, setLoading] = useState(false)
|
| 194 |
+
|
| 195 |
+
// Lazily top up generation_config / score_details from the model's full
|
| 196 |
+
// record. The eval-detail leaderboard endpoint omits these fields; we only
|
| 197 |
+
// pay the fetch cost when a researcher actually expands a row.
|
| 198 |
+
useEffect(() => {
|
| 199 |
+
const inlineGen = modelResult.result.generation_config
|
| 200 |
+
const inlineScoreOk =
|
| 201 |
+
modelResult.score_details.standard_error != null ||
|
| 202 |
+
modelResult.score_details.confidence_interval != null ||
|
| 203 |
+
modelResult.score_details.sample_size != null
|
| 204 |
+
const hasInlineArgs =
|
| 205 |
+
!!inlineGen &&
|
| 206 |
+
typeof inlineGen === "object" &&
|
| 207 |
+
"generation_args" in inlineGen &&
|
| 208 |
+
Object.keys((inlineGen as { generation_args?: Record<string, unknown> }).generation_args ?? {}).length > 0
|
| 209 |
+
if (hasInlineArgs && inlineScoreOk) return // nothing to fetch
|
| 210 |
+
|
| 211 |
+
const modelId = modelResult.model_info.id
|
| 212 |
+
if (!modelId) return
|
| 213 |
+
|
| 214 |
+
const params = new URLSearchParams({ model_id: modelId })
|
| 215 |
+
if (benchmarkKey) params.set("benchmark_key", benchmarkKey)
|
| 216 |
+
if (evalName) params.set("eval_name", evalName)
|
| 217 |
+
|
| 218 |
+
let cancelled = false
|
| 219 |
+
setLoading(true)
|
| 220 |
+
fetch(`/api/eval-row-config?${params.toString()}`)
|
| 221 |
+
.then((r) => (r.ok ? r.json() : null))
|
| 222 |
+
.then((data) => {
|
| 223 |
+
if (cancelled || !data) return
|
| 224 |
+
if (data.generation_config) setEnrichedGen(data.generation_config as GenerationConfig)
|
| 225 |
+
if (data.score_details) setEnrichedScore(data.score_details as ScoreDetails)
|
| 226 |
+
})
|
| 227 |
+
.catch(() => {})
|
| 228 |
+
.finally(() => {
|
| 229 |
+
if (!cancelled) setLoading(false)
|
| 230 |
+
})
|
| 231 |
+
return () => {
|
| 232 |
+
cancelled = true
|
| 233 |
+
}
|
| 234 |
+
}, [modelResult, benchmarkKey, evalName])
|
| 235 |
+
|
| 236 |
+
const gen = modelResult.result.generation_config ?? enrichedGen ?? undefined
|
| 237 |
+
const args = gen?.generation_args ?? {}
|
| 238 |
+
const scoreDetails = {
|
| 239 |
+
...modelResult.score_details,
|
| 240 |
+
sample_size: modelResult.score_details.sample_size ?? enrichedScore?.sample_size,
|
| 241 |
+
standard_error: modelResult.score_details.standard_error ?? enrichedScore?.standard_error,
|
| 242 |
+
confidence_interval:
|
| 243 |
+
modelResult.score_details.confidence_interval ?? enrichedScore?.confidence_interval,
|
| 244 |
+
}
|
| 245 |
+
const additional =
|
| 246 |
+
typeof gen?.additional_details === "object" && gen?.additional_details !== null
|
| 247 |
+
? (gen.additional_details as Record<string, unknown>)
|
| 248 |
+
: null
|
| 249 |
+
|
| 250 |
+
const argsKeys = Object.keys(args)
|
| 251 |
+
const extraDecodingKeys = argsKeys.filter(
|
| 252 |
+
(k) =>
|
| 253 |
+
!KNOWN_DECODING_KEYS.includes(k as (typeof KNOWN_DECODING_KEYS)[number]) &&
|
| 254 |
+
!KNOWN_AGENT_KEYS.includes(k as (typeof KNOWN_AGENT_KEYS)[number])
|
| 255 |
+
)
|
| 256 |
+
const agentKeysPresent = KNOWN_AGENT_KEYS.filter((k) => args[k] != null)
|
| 257 |
+
const hasAgentSetup = agentKeysPresent.length > 0
|
| 258 |
+
|
| 259 |
+
const shots = pickFromAdditional(additional, ["num_fewshot", "n_shot", "shots", "fewshot"])
|
| 260 |
+
const samplesPerPrompt = pickFromAdditional(additional, ["n", "num_samples", "samples_per_prompt"]) ?? args["n"]
|
| 261 |
+
const bestOf = pickFromAdditional(additional, ["best_of", "best_of_n"]) ?? args["best_of"]
|
| 262 |
+
const numRuns = pickFromAdditional(additional, ["num_runs", "n_runs", "runs"])
|
| 263 |
+
const scoringMethod = pickFromAdditional(additional, ["scoring", "scoring_method", "judge", "evaluator"])
|
| 264 |
+
const evalLibrary = pickFromAdditional(additional, ["eval_library", "harness", "framework"])
|
| 265 |
+
const evalLibraryVersion = pickFromAdditional(additional, ["eval_library_version", "harness_version"])
|
| 266 |
+
const promptTemplate = gen?.prompt_template?.trim() || null
|
| 267 |
+
|
| 268 |
+
const groups: FieldGroup[] = [
|
| 269 |
+
{
|
| 270 |
+
title: "Decoding",
|
| 271 |
+
fields: [
|
| 272 |
+
{
|
| 273 |
+
label: "temperature",
|
| 274 |
+
termKey: "temperature",
|
| 275 |
+
value: formatValue(args.temperature),
|
| 276 |
+
hint: "Temperature controls randomness — without it, others can't recreate the same outputs.",
|
| 277 |
+
},
|
| 278 |
+
{ label: "top-p", termKey: "top-p", value: formatValue(args.top_p) },
|
| 279 |
+
{ label: "top-k", termKey: "top-k", value: formatValue(args.top_k) },
|
| 280 |
+
{ label: "max tokens", value: formatValue(args.max_tokens) },
|
| 281 |
+
{ label: "seed", value: formatValue(args.seed) },
|
| 282 |
+
...extraDecodingKeys.map((k) => ({
|
| 283 |
+
label: KEY_LABEL[k] ?? k.replace(/_/g, " "),
|
| 284 |
+
value: formatValue(args[k]),
|
| 285 |
+
})),
|
| 286 |
+
],
|
| 287 |
+
},
|
| 288 |
+
{
|
| 289 |
+
title: "Sampling",
|
| 290 |
+
fields: [
|
| 291 |
+
{
|
| 292 |
+
label: "few-shot examples",
|
| 293 |
+
termKey: "few-shot",
|
| 294 |
+
value: formatValue(shots),
|
| 295 |
+
hint: "How many worked examples were included in the prompt before the question.",
|
| 296 |
+
},
|
| 297 |
+
{
|
| 298 |
+
label: "samples per prompt",
|
| 299 |
+
value: formatValue(samplesPerPrompt),
|
| 300 |
+
hint: "Number of completions generated per question.",
|
| 301 |
+
},
|
| 302 |
+
{
|
| 303 |
+
label: "best-of-N",
|
| 304 |
+
termKey: "best-of-n",
|
| 305 |
+
value: formatValue(bestOf),
|
| 306 |
+
hint: "Whether the score reflects the best of multiple attempts (inflates results vs. single-attempt).",
|
| 307 |
+
},
|
| 308 |
+
{
|
| 309 |
+
label: "runs averaged",
|
| 310 |
+
value: formatValue(numRuns),
|
| 311 |
+
hint: "How many evaluation runs were averaged to produce the reported number.",
|
| 312 |
+
},
|
| 313 |
+
{
|
| 314 |
+
label: "test instances",
|
| 315 |
+
value: formatValue(scoreDetails.sample_size),
|
| 316 |
+
hint: "Number of items in the test set the model was scored on.",
|
| 317 |
+
},
|
| 318 |
+
],
|
| 319 |
+
},
|
| 320 |
+
...(hasAgentSetup
|
| 321 |
+
? [
|
| 322 |
+
{
|
| 323 |
+
title: "Agent setup",
|
| 324 |
+
fields: agentKeysPresent.map((k) => ({
|
| 325 |
+
label: KEY_LABEL[k] ?? k.replace(/_/g, " "),
|
| 326 |
+
value: formatAgentValue(k, args[k]) ?? formatValue(args[k]),
|
| 327 |
+
hint:
|
| 328 |
+
k === "agentic_eval_config"
|
| 329 |
+
? "Tools the agent could call during the run."
|
| 330 |
+
: k === "max_attempts"
|
| 331 |
+
? "Maximum independent attempts the agent gets per task."
|
| 332 |
+
: k === "eval_limits"
|
| 333 |
+
? "Hard caps the harness enforced on the run (messages, tokens, etc.)."
|
| 334 |
+
: k === "eval_plan"
|
| 335 |
+
? "Solver/plan the harness used to drive the agent."
|
| 336 |
+
: k === "sandbox"
|
| 337 |
+
? "Environment in which the agent ran (e.g. docker, local)."
|
| 338 |
+
: undefined,
|
| 339 |
+
})),
|
| 340 |
+
} as FieldGroup,
|
| 341 |
+
]
|
| 342 |
+
: []),
|
| 343 |
+
{
|
| 344 |
+
title: "Scoring & uncertainty",
|
| 345 |
+
fields: [
|
| 346 |
+
{
|
| 347 |
+
label: "scoring method",
|
| 348 |
+
value: formatValue(scoringMethod),
|
| 349 |
+
hint: "Exact match, LLM-as-judge, human grading, etc. Determines what 'correct' means.",
|
| 350 |
+
},
|
| 351 |
+
{ label: "standard error", value: formatValue(scoreDetails.standard_error) },
|
| 352 |
+
{
|
| 353 |
+
label: "confidence interval",
|
| 354 |
+
value: scoreDetails.confidence_interval
|
| 355 |
+
? `${scoreDetails.confidence_interval.lower}–${scoreDetails.confidence_interval.upper} (${scoreDetails.confidence_interval.confidence_level}%)`
|
| 356 |
+
: null,
|
| 357 |
+
},
|
| 358 |
+
{ label: "eval library", value: formatValue(evalLibrary) },
|
| 359 |
+
{ label: "library version", value: formatValue(evalLibraryVersion) },
|
| 360 |
+
],
|
| 361 |
+
},
|
| 362 |
+
]
|
| 363 |
+
|
| 364 |
+
const totalFields = groups.reduce((n, g) => n + g.fields.length, 0)
|
| 365 |
+
const disclosedFields = groups.reduce(
|
| 366 |
+
(n, g) => n + g.fields.filter((f) => f.value !== null).length,
|
| 367 |
+
0
|
| 368 |
+
)
|
| 369 |
+
const disclosureRatio = totalFields > 0 ? disclosedFields / totalFields : 0
|
| 370 |
+
const disclosedGroups = groups
|
| 371 |
+
.map((g) => ({ ...g, fields: g.fields.filter((f) => f.value !== null) }))
|
| 372 |
+
.filter((g) => g.fields.length > 0)
|
| 373 |
+
|
| 374 |
+
// If fewer than ~30% of fields are disclosed (and at least one is missing),
|
| 375 |
+
// start in compact mode so the card isn't a wall of "Not disclosed".
|
| 376 |
+
const shouldStartCompact = disclosureRatio < 0.3 && disclosedFields < totalFields
|
| 377 |
+
const [showAll, setShowAll] = useState(!shouldStartCompact)
|
| 378 |
+
const isCompact = shouldStartCompact && !showAll
|
| 379 |
+
|
| 380 |
+
return (
|
| 381 |
+
<section className="rounded-2xl border bg-background/70 p-4">
|
| 382 |
+
<header className="mb-3 flex items-start justify-between gap-3">
|
| 383 |
+
<div className="flex items-start gap-2 min-w-0">
|
| 384 |
+
<FlaskConical className="mt-0.5 h-4 w-4 shrink-0 text-primary" />
|
| 385 |
+
<div className="min-w-0">
|
| 386 |
+
<div className="text-sm font-semibold">Reproducibility</div>
|
| 387 |
+
<div className="text-xs text-muted-foreground">
|
| 388 |
+
{isCompact
|
| 389 |
+
? `Limited disclosure — only ${disclosedFields} of ${totalFields} reproducibility fields are reported.`
|
| 390 |
+
: "Everything someone would need to re-run this evaluation. Missing fields are flagged."}
|
| 391 |
+
</div>
|
| 392 |
+
</div>
|
| 393 |
+
</div>
|
| 394 |
+
<span className="shrink-0 rounded-full border border-border/60 bg-muted/30 px-2 py-0.5 text-[11px] font-medium tabular-nums text-muted-foreground">
|
| 395 |
+
{loading ? "loading…" : `${disclosedFields}/${totalFields} disclosed`}
|
| 396 |
+
</span>
|
| 397 |
+
</header>
|
| 398 |
+
|
| 399 |
+
{isCompact ? (
|
| 400 |
+
<>
|
| 401 |
+
{disclosedGroups.length > 0 ? (
|
| 402 |
+
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
| 403 |
+
{disclosedGroups.map((g) => (
|
| 404 |
+
<div key={g.title}>
|
| 405 |
+
<div className="mb-2 text-[10px] font-semibold uppercase tracking-[0.18em] text-muted-foreground">
|
| 406 |
+
{g.title}
|
| 407 |
+
</div>
|
| 408 |
+
{g.fields.map((f) => (
|
| 409 |
+
<ParamRow key={f.label} label={f.label} termKey={f.termKey} value={f.value} hint={f.hint} />
|
| 410 |
+
))}
|
| 411 |
+
</div>
|
| 412 |
+
))}
|
| 413 |
+
</div>
|
| 414 |
+
) : (
|
| 415 |
+
<div className="rounded-xl border border-dashed border-amber-300/60 bg-amber-50/40 px-3 py-2 text-xs text-amber-800 dark:border-amber-900/60 dark:bg-amber-950/20 dark:text-amber-200">
|
| 416 |
+
No reproducibility metadata was disclosed by the source.
|
| 417 |
+
</div>
|
| 418 |
+
)}
|
| 419 |
+
</>
|
| 420 |
+
) : (
|
| 421 |
+
<div className="grid gap-4 lg:grid-cols-3">
|
| 422 |
+
{groups.map((g) => (
|
| 423 |
+
<div key={g.title}>
|
| 424 |
+
<div className="mb-2 text-[10px] font-semibold uppercase tracking-[0.18em] text-muted-foreground">
|
| 425 |
+
{g.title}
|
| 426 |
+
</div>
|
| 427 |
+
{g.fields.map((f) => (
|
| 428 |
+
<ParamRow key={f.label} label={f.label} termKey={f.termKey} value={f.value} hint={f.hint} />
|
| 429 |
+
))}
|
| 430 |
+
</div>
|
| 431 |
+
))}
|
| 432 |
+
</div>
|
| 433 |
+
)}
|
| 434 |
+
|
| 435 |
+
{shouldStartCompact && (
|
| 436 |
+
<button
|
| 437 |
+
type="button"
|
| 438 |
+
onClick={() => setShowAll((v) => !v)}
|
| 439 |
+
className="mt-3 inline-flex items-center text-xs font-medium text-primary underline-offset-4 hover:underline"
|
| 440 |
+
>
|
| 441 |
+
{showAll
|
| 442 |
+
? "Hide undisclosed fields"
|
| 443 |
+
: `Show all ${totalFields} checked fields (${totalFields - disclosedFields} not disclosed)`}
|
| 444 |
+
</button>
|
| 445 |
+
)}
|
| 446 |
+
|
| 447 |
+
{promptTemplate && (
|
| 448 |
+
<details className="mt-4 rounded-xl border bg-muted/10">
|
| 449 |
+
<summary className="cursor-pointer px-3 py-2 text-xs font-semibold uppercase tracking-[0.16em] text-muted-foreground">
|
| 450 |
+
Prompt template
|
| 451 |
+
</summary>
|
| 452 |
+
<pre className="max-h-[18rem] overflow-auto whitespace-pre-wrap break-words border-t bg-background/60 px-3 py-3 text-xs leading-5">
|
| 453 |
+
{promptTemplate}
|
| 454 |
+
</pre>
|
| 455 |
+
</details>
|
| 456 |
+
)}
|
| 457 |
+
{!promptTemplate && !isCompact && (
|
| 458 |
+
<div className="mt-4 flex items-center gap-1.5 rounded-xl border border-dashed border-amber-300/60 bg-amber-50/40 px-3 py-2 text-xs text-amber-800 dark:border-amber-900/60 dark:bg-amber-950/20 dark:text-amber-200">
|
| 459 |
+
<AlertTriangle className="h-3.5 w-3.5" />
|
| 460 |
+
<span>
|
| 461 |
+
Prompt template not disclosed by the source.
|
| 462 |
+
<SignalTooltip content="Without the prompt, scores can't be reliably reproduced because phrasing changes results.">
|
| 463 |
+
<span className="ml-1 underline decoration-dotted underline-offset-4 cursor-help">Why this matters</span>
|
| 464 |
+
</SignalTooltip>
|
| 465 |
+
</span>
|
| 466 |
+
</div>
|
| 467 |
+
)}
|
| 468 |
+
|
| 469 |
+
{modelResult.source_metadata.source_url && (
|
| 470 |
+
<div className="mt-3 text-xs">
|
| 471 |
+
<a
|
| 472 |
+
href={modelResult.source_metadata.source_url}
|
| 473 |
+
target="_blank"
|
| 474 |
+
rel="noreferrer"
|
| 475 |
+
className="inline-flex items-center gap-1 text-primary underline-offset-4 hover:underline"
|
| 476 |
+
>
|
| 477 |
+
View original source <ExternalLink className="h-3 w-3" />
|
| 478 |
+
</a>
|
| 479 |
+
</div>
|
| 480 |
+
)}
|
| 481 |
+
</section>
|
| 482 |
+
)
|
| 483 |
+
}
|
|
@@ -1,11 +1,12 @@
|
|
| 1 |
"use client"
|
| 2 |
|
| 3 |
import type { ReactNode } from "react"
|
| 4 |
-
import { ChevronDown, GitCompareArrows, UsersRound } from "lucide-react"
|
| 5 |
|
| 6 |
import { useAudienceMode } from "@/components/audience-mode-provider"
|
| 7 |
import { Badge } from "@/components/ui/badge"
|
| 8 |
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"
|
|
|
|
| 9 |
import type { BenchmarkComparability, ComparabilitySummary, DifferingSetupField } from "@/lib/backend-artifacts"
|
| 10 |
import {
|
| 11 |
formatFieldLabel,
|
|
@@ -31,7 +32,10 @@ export function ComparabilityPanel({
|
|
| 31 |
}
|
| 32 |
|
| 33 |
return (
|
| 34 |
-
<section
|
|
|
|
|
|
|
|
|
|
| 35 |
<div className="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
|
| 36 |
<div className="space-y-1">
|
| 37 |
<div className="flex items-center gap-2">
|
|
@@ -60,44 +64,54 @@ export function ComparabilityPanel({
|
|
| 60 |
</div>
|
| 61 |
)}
|
| 62 |
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
>
|
| 70 |
-
{variantGroups.
|
| 71 |
-
<
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
|
|
|
|
|
|
|
|
|
| 101 |
</section>
|
| 102 |
)
|
| 103 |
}
|
|
@@ -107,11 +121,13 @@ function GroupList({
|
|
| 107 |
title,
|
| 108 |
count,
|
| 109 |
children,
|
|
|
|
| 110 |
}: {
|
| 111 |
icon: "variant" | "cross-party"
|
| 112 |
title: string
|
| 113 |
count: number
|
| 114 |
children: ReactNode
|
|
|
|
| 115 |
}) {
|
| 116 |
const Icon = icon === "variant" ? GitCompareArrows : UsersRound
|
| 117 |
|
|
@@ -130,13 +146,58 @@ function GroupList({
|
|
| 130 |
<ChevronDown className="h-4 w-4 text-muted-foreground" />
|
| 131 |
</button>
|
| 132 |
</CollapsibleTrigger>
|
| 133 |
-
<CollapsibleContent className=
|
| 134 |
{children}
|
| 135 |
</CollapsibleContent>
|
| 136 |
</Collapsible>
|
| 137 |
)
|
| 138 |
}
|
| 139 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
function DivergenceGroupItem({
|
| 141 |
modelRouteId,
|
| 142 |
magnitude,
|
|
@@ -150,31 +211,80 @@ function DivergenceGroupItem({
|
|
| 150 |
fields: DifferingSetupField[]
|
| 151 |
scoresByOrganization?: Record<string, number>
|
| 152 |
}) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
return (
|
| 154 |
<a
|
| 155 |
href={`#row-${modelRouteId}`}
|
| 156 |
-
className="block rounded-xl border border-border/60 bg-background px-3 py-2 text-sm transition-colors hover:bg-muted/20"
|
| 157 |
>
|
| 158 |
<div className="flex items-start justify-between gap-3">
|
| 159 |
<div className="min-w-0">
|
| 160 |
-
<div className="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
<div className="mt-1 text-xs text-muted-foreground">
|
| 162 |
-
|
| 163 |
</div>
|
| 164 |
</div>
|
| 165 |
<span className="shrink-0 text-xs font-medium text-primary">Jump to row</span>
|
| 166 |
</div>
|
| 167 |
|
| 168 |
-
{fields.
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
|
|
|
| 174 |
</div>
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
|
| 179 |
{scoresByOrganization && Object.keys(scoresByOrganization).length > 0 && (
|
| 180 |
<div className="mt-2 flex flex-wrap gap-1.5">
|
|
|
|
| 1 |
"use client"
|
| 2 |
|
| 3 |
import type { ReactNode } from "react"
|
| 4 |
+
import { ChevronDown, GitCompareArrows, Info, UsersRound } from "lucide-react"
|
| 5 |
|
| 6 |
import { useAudienceMode } from "@/components/audience-mode-provider"
|
| 7 |
import { Badge } from "@/components/ui/badge"
|
| 8 |
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"
|
| 9 |
+
import { SignalTooltip } from "@/components/signals/signal-tooltip"
|
| 10 |
import type { BenchmarkComparability, ComparabilitySummary, DifferingSetupField } from "@/lib/backend-artifacts"
|
| 11 |
import {
|
| 12 |
formatFieldLabel,
|
|
|
|
| 32 |
}
|
| 33 |
|
| 34 |
return (
|
| 35 |
+
<section
|
| 36 |
+
id="comparability-panel"
|
| 37 |
+
className="rounded-2xl border border-border/70 bg-background/70 p-4 sm:p-5 scroll-mt-24"
|
| 38 |
+
>
|
| 39 |
<div className="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
|
| 40 |
<div className="space-y-1">
|
| 41 |
<div className="flex items-center gap-2">
|
|
|
|
| 64 |
</div>
|
| 65 |
)}
|
| 66 |
|
| 67 |
+
{(() => {
|
| 68 |
+
const onlyOne =
|
| 69 |
+
(variantGroups.length > 0 ? 1 : 0) + (crossPartyGroups.length > 0 ? 1 : 0) === 1
|
| 70 |
+
const sectionClass = onlyOne ? "" : "lg:grid lg:grid-cols-2 lg:gap-3"
|
| 71 |
+
const itemsClass = onlyOne ? "grid gap-2 md:grid-cols-2" : "space-y-2"
|
| 72 |
+
return (
|
| 73 |
+
<div className={`mt-4 ${sectionClass}`}>
|
| 74 |
+
{variantGroups.length > 0 && (
|
| 75 |
+
<GroupList
|
| 76 |
+
icon="variant"
|
| 77 |
+
title="Variant divergence"
|
| 78 |
+
count={variantGroups.length}
|
| 79 |
+
itemsClassName={itemsClass}
|
| 80 |
+
>
|
| 81 |
+
{variantGroups.slice(0, 8).map((group) => (
|
| 82 |
+
<DivergenceGroupItem
|
| 83 |
+
key={group.group_id}
|
| 84 |
+
modelRouteId={group.model_route_id}
|
| 85 |
+
magnitude={group.divergence_magnitude}
|
| 86 |
+
threshold={group.threshold_used}
|
| 87 |
+
fields={group.differing_setup_fields}
|
| 88 |
+
/>
|
| 89 |
+
))}
|
| 90 |
+
</GroupList>
|
| 91 |
+
)}
|
| 92 |
|
| 93 |
+
{crossPartyGroups.length > 0 && (
|
| 94 |
+
<GroupList
|
| 95 |
+
icon="cross-party"
|
| 96 |
+
title="Cross-party divergence"
|
| 97 |
+
count={crossPartyGroups.length}
|
| 98 |
+
itemsClassName={itemsClass}
|
| 99 |
+
>
|
| 100 |
+
{crossPartyGroups.slice(0, 8).map((group) => (
|
| 101 |
+
<DivergenceGroupItem
|
| 102 |
+
key={group.group_id}
|
| 103 |
+
modelRouteId={group.model_route_id}
|
| 104 |
+
magnitude={group.divergence_magnitude}
|
| 105 |
+
threshold={group.threshold_used}
|
| 106 |
+
fields={group.differing_setup_fields}
|
| 107 |
+
scoresByOrganization={group.scores_by_organization}
|
| 108 |
+
/>
|
| 109 |
+
))}
|
| 110 |
+
</GroupList>
|
| 111 |
+
)}
|
| 112 |
+
</div>
|
| 113 |
+
)
|
| 114 |
+
})()}
|
| 115 |
</section>
|
| 116 |
)
|
| 117 |
}
|
|
|
|
| 121 |
title,
|
| 122 |
count,
|
| 123 |
children,
|
| 124 |
+
itemsClassName = "space-y-2",
|
| 125 |
}: {
|
| 126 |
icon: "variant" | "cross-party"
|
| 127 |
title: string
|
| 128 |
count: number
|
| 129 |
children: ReactNode
|
| 130 |
+
itemsClassName?: string
|
| 131 |
}) {
|
| 132 |
const Icon = icon === "variant" ? GitCompareArrows : UsersRound
|
| 133 |
|
|
|
|
| 146 |
<ChevronDown className="h-4 w-4 text-muted-foreground" />
|
| 147 |
</button>
|
| 148 |
</CollapsibleTrigger>
|
| 149 |
+
<CollapsibleContent className={`mt-2 ${itemsClassName}`}>
|
| 150 |
{children}
|
| 151 |
</CollapsibleContent>
|
| 152 |
</Collapsible>
|
| 153 |
)
|
| 154 |
}
|
| 155 |
|
| 156 |
+
/**
|
| 157 |
+
* Try to extract a human-readable label from a structured setup-field value.
|
| 158 |
+
* Common shape from agentic evals: { additional_details: { agent_name, agent_framework } }.
|
| 159 |
+
* Falls back to picking the first short string property, or null when the
|
| 160 |
+
* value can't be summarized cleanly.
|
| 161 |
+
*/
|
| 162 |
+
function extractFriendlyLabel(value: unknown): string | null {
|
| 163 |
+
if (value == null) return null
|
| 164 |
+
if (typeof value === "string") return value.length > 60 ? null : value
|
| 165 |
+
if (typeof value === "number" || typeof value === "boolean") return String(value)
|
| 166 |
+
if (typeof value !== "object") return null
|
| 167 |
+
|
| 168 |
+
const obj = value as Record<string, unknown>
|
| 169 |
+
const details = (obj.additional_details && typeof obj.additional_details === "object")
|
| 170 |
+
? (obj.additional_details as Record<string, unknown>)
|
| 171 |
+
: obj
|
| 172 |
+
|
| 173 |
+
const agentName = typeof details.agent_name === "string" ? details.agent_name : null
|
| 174 |
+
const agentFramework = typeof details.agent_framework === "string" ? details.agent_framework : null
|
| 175 |
+
if (agentName) {
|
| 176 |
+
return agentFramework && agentFramework !== agentName ? `${agentName} (${agentFramework})` : agentName
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
for (const key of ["name", "label", "id", "title"]) {
|
| 180 |
+
const v = details[key]
|
| 181 |
+
if (typeof v === "string" && v.length <= 60) return v
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
return null
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
function chipsForFieldValues(values: unknown[]): { label: string; raw: unknown }[] {
|
| 188 |
+
const seen = new Set<string>()
|
| 189 |
+
const result: { label: string; raw: unknown }[] = []
|
| 190 |
+
for (const v of values) {
|
| 191 |
+
const friendly = extractFriendlyLabel(v)
|
| 192 |
+
const label = friendly ?? formatSignalValue(v)
|
| 193 |
+
const truncated = label.length > 80 ? label.slice(0, 80) + "…" : label
|
| 194 |
+
if (seen.has(truncated)) continue
|
| 195 |
+
seen.add(truncated)
|
| 196 |
+
result.push({ label: truncated, raw: v })
|
| 197 |
+
}
|
| 198 |
+
return result
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
function DivergenceGroupItem({
|
| 202 |
modelRouteId,
|
| 203 |
magnitude,
|
|
|
|
| 211 |
fields: DifferingSetupField[]
|
| 212 |
scoresByOrganization?: Record<string, number>
|
| 213 |
}) {
|
| 214 |
+
const fieldLabels = fields.map((f) => formatFieldLabel(f.field))
|
| 215 |
+
const summarySentence =
|
| 216 |
+
fieldLabels.length === 0
|
| 217 |
+
? `Reported scores diverge by ${formatSignalNumber(magnitude)}, above the ${formatSignalNumber(threshold)} threshold. The setup difference is not labelled.`
|
| 218 |
+
: `Reported scores diverge by ${formatSignalNumber(magnitude)} (threshold ${formatSignalNumber(threshold)}) because the runs differ on ${
|
| 219 |
+
fieldLabels.length === 1
|
| 220 |
+
? fieldLabels[0]
|
| 221 |
+
: fieldLabels.slice(0, -1).join(", ") + " and " + fieldLabels[fieldLabels.length - 1]
|
| 222 |
+
}. The chips below show each variant.`
|
| 223 |
+
|
| 224 |
return (
|
| 225 |
<a
|
| 226 |
href={`#row-${modelRouteId}`}
|
| 227 |
+
className="block rounded-xl border border-border/60 bg-background px-3 py-2.5 text-sm transition-colors hover:bg-muted/20"
|
| 228 |
>
|
| 229 |
<div className="flex items-start justify-between gap-3">
|
| 230 |
<div className="min-w-0">
|
| 231 |
+
<div className="flex items-center gap-1.5">
|
| 232 |
+
<span className="font-medium">{modelRouteId}</span>
|
| 233 |
+
<SignalTooltip content={summarySentence}>
|
| 234 |
+
<Info className="h-3.5 w-3.5 shrink-0 cursor-help text-muted-foreground" />
|
| 235 |
+
</SignalTooltip>
|
| 236 |
+
</div>
|
| 237 |
<div className="mt-1 text-xs text-muted-foreground">
|
| 238 |
+
Diverges by {formatSignalNumber(magnitude)} (threshold {formatSignalNumber(threshold)})
|
| 239 |
</div>
|
| 240 |
</div>
|
| 241 |
<span className="shrink-0 text-xs font-medium text-primary">Jump to row</span>
|
| 242 |
</div>
|
| 243 |
|
| 244 |
+
{fields.slice(0, 3).map((field) => {
|
| 245 |
+
const chips = chipsForFieldValues(field.values).slice(0, 6)
|
| 246 |
+
const overflow = field.values.length - chips.length
|
| 247 |
+
return (
|
| 248 |
+
<div key={field.field} className="mt-2">
|
| 249 |
+
<div className="mb-1 text-[11px] font-semibold uppercase tracking-[0.14em] text-muted-foreground">
|
| 250 |
+
Differs by {formatFieldLabel(field.field)}
|
| 251 |
</div>
|
| 252 |
+
<div className="flex flex-wrap gap-1.5">
|
| 253 |
+
{chips.map((chip, idx) => {
|
| 254 |
+
const friendly = extractFriendlyLabel(chip.raw)
|
| 255 |
+
const tooltipBody = friendly ? formatSignalValue(chip.raw) : null
|
| 256 |
+
const pill = (
|
| 257 |
+
<span
|
| 258 |
+
className="inline-flex max-w-[18rem] items-center rounded-full border border-border/60 bg-muted/20 px-2 py-0.5 text-[11px] text-foreground/90 truncate"
|
| 259 |
+
title={!tooltipBody ? chip.label : undefined}
|
| 260 |
+
>
|
| 261 |
+
{chip.label}
|
| 262 |
+
</span>
|
| 263 |
+
)
|
| 264 |
+
return tooltipBody ? (
|
| 265 |
+
<SignalTooltip
|
| 266 |
+
key={`${field.field}-${idx}`}
|
| 267 |
+
content={
|
| 268 |
+
<span className="block max-w-[24rem] break-all font-mono text-[10px] leading-snug">
|
| 269 |
+
{tooltipBody}
|
| 270 |
+
</span>
|
| 271 |
+
}
|
| 272 |
+
>
|
| 273 |
+
{pill}
|
| 274 |
+
</SignalTooltip>
|
| 275 |
+
) : (
|
| 276 |
+
<span key={`${field.field}-${idx}`}>{pill}</span>
|
| 277 |
+
)
|
| 278 |
+
})}
|
| 279 |
+
{overflow > 0 && (
|
| 280 |
+
<span className="inline-flex items-center rounded-full border border-dashed border-border/60 px-2 py-0.5 text-[11px] text-muted-foreground">
|
| 281 |
+
+{overflow} more
|
| 282 |
+
</span>
|
| 283 |
+
)}
|
| 284 |
+
</div>
|
| 285 |
+
</div>
|
| 286 |
+
)
|
| 287 |
+
})}
|
| 288 |
|
| 289 |
{scoresByOrganization && Object.keys(scoresByOrganization).length > 0 && (
|
| 290 |
<div className="mt-2 flex flex-wrap gap-1.5">
|
|
@@ -22,7 +22,7 @@ export function ReproducibilityPanel({
|
|
| 22 |
<div className="rounded-2xl border bg-background/70 p-4">
|
| 23 |
<div className="mb-4 flex items-start gap-2">
|
| 24 |
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0 text-amber-600 dark:text-amber-300" />
|
| 25 |
-
<div>
|
| 26 |
<div className="font-semibold">
|
| 27 |
{isResearchView ? "Reproducibility" : "Re-runnability"}
|
| 28 |
</div>
|
|
|
|
| 22 |
<div className="rounded-2xl border bg-background/70 p-4">
|
| 23 |
<div className="mb-4 flex items-start gap-2">
|
| 24 |
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0 text-amber-600 dark:text-amber-300" />
|
| 25 |
+
<div className="min-w-0 flex-1">
|
| 26 |
<div className="font-semibold">
|
| 27 |
{isResearchView ? "Reproducibility" : "Re-runnability"}
|
| 28 |
</div>
|
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client"
|
| 2 |
+
|
| 3 |
+
import { AlertTriangle, ShieldCheck } from "lucide-react"
|
| 4 |
+
import { useAudienceMode } from "@/components/audience-mode-provider"
|
| 5 |
+
import type { RowAnnotations } from "@/lib/backend-artifacts"
|
| 6 |
+
import { cn } from "@/lib/utils"
|
| 7 |
+
import { SignalTooltip } from "./signal-tooltip"
|
| 8 |
+
import { formatMissingField } from "./signal-utils"
|
| 9 |
+
import { getRelationshipShortLabel } from "./provenance-badge"
|
| 10 |
+
|
| 11 |
+
/**
|
| 12 |
+
* Compact, single-icon row signal indicator. Shows a warning icon when any
|
| 13 |
+
* row-level concern fires (reproducibility gap, first-party-only reporting),
|
| 14 |
+
* with a tooltip listing the specifics. Replaces the two full coloured badges
|
| 15 |
+
* that were taking up vertical space under every model name.
|
| 16 |
+
*
|
| 17 |
+
* Returns null when there are no concerns, so green rows stay quiet.
|
| 18 |
+
*/
|
| 19 |
+
export function RowSignalsCompact({
|
| 20 |
+
annotations,
|
| 21 |
+
className,
|
| 22 |
+
showWhenClean = false,
|
| 23 |
+
}: {
|
| 24 |
+
annotations?: RowAnnotations | null
|
| 25 |
+
className?: string
|
| 26 |
+
showWhenClean?: boolean
|
| 27 |
+
}) {
|
| 28 |
+
const { mode } = useAudienceMode()
|
| 29 |
+
|
| 30 |
+
if (!annotations) return null
|
| 31 |
+
|
| 32 |
+
const reproGap = annotations.reproducibility_gap
|
| 33 |
+
const provenance = annotations.provenance
|
| 34 |
+
const hasReproGap = reproGap?.has_reproducibility_gap === true
|
| 35 |
+
const firstPartyOnly = provenance?.first_party_only === true
|
| 36 |
+
|
| 37 |
+
const concerns: { title: string; detail: string }[] = []
|
| 38 |
+
|
| 39 |
+
if (hasReproGap && reproGap) {
|
| 40 |
+
const missing = reproGap.missing_fields.map(formatMissingField)
|
| 41 |
+
concerns.push({
|
| 42 |
+
title: mode === "policy" ? "Setup not documented" : "Reproducibility gap",
|
| 43 |
+
detail:
|
| 44 |
+
mode === "policy"
|
| 45 |
+
? `${reproGap.populated_field_count} of ${reproGap.required_field_count} setup fields recorded. This score may be hard to re-run.`
|
| 46 |
+
: `Missing: ${missing.join(", ") || "none listed"}. ${reproGap.populated_field_count} of ${reproGap.required_field_count} setup fields recorded.`,
|
| 47 |
+
})
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
if (firstPartyOnly) {
|
| 51 |
+
concerns.push({
|
| 52 |
+
title:
|
| 53 |
+
mode === "policy" ? "Only model developer reported" : `${getRelationshipShortLabel("first_party", mode)} only`,
|
| 54 |
+
detail:
|
| 55 |
+
mode === "policy"
|
| 56 |
+
? "Only the model developer reported this score; no independent replication is recorded."
|
| 57 |
+
: "First-party only — no independent replication is recorded for this group.",
|
| 58 |
+
})
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
if (concerns.length === 0) {
|
| 62 |
+
if (!showWhenClean) return null
|
| 63 |
+
return (
|
| 64 |
+
<SignalTooltip content="No row-level concerns flagged for this score.">
|
| 65 |
+
<span className={cn("inline-flex h-5 w-5 items-center justify-center", className)}>
|
| 66 |
+
<ShieldCheck className="h-3.5 w-3.5 text-emerald-600 dark:text-emerald-400" />
|
| 67 |
+
</span>
|
| 68 |
+
</SignalTooltip>
|
| 69 |
+
)
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
return (
|
| 73 |
+
<SignalTooltip
|
| 74 |
+
content={
|
| 75 |
+
<span className="block space-y-1.5">
|
| 76 |
+
<span className="block font-semibold">{concerns.length === 1 ? "1 concern" : `${concerns.length} concerns`}</span>
|
| 77 |
+
{concerns.map((c) => (
|
| 78 |
+
<span key={c.title} className="block">
|
| 79 |
+
<span className="font-medium">{c.title}.</span>{" "}
|
| 80 |
+
<span className="text-muted-foreground">{c.detail}</span>
|
| 81 |
+
</span>
|
| 82 |
+
))}
|
| 83 |
+
</span>
|
| 84 |
+
}
|
| 85 |
+
>
|
| 86 |
+
<span
|
| 87 |
+
className={cn(
|
| 88 |
+
"inline-flex h-5 items-center gap-1 rounded-full bg-amber-50 px-1.5 text-amber-700 dark:bg-amber-950/40 dark:text-amber-200 cursor-help",
|
| 89 |
+
className
|
| 90 |
+
)}
|
| 91 |
+
aria-label={`${concerns.length} reporting concern${concerns.length === 1 ? "" : "s"} for this row`}
|
| 92 |
+
>
|
| 93 |
+
<AlertTriangle className="h-3 w-3" />
|
| 94 |
+
{concerns.length > 1 && <span className="text-[10px] font-semibold leading-none">{concerns.length}</span>}
|
| 95 |
+
</span>
|
| 96 |
+
</SignalTooltip>
|
| 97 |
+
)
|
| 98 |
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client"
|
| 2 |
+
|
| 3 |
+
import type { ReactNode } from "react"
|
| 4 |
+
import { lookupTerm } from "@/lib/glossary"
|
| 5 |
+
import { SignalTooltip } from "@/components/signals/signal-tooltip"
|
| 6 |
+
|
| 7 |
+
interface TermProps {
|
| 8 |
+
/**
|
| 9 |
+
* Glossary key to look up. Defaults to the visible text.
|
| 10 |
+
*/
|
| 11 |
+
term?: string
|
| 12 |
+
children: ReactNode
|
| 13 |
+
/**
|
| 14 |
+
* Override the tooltip body. If omitted, glossary entry is used.
|
| 15 |
+
*/
|
| 16 |
+
explain?: ReactNode
|
| 17 |
+
className?: string
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
export function Term({ term, children, explain, className }: TermProps) {
|
| 21 |
+
const key = term ?? (typeof children === "string" ? children : undefined)
|
| 22 |
+
const entry = key ? lookupTerm(key) : undefined
|
| 23 |
+
|
| 24 |
+
const content = explain ?? (entry ? (
|
| 25 |
+
<span className="block space-y-1">
|
| 26 |
+
<span className="block">{entry.short}</span>
|
| 27 |
+
{entry.long ? <span className="block text-muted-foreground">{entry.long}</span> : null}
|
| 28 |
+
</span>
|
| 29 |
+
) : null)
|
| 30 |
+
|
| 31 |
+
if (!content) {
|
| 32 |
+
return <span className={className}>{children}</span>
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
return (
|
| 36 |
+
<SignalTooltip content={content}>
|
| 37 |
+
<span
|
| 38 |
+
className={
|
| 39 |
+
"underline decoration-dotted decoration-muted-foreground/60 underline-offset-4 cursor-help " +
|
| 40 |
+
(className ?? "")
|
| 41 |
+
}
|
| 42 |
+
>
|
| 43 |
+
{children}
|
| 44 |
+
</span>
|
| 45 |
+
</SignalTooltip>
|
| 46 |
+
)
|
| 47 |
+
}
|
|
@@ -117,6 +117,8 @@ export interface ModelResultForBenchmark {
|
|
| 117 |
source_metadata: SourceMetadata
|
| 118 |
source_data: BenchmarkEvaluation['source_data']
|
| 119 |
result: EvaluationResult
|
|
|
|
|
|
|
| 120 |
aggregate_components?: Array<{
|
| 121 |
evaluation_id: string
|
| 122 |
composite_benchmark_key: string
|
|
|
|
| 117 |
source_metadata: SourceMetadata
|
| 118 |
source_data: BenchmarkEvaluation['source_data']
|
| 119 |
result: EvaluationResult
|
| 120 |
+
/** URL to the underlying record JSON in the upstream HF dataset, when known. */
|
| 121 |
+
source_record_url?: string
|
| 122 |
aggregate_components?: Array<{
|
| 123 |
evaluation_id: string
|
| 124 |
composite_benchmark_key: string
|
|
@@ -0,0 +1,136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export interface GlossaryEntry {
|
| 2 |
+
short: string
|
| 3 |
+
long?: string
|
| 4 |
+
}
|
| 5 |
+
|
| 6 |
+
const ENTRIES: Record<string, GlossaryEntry> = {
|
| 7 |
+
"0-shot": {
|
| 8 |
+
short: "Model answers without seeing any worked examples first.",
|
| 9 |
+
long: "The benchmark gives the model only the question. Tests how the model performs cold, without in-context demonstrations.",
|
| 10 |
+
},
|
| 11 |
+
"zero-shot": {
|
| 12 |
+
short: "Model answers without seeing any worked examples first.",
|
| 13 |
+
},
|
| 14 |
+
"1-shot": {
|
| 15 |
+
short: "Model sees one worked example before answering.",
|
| 16 |
+
},
|
| 17 |
+
"5-shot": {
|
| 18 |
+
short: "Model sees five worked examples before answering.",
|
| 19 |
+
long: "The five examples are included in the prompt as demonstrations. More shots usually raise scores; comparing a 0-shot result to a 5-shot result is not apples-to-apples.",
|
| 20 |
+
},
|
| 21 |
+
"few-shot": {
|
| 22 |
+
short: "Model sees a small number of worked examples before answering.",
|
| 23 |
+
},
|
| 24 |
+
"n-shot": {
|
| 25 |
+
short: "Number of worked examples shown to the model in the prompt.",
|
| 26 |
+
},
|
| 27 |
+
"pass@1": {
|
| 28 |
+
short: "The model gets one attempt; counted correct only if that single attempt passes.",
|
| 29 |
+
long: "Common in code benchmarks. Stricter than pass@k for k>1, since the model cannot retry.",
|
| 30 |
+
},
|
| 31 |
+
"pass@16": {
|
| 32 |
+
short: "The model gets 16 attempts; counted correct if any one of them passes.",
|
| 33 |
+
long: "Higher numbers tend to be inflated relative to pass@1 because the model has many tries.",
|
| 34 |
+
},
|
| 35 |
+
"pass@k": {
|
| 36 |
+
short: "The model gets k attempts; counted correct if any one of them passes.",
|
| 37 |
+
},
|
| 38 |
+
"majority voting": {
|
| 39 |
+
short: "The model answers many times; the most common answer is taken as the final answer.",
|
| 40 |
+
long: "Also called self-consistency. Tends to raise scores on reasoning tasks but costs more compute per question.",
|
| 41 |
+
},
|
| 42 |
+
"self-consistency": {
|
| 43 |
+
short: "The model answers many times; the most common answer is taken as the final answer.",
|
| 44 |
+
},
|
| 45 |
+
"best-of-n": {
|
| 46 |
+
short: "The model produces N answers; the best one (by a scorer or oracle) is reported.",
|
| 47 |
+
long: "Inflates scores compared to a single attempt. Worth flagging when comparing models.",
|
| 48 |
+
},
|
| 49 |
+
"exact match": {
|
| 50 |
+
short: "The model's answer is counted correct only if it matches the reference string exactly.",
|
| 51 |
+
long: "Strict — small formatting differences fail. Often used for short-answer tasks.",
|
| 52 |
+
},
|
| 53 |
+
"llm-as-judge": {
|
| 54 |
+
short: "Another language model grades the answers.",
|
| 55 |
+
long: "Faster than human grading but the judge model can have biases (length, style, self-preference). Worth knowing the judge identity.",
|
| 56 |
+
},
|
| 57 |
+
"llm as judge": {
|
| 58 |
+
short: "Another language model grades the answers.",
|
| 59 |
+
},
|
| 60 |
+
"human-in-the-loop": {
|
| 61 |
+
short: "Human graders score or check the model's answers.",
|
| 62 |
+
},
|
| 63 |
+
temperature: {
|
| 64 |
+
short: "Controls randomness in the model's output.",
|
| 65 |
+
long: "Lower (e.g. 0) is deterministic; higher (e.g. 1) is more varied. Affects scores — different temperatures make scores hard to compare.",
|
| 66 |
+
},
|
| 67 |
+
"top-p": {
|
| 68 |
+
short: "Limits the model to sampling from the most likely next tokens (nucleus sampling).",
|
| 69 |
+
},
|
| 70 |
+
"top-k": {
|
| 71 |
+
short: "Limits the model to sampling from the K most likely next tokens.",
|
| 72 |
+
},
|
| 73 |
+
perplexity: {
|
| 74 |
+
short: "How surprised the model is by the text. Lower is better.",
|
| 75 |
+
},
|
| 76 |
+
"average normalized score": {
|
| 77 |
+
short: "Each benchmark's score is rescaled to a 0–1 range, then averaged.",
|
| 78 |
+
long: "Lets you combine benchmarks that use different scales. The exact rescaling rule matters — check the methodology.",
|
| 79 |
+
},
|
| 80 |
+
agentic: {
|
| 81 |
+
short: "The model uses tools, takes multiple steps, and acts on an environment to complete a task.",
|
| 82 |
+
},
|
| 83 |
+
"agent budget": {
|
| 84 |
+
short: "The maximum compute, tokens, or steps the agent is allowed before it must stop.",
|
| 85 |
+
},
|
| 86 |
+
"f1 score": {
|
| 87 |
+
short: "A single number combining precision and recall. Higher is better.",
|
| 88 |
+
},
|
| 89 |
+
bleu: {
|
| 90 |
+
short: "A score for how close generated text is to a reference, by overlapping word sequences.",
|
| 91 |
+
},
|
| 92 |
+
rouge: {
|
| 93 |
+
short: "A score for how much a generated summary overlaps with a reference summary.",
|
| 94 |
+
},
|
| 95 |
+
benchmark: {
|
| 96 |
+
short: "A standardized test used to compare models on a specific capability.",
|
| 97 |
+
},
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
const KEY_ALIASES: Record<string, string> = {
|
| 101 |
+
"0 shot": "0-shot",
|
| 102 |
+
"1 shot": "1-shot",
|
| 103 |
+
"5 shot": "5-shot",
|
| 104 |
+
"few shot": "few-shot",
|
| 105 |
+
"n shot": "n-shot",
|
| 106 |
+
"zero shot": "zero-shot",
|
| 107 |
+
"best of n": "best-of-n",
|
| 108 |
+
"best-of-N": "best-of-n",
|
| 109 |
+
"LLM-as-judge": "llm-as-judge",
|
| 110 |
+
"LLM as judge": "llm as judge",
|
| 111 |
+
"self consistency": "self-consistency",
|
| 112 |
+
"Pass@1": "pass@1",
|
| 113 |
+
"Pass@16": "pass@16",
|
| 114 |
+
"Pass@K": "pass@k",
|
| 115 |
+
"F1": "f1 score",
|
| 116 |
+
"F1 score": "f1 score",
|
| 117 |
+
"BLEU": "bleu",
|
| 118 |
+
"ROUGE": "rouge",
|
| 119 |
+
"Agentic": "agentic",
|
| 120 |
+
"Temperature": "temperature",
|
| 121 |
+
"Perplexity": "perplexity",
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
export function lookupTerm(term: string): GlossaryEntry | undefined {
|
| 125 |
+
const trimmed = term.trim()
|
| 126 |
+
if (!trimmed) return undefined
|
| 127 |
+
const direct = ENTRIES[trimmed] ?? ENTRIES[trimmed.toLowerCase()]
|
| 128 |
+
if (direct) return direct
|
| 129 |
+
const aliased = KEY_ALIASES[trimmed] ?? KEY_ALIASES[trimmed.toLowerCase()]
|
| 130 |
+
if (aliased) return ENTRIES[aliased]
|
| 131 |
+
return undefined
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
export function hasTerm(term: string): boolean {
|
| 135 |
+
return lookupTerm(term) !== undefined
|
| 136 |
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import knownIssuesData from "@/metadata/benchmark_known_issues.json"
|
| 2 |
+
|
| 3 |
+
export interface KnownIssue {
|
| 4 |
+
title: string
|
| 5 |
+
summary: string
|
| 6 |
+
severity: "info" | "warning" | "critical"
|
| 7 |
+
source_url?: string
|
| 8 |
+
published?: string
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
const RAW_ISSUES = (knownIssuesData as { issues?: Record<string, KnownIssue[]> }).issues ?? {}
|
| 12 |
+
|
| 13 |
+
function normalizeKey(value: string): string {
|
| 14 |
+
return value
|
| 15 |
+
.toLowerCase()
|
| 16 |
+
.trim()
|
| 17 |
+
.replace(/[\s_\-/]+/g, " ")
|
| 18 |
+
.replace(/\s+/g, " ")
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
const NORMALIZED_LOOKUP = new Map<string, KnownIssue[]>()
|
| 22 |
+
for (const [rawKey, issues] of Object.entries(RAW_ISSUES)) {
|
| 23 |
+
if (!Array.isArray(issues) || issues.length === 0) continue
|
| 24 |
+
NORMALIZED_LOOKUP.set(normalizeKey(rawKey), issues)
|
| 25 |
+
// Also index a hyphenated variant since some benchmark keys use dashes
|
| 26 |
+
NORMALIZED_LOOKUP.set(normalizeKey(rawKey).replace(/\s+/g, "-"), issues)
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
/**
|
| 30 |
+
* Look up curated known issues for a benchmark by trying any of the supplied
|
| 31 |
+
* names/keys (e.g. evaluation_name, composite_benchmark_key, family_key).
|
| 32 |
+
* Returns the first matching list, or an empty array if nothing is recorded.
|
| 33 |
+
*/
|
| 34 |
+
export function getKnownIssues(...candidates: Array<string | undefined | null>): KnownIssue[] {
|
| 35 |
+
for (const candidate of candidates) {
|
| 36 |
+
if (!candidate) continue
|
| 37 |
+
const key = normalizeKey(candidate)
|
| 38 |
+
const direct = NORMALIZED_LOOKUP.get(key)
|
| 39 |
+
if (direct) return direct
|
| 40 |
+
// Try collapsed (no spaces/dashes) form too, since some sources mash names together
|
| 41 |
+
const collapsed = key.replace(/\s+/g, "")
|
| 42 |
+
for (const [registered, issues] of NORMALIZED_LOOKUP) {
|
| 43 |
+
if (registered.replace(/\s+/g, "") === collapsed) return issues
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
+
return []
|
| 47 |
+
}
|
|
@@ -749,6 +749,7 @@ function toModelResultsForMetric(
|
|
| 749 |
},
|
| 750 |
source_data: detail.source_data ?? { dataset_name: benchmarkKey },
|
| 751 |
result: evaluationResult,
|
|
|
|
| 752 |
}
|
| 753 |
})
|
| 754 |
}
|
|
|
|
| 749 |
},
|
| 750 |
source_data: detail.source_data ?? { dataset_name: benchmarkKey },
|
| 751 |
result: evaluationResult,
|
| 752 |
+
source_record_url: mr.source_record_url,
|
| 753 |
}
|
| 754 |
})
|
| 755 |
}
|
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_doc": "Curated known issues for specific benchmarks. Keyed by lowercased benchmark identifier. Severity is info | warning | critical. Each entry: title, summary, severity, optional source_url, optional published date (YYYY-MM-DD). This registry is for benchmark-specific concerns documented in academic work or by maintainers — not generic AI risks. Lookup is case-insensitive and tolerant of separator differences (spaces, dashes, underscores).",
|
| 3 |
+
"issues": {
|
| 4 |
+
"mmlu": [
|
| 5 |
+
{
|
| 6 |
+
"title": "Test-set contamination in pretraining corpora",
|
| 7 |
+
"summary": "Multiple analyses find MMLU questions and exact answers in publicly scraped pretraining data, inflating scores for models trained on that data.",
|
| 8 |
+
"severity": "warning",
|
| 9 |
+
"source_url": "https://arxiv.org/abs/2310.16787",
|
| 10 |
+
"published": "2023-10-25"
|
| 11 |
+
},
|
| 12 |
+
{
|
| 13 |
+
"title": "Errors and ambiguities in test items",
|
| 14 |
+
"summary": "Independent audits found mislabeled answers, ambiguous wording, and duplicated questions across MMLU subjects, capping reliable accuracy below 100%.",
|
| 15 |
+
"severity": "warning",
|
| 16 |
+
"source_url": "https://arxiv.org/abs/2406.04127"
|
| 17 |
+
}
|
| 18 |
+
],
|
| 19 |
+
"mmlu-pro": [
|
| 20 |
+
{
|
| 21 |
+
"title": "Inherits MMLU contamination concerns for some subjects",
|
| 22 |
+
"summary": "MMLU-Pro reuses items from MMLU and other public sources; subjects overlapping with the MMLU origin may exhibit similar pretraining-data leakage.",
|
| 23 |
+
"severity": "info",
|
| 24 |
+
"source_url": "https://arxiv.org/abs/2406.01574"
|
| 25 |
+
}
|
| 26 |
+
],
|
| 27 |
+
"gsm8k": [
|
| 28 |
+
{
|
| 29 |
+
"title": "Memorization on the test split",
|
| 30 |
+
"summary": "Studies show large models reproduce verbatim GSM8K test problems, suggesting training-time exposure rather than reasoning. Independent replication on perturbed variants (GSM-Symbolic, GSM-1k) gives lower scores.",
|
| 31 |
+
"severity": "warning",
|
| 32 |
+
"source_url": "https://arxiv.org/abs/2410.05229"
|
| 33 |
+
}
|
| 34 |
+
],
|
| 35 |
+
"hellaswag": [
|
| 36 |
+
{
|
| 37 |
+
"title": "Annotation noise and outdated cultural references",
|
| 38 |
+
"summary": "A non-trivial fraction of HellaSwag items are mislabeled or rely on now-stale references, capping ceiling performance and complicating cross-year comparisons.",
|
| 39 |
+
"severity": "info"
|
| 40 |
+
}
|
| 41 |
+
],
|
| 42 |
+
"humaneval": [
|
| 43 |
+
{
|
| 44 |
+
"title": "Limited and saturated test set",
|
| 45 |
+
"summary": "Only 164 problems; top models exceed 90% pass@1 and the benchmark is widely viewed as saturated. Use newer code benchmarks (LiveCodeBench, SWE-bench) for current capability claims.",
|
| 46 |
+
"severity": "warning"
|
| 47 |
+
},
|
| 48 |
+
{
|
| 49 |
+
"title": "Test-case adequacy",
|
| 50 |
+
"summary": "HumanEval+ found that the original tests miss many incorrect solutions; reported scores can overstate real correctness.",
|
| 51 |
+
"severity": "info",
|
| 52 |
+
"source_url": "https://arxiv.org/abs/2305.01210"
|
| 53 |
+
}
|
| 54 |
+
],
|
| 55 |
+
"narrativeqa": [
|
| 56 |
+
{
|
| 57 |
+
"title": "Largely deprecated in current evaluations",
|
| 58 |
+
"summary": "Few recent leading models report NarrativeQA. Treat scores as historical context rather than current capability evidence.",
|
| 59 |
+
"severity": "info"
|
| 60 |
+
}
|
| 61 |
+
],
|
| 62 |
+
"boolq": [
|
| 63 |
+
{
|
| 64 |
+
"title": "Largely saturated",
|
| 65 |
+
"summary": "Top models score above 90%; small differences are noise. Use as a sanity check, not a discriminator.",
|
| 66 |
+
"severity": "info"
|
| 67 |
+
}
|
| 68 |
+
],
|
| 69 |
+
"ifeval": [
|
| 70 |
+
{
|
| 71 |
+
"title": "Narrow instruction taxonomy",
|
| 72 |
+
"summary": "IFEval covers a fixed set of verifiable instruction types. High scores do not generalise to open-ended instruction following.",
|
| 73 |
+
"severity": "info"
|
| 74 |
+
}
|
| 75 |
+
],
|
| 76 |
+
"humanity's last exam": [
|
| 77 |
+
{
|
| 78 |
+
"title": "Marketing framing vs. methodological scope",
|
| 79 |
+
"summary": "The name implies a definitive ceiling for AI capability; the underlying benchmark is a 3,000-item exam in selected academic disciplines and does not measure general intelligence or safety. Read scores in context.",
|
| 80 |
+
"severity": "warning"
|
| 81 |
+
}
|
| 82 |
+
]
|
| 83 |
+
}
|
| 84 |
+
}
|