j-chim commited on
Commit
e74e7be
·
1 Parent(s): b12c58d

Fix embeds to filter by canonical benchmark only (no splits in embed until we decide how to display splits) + suppress popup on embed routes

Browse files
app/embed/eval/distribution/[...id]/page.tsx CHANGED
@@ -4,7 +4,10 @@ import { useEffect, useMemo, useState } from "react"
4
  import { useParams, useSearchParams } from "next/navigation"
5
  import { ScoreDistribution } from "@/components/score-distribution"
6
  import { fetchEvalSummary } from "@/lib/dashboard-data-client"
7
- import { getMetricChipLabel } from "@/lib/metric-labels"
 
 
 
8
  import { routeIdFromSegments } from "@/lib/utils"
9
  import type { BenchmarkEvalSummary } from "@/lib/eval-processing"
10
 
@@ -53,26 +56,10 @@ export default function EmbedEvalDistribution() {
53
 
54
  // Slice axis — present when the eval has multiple subtask-scope metrics
55
  // sharing a primary root metric (e.g. Global MMLU's 24 language splits).
56
- const sliceAxis = useMemo(() => {
57
- if (!summary) return null
58
- const metrics = summary.leaderboard_metrics ?? []
59
- const primary = metrics.find((m) => m.scope !== "subtask")
60
- if (!primary?.column_key) return null
61
- const seen = new Map<string, string>()
62
- for (const m of metrics) {
63
- if (m.scope === "subtask" && m.subtask_key && !seen.has(m.subtask_key)) {
64
- seen.set(m.subtask_key, m.subtask_name ?? m.subtask_key)
65
- }
66
- }
67
- if (seen.size <= 1) return null
68
- return {
69
- primaryColumn: primary.column_key,
70
- primaryLabel: getMetricChipLabel(primary),
71
- unit: primary.unit ?? summary.metric_config.unit,
72
- lowerIsBetter: Boolean(primary.lower_is_better ?? summary.metric_config.lower_is_better),
73
- slices: Array.from(seen, ([key, label]) => ({ key, label })),
74
- }
75
- }, [summary])
76
 
77
  const ALL_SLICE_KEY = "__all__"
78
  const [activeSlice, setActiveSlice] = useState<string>(() => {
@@ -89,78 +76,10 @@ export default function EmbedEvalDistribution() {
89
  }
90
  }, [activeSlice, sliceAxis])
91
 
92
- const series = useMemo(() => {
93
- if (!summary) return null
94
- const rows = summary.leaderboard_rows ?? []
95
-
96
- // Slice-axis path: render one series for the active slice (Overall or
97
- // a specific subtask). Drives the SPLIT dropdown UX.
98
- if (sliceAxis) {
99
- const columnKey =
100
- activeSlice === ALL_SLICE_KEY
101
- ? sliceAxis.primaryColumn
102
- : `${sliceAxis.primaryColumn}::${activeSlice}`
103
- const points: { score: number; releaseDate: string | null; modelName: string }[] = []
104
- for (const row of rows) {
105
- const raw = (row.values as Record<string, unknown> | undefined)?.[columnKey]
106
- const numeric = typeof raw === "number" ? raw : Number(raw)
107
- if (!Number.isFinite(numeric)) continue
108
- const modelInfo = (row as { model_info?: { name?: string; release_date?: string | null } }).model_info
109
- points.push({
110
- score: numeric,
111
- modelName: modelInfo?.name ?? "",
112
- releaseDate: modelInfo?.release_date ?? null,
113
- })
114
- }
115
- if (points.length < 3) return null
116
- const sliceLabel =
117
- activeSlice === ALL_SLICE_KEY
118
- ? "Overall"
119
- : sliceAxis.slices.find((s) => s.key === activeSlice)?.label ?? activeSlice
120
- return [
121
- {
122
- key: `${sliceAxis.primaryColumn}::${activeSlice}`,
123
- label: `${sliceAxis.primaryLabel} · ${sliceLabel}`,
124
- values: points.map((p) => p.score),
125
- unit: sliceAxis.unit,
126
- lowerIsBetter: sliceAxis.lowerIsBetter,
127
- points,
128
- },
129
- ]
130
- }
131
-
132
- // Non-slice path: one series per metric (e.g. agentharm's multi-metric
133
- // histogram). ScoreDistribution surfaces a metric chip picker.
134
- const metrics = summary.leaderboard_metrics ?? []
135
- const built = metrics
136
- .map((metric) => {
137
- const columnKey = metric.column_key ?? metric.metric_summary_id
138
- if (!columnKey) return null
139
- const points: { score: number; releaseDate: string | null; modelName: string }[] = []
140
- for (const row of rows) {
141
- const raw = (row.values as Record<string, unknown> | undefined)?.[columnKey]
142
- const numeric = typeof raw === "number" ? raw : Number(raw)
143
- if (!Number.isFinite(numeric)) continue
144
- const modelInfo = (row as { model_info?: { name?: string; release_date?: string | null } }).model_info
145
- points.push({
146
- score: numeric,
147
- modelName: modelInfo?.name ?? "",
148
- releaseDate: modelInfo?.release_date ?? null,
149
- })
150
- }
151
- if (points.length < 3) return null
152
- return {
153
- key: columnKey,
154
- label: getMetricChipLabel(metric),
155
- values: points.map((p) => p.score),
156
- unit: metric.unit ?? summary.metric_config.unit,
157
- lowerIsBetter: Boolean(metric.lower_is_better ?? summary.metric_config.lower_is_better),
158
- points,
159
- }
160
- })
161
- .filter((s): s is NonNullable<typeof s> => s !== null)
162
- return built.length > 0 ? built : null
163
- }, [summary, sliceAxis, activeSlice])
164
 
165
  if (error) {
166
  return (
 
4
  import { useParams, useSearchParams } from "next/navigation"
5
  import { ScoreDistribution } from "@/components/score-distribution"
6
  import { fetchEvalSummary } from "@/lib/dashboard-data-client"
7
+ import {
8
+ buildDistributionSeries,
9
+ buildDistributionSliceAxis,
10
+ } from "@/lib/distribution-series"
11
  import { routeIdFromSegments } from "@/lib/utils"
12
  import type { BenchmarkEvalSummary } from "@/lib/eval-processing"
13
 
 
56
 
57
  // Slice axis — present when the eval has multiple subtask-scope metrics
58
  // sharing a primary root metric (e.g. Global MMLU's 24 language splits).
59
+ const sliceAxis = useMemo(
60
+ () => (summary ? buildDistributionSliceAxis(summary) : null),
61
+ [summary],
62
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
  const ALL_SLICE_KEY = "__all__"
65
  const [activeSlice, setActiveSlice] = useState<string>(() => {
 
76
  }
77
  }, [activeSlice, sliceAxis])
78
 
79
+ const series = useMemo(
80
+ () => (summary ? buildDistributionSeries(summary, sliceAxis, activeSlice, ALL_SLICE_KEY) : null),
81
+ [summary, sliceAxis, activeSlice],
82
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
 
84
  if (error) {
85
  return (
components/quick-start.tsx CHANGED
@@ -2,6 +2,7 @@
2
 
3
  import { createContext, useContext, useEffect, useMemo, useState } from "react"
4
  import Link from "next/link"
 
5
  import {
6
  ArrowRight,
7
  BarChart3,
@@ -87,11 +88,17 @@ const SLIDES: Slide[] = [
87
 
88
  export function QuickStartProvider({ children }: { children: React.ReactNode }) {
89
  const [open, setOpen] = useState(false)
 
90
 
91
  // Auto-open on a visitor's first time, mirroring the audience-mode pattern.
92
  useEffect(() => {
93
- // Embedded views (iframes) never get the tour: storage partitioning means
94
- // the seen-flag can't persist there, so it would replay on every load.
 
 
 
 
 
95
  if (window.self !== window.top) return
96
  try {
97
  if (!window.localStorage.getItem(STORAGE_KEY)) {
@@ -100,7 +107,7 @@ export function QuickStartProvider({ children }: { children: React.ReactNode })
100
  } catch {
101
  // localStorage unavailable (e.g. privacy mode) — skip the tour silently.
102
  }
103
- }, [])
104
 
105
  const markSeen = () => {
106
  try {
 
2
 
3
  import { createContext, useContext, useEffect, useMemo, useState } from "react"
4
  import Link from "next/link"
5
+ import { usePathname } from "next/navigation"
6
  import {
7
  ArrowRight,
8
  BarChart3,
 
88
 
89
  export function QuickStartProvider({ children }: { children: React.ReactNode }) {
90
  const [open, setOpen] = useState(false)
91
+ const pathname = usePathname()
92
 
93
  // Auto-open on a visitor's first time, mirroring the audience-mode pattern.
94
  useEffect(() => {
95
+ // Embed surfaces never get the tour. Two independent guards:
96
+ // 1. Route-based anything under /embed/* is a self-contained card meant
97
+ // for iframing, so the tour must not appear even if the URL is opened
98
+ // directly as a top-level tab (e.g. a "preview" link).
99
+ // 2. Frame-based — when iframed, storage partitioning means the seen-flag
100
+ // can't persist, so the tour would replay on every load.
101
+ if (pathname?.startsWith("/embed")) return
102
  if (window.self !== window.top) return
103
  try {
104
  if (!window.localStorage.getItem(STORAGE_KEY)) {
 
107
  } catch {
108
  // localStorage unavailable (e.g. privacy mode) — skip the tour silently.
109
  }
110
+ }, [pathname])
111
 
112
  const markSeen = () => {
113
  try {
lib/distribution-series.ts ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { getMetricChipLabel } from "@/lib/metric-labels"
2
+ import type { BenchmarkEvalSummary } from "@/lib/eval-processing"
3
+
4
+ /**
5
+ * Series-building logic for the score-distribution embed
6
+ * (`app/embed/eval/distribution/[...id]/page.tsx`), extracted as pure
7
+ * functions so the chip-selection rules can be unit-tested against real
8
+ * eval-summary payloads without spinning up the React page.
9
+ */
10
+
11
+ export type DistributionPoint = {
12
+ score: number
13
+ releaseDate: string | null
14
+ modelName: string
15
+ }
16
+
17
+ export type DistributionSeries = {
18
+ key: string
19
+ label: string
20
+ values: number[]
21
+ unit?: string
22
+ lowerIsBetter: boolean
23
+ points: DistributionPoint[]
24
+ }
25
+
26
+ export type DistributionSliceAxis = {
27
+ primaryColumn: string
28
+ primaryLabel: string
29
+ unit?: string
30
+ lowerIsBetter: boolean
31
+ slices: Array<{ key: string; label: string }>
32
+ }
33
+
34
+ /**
35
+ * A slice axis exists only when the eval carries MORE THAN ONE distinct
36
+ * subtask slice sharing a root primary metric (e.g. Global MMLU's per-language
37
+ * splits). A lone self-slice (one subtask key that just echoes the eval) does
38
+ * NOT qualify — it falls through to the non-slice path where it is dropped as a
39
+ * redundant twin of the root metric.
40
+ */
41
+ export function buildDistributionSliceAxis(
42
+ summary: BenchmarkEvalSummary,
43
+ ): DistributionSliceAxis | null {
44
+ const metrics = summary.leaderboard_metrics ?? []
45
+ const primary = metrics.find((m) => m.scope !== "subtask")
46
+ if (!primary?.column_key) return null
47
+ const seen = new Map<string, string>()
48
+ for (const m of metrics) {
49
+ if (m.scope === "subtask" && m.subtask_key && !seen.has(m.subtask_key)) {
50
+ seen.set(m.subtask_key, m.subtask_name ?? m.subtask_key)
51
+ }
52
+ }
53
+ if (seen.size <= 1) return null
54
+ return {
55
+ primaryColumn: primary.column_key,
56
+ primaryLabel: getMetricChipLabel(primary),
57
+ unit: primary.unit ?? summary.metric_config.unit,
58
+ lowerIsBetter: Boolean(primary.lower_is_better ?? summary.metric_config.lower_is_better),
59
+ slices: Array.from(seen, ([key, label]) => ({ key, label })),
60
+ }
61
+ }
62
+
63
+ function pointsForColumn(
64
+ rows: BenchmarkEvalSummary["leaderboard_rows"],
65
+ columnKey: string,
66
+ ): DistributionPoint[] {
67
+ const points: DistributionPoint[] = []
68
+ for (const row of rows ?? []) {
69
+ const raw = (row.values as Record<string, unknown> | undefined)?.[columnKey]
70
+ const numeric = typeof raw === "number" ? raw : Number(raw)
71
+ if (!Number.isFinite(numeric)) continue
72
+ const modelInfo = (row as { model_info?: { name?: string; release_date?: string | null } }).model_info
73
+ points.push({
74
+ score: numeric,
75
+ modelName: modelInfo?.name ?? "",
76
+ releaseDate: modelInfo?.release_date ?? null,
77
+ })
78
+ }
79
+ return points
80
+ }
81
+
82
+ /**
83
+ * Build the score-distribution series for the embed. Returns null when there
84
+ * is nothing renderable (fewer than 3 data points everywhere).
85
+ */
86
+ export function buildDistributionSeries(
87
+ summary: BenchmarkEvalSummary,
88
+ sliceAxis: DistributionSliceAxis | null,
89
+ activeSlice: string,
90
+ allSliceKey: string,
91
+ ): DistributionSeries[] | null {
92
+ const rows = summary.leaderboard_rows ?? []
93
+
94
+ // Slice-axis path: render one series for the active slice (Overall or a
95
+ // specific subtask). Drives the SPLIT dropdown UX.
96
+ if (sliceAxis) {
97
+ const columnKey =
98
+ activeSlice === allSliceKey
99
+ ? sliceAxis.primaryColumn
100
+ : `${sliceAxis.primaryColumn}::${activeSlice}`
101
+ const points = pointsForColumn(rows, columnKey)
102
+ if (points.length < 3) return null
103
+ const sliceLabel =
104
+ activeSlice === allSliceKey
105
+ ? "Overall"
106
+ : sliceAxis.slices.find((s) => s.key === activeSlice)?.label ?? activeSlice
107
+ return [
108
+ {
109
+ key: `${sliceAxis.primaryColumn}::${activeSlice}`,
110
+ label: `${sliceAxis.primaryLabel} · ${sliceLabel}`,
111
+ values: points.map((p) => p.score),
112
+ unit: sliceAxis.unit,
113
+ lowerIsBetter: sliceAxis.lowerIsBetter,
114
+ points,
115
+ },
116
+ ]
117
+ }
118
+
119
+ // Non-slice path: one series per ROOT metric (e.g. agentharm's multi-metric
120
+ // histogram). ScoreDistribution surfaces a metric chip picker. We mirror the
121
+ // full eval page (eval-detail.tsx) which builds chips only from root-scope
122
+ // metrics: the producer also emits a redundant self-slice subtask for some
123
+ // evals — a metric whose subtask_key is just the slugified eval (e.g.
124
+ // vals-ai/math-500 carries both root `accuracy` and subtask
125
+ // `accuracy::vals ai math500`, same metric_summary_id and label). Multi-slice
126
+ // evals (distinct subtask keys) are handled by the sliceAxis path above, so
127
+ // the only subtasks reaching here are these redundant twins; rendering them
128
+ // would duplicate the chip (two identical "Accuracy" buttons). Fall back to
129
+ // the full set only if an eval somehow carries no root metric, so a
130
+ // subtask-only eval still renders rather than going blank.
131
+ const allMetrics = summary.leaderboard_metrics ?? []
132
+ const rootMetrics = allMetrics.filter((m) => m.scope !== "subtask")
133
+ const metrics = rootMetrics.length > 0 ? rootMetrics : allMetrics
134
+ const built = metrics
135
+ .map((metric) => {
136
+ const columnKey = metric.column_key ?? metric.metric_summary_id
137
+ if (!columnKey) return null
138
+ const points = pointsForColumn(rows, columnKey)
139
+ if (points.length < 3) return null
140
+ return {
141
+ key: columnKey,
142
+ label: getMetricChipLabel(metric),
143
+ values: points.map((p) => p.score),
144
+ unit: metric.unit ?? summary.metric_config.unit,
145
+ lowerIsBetter: Boolean(metric.lower_is_better ?? summary.metric_config.lower_is_better),
146
+ points,
147
+ }
148
+ })
149
+ .filter((s): s is NonNullable<typeof s> => s !== null)
150
+ return built.length > 0 ? built : null
151
+ }
tests/distribution-series.test.ts ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { describe, expect, it } from "vitest"
2
+
3
+ import {
4
+ buildDistributionSeries,
5
+ buildDistributionSliceAxis,
6
+ } from "@/lib/distribution-series"
7
+ import type { BenchmarkEvalSummary } from "@/lib/eval-processing"
8
+ import fixtures from "./fixtures/distribution-eval-summaries.json"
9
+
10
+ // Fixtures are trimmed real payloads captured from the live
11
+ // /api/eval-summary route (evalcards.evalevalai.com), so these assertions
12
+ // exercise the actual deployed data shape, not a hand-built mock.
13
+ const ALL = "__all__"
14
+ const summaryOf = (k: keyof typeof fixtures) =>
15
+ fixtures[k] as unknown as BenchmarkEvalSummary
16
+
17
+ describe("buildDistributionSeries — metric chip selection", () => {
18
+ it("vals-ai/math-500: collapses the redundant self-slice subtask to a single Accuracy chip", () => {
19
+ const summary = summaryOf("math500")
20
+ // The live payload carries two metrics with the same metric_summary_id:
21
+ // root `accuracy` + subtask `accuracy::vals ai math500`. The subtask is a
22
+ // self-slice (its key is just the slugified eval), so it must NOT become a
23
+ // second chip.
24
+ expect((summary.leaderboard_metrics ?? []).map((m) => m.scope).sort()).toEqual([
25
+ "root",
26
+ "subtask",
27
+ ])
28
+ const sliceAxis = buildDistributionSliceAxis(summary)
29
+ // A lone self-slice is not a real slice axis.
30
+ expect(sliceAxis).toBeNull()
31
+ const series = buildDistributionSeries(summary, sliceAxis, ALL, ALL)
32
+ expect(series).not.toBeNull()
33
+ expect(series!.length).toBe(1)
34
+ expect(series![0].label).toBe("Accuracy")
35
+ })
36
+
37
+ it("agentharm: keeps every distinct root metric (multi-metric embed unbroken)", () => {
38
+ const summary = summaryOf("agentharm")
39
+ const rootCount = (summary.leaderboard_metrics ?? []).filter(
40
+ (m) => m.scope !== "subtask",
41
+ ).length
42
+ expect(rootCount).toBeGreaterThan(1)
43
+ const sliceAxis = buildDistributionSliceAxis(summary)
44
+ const series = buildDistributionSeries(summary, sliceAxis, ALL, ALL)
45
+ expect(series).not.toBeNull()
46
+ expect(series!.length).toBe(rootCount)
47
+ // No two chips share a label (the duplication the fix targets).
48
+ const labels = series!.map((s) => s.label)
49
+ expect(new Set(labels).size).toBe(labels.length)
50
+ })
51
+ })
tests/fixtures/distribution-eval-summaries.json ADDED
@@ -0,0 +1,282 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "math500": {
3
+ "evaluation_name": "MATH-500",
4
+ "metric_config": {
5
+ "evaluation_description": "Accuracy",
6
+ "lower_is_better": false,
7
+ "score_type": null,
8
+ "min_score": 0,
9
+ "max_score": 1,
10
+ "unit": "percent"
11
+ },
12
+ "leaderboard_metrics": [
13
+ {
14
+ "column_key": "accuracy",
15
+ "metric_summary_id": "math-500%3Aaccuracy",
16
+ "metric_id": "accuracy",
17
+ "metric_name": "Accuracy",
18
+ "display_name": "Accuracy",
19
+ "canonical_display_name": "Accuracy",
20
+ "lower_is_better": false,
21
+ "unit": "percent",
22
+ "scope": "root",
23
+ "subtask_key": null,
24
+ "subtask_name": null
25
+ },
26
+ {
27
+ "column_key": "accuracy::vals ai math500",
28
+ "metric_summary_id": "math-500%3Aaccuracy",
29
+ "metric_id": "accuracy",
30
+ "metric_name": "Accuracy",
31
+ "display_name": "Accuracy",
32
+ "canonical_display_name": "Accuracy",
33
+ "lower_is_better": false,
34
+ "unit": "percent",
35
+ "scope": "subtask",
36
+ "subtask_key": "vals ai math500",
37
+ "subtask_name": "vals ai math500"
38
+ }
39
+ ],
40
+ "leaderboard_rows": [
41
+ {
42
+ "values": {
43
+ "accuracy": 89,
44
+ "accuracy::vals ai math500": 89
45
+ },
46
+ "model_info": {
47
+ "name": "MiniMax M2.1",
48
+ "release_date": "2025-12-20"
49
+ }
50
+ },
51
+ {
52
+ "values": {
53
+ "accuracy": 54.8,
54
+ "accuracy::vals ai math500": 54.8
55
+ },
56
+ "model_info": {
57
+ "name": "Jamba Large 1.6",
58
+ "release_date": null
59
+ }
60
+ },
61
+ {
62
+ "values": {
63
+ "accuracy": 25.4,
64
+ "accuracy::vals ai math500": 25.4
65
+ },
66
+ "model_info": {
67
+ "name": "Jamba Mini 1.6",
68
+ "release_date": null
69
+ }
70
+ },
71
+ {
72
+ "values": {
73
+ "accuracy": 76.8
74
+ },
75
+ "model_info": {
76
+ "name": "Claude 3.7 Sonnet",
77
+ "release_date": "2025-02-19"
78
+ }
79
+ },
80
+ {
81
+ "values": {
82
+ "accuracy": 64.2
83
+ },
84
+ "model_info": {
85
+ "name": "Claude 3 5 Haiku",
86
+ "release_date": "2024-10-22"
87
+ }
88
+ },
89
+ {
90
+ "values": {
91
+ "accuracy": 91.6,
92
+ "accuracy::vals ai math500": 91.6
93
+ },
94
+ "model_info": {
95
+ "name": "Claude 3 7 Sonnet 20250219 Thinking",
96
+ "release_date": null
97
+ }
98
+ },
99
+ {
100
+ "values": {
101
+ "accuracy": 72.4
102
+ },
103
+ "model_info": {
104
+ "name": "Claude 3.5 Sonnet",
105
+ "release_date": "2024-10-22"
106
+ }
107
+ },
108
+ {
109
+ "values": {
110
+ "accuracy": 90.4
111
+ },
112
+ "model_info": {
113
+ "name": "Claude Opus 4",
114
+ "release_date": "2025-05-22"
115
+ }
116
+ }
117
+ ]
118
+ },
119
+ "agentharm": {
120
+ "evaluation_name": "AgentHarm",
121
+ "metric_config": {
122
+ "evaluation_description": null,
123
+ "lower_is_better": false,
124
+ "score_type": null,
125
+ "min_score": null,
126
+ "max_score": null,
127
+ "unit": "proportion"
128
+ },
129
+ "leaderboard_metrics": [
130
+ {
131
+ "column_key": "inspect_evals/avg_full_score",
132
+ "metric_summary_id": "agentharm%3Ainspect_evals%2Favg_full_score",
133
+ "metric_id": "inspect_evals/avg_full_score",
134
+ "metric_name": null,
135
+ "display_name": null,
136
+ "canonical_display_name": null,
137
+ "lower_is_better": false,
138
+ "unit": "proportion",
139
+ "scope": "root",
140
+ "subtask_key": null,
141
+ "subtask_name": null
142
+ },
143
+ {
144
+ "column_key": "inspect_evals/avg_refusals",
145
+ "metric_summary_id": "agentharm%3Ainspect_evals%2Favg_refusals",
146
+ "metric_id": "inspect_evals/avg_refusals",
147
+ "metric_name": null,
148
+ "display_name": null,
149
+ "canonical_display_name": null,
150
+ "lower_is_better": false,
151
+ "unit": "proportion",
152
+ "scope": "root",
153
+ "subtask_key": null,
154
+ "subtask_name": null
155
+ },
156
+ {
157
+ "column_key": "inspect_evals/avg_score",
158
+ "metric_summary_id": "agentharm%3Ainspect_evals%2Favg_score",
159
+ "metric_id": "inspect_evals/avg_score",
160
+ "metric_name": null,
161
+ "display_name": null,
162
+ "canonical_display_name": null,
163
+ "lower_is_better": false,
164
+ "unit": "proportion",
165
+ "scope": "root",
166
+ "subtask_key": null,
167
+ "subtask_name": null
168
+ },
169
+ {
170
+ "column_key": "inspect_evals/avg_score_non_refusals",
171
+ "metric_summary_id": "agentharm%3Ainspect_evals%2Favg_score_non_refusals",
172
+ "metric_id": "inspect_evals/avg_score_non_refusals",
173
+ "metric_name": null,
174
+ "display_name": null,
175
+ "canonical_display_name": null,
176
+ "lower_is_better": false,
177
+ "unit": "proportion",
178
+ "scope": "root",
179
+ "subtask_key": null,
180
+ "subtask_name": null
181
+ }
182
+ ],
183
+ "leaderboard_rows": [
184
+ {
185
+ "values": {
186
+ "inspect_evals/avg_full_score": 0.23863636363636365,
187
+ "inspect_evals/avg_refusals": 0.6534090909090909,
188
+ "inspect_evals/avg_score": 0.339788715072806,
189
+ "inspect_evals/avg_score_non_refusals": 0.8732825917252146
190
+ },
191
+ "model_info": {
192
+ "name": "Claude 3.7 Sonnet",
193
+ "release_date": "2025-02-19"
194
+ }
195
+ },
196
+ {
197
+ "values": {
198
+ "inspect_evals/avg_full_score": 0.375,
199
+ "inspect_evals/avg_refusals": 0.022727272727272728,
200
+ "inspect_evals/avg_score": 0.7212594696969696,
201
+ "inspect_evals/avg_score_non_refusals": 0.7380329457364341
202
+ },
203
+ "model_info": {
204
+ "name": "DeepSeek",
205
+ "release_date": "2025-12-01"
206
+ }
207
+ },
208
+ {
209
+ "values": {
210
+ "inspect_evals/avg_full_score": 0.29545454545454547,
211
+ "inspect_evals/avg_refusals": 0.3352272727272727,
212
+ "inspect_evals/avg_score": 0.47781385281385286,
213
+ "inspect_evals/avg_score_non_refusals": 0.7170533170533171
214
+ },
215
+ "model_info": {
216
+ "name": "Gemini 2.0 Flash",
217
+ "release_date": "2024-12-11"
218
+ }
219
+ },
220
+ {
221
+ "values": {
222
+ "inspect_evals/avg_full_score": 0.125,
223
+ "inspect_evals/avg_refusals": 0.38636363636363635,
224
+ "inspect_evals/avg_score": 0.3750411993309721,
225
+ "inspect_evals/avg_score_non_refusals": 0.582783189033189
226
+ },
227
+ "model_info": {
228
+ "name": "Llama 3.3",
229
+ "release_date": null
230
+ }
231
+ },
232
+ {
233
+ "values": {
234
+ "inspect_evals/avg_full_score": 0.4034090909090909,
235
+ "inspect_evals/avg_refusals": 0.045454545454545456,
236
+ "inspect_evals/avg_score": 0.6345533254624164,
237
+ "inspect_evals/avg_score_non_refusals": 0.6624317151102865
238
+ },
239
+ "model_info": {
240
+ "name": "Mistral Large",
241
+ "release_date": "2024-11-01"
242
+ }
243
+ },
244
+ {
245
+ "values": {
246
+ "inspect_evals/avg_full_score": 0.3125,
247
+ "inspect_evals/avg_refusals": 0.4772727272727273,
248
+ "inspect_evals/avg_score": 0.4302323150334514,
249
+ "inspect_evals/avg_score_non_refusals": 0.8007963956333523
250
+ },
251
+ "model_info": {
252
+ "name": "Mistral Small",
253
+ "release_date": "2026-03-16"
254
+ }
255
+ },
256
+ {
257
+ "values": {
258
+ "inspect_evals/avg_full_score": 0.35795454545454547,
259
+ "inspect_evals/avg_refusals": 0.42613636363636365,
260
+ "inspect_evals/avg_score": 0.4916531385281386,
261
+ "inspect_evals/avg_score_non_refusals": 0.8254125412541253
262
+ },
263
+ "model_info": {
264
+ "name": "GPT-4o",
265
+ "release_date": "2024-05-13"
266
+ }
267
+ },
268
+ {
269
+ "values": {
270
+ "inspect_evals/avg_full_score": 0.32386363636363635,
271
+ "inspect_evals/avg_refusals": 0.19318181818181818,
272
+ "inspect_evals/avg_score": 0.5942812868949233,
273
+ "inspect_evals/avg_score_non_refusals": 0.7107523931467593
274
+ },
275
+ "model_info": {
276
+ "name": "GPT-4o mini",
277
+ "release_date": "2024-07-18"
278
+ }
279
+ }
280
+ ]
281
+ }
282
+ }