j-chim Claude Fable 5 commited on
Commit
b597ca9
·
1 Parent(s): 68d591a

Assisted runs: shown, labeled, and unranked by default

Browse files

Protocol-varied collections (first: the UK AISI inference-scaling study)
deliver several results per model, including oracle answer-feedback runs.
The backend never serves those a rank, but the single-metric table (and
the merged page, which renders through it) re-rank client-side by score —
which would have crowned an assisted 1.00 as #1.

- Plumb collection_id / protocol_condition through the eval-summary and
merged queries, probe-gated so pre-collections snapshots keep NULL
aliases (no binder errors, feature self-disabling).
- Assisted rows keep their place in the table but take no rank (em dash +
"assisted" chip); an explicit banner control re-includes them in
ranking (default off).
- Study-protocol caveat banner on pages carrying protocol rows: expanded
budgets are not comparable to standard published results.
- Distribution/Frontier plotbox stats exclude assisted rows — a "best"
that needed the answer oracle is not a frontier.

Pages without protocol data render identically (single activation gate;
NULL columns on old snapshots).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

components/eval-detail.tsx CHANGED
@@ -53,6 +53,7 @@ import {
53
  } from "lucide-react"
54
  import type { BenchmarkCard, SourceData } from "@/lib/benchmark-schema"
55
  import { tagLabel } from "@/lib/benchmark-schema"
 
56
  import type { BenchmarkEvalSummary, ModelResultForBenchmark } from "@/lib/eval-processing"
57
  import { isRecognizedEvaluator } from "@/lib/evaluators"
58
  import { useEvaluatorSlug } from "@/components/org-metadata-provider"
@@ -851,6 +852,21 @@ export function EvalDetail({
851
 
852
  const [showUnknownSize, setShowUnknownSize] = useState(true)
853
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
854
  const hasParameterData = useMemo(
855
  () => sortedResults.some((result) => getParamsBillions(result) != null),
856
  [sortedResults]
@@ -871,21 +887,39 @@ export function EvalDetail({
871
  const leaderboardRows = useMemo<LeaderboardRow[]>(() => {
872
  let currentRank = 0
873
  let previousScore: number | null = null
 
874
 
875
  return filteredResults.map((modelResult, index) => {
876
- if (previousScore === null || Math.abs(modelResult.score - previousScore) > 1e-9) {
877
- currentRank = index + 1
878
- previousScore = modelResult.score
 
 
 
 
 
 
 
 
 
879
  }
880
 
881
  return {
882
  key: `${modelResult.model_info.id}-${index}`,
883
- rank: currentRank,
884
  modelResult,
885
  normalizedScore: normalizeScore(modelResult.score),
886
  }
887
  })
888
- }, [filteredResults])
 
 
 
 
 
 
 
 
889
 
890
  // Optional user-driven sort. `default` keeps the score-ordered rows
891
  // the ranker already produced. The rank label is always by score
@@ -1560,7 +1594,43 @@ export function EvalDetail({
1560
 
1561
  {/* Score distribution — paper-themed mean/median/quartile summary,
1562
  with an optional Frontier toggle when models carry release dates. */}
1563
- {leaderboardRows.length >= 3 && (
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1564
  <div className="mb-4">
1565
  <div className="flex justify-end mb-2">
1566
  <EmbedButton
@@ -1590,10 +1660,10 @@ export function EvalDetail({
1590
  series={[{
1591
  key: "primary",
1592
  label: lb.metric_config.unit ?? "Score",
1593
- values: leaderboardRows.map((r) => r.modelResult.score),
1594
  unit: lb.metric_config.unit,
1595
  lowerIsBetter: lb.metric_config.lower_is_better,
1596
- points: leaderboardRows.map((r) => ({
1597
  score: r.modelResult.score,
1598
  releaseDate: r.modelResult.model_info.release_date,
1599
  modelName: r.modelResult.model_info.name,
@@ -1866,6 +1936,7 @@ export function EvalDetail({
1866
  : null
1867
  const isTopRank = rank === 1
1868
  const rankColor = rank === 1 ? "var(--accent)" : "var(--fg-muted)"
 
1869
 
1870
  return (
1871
  <Fragment key={key}>
@@ -1882,10 +1953,13 @@ export function EvalDetail({
1882
  style={{
1883
  fontSize: 14,
1884
  fontWeight: isTopRank ? 600 : 500,
1885
- color: rankColor,
1886
  }}
 
 
 
1887
  >
1888
- #{rank}
1889
  </span>
1890
  </td>
1891
 
@@ -1911,6 +1985,22 @@ export function EvalDetail({
1911
  >
1912
  {modelResult.model_info.name}
1913
  </Link>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1914
  {familyLabel && (
1915
  <div
1916
  className="mt-0.5 font-mono uppercase truncate"
 
53
  } from "lucide-react"
54
  import type { BenchmarkCard, SourceData } from "@/lib/benchmark-schema"
55
  import { tagLabel } from "@/lib/benchmark-schema"
56
+ import { isAssistedResult } from "@/lib/eval-processing"
57
  import type { BenchmarkEvalSummary, ModelResultForBenchmark } from "@/lib/eval-processing"
58
  import { isRecognizedEvaluator } from "@/lib/evaluators"
59
  import { useEvaluatorSlug } from "@/components/org-metadata-provider"
 
852
 
853
  const [showUnknownSize, setShowUnknownSize] = useState(true)
854
 
855
+ // Protocol-varied collections (e.g. the AISI inference-scaling study):
856
+ // rows may carry a protocol_condition. Assisted (answer-feedback) runs
857
+ // are shown but UNRANKED by default — mirroring the backend, which never
858
+ // serves them a rank — with an explicit opt-in that re-includes them in
859
+ // the client-side ranking.
860
+ const hasProtocolRows = useMemo(
861
+ () => lb.model_results.some((r) => r.protocol_condition != null),
862
+ [lb.model_results]
863
+ )
864
+ const hasAssistedRows = useMemo(
865
+ () => lb.model_results.some((r) => isAssistedResult(r.protocol_condition)),
866
+ [lb.model_results]
867
+ )
868
+ const [includeAssistedInRanking, setIncludeAssistedInRanking] = useState(false)
869
+
870
  const hasParameterData = useMemo(
871
  () => sortedResults.some((result) => getParamsBillions(result) != null),
872
  [sortedResults]
 
887
  const leaderboardRows = useMemo<LeaderboardRow[]>(() => {
888
  let currentRank = 0
889
  let previousScore: number | null = null
890
+ let rankedCount = 0
891
 
892
  return filteredResults.map((modelResult, index) => {
893
+ // Assisted runs are visible but take no rank (rank 0 sentinel)
894
+ // unless the reader explicitly re-includes them.
895
+ const unranked =
896
+ !includeAssistedInRanking && isAssistedResult(modelResult.protocol_condition)
897
+ let rank = 0
898
+ if (!unranked) {
899
+ rankedCount += 1
900
+ if (previousScore === null || Math.abs(modelResult.score - previousScore) > 1e-9) {
901
+ currentRank = rankedCount
902
+ previousScore = modelResult.score
903
+ }
904
+ rank = currentRank
905
  }
906
 
907
  return {
908
  key: `${modelResult.model_info.id}-${index}`,
909
+ rank,
910
  modelResult,
911
  normalizedScore: normalizeScore(modelResult.score),
912
  }
913
  })
914
+ }, [filteredResults, includeAssistedInRanking])
915
+
916
+ // Plotbox input: assisted runs stay out of the distribution stats and
917
+ // the frontier cumulative-best regardless of the ranking toggle — a
918
+ // "best" that needed the answer oracle is not a frontier.
919
+ const unassistedLeaderboardRows = useMemo(
920
+ () => leaderboardRows.filter((r) => !isAssistedResult(r.modelResult.protocol_condition)),
921
+ [leaderboardRows]
922
+ )
923
 
924
  // Optional user-driven sort. `default` keeps the score-ordered rows
925
  // the ranker already produced. The rank label is always by score
 
1594
 
1595
  {/* Score distribution — paper-themed mean/median/quartile summary,
1596
  with an optional Frontier toggle when models carry release dates. */}
1597
+ {hasProtocolRows && (
1598
+ <div
1599
+ className="mb-4 flex items-start gap-3 px-4 py-3"
1600
+ style={{
1601
+ border: "1px solid var(--border-soft)",
1602
+ borderLeft: "2px solid var(--fg-muted)",
1603
+ background: "var(--bg-warm)",
1604
+ color: "var(--fg)",
1605
+ }}
1606
+ >
1607
+ <div className="text-[13px] leading-relaxed">
1608
+ <span style={{ fontWeight: 600 }}>Study-specific protocol.</span>{" "}
1609
+ These results were run under expanded, study-specific inference
1610
+ budgets and are not directly comparable to standard published
1611
+ benchmark results.
1612
+ {hasAssistedRows && (
1613
+ <>
1614
+ {" "}Assisted runs — where the model is told when its answer
1615
+ is correct — are labeled and excluded from ranking
1616
+ {includeAssistedInRanking ? " (currently included)" : ""}.{" "}
1617
+ <button
1618
+ type="button"
1619
+ onClick={() => setIncludeAssistedInRanking((v) => !v)}
1620
+ className="underline underline-offset-2 hover:text-[color:var(--accent)]"
1621
+ style={{ color: "var(--fg-muted)" }}
1622
+ >
1623
+ {includeAssistedInRanking
1624
+ ? "Exclude assisted runs from ranking"
1625
+ : "Include assisted runs in ranking"}
1626
+ </button>
1627
+ </>
1628
+ )}
1629
+ </div>
1630
+ </div>
1631
+ )}
1632
+
1633
+ {unassistedLeaderboardRows.length >= 3 && (
1634
  <div className="mb-4">
1635
  <div className="flex justify-end mb-2">
1636
  <EmbedButton
 
1660
  series={[{
1661
  key: "primary",
1662
  label: lb.metric_config.unit ?? "Score",
1663
+ values: unassistedLeaderboardRows.map((r) => r.modelResult.score),
1664
  unit: lb.metric_config.unit,
1665
  lowerIsBetter: lb.metric_config.lower_is_better,
1666
+ points: unassistedLeaderboardRows.map((r) => ({
1667
  score: r.modelResult.score,
1668
  releaseDate: r.modelResult.model_info.release_date,
1669
  modelName: r.modelResult.model_info.name,
 
1936
  : null
1937
  const isTopRank = rank === 1
1938
  const rankColor = rank === 1 ? "var(--accent)" : "var(--fg-muted)"
1939
+ const isAssisted = isAssistedResult(modelResult.protocol_condition)
1940
 
1941
  return (
1942
  <Fragment key={key}>
 
1953
  style={{
1954
  fontSize: 14,
1955
  fontWeight: isTopRank ? 600 : 500,
1956
+ color: rank === 0 ? "var(--fg-subtle)" : rankColor,
1957
  }}
1958
+ title={rank === 0
1959
+ ? "Assisted run (answer feedback) — shown, not ranked"
1960
+ : undefined}
1961
  >
1962
+ {rank === 0 ? "—" : `#${rank}`}
1963
  </span>
1964
  </td>
1965
 
 
1985
  >
1986
  {modelResult.model_info.name}
1987
  </Link>
1988
+ {isAssisted && (
1989
+ <span
1990
+ className="ml-1.5 inline-block align-middle font-mono uppercase"
1991
+ style={{
1992
+ fontSize: 9,
1993
+ letterSpacing: "0.08em",
1994
+ color: "var(--fg-muted)",
1995
+ border: "1px solid var(--border-soft)",
1996
+ borderRadius: 3,
1997
+ padding: "1px 4px",
1998
+ }}
1999
+ title="The model was told when its answer was correct (oracle answer feedback)."
2000
+ >
2001
+ assisted
2002
+ </span>
2003
+ )}
2004
  {familyLabel && (
2005
  <div
2006
  className="mt-0.5 font-mono uppercase truncate"
lib/eval-processing.ts CHANGED
@@ -23,6 +23,23 @@ import type { EvalcardsAnnotations, RowAnnotations, SignalSummaries } from './ba
23
  export type { BenchmarkCard }
24
  export type { ModelEvaluationSummary }
25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  export interface ModelResultForBenchmark {
27
  model_info: ModelInfo
28
  model_route_id?: string
@@ -273,6 +290,11 @@ export interface MergedObservationRow {
273
  source_metadata: SourceMetadata
274
  generation_config?: GenerationConfig
275
  is_verified_evaluator?: boolean
 
 
 
 
 
276
  }
277
 
278
  export interface MergedBenchmarkSummary {
 
23
  export type { BenchmarkCard }
24
  export type { ModelEvaluationSummary }
25
 
26
+
27
+ /**
28
+ * True when a result ran under the oracle answer-feedback arm of a
29
+ * protocol-varied collection (the model was told when its answer was
30
+ * correct). Assisted rows are shown but excluded from client-side ranking
31
+ * by default — mirroring the backend, which never serves them a rank.
32
+ */
33
+ export function isAssistedResult(protocolCondition: string | null | undefined): boolean {
34
+ if (!protocolCondition) return false
35
+ try {
36
+ const parsed = JSON.parse(protocolCondition) as { feedback?: unknown } | null
37
+ return parsed != null && typeof parsed === "object" && parsed.feedback === "answer_feedback"
38
+ } catch {
39
+ return false
40
+ }
41
+ }
42
+
43
  export interface ModelResultForBenchmark {
44
  model_info: ModelInfo
45
  model_route_id?: string
 
290
  source_metadata: SourceMetadata
291
  generation_config?: GenerationConfig
292
  is_verified_evaluator?: boolean
293
+ /** Collections: submission-channel id of the observation's source row. */
294
+ collection_id?: string
295
+ /** Protocol point (canonical sorted-key JSON) for protocol-varied
296
+ * collections; absent/null for ordinary observations. */
297
+ protocol_condition?: string | null
298
  }
299
 
300
  export interface MergedBenchmarkSummary {
lib/merged-adapter.ts CHANGED
@@ -99,6 +99,8 @@ export function mergedSummaryToEvalSummary(merged: MergedBenchmarkSummary): Benc
99
  source_data: sourceData,
100
  merged_source_slug: row.composite_slug,
101
  is_verified_evaluator: row.is_verified_evaluator,
 
 
102
  result: {
103
  evaluation_name: metricDisplayName,
104
  display_name: metricDisplayName,
 
99
  source_data: sourceData,
100
  merged_source_slug: row.composite_slug,
101
  is_verified_evaluator: row.is_verified_evaluator,
102
+ collection_id: row.collection_id,
103
+ protocol_condition: row.protocol_condition,
104
  result: {
105
  evaluation_name: metricDisplayName,
106
  display_name: metricDisplayName,
lib/view-data.ts CHANGED
@@ -641,6 +641,8 @@ function reshapeCellToModelResult(row: Row): ModelResultForBenchmark {
641
  source_data: sourceDataFromRow(row),
642
  source_record_url: optionalString(row.source_record_url),
643
  eee_record_url: optionalString(row.eee_record_url),
 
 
644
  aggregate_components: asArray<NonNullable<ModelResultForBenchmark["aggregate_components"]>[number]>(
645
  aggregateComponents
646
  ),
@@ -780,6 +782,32 @@ async function evalsViewHasParentDisplayName(): Promise<boolean> {
780
  return evalsViewParentDisplayNameCache
781
  }
782
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
783
  async function getModelEvaluationRows(modelKey: string): Promise<Row[]> {
784
  const hasParentDisplayName = await evalsViewHasParentDisplayName()
785
  // model_key is the producer's addressable identifier — non-null for both
@@ -964,8 +992,9 @@ export async function getEvalSummaryById(evalId: string): Promise<BenchmarkEvalS
964
  const evalRow = evalRows[0]
965
  if (!evalRow) return null
966
 
 
967
  let cellRows = await readRows<Row>(
968
- `SELECT ${EVAL_CELL_JOIN_COLUMNS}
969
  FROM eval_results_view r
970
  LEFT JOIN evals_view e ON r.evaluation_id = e.evaluation_id
971
  WHERE r.evaluation_id = ?
@@ -978,7 +1007,7 @@ export async function getEvalSummaryById(evalId: string): Promise<BenchmarkEvalS
978
 
979
  if (cellRows.length === 0) {
980
  cellRows = await readRows<Row>(
981
- `SELECT ${EVAL_CELL_JOIN_COLUMNS}
982
  FROM eval_results_view r
983
  LEFT JOIN evals_view e ON r.evaluation_id = e.evaluation_id
984
  WHERE r.evaluation_id = ?
@@ -1170,6 +1199,13 @@ const MERGED_ROW_COLUMNS = `
1170
  CAST(to_json(slices) AS VARCHAR) AS slices
1171
  `
1172
 
 
 
 
 
 
 
 
1173
  const MERGED_RESULT_COLUMNS = `
1174
  r.evaluation_id,
1175
  r.benchmark_id,
@@ -1206,6 +1242,8 @@ function mergedObservationFromRow(row: Row): MergedObservationRow {
1206
  generation_config: (generationConfig ?? undefined) as GenerationConfig | undefined,
1207
  is_verified_evaluator:
1208
  row.is_verified_evaluator == null ? undefined : Boolean(row.is_verified_evaluator),
 
 
1209
  }
1210
  }
1211
 
@@ -1277,7 +1315,7 @@ export async function getMergedBenchmarkSummary(
1277
  if (grain === "benchmark" || selectedSliceId) {
1278
  const targetBenchmarkId = grain === "slice" ? selectedSliceId : asString(row.benchmark_id)
1279
  resultRows = await readRows<Row>(
1280
- `SELECT ${MERGED_RESULT_COLUMNS}
1281
  FROM eval_results_view r
1282
  WHERE r.benchmark_id = ?
1283
  AND r.metric_id_effective = ?
 
641
  source_data: sourceDataFromRow(row),
642
  source_record_url: optionalString(row.source_record_url),
643
  eee_record_url: optionalString(row.eee_record_url),
644
+ collection_id: optionalString(row.collection_id),
645
+ protocol_condition: optionalString(row.protocol_condition) ?? undefined,
646
  aggregate_components: asArray<NonNullable<ModelResultForBenchmark["aggregate_components"]>[number]>(
647
  aggregateComponents
648
  ),
 
782
  return evalsViewParentDisplayNameCache
783
  }
784
 
785
+ // Probe (once per process) whether the loaded snapshot's eval_results_view
786
+ // carries the additive collections columns (collection_id /
787
+ // protocol_condition), so projections degrade to NULL aliases on older
788
+ // snapshots instead of binder-erroring the query. Same lifecycle as the
789
+ // parent_display_name probe above.
790
+ let ervCollectionColumnsCache: boolean | undefined
791
+ async function evalResultsViewHasCollectionColumns(): Promise<boolean> {
792
+ if (ervCollectionColumnsCache === undefined) {
793
+ try {
794
+ const columns = await readRows<{ column_name: string }>("DESCRIBE eval_results_view")
795
+ const names = new Set(columns.map((column) => column.column_name))
796
+ ervCollectionColumnsCache = names.has("collection_id") && names.has("protocol_condition")
797
+ } catch {
798
+ return false
799
+ }
800
+ }
801
+ return ervCollectionColumnsCache
802
+ }
803
+
804
+ function evalCellJoinColumns(hasCollections: boolean) {
805
+ return `${EVAL_CELL_JOIN_COLUMNS},
806
+ ${hasCollections
807
+ ? "r.collection_id, r.protocol_condition"
808
+ : "CAST(NULL AS VARCHAR) AS collection_id, CAST(NULL AS VARCHAR) AS protocol_condition"}`
809
+ }
810
+
811
  async function getModelEvaluationRows(modelKey: string): Promise<Row[]> {
812
  const hasParentDisplayName = await evalsViewHasParentDisplayName()
813
  // model_key is the producer's addressable identifier — non-null for both
 
992
  const evalRow = evalRows[0]
993
  if (!evalRow) return null
994
 
995
+ const hasCollectionColumns = await evalResultsViewHasCollectionColumns()
996
  let cellRows = await readRows<Row>(
997
+ `SELECT ${evalCellJoinColumns(hasCollectionColumns)}
998
  FROM eval_results_view r
999
  LEFT JOIN evals_view e ON r.evaluation_id = e.evaluation_id
1000
  WHERE r.evaluation_id = ?
 
1007
 
1008
  if (cellRows.length === 0) {
1009
  cellRows = await readRows<Row>(
1010
+ `SELECT ${evalCellJoinColumns(hasCollectionColumns)}
1011
  FROM eval_results_view r
1012
  LEFT JOIN evals_view e ON r.evaluation_id = e.evaluation_id
1013
  WHERE r.evaluation_id = ?
 
1199
  CAST(to_json(slices) AS VARCHAR) AS slices
1200
  `
1201
 
1202
+ function mergedResultColumns(hasCollections: boolean) {
1203
+ return `${MERGED_RESULT_COLUMNS},
1204
+ ${hasCollections
1205
+ ? "r.collection_id, r.protocol_condition"
1206
+ : "CAST(NULL AS VARCHAR) AS collection_id, CAST(NULL AS VARCHAR) AS protocol_condition"}`
1207
+ }
1208
+
1209
  const MERGED_RESULT_COLUMNS = `
1210
  r.evaluation_id,
1211
  r.benchmark_id,
 
1242
  generation_config: (generationConfig ?? undefined) as GenerationConfig | undefined,
1243
  is_verified_evaluator:
1244
  row.is_verified_evaluator == null ? undefined : Boolean(row.is_verified_evaluator),
1245
+ collection_id: optionalString(row.collection_id),
1246
+ protocol_condition: optionalString(row.protocol_condition) ?? undefined,
1247
  }
1248
  }
1249
 
 
1315
  if (grain === "benchmark" || selectedSliceId) {
1316
  const targetBenchmarkId = grain === "slice" ? selectedSliceId : asString(row.benchmark_id)
1317
  resultRows = await readRows<Row>(
1318
+ `SELECT ${mergedResultColumns(await evalResultsViewHasCollectionColumns())}
1319
  FROM eval_results_view r
1320
  WHERE r.benchmark_id = ?
1321
  AND r.metric_id_effective = ?
tests/assisted-results.test.ts ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { describe, expect, it } from "vitest"
2
+
3
+ import { isAssistedResult } from "@/lib/eval-processing"
4
+
5
+ // Assisted (oracle answer-feedback) classification drives shown-but-unranked
6
+ // leaderboard handling; misclassification either ranks an assisted run or
7
+ // strips a rank from a clean one.
8
+ describe("isAssistedResult", () => {
9
+ it("classifies the answer_feedback arm", () => {
10
+ expect(
11
+ isAssistedResult(
12
+ '{"compaction":false,"feedback":"answer_feedback","scaffold":"S-adaptive","token_limit":5000000}',
13
+ ),
14
+ ).toBe(true)
15
+ })
16
+
17
+ it("keeps clean and unknown arms unassisted", () => {
18
+ expect(isAssistedResult('{"feedback":"none"}')).toBe(false)
19
+ expect(isAssistedResult('{"feedback":"unknown"}')).toBe(false)
20
+ expect(isAssistedResult('{"token_limit":5000000}')).toBe(false)
21
+ })
22
+
23
+ it("treats absent/invalid protocol data as unassisted", () => {
24
+ expect(isAssistedResult(undefined)).toBe(false)
25
+ expect(isAssistedResult(null)).toBe(false)
26
+ expect(isAssistedResult("")).toBe(false)
27
+ expect(isAssistedResult("not json")).toBe(false)
28
+ expect(isAssistedResult('"answer_feedback"')).toBe(false)
29
+ })
30
+ })