Files changed (44) hide show
  1. .gitignore +8 -0
  2. README.md +49 -38
  3. app/about/page.tsx +3 -6
  4. components/benchmark-detail.tsx +77 -23
  5. components/eval-detail.tsx +4 -5
  6. lib/backend-artifacts.ts +6 -0
  7. lib/benchmark-schema.ts +15 -1
  8. lib/clean-hierarchy.ts +3 -3
  9. lib/eval-processing.ts +8 -0
  10. lib/hf-data.ts +1 -2
  11. lib/known-developers.ts +29 -3
  12. lib/model-family.ts +0 -202
  13. lib/model-url-redirects-build.ts +120 -0
  14. lib/model-url-redirects.ts +1128 -0
  15. lib/utils.ts +16 -0
  16. lib/view-data.ts +60 -9
  17. middleware.ts +70 -0
  18. package.json +4 -0
  19. playwright.config.ts +30 -0
  20. pnpm-lock.yaml +47 -5
  21. scripts/generate-model-redirects.ts +88 -0
  22. scripts/verify-identity.mjs +0 -36
  23. scripts/verify-setup-alias.mjs +0 -99
  24. test_redirects_manual.mjs +23 -0
  25. tests/PREFLIGHT.md +76 -0
  26. tests/e2e/frontend-preflight.spec.ts +169 -0
  27. tests/fixtures/developers/01-ai.json +20 -20
  28. tests/fixtures/developers/anthropic.json +58 -58
  29. tests/fixtures/developers/openai.json +111 -111
  30. tests/fixtures/model-cards/01-ai__yi-34b.json +1 -1
  31. tests/fixtures/model-cards/anthropic__claude-opus-4.5.json +1 -1
  32. tests/fixtures/model-cards/openai__gpt-5.json +1 -1
  33. tests/fixtures/models/ai21__j1-grande-v1-17b.json +1 -1
  34. tests/fixtures/models/bytedance__seed-2-0-lite.json +1 -1
  35. tests/fixtures/models/google__gemini-3-flash.json +1 -1
  36. tests/fixtures/models/openai__gpt-5-2-pro.json +1 -1
  37. tests/pipeline-contract.test.ts +5 -5
  38. tests/redirect-integrity.test.ts +93 -0
  39. tests/transformations/identity-canonicalization.test.ts +0 -251
  40. tests/transformations/model-url-redirects.test.ts +219 -0
  41. tests/transformations/setup-alias-merging.test.ts +0 -268
  42. tests/upstream-drift.test.ts +2 -2
  43. tests/view-data.test.ts +1 -1
  44. vitest.config.ts +3 -1
.gitignore CHANGED
@@ -49,3 +49,11 @@ whisker-render.mjs
49
  output/
50
  public/peer-ranks.json
51
  **/* (1).tsx
 
 
 
 
 
 
 
 
 
49
  output/
50
  public/peer-ranks.json
51
  **/* (1).tsx
52
+
53
+ # private working notes / design specs / migration plans — not for publishing
54
+ notes/
55
+ docs/INTERPRETIVE_SIGNALS.md
56
+
57
+ # Playwright e2e output (tests/e2e via @playwright/test)
58
+ test-results/
59
+ playwright-report/
README.md CHANGED
@@ -16,7 +16,10 @@ tags:
16
 
17
  # Eval Cards
18
 
19
- This repository is a Next.js application for viewing and authoring AI evaluations. It provides a comprehensive platform for documenting and sharing AI system evaluations across multiple dimensions including capabilities and risks.
 
 
 
20
 
21
  ## Project Goals
22
 
@@ -27,56 +30,56 @@ The Eval Cards project aims to:
27
  - **Support research and policy** by consolidating evaluation data in an accessible format
28
  - **Promote responsible AI development** through comprehensive risk assessment
29
 
30
- ## For External Collaborators
31
 
32
- ### Making Changes to Evaluation Categories and Schema
 
 
 
 
 
33
 
34
- All evaluation categories, form fields, and data structures are centrally managed in the `schema/` folder. **This is the primary location for making structural changes to the evaluation framework.**
 
 
35
 
36
- Key schema files:
37
- - **`schema/evaluation-schema.json`** - Defines all evaluation categories (capabilities and risks)
38
- - **`schema/output-schema.json`** - Defines the complete data structure for evaluation outputs
39
- - **`schema/system-info-schema.json`** - Defines form field options for system information
40
- - **`schema/category-details.json`** - Contains detailed descriptions and criteria for each category
41
- - **`schema/form-hints.json`** - Provides help text and guidance for form fields
42
-
43
- ### Standards and Frameworks Used
44
-
45
- The evaluation framework is based on established standards:
46
- - **Risk categories** are derived from **NIST AI 600-1** (AI Risk Management Framework)
47
- - **Capability categories** are based on the **OECD AI Classification Framework**
48
-
49
- This ensures consistency with international AI governance standards and facilitates interoperability with other evaluation systems.
50
-
51
- ### Contributing Evaluation Data
52
 
53
- Evaluation data files are stored in `public/benchmarks/` as JSON files. Each file represents a complete evaluation of an AI system and must conform to the schema defined in `schema/output-schema.json`.
54
 
55
- To add a new evaluation:
56
- 1. Create a new JSON file in `public/benchmarks/`
57
- 2. Follow the structure defined in `schema/output-schema.json`
58
- 3. Ensure all required fields are populated
59
- 4. Validate against the schema before submission
60
 
61
- ### Development Setup
62
 
63
- ## Run locally
 
 
64
 
65
- Install dependencies and run the dev server:
66
 
67
  ```bash
68
- npm ci
69
- npm run dev
70
  ```
71
 
72
- Build for production and run:
73
 
74
  ```bash
75
- npm ci
76
- npm run build
77
- NODE_ENV=production PORT=3000 npm run start
78
  ```
79
 
 
 
 
 
 
 
 
 
 
80
  ## Docker (recommended for Hugging Face Spaces)
81
 
82
  A `Dockerfile` is included for deploying this app as a dynamic service on Hugging Face Spaces (Docker runtime).
@@ -90,7 +93,8 @@ docker build -t ai-eval-dashboard .
90
  Run the container (expose port 3000):
91
 
92
  ```bash
93
- docker run -p 3000:3000 -e HF_TOKEN="$HF_TOKEN" ai-eval-dashboard
 
94
  ```
95
 
96
  Visit `http://localhost:3000` to verify.
@@ -98,7 +102,14 @@ Visit `http://localhost:3000` to verify.
98
  ### Deploy to Hugging Face Spaces
99
 
100
  1. Create a new Space at https://huggingface.co/new-space and choose **Docker** as the runtime.
101
- 2. Push this repository to the Space Git (or upload files through the UI). The Space will build the Docker image using the included `Dockerfile` and serve your app on port 3000.
102
 
103
  Notes:
104
- - If your build needs native dependencies (e.g. `sharp`), the Docker image may require extra apt packages; update the Dockerfile accordingly.
 
 
 
 
 
 
 
 
16
 
17
  # Eval Cards
18
 
19
+ A Next.js application for **viewing** AI evaluations. It is the reader frontend of the
20
+ Eval Cards platform: it does not author or store evaluation data itself — it renders a
21
+ materialised warehouse **view layer** produced upstream by the `eval_card_backend`
22
+ pipeline, and deploys to the Hugging Face Space `evaleval/eval-cards` (Docker runtime).
23
 
24
  ## Project Goals
25
 
 
30
  - **Support research and policy** by consolidating evaluation data in an accessible format
31
  - **Promote responsible AI development** through comprehensive risk assessment
32
 
33
+ ## Architecture
34
 
35
+ This app is a **read-only consumer** of a snapshot. The producer (`eval_card_backend`)
36
+ canonicalises raw evaluation data into a typed Parquet warehouse plus a Stage J view
37
+ layer (`*.parquet` view tables + JSON sidecars), and the frontend reads that snapshot at
38
+ runtime via DuckDB — it performs no identity resolution or aggregation of its own. The
39
+ view-layer column names match this app's TypeScript interfaces by contract (declared in
40
+ `lib/view-data.ts`).
41
 
42
+ Data is selected by the `DATA_BACKEND` env var. The current path is `DATA_BACKEND=v2`,
43
+ which reads a snapshot pointed at by `SNAPSHOT_URL` (a local `file://` path in dev, or an
44
+ `https://huggingface.co/datasets/.../resolve/<rev>/warehouse/<snapshot_id>` URL in prod).
45
 
46
+ ## Run locally
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
48
+ This repo uses **pnpm** (pinned via `packageManager: pnpm@10.25.0`).
49
 
50
+ ```bash
51
+ pnpm install
52
+ ```
 
 
53
 
54
+ Run the dev server against a local Stage J snapshot (produced by `eval_card_backend canonicalise`):
55
 
56
+ ```bash
57
+ DATA_BACKEND=v2 SNAPSHOT_URL=file:///abs/path/to/warehouse/<snapshot_id> pnpm dev
58
+ ```
59
 
60
+ Build for production and run:
61
 
62
  ```bash
63
+ pnpm build # runs scripts/cache-hf-data.mjs + scripts/build-eval-matrices.mjs, then next build
64
+ DATA_BACKEND=v2 SNAPSHOT_URL=<file:// or HF resolve URL> pnpm start
65
  ```
66
 
67
+ Run the test suite (Vitest):
68
 
69
  ```bash
70
+ pnpm test # full suite
71
+ pnpm test -- tests/<file>.test.ts # a single test
 
72
  ```
73
 
74
+ ## Configuration
75
+
76
+ | Env var | Purpose |
77
+ | --- | --- |
78
+ | `DATA_BACKEND` | Selects the data source. `v2` (a.k.a. `stage-j`) is the current view-layer backend. |
79
+ | `SNAPSHOT_URL` | **Required when `DATA_BACKEND=v2`** — points at a Stage J snapshot directory (`file://…` locally, or an HF `…/resolve/<rev>/warehouse/<snapshot_id>` URL in prod). |
80
+ | `SIDECAR_CACHE_DIR` / `SIDECAR_CACHE_TTL_SECONDS` / `SIDECAR_CACHE_PURGE` / `SIDECAR_BUILD_ID` | Tuning for the JSON-sidecar fetch cache. |
81
+ | `HF_DATA_*` (`HF_DATA_LOCAL_DIR`, `HF_DATA_OFFLINE`, `HF_DATA_CACHE_TTL_MS`, …) | Knobs for the legacy v1 Hugging Face data path; not used by the v2 backend. |
82
+
83
  ## Docker (recommended for Hugging Face Spaces)
84
 
85
  A `Dockerfile` is included for deploying this app as a dynamic service on Hugging Face Spaces (Docker runtime).
 
93
  Run the container (expose port 3000):
94
 
95
  ```bash
96
+ docker run -p 3000:3000 -e HF_TOKEN="$HF_TOKEN" \
97
+ -e DATA_BACKEND=v2 -e SNAPSHOT_URL="<HF resolve URL>" ai-eval-dashboard
98
  ```
99
 
100
  Visit `http://localhost:3000` to verify.
 
102
  ### Deploy to Hugging Face Spaces
103
 
104
  1. Create a new Space at https://huggingface.co/new-space and choose **Docker** as the runtime.
105
+ 2. Push this repository to the Space Git (or upload files through the UI). The Space builds the Docker image using the included `Dockerfile` and serves the app on port 3000.
106
 
107
  Notes:
108
+ - If your build needs native dependencies (e.g. `sharp`), the Docker image may require extra apt packages; update the Dockerfile accordingly.
109
+
110
+ ## Background: evaluation framework
111
+
112
+ The evaluation categories surfaced in the cards trace to established standards — risk
113
+ categories from **NIST AI 600-1** (AI Risk Management Framework) and capability
114
+ categories from the **OECD AI Classification Framework** — for consistency with
115
+ international AI governance standards and interoperability with other evaluation systems.
app/about/page.tsx CHANGED
@@ -10,7 +10,7 @@ export default function AboutPage() {
10
 
11
  <main className="mx-auto w-full max-w-[64rem] px-4 pb-24 pt-12 sm:px-8">
12
  {/* HEADER --------------------------------------------------------- */}
13
- <div className="kicker">About · Working paper v0.4</div>
14
  <h1
15
  className="mt-2 mb-7"
16
  style={{
@@ -347,19 +347,16 @@ export default function AboutPage() {
347
  <div className="kicker mb-2.5">Cite as</div>
348
  <p className="font-mono m-0 text-[12px] leading-[1.7] text-[color:var(--fg)]">
349
  EvalEval Coalition. (2026). Evaluation Cards: a reporting layer for AI evaluation
350
- (Working paper v0.4, snapshot 18 Apr 2026). evalcards.evalevalai.com
351
  </p>
352
  </div>
353
  <div className="border border-[color:var(--border-soft)] p-[22px]">
354
  <div className="kicker mb-2.5">Submit a correction</div>
355
- <p className="m-0 mb-2 text-[13.5px] leading-[1.65] text-[color:var(--fg-muted)]">
356
  Each record links a correction path. Disclosure gaps close as developers and
357
  third parties publish; we accept patches against any (model, benchmark,
358
  metric-path) tuple with a citation.
359
  </p>
360
- <span className="font-mono text-[color:var(--fg-subtle)] text-[11px] uppercase tracking-[0.1em]">
361
- corrections@evalevalai.com
362
- </span>
363
  </div>
364
  </div>
365
  </section>
 
10
 
11
  <main className="mx-auto w-full max-w-[64rem] px-4 pb-24 pt-12 sm:px-8">
12
  {/* HEADER --------------------------------------------------------- */}
13
+ <div className="kicker">About</div>
14
  <h1
15
  className="mt-2 mb-7"
16
  style={{
 
347
  <div className="kicker mb-2.5">Cite as</div>
348
  <p className="font-mono m-0 text-[12px] leading-[1.7] text-[color:var(--fg)]">
349
  EvalEval Coalition. (2026). Evaluation Cards: a reporting layer for AI evaluation
350
+ (snapshot 5 Jun 2026). evalcards.evalevalai.com
351
  </p>
352
  </div>
353
  <div className="border border-[color:var(--border-soft)] p-[22px]">
354
  <div className="kicker mb-2.5">Submit a correction</div>
355
+ <p className="m-0 text-[13.5px] leading-[1.65] text-[color:var(--fg-muted)]">
356
  Each record links a correction path. Disclosure gaps close as developers and
357
  third parties publish; we accept patches against any (model, benchmark,
358
  metric-path) tuple with a citation.
359
  </p>
 
 
 
360
  </div>
361
  </div>
362
  </section>
components/benchmark-detail.tsx CHANGED
@@ -5,7 +5,7 @@ import Link from "next/link"
5
  import { usePathname, useRouter, useSearchParams } from "next/navigation"
6
  import { useAudienceMode } from "@/components/audience-mode-provider"
7
  import { EmbedButton } from "@/components/embed-button"
8
- import { formatDateISO, humanizeBenchmarkName, humanizeEvaluationId, routeIdToPath } from "@/lib/utils"
9
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
10
  import { Badge } from "@/components/ui/badge"
11
  import { Button } from "@/components/ui/button"
@@ -779,7 +779,7 @@ function getComparisonScoreEntryForVariant(
779
  const modelId = row.variant.evaluation.model_info.id
780
  return (
781
  metricEntry.scores.find(
782
- (score) => score.model_route_id === modelId || score.model_family_id === modelId
783
  ) ?? null
784
  )
785
  }
@@ -2733,10 +2733,19 @@ export function BenchmarkDetail({
2733
  // Used to (a) pull our own score out of `by_model` and (b) drop ourselves
2734
  // out of the peer score list.
2735
  const currentModelIdentityKeys = useMemo(() => {
 
 
 
 
 
2736
  const keys = new Set<string>(
2737
  [
2738
- summary.model_info.id,
2739
- (summary as any).model_family_id,
 
 
 
 
2740
  (summary.model_info as any).family_id,
2741
  (summary.model_info as any).model_route_id,
2742
  ...((summary as any).raw_model_ids ?? []),
@@ -2745,15 +2754,19 @@ export function BenchmarkDetail({
2745
  return keys
2746
  }, [summary])
2747
 
2748
- // The primary model_route_id that keys into comparison-index.by_model for
2749
- // this page. Prefer an explicit route id; otherwise derive one.
 
 
 
2750
  const currentModelRouteId = useMemo(() => {
2751
  const explicit =
 
2752
  (summary.model_info as any).model_route_id ||
2753
- (summary as any).model_route_id
2754
  if (typeof explicit === "string" && explicit.length > 0) return explicit
2755
  const id = summary.model_info.id || ""
2756
- return id.replace(/[/]/g, "__")
2757
  }, [summary])
2758
 
2759
  // Cross-suite overlaps: walk `benchmark_index[]` (already pre-filtered by
@@ -2779,7 +2792,7 @@ export function BenchmarkDetail({
2779
  for (const row of metric.scores) {
2780
  if (
2781
  currentModelIdentityKeys.has(row.model_route_id) ||
2782
- currentModelIdentityKeys.has(row.model_family_id)
2783
  ) {
2784
  if (Number.isFinite(row.score)) return row.score
2785
  }
@@ -2985,7 +2998,7 @@ export function BenchmarkDetail({
2985
  for (const s of metric.scores) {
2986
  if (
2987
  currentModelIdentityKeys.has(s.model_route_id) ||
2988
- currentModelIdentityKeys.has(s.model_family_id)
2989
  ) {
2990
  currentRow = s
2991
  break
@@ -3012,7 +3025,7 @@ export function BenchmarkDetail({
3012
  const peerRows = metric.scores.filter(
3013
  (s) =>
3014
  !currentModelIdentityKeys.has(s.model_route_id) &&
3015
- !currentModelIdentityKeys.has(s.model_family_id)
3016
  )
3017
 
3018
  const defaults = new Set<string>()
@@ -3039,11 +3052,10 @@ export function BenchmarkDetail({
3039
  .filter((p) => selectedIds.has(p.model_route_id))
3040
  .map((p) => ({
3041
  modelId: p.model_route_id,
3042
- // Fall back to model_family_id when the registry has no display
3043
- // name for this model. Many score entries land here; the root cause
3044
- // is registry coverage. The id is
3045
- // already human-readable in this codebase ("anthropic/Sonnet 4.5"),
3046
- // so the fallback is more useful than "Unknown Model".
3047
  modelName: getModelDisplayName(p.model_family_name || p.model_family_id),
3048
  score: p.score,
3049
  isCurrent: false,
@@ -3077,12 +3089,10 @@ export function BenchmarkDetail({
3077
  .filter((p) => !selectedIds.has(p.model_route_id))
3078
  .map((p) => ({
3079
  id: p.model_route_id,
3080
- // Same fallback as the rendered bar (peerBars above) when
3081
- // the registry lacks a display name, fall back to the
3082
- // model_family_id (which is already human-readable in this
3083
- // codebase, e.g. "anthropic/sonnet-4.5"). Without this the
3084
- // dropdown label resolves to "Unknown Model" even though
3085
- // the bar that appears after selection reads correctly.
3086
  name: p.model_family_name || p.model_family_id,
3087
  score: p.score,
3088
  submissionCount: p.submission_count,
@@ -3924,7 +3934,7 @@ export function BenchmarkDetail({
3924
  for (const row of siblingMetric.scores) {
3925
  if (
3926
  currentModelIdentityKeys.has(row.model_route_id) ||
3927
- currentModelIdentityKeys.has(row.model_family_id)
3928
  ) {
3929
  if (Number.isFinite(row.score)) {
3930
  siblingScore = row.score
@@ -4395,6 +4405,8 @@ export function BenchmarkDetail({
4395
  return (
4396
  <div
4397
  key={bar.modelId}
 
 
4398
  className="group flex min-w-0 flex-col items-center"
4399
  >
4400
  <div className="relative flex h-44 w-full items-end">
@@ -4708,6 +4720,13 @@ export function BenchmarkDetail({
4708
  <span>{formatParamsBillions(summary.model_info.additional_details?.params_billions)}</span>
4709
  </>
4710
  )}
 
 
 
 
 
 
 
4711
  </div>
4712
  </div>
4713
 
@@ -4731,6 +4750,41 @@ export function BenchmarkDetail({
4731
  <div className="mt-1 text-right font-mono text-[10px] uppercase tracking-[0.15em] text-[color:var(--fg-subtle)]">
4732
  {Math.max(0, reproducibilityResultsTotal - reproducibilityGapCount)} / {reproducibilityResultsTotal} reported
4733
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4734
  </div>
4735
  </div>
4736
  </header>
 
5
  import { usePathname, useRouter, useSearchParams } from "next/navigation"
6
  import { useAudienceMode } from "@/components/audience-mode-provider"
7
  import { EmbedButton } from "@/components/embed-button"
8
+ import { formatDateISO, humanizeBenchmarkName, humanizeEvaluationId, routeIdFromModelId, routeIdToPath } from "@/lib/utils"
9
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
10
  import { Badge } from "@/components/ui/badge"
11
  import { Button } from "@/components/ui/button"
 
779
  const modelId = row.variant.evaluation.model_info.id
780
  return (
781
  metricEntry.scores.find(
782
+ (score) => score.model_route_id === modelId || score.model_group_id === modelId
783
  ) ?? null
784
  )
785
  }
 
2733
  // Used to (a) pull our own score out of `by_model` and (b) drop ourselves
2734
  // out of the peer score list.
2735
  const currentModelIdentityKeys = useMemo(() => {
2736
+ const id = summary.model_info.id || ""
2737
+ // Include BOTH the plain id and its encoded (route_id) form: comparison
2738
+ // score rows are keyed by the percent-encoded route, while the summary
2739
+ // exposes the plain `id`. Without the encoded forms the current model
2740
+ // never matches its own score row (so it isn't excluded from peers).
2741
  const keys = new Set<string>(
2742
  [
2743
+ id,
2744
+ id && encodeURIComponent(id),
2745
+ (summary as any).model_route_id,
2746
+ (summary as any).variant_key,
2747
+ (summary as any).model_group_id,
2748
+ (summary as any).model_group_id && encodeURIComponent((summary as any).model_group_id),
2749
  (summary.model_info as any).family_id,
2750
  (summary.model_info as any).model_route_id,
2751
  ...((summary as any).raw_model_ids ?? []),
 
2754
  return keys
2755
  }, [summary])
2756
 
2757
+ // The model_route_id that keys into comparison-index.by_model (which is keyed
2758
+ // by the GROUP route_id). The page renders the selected variant as `summary`,
2759
+ // so prefer its carried-through model_route_id, then variant_key, and finally
2760
+ // encode the plain id — never the legacy `__` form, which the v2 backend's
2761
+ // percent-encoded keys don't use.
2762
  const currentModelRouteId = useMemo(() => {
2763
  const explicit =
2764
+ (summary as any).model_route_id ||
2765
  (summary.model_info as any).model_route_id ||
2766
+ (summary as any).variant_key
2767
  if (typeof explicit === "string" && explicit.length > 0) return explicit
2768
  const id = summary.model_info.id || ""
2769
+ return id ? encodeURIComponent(id) : ""
2770
  }, [summary])
2771
 
2772
  // Cross-suite overlaps: walk `benchmark_index[]` (already pre-filtered by
 
2792
  for (const row of metric.scores) {
2793
  if (
2794
  currentModelIdentityKeys.has(row.model_route_id) ||
2795
+ currentModelIdentityKeys.has(row.model_group_id)
2796
  ) {
2797
  if (Number.isFinite(row.score)) return row.score
2798
  }
 
2998
  for (const s of metric.scores) {
2999
  if (
3000
  currentModelIdentityKeys.has(s.model_route_id) ||
3001
+ currentModelIdentityKeys.has(s.model_group_id)
3002
  ) {
3003
  currentRow = s
3004
  break
 
3025
  const peerRows = metric.scores.filter(
3026
  (s) =>
3027
  !currentModelIdentityKeys.has(s.model_route_id) &&
3028
+ !currentModelIdentityKeys.has(s.model_group_id)
3029
  )
3030
 
3031
  const defaults = new Set<string>()
 
3052
  .filter((p) => selectedIds.has(p.model_route_id))
3053
  .map((p) => ({
3054
  modelId: p.model_route_id,
3055
+ // Most comparison score rows carry an empty model_family_name, so
3056
+ // fall back to model_family_id (always present, and already
3057
+ // human-readable in this codebase, e.g. "anthropic/sonnet-4.5").
3058
+ // Without this the peer bars all read "Unknown Model".
 
3059
  modelName: getModelDisplayName(p.model_family_name || p.model_family_id),
3060
  score: p.score,
3061
  isCurrent: false,
 
3089
  .filter((p) => !selectedIds.has(p.model_route_id))
3090
  .map((p) => ({
3091
  id: p.model_route_id,
3092
+ // Same fallback as the rendered bar (peerBars above): score rows
3093
+ // usually have an empty model_family_name, so fall back to
3094
+ // model_family_id (always present, human-readable). Without this the
3095
+ // dropdown label resolves to "Unknown Model".
 
 
3096
  name: p.model_family_name || p.model_family_id,
3097
  score: p.score,
3098
  submissionCount: p.submission_count,
 
3934
  for (const row of siblingMetric.scores) {
3935
  if (
3936
  currentModelIdentityKeys.has(row.model_route_id) ||
3937
+ currentModelIdentityKeys.has(row.model_group_id)
3938
  ) {
3939
  if (Number.isFinite(row.score)) {
3940
  siblingScore = row.score
 
4405
  return (
4406
  <div
4407
  key={bar.modelId}
4408
+ data-model-bar={bar.modelId}
4409
+ data-bar-current={bar.isCurrent ? "1" : "0"}
4410
  className="group flex min-w-0 flex-col items-center"
4411
  >
4412
  <div className="relative flex h-44 w-full items-end">
 
4720
  <span>{formatParamsBillions(summary.model_info.additional_details?.params_billions)}</span>
4721
  </>
4722
  )}
4723
+ {/* model-resolution-rework: served-by inference platform (nullable). */}
4724
+ {summary.model_info.inference_platform && (
4725
+ <>
4726
+ <span className="text-[color:var(--fg-subtle)]">·</span>
4727
+ <span>Served by {summary.model_info.inference_platform}</span>
4728
+ </>
4729
+ )}
4730
  </div>
4731
  </div>
4732
 
 
4750
  <div className="mt-1 text-right font-mono text-[10px] uppercase tracking-[0.15em] text-[color:var(--fg-subtle)]">
4751
  {Math.max(0, reproducibilityResultsTotal - reproducibilityGapCount)} / {reproducibilityResultsTotal} reported
4752
  </div>
4753
+
4754
+ {/* model-resolution-rework: lineage + resolution provenance.
4755
+ All fields are nullable (server-provided); the block only
4756
+ renders rows that are present. */}
4757
+ {(summary.lineage_origin_model_id ||
4758
+ summary.resolution_source ||
4759
+ summary.resolution_granularity) && (
4760
+ <dl className="mt-4 space-y-1 text-[11px] text-[color:var(--fg-muted)]">
4761
+ {summary.lineage_origin_model_id && (
4762
+ <div className="flex items-baseline justify-between gap-2">
4763
+ <dt className="kicker shrink-0">Base model</dt>
4764
+ <dd className="min-w-0 truncate text-right">
4765
+ <Link
4766
+ href={`/models/${routeIdToPath(routeIdFromModelId(summary.lineage_origin_model_id))}`}
4767
+ className="font-mono hover:text-[color:var(--accent)]"
4768
+ >
4769
+ {summary.lineage_origin_model_id}
4770
+ </Link>
4771
+ </dd>
4772
+ </div>
4773
+ )}
4774
+ {summary.resolution_source && (
4775
+ <div className="flex items-baseline justify-between gap-2">
4776
+ <dt className="kicker shrink-0">Resolved via</dt>
4777
+ <dd className="font-mono">{summary.resolution_source}</dd>
4778
+ </div>
4779
+ )}
4780
+ {summary.resolution_granularity && (
4781
+ <div className="flex items-baseline justify-between gap-2">
4782
+ <dt className="kicker shrink-0">Granularity</dt>
4783
+ <dd className="font-mono">{summary.resolution_granularity}</dd>
4784
+ </div>
4785
+ )}
4786
+ </dl>
4787
+ )}
4788
  </div>
4789
  </div>
4790
  </header>
components/eval-detail.tsx CHANGED
@@ -33,8 +33,7 @@ import {
33
  DropdownMenuSeparator,
34
  DropdownMenuTrigger,
35
  } from "@/components/ui/dropdown-menu"
36
- import { getModelFamilyRouteId } from "@/lib/model-family"
37
- import { cn, formatDateISO, humanizeEvaluationId, routeIdToPath } from "@/lib/utils"
38
  import {
39
  AlertTriangle,
40
  BarChart3,
@@ -1580,7 +1579,7 @@ export function EvalDetail({
1580
  </td>
1581
  <td style={{ padding: "10px 8px", color: "var(--fg)" }}>
1582
  <Link
1583
- href={`/models/${routeIdToPath(modelResult.model_route_id ?? getModelFamilyRouteId(modelResult.model_info))}`}
1584
  className="hover:text-[color:var(--accent)] transition-colors"
1585
  style={{ color: "var(--fg)", fontWeight: 500, fontSize: 14 }}
1586
  >
@@ -1783,7 +1782,7 @@ export function EvalDetail({
1783
  )}
1784
  <div className="min-w-0">
1785
  <Link
1786
- href={`/models/${routeIdToPath(modelResult.model_route_id ?? getModelFamilyRouteId(modelResult.model_info))}`}
1787
  className="font-semibold text-[14px] hover:text-[color:var(--accent)] transition-colors"
1788
  style={{ color: "var(--fg)" }}
1789
  >
@@ -2768,7 +2767,7 @@ function MultiMetricLeaderboard({
2768
  )}
2769
  <div className="min-w-0">
2770
  <Link
2771
- href={`/models/${routeIdToPath(row.model_route_id ?? getModelFamilyRouteId(row.model_info))}`}
2772
  className="font-semibold text-[14px] hover:text-[color:var(--accent)] transition-colors"
2773
  style={{ color: "var(--fg)" }}
2774
  >
 
33
  DropdownMenuSeparator,
34
  DropdownMenuTrigger,
35
  } from "@/components/ui/dropdown-menu"
36
+ import { cn, formatDateISO, humanizeEvaluationId, routeIdFromModelId, routeIdToPath } from "@/lib/utils"
 
37
  import {
38
  AlertTriangle,
39
  BarChart3,
 
1579
  </td>
1580
  <td style={{ padding: "10px 8px", color: "var(--fg)" }}>
1581
  <Link
1582
+ href={`/models/${routeIdToPath(modelResult.model_route_id ?? routeIdFromModelId(modelResult.model_group_id))}`}
1583
  className="hover:text-[color:var(--accent)] transition-colors"
1584
  style={{ color: "var(--fg)", fontWeight: 500, fontSize: 14 }}
1585
  >
 
1782
  )}
1783
  <div className="min-w-0">
1784
  <Link
1785
+ href={`/models/${routeIdToPath(modelResult.model_route_id ?? routeIdFromModelId(modelResult.model_group_id))}`}
1786
  className="font-semibold text-[14px] hover:text-[color:var(--accent)] transition-colors"
1787
  style={{ color: "var(--fg)" }}
1788
  >
 
2767
  )}
2768
  <div className="min-w-0">
2769
  <Link
2770
+ href={`/models/${routeIdToPath(row.model_route_id ?? routeIdFromModelId(row.model_group_id))}`}
2771
  className="font-semibold text-[14px] hover:text-[color:var(--accent)] transition-colors"
2772
  style={{ color: "var(--fg)" }}
2773
  >
lib/backend-artifacts.ts CHANGED
@@ -414,7 +414,13 @@ export interface ComparisonSubmission {
414
 
415
  export interface ComparisonScoreEntry {
416
  model_route_id: string
 
 
 
 
 
417
  model_family_id: string
 
418
  model_family_name: string
419
  developer: string
420
  variant_key: string
 
414
 
415
  export interface ComparisonScoreEntry {
416
  model_route_id: string
417
+ // The group root id, slash-form (e.g. "zhipu/glm-4-6-fc-thinking") — the field
418
+ // comparison-index score rows actually carry. Peer labels fall back to it when
419
+ // model_family_name is empty (most rows). NOTE: `model_group_id` below is NOT
420
+ // present on these rows; it stays declared only because other consumers read
421
+ // it as a `|| model_group_id` fallback (always undefined here, harmless).
422
  model_family_id: string
423
+ model_group_id: string
424
  model_family_name: string
425
  developer: string
426
  variant_key: string
lib/benchmark-schema.ts CHANGED
@@ -261,6 +261,12 @@ export interface ModelSummaryCore extends SignalSummaries {
261
  total_evaluations: number
262
  last_updated: string
263
  tags_covered: EvalTag[]
 
 
 
 
 
 
264
  }
265
 
266
  export interface ModelVariantSummary extends ModelSummaryCore {
@@ -276,7 +282,7 @@ export interface ModelVariantSummary extends ModelSummaryCore {
276
  }
277
 
278
  export interface ModelEvaluationSummary extends ModelSummaryCore {
279
- model_family_id: string
280
  model_route_id: string
281
  model_family_name: string
282
  raw_model_ids: string[]
@@ -347,6 +353,14 @@ export interface EvaluationCardData {
347
  params?: string
348
  inference_engine?: string
349
  inference_platform?: string
 
 
 
 
 
 
 
 
350
  }
351
 
352
  // ── Benchmark Card types (from metadata/benchmark_card_*.json) ────────────────
 
261
  total_evaluations: number
262
  last_updated: string
263
  tags_covered: EvalTag[]
264
+ // model-resolution-rework (additive, all nullable). Server-provided model
265
+ // identity provenance; surfaced on the model detail page. Carried here on
266
+ // the core so both the summary and variant shapes expose them.
267
+ lineage_origin_model_id?: string // deepest non-variant ancestor (base model)
268
+ resolution_source?: string // enum: hf | models_dev | curated | inferred | none
269
+ resolution_granularity?: string // enum: variant | group | family
270
  }
271
 
272
  export interface ModelVariantSummary extends ModelSummaryCore {
 
282
  }
283
 
284
  export interface ModelEvaluationSummary extends ModelSummaryCore {
285
+ model_group_id: string
286
  model_route_id: string
287
  model_family_name: string
288
  raw_model_ids: string[]
 
353
  params?: string
354
  inference_engine?: string
355
  inference_platform?: string
356
+
357
+ // model-resolution-rework (additive, all nullable). Server-provided
358
+ // (producer view layer) — the frontend no longer computes families
359
+ // client-side. See notes/backend-v2-migration.md.
360
+ model_group_id?: string // group canonical id (membership / grouping root)
361
+ lineage_origin_model_id?: string // deepest non-variant ancestor (base model)
362
+ resolution_source?: string // enum: hf | models_dev | curated | inferred | none
363
+ resolution_granularity?: string // enum: variant | group | family
364
  }
365
 
366
  // ── Benchmark Card types (from metadata/benchmark_card_*.json) ────────────────
lib/clean-hierarchy.ts CHANGED
@@ -195,7 +195,7 @@ type ComparisonIndexLike = {
195
  metric_name?: string | null
196
  scores: Array<{
197
  model_route_id?: string | null
198
- model_family_id?: string | null
199
  score?: number | null
200
  }>
201
  }>
@@ -280,7 +280,7 @@ function buildModelCoverageMap(
280
  for (const [evalId, entry] of Object.entries(comparisonIndex.evals ?? {})) {
281
  for (const metric of entry.metrics ?? []) {
282
  for (const row of metric.scores ?? []) {
283
- const modelId = row.model_route_id || row.model_family_id
284
  if (!modelId || row.score == null || !Number.isFinite(row.score as number)) continue
285
  const set = modelEvals.get(modelId) ?? new Set<string>()
286
  set.add(evalId)
@@ -415,7 +415,7 @@ function dedupAggregatorBenchesByScore(
415
  usableMetrics[0]
416
  const map = new Map<string, number>()
417
  for (const row of target.scores ?? []) {
418
- const id = row.model_route_id || row.model_family_id
419
  if (!id || row.score == null || !Number.isFinite(row.score)) continue
420
  map.set(id, row.score as number)
421
  }
 
195
  metric_name?: string | null
196
  scores: Array<{
197
  model_route_id?: string | null
198
+ model_group_id?: string | null
199
  score?: number | null
200
  }>
201
  }>
 
280
  for (const [evalId, entry] of Object.entries(comparisonIndex.evals ?? {})) {
281
  for (const metric of entry.metrics ?? []) {
282
  for (const row of metric.scores ?? []) {
283
+ const modelId = row.model_route_id || row.model_group_id
284
  if (!modelId || row.score == null || !Number.isFinite(row.score as number)) continue
285
  const set = modelEvals.get(modelId) ?? new Set<string>()
286
  set.add(evalId)
 
415
  usableMetrics[0]
416
  const map = new Map<string, number>()
417
  for (const row of target.scores ?? []) {
418
+ const id = row.model_route_id || row.model_group_id
419
  if (!id || row.score == null || !Number.isFinite(row.score)) continue
420
  map.set(id, row.score as number)
421
  }
lib/eval-processing.ts CHANGED
@@ -25,6 +25,12 @@ export type { ModelEvaluationSummary }
25
  export interface ModelResultForBenchmark {
26
  model_info: ModelInfo
27
  model_route_id?: string
 
 
 
 
 
 
28
  score: number
29
  score_details: ScoreDetails
30
  evaluation_timestamp: string
@@ -163,6 +169,8 @@ export interface BenchmarkLeaderboardMetric {
163
  export interface BenchmarkLeaderboardRow {
164
  model_info: ModelInfo
165
  model_route_id?: string
 
 
166
  evaluation_timestamp: string
167
  source_metadata: SourceMetadata
168
  source_data: BenchmarkEvaluation["source_data"]
 
25
  export interface ModelResultForBenchmark {
26
  model_info: ModelInfo
27
  model_route_id?: string
28
+ /**
29
+ * model-resolution-rework: server-provided group canonical id. Used as
30
+ * the routing fallback when `model_route_id` is absent — replaces the
31
+ * old client-side family-route computation (since removed).
32
+ */
33
+ model_group_id?: string
34
  score: number
35
  score_details: ScoreDetails
36
  evaluation_timestamp: string
 
169
  export interface BenchmarkLeaderboardRow {
170
  model_info: ModelInfo
171
  model_route_id?: string
172
+ /** model-resolution-rework: server group id, routing fallback. */
173
+ model_group_id?: string
174
  evaluation_timestamp: string
175
  source_metadata: SourceMetadata
176
  source_data: BenchmarkEvaluation["source_data"]
lib/hf-data.ts CHANGED
@@ -31,7 +31,6 @@ import type {
31
  SourceData,
32
  SourceMetadata,
33
  } from "@/lib/benchmark-schema"
34
- import { getCanonicalModelIdentity, getModelFamilyRouteId } from "@/lib/model-family"
35
 
36
  // ---------------------------------------------------------------------------
37
  // HuggingFace dataset base URL
@@ -512,7 +511,7 @@ export interface HFModelDetail extends SignalSummaries {
512
  variant_label?: string
513
  model_route_id?: string
514
  }
515
- model_family_id: string
516
  model_route_id: string
517
  model_family_name?: string
518
  raw_model_ids?: string[]
 
31
  SourceData,
32
  SourceMetadata,
33
  } from "@/lib/benchmark-schema"
 
34
 
35
  // ---------------------------------------------------------------------------
36
  // HuggingFace dataset base URL
 
511
  variant_label?: string
512
  model_route_id?: string
513
  }
514
+ model_group_id: string
515
  model_route_id: string
516
  model_family_name?: string
517
  raw_model_ids?: string[]
lib/known-developers.ts CHANGED
@@ -20,12 +20,38 @@ export const KNOWN_DEVELOPER_NAMES: Record<string, string> = {
20
  "x-ai": "xAI",
21
  }
22
 
23
- // Canonical names treated as "official" model-developer organisations.
24
- // Seeded from KNOWN_DEVELOPER_NAMES values; extend with org names that
25
- // arrive already-canonical from the backend registry (no case fix needed).
 
 
 
 
26
  export const OFFICIAL_DEVELOPER_NAMES: Set<string> = new Set([
27
  ...Object.values(KNOWN_DEVELOPER_NAMES),
 
 
28
  "Allen Institute for AI",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  ])
30
 
31
  export function normalizeDeveloperName(name: string): string {
 
20
  "x-ai": "xAI",
21
  }
22
 
23
+ // Canonical display names treated as "official" model-developer organisations.
24
+ // MIRRORS the registry's `canonical_orgs.kind == 'lab'` set the `developer`
25
+ // string the backend emits is already the org's canonical display_name, so we
26
+ // match on it directly. Keep in sync when curated labs are added to the registry
27
+ // (eval-card-registry/seed/orgs.yaml). Durable follow-up: have the producer emit
28
+ // an `is_official` flag on the developer list so this hand-maintained set can be
29
+ // retired. (KNOWN_DEVELOPER_NAMES values are a subset, included via the spread.)
30
  export const OFFICIAL_DEVELOPER_NAMES: Set<string> = new Set([
31
  ...Object.values(KNOWN_DEVELOPER_NAMES),
32
+ "01.AI",
33
+ "AI21 Labs",
34
  "Allen Institute for AI",
35
+ "BigScience",
36
+ "ByteDance",
37
+ "Databricks",
38
+ "EleutherAI",
39
+ "Google DeepMind",
40
+ "Hugging Face",
41
+ "Inception Labs",
42
+ "MiniMax",
43
+ "Moonshot AI",
44
+ "Nous Research",
45
+ "Perplexity AI",
46
+ "Sarvam AI",
47
+ "Stability AI",
48
+ "Stanford CRFM",
49
+ "Stanford University",
50
+ "StepFun",
51
+ "Technology Innovation Institute",
52
+ "Upstage",
53
+ "Writer",
54
+ "Z.AI",
55
  ])
56
 
57
  export function normalizeDeveloperName(name: string): string {
lib/model-family.ts DELETED
@@ -1,202 +0,0 @@
1
- import type { ModelInfo } from "@/lib/benchmark-schema"
2
-
3
- interface ParsedModelIdentity {
4
- namespace: string
5
- rawHandle: string
6
- normalizedHandle: string
7
- familySlug: string
8
- familyId: string
9
- familyName: string
10
- variantKey: string
11
- variantLabel: string
12
- variantDisplayName: string
13
- versionDate?: string
14
- versionQualifier?: string
15
- }
16
-
17
- const TOKEN_CASE_MAP: Record<string, string> = {
18
- ai: "AI",
19
- coder: "Coder",
20
- command: "Command",
21
- chat: "Chat",
22
- claude: "Claude",
23
- gemini: "Gemini",
24
- gemma: "Gemma",
25
- gpt: "GPT",
26
- haiku: "Haiku",
27
- instruct: "Instruct",
28
- instant: "Instant",
29
- llama: "Llama",
30
- max: "Max",
31
- mini: "Mini",
32
- mistral: "Mistral",
33
- opus: "Opus",
34
- phi: "Phi",
35
- plus: "Plus",
36
- preview: "Preview",
37
- pro: "Pro",
38
- qwen: "Qwen",
39
- reasoning: "Reasoning",
40
- sonnet: "Sonnet",
41
- thinking: "Thinking",
42
- turbo: "Turbo",
43
- yi: "Yi",
44
- }
45
-
46
- function stripNamespace(value: string, namespace: string) {
47
- const trimmed = value.trim()
48
- if (!trimmed) {
49
- return trimmed
50
- }
51
-
52
- const lowerTrimmed = trimmed.toLowerCase()
53
- const namespacePrefix = `${namespace.toLowerCase()}/`
54
-
55
- if (lowerTrimmed.startsWith(namespacePrefix)) {
56
- return trimmed.slice(namespacePrefix.length)
57
- }
58
-
59
- return trimmed
60
- }
61
-
62
- function getNamespace(modelInfo: ModelInfo) {
63
- const idParts = modelInfo.id.split("/")
64
- if (idParts.length > 1 && idParts[0]) {
65
- return idParts[0].trim().toLowerCase()
66
- }
67
-
68
- return (modelInfo.developer ?? "unknown").trim().toLowerCase().replace(/\s+/g, "-")
69
- }
70
-
71
- function getRawHandle(modelInfo: ModelInfo, namespace: string) {
72
- const idParts = modelInfo.id.split("/")
73
- const idHandle = idParts[idParts.length - 1]?.trim()
74
- if (idHandle) {
75
- return idHandle
76
- }
77
-
78
- const strippedName = stripNamespace(modelInfo.name, namespace)
79
- return strippedName || modelInfo.name.trim()
80
- }
81
-
82
- function normalizeHandle(rawHandle: string) {
83
- return rawHandle
84
- .trim()
85
- .toLowerCase()
86
- .replace(/[_\s/]+/g, "-")
87
- .replace(/(\d)-(?=\d(?:-|$))/g, "$1.")
88
- .replace(/-+/g, "-")
89
- .replace(/^-|-$/g, "")
90
- }
91
-
92
- function formatVersionDate(dateToken: string) {
93
- if (!/^(?:19|20)\d{6}$/.test(dateToken)) {
94
- return dateToken
95
- }
96
-
97
- return `${dateToken.slice(0, 4)}-${dateToken.slice(4, 6)}-${dateToken.slice(6, 8)}`
98
- }
99
-
100
- function titleCaseToken(token: string) {
101
- if (!token) {
102
- return token
103
- }
104
-
105
- const knownCase = TOKEN_CASE_MAP[token]
106
- if (knownCase) {
107
- return knownCase
108
- }
109
-
110
- if (/^\d+(\.\d+)?$/.test(token)) {
111
- return token
112
- }
113
-
114
- if (/^\d+(\.\d+)?[bkmt]$/i.test(token)) {
115
- return `${token.slice(0, -1)}${token.slice(-1).toUpperCase()}`
116
- }
117
-
118
- if (/^v\d/i.test(token)) {
119
- return `v${token.slice(1)}`
120
- }
121
-
122
- return token.charAt(0).toUpperCase() + token.slice(1)
123
- }
124
-
125
- function humanizeHandle(handle: string) {
126
- return handle
127
- .split("-")
128
- .filter(Boolean)
129
- .map((token) => {
130
- if (/^(?:19|20)\d{6}$/.test(token)) {
131
- return formatVersionDate(token)
132
- }
133
-
134
- return titleCaseToken(token)
135
- })
136
- .join(" ")
137
- }
138
-
139
- function splitVersionParts(normalizedHandle: string) {
140
- const match = normalizedHandle.match(/^(.*?)-((?:19|20)\d{6})(?:-(.+))?$/)
141
-
142
- if (!match) {
143
- return {
144
- familySlug: normalizedHandle,
145
- variantKey: "base",
146
- variantLabel: "Current",
147
- versionDate: undefined,
148
- versionQualifier: undefined,
149
- }
150
- }
151
-
152
- const [, baseSlug, dateToken, qualifier] = match
153
- const qualifierLabel = qualifier ? humanizeHandle(qualifier) : null
154
- const formattedDate = formatVersionDate(dateToken)
155
-
156
- return {
157
- familySlug: baseSlug,
158
- variantKey: qualifier ? `${dateToken}-${qualifier}` : dateToken,
159
- variantLabel: qualifierLabel ? `${formattedDate} · ${qualifierLabel}` : formattedDate,
160
- versionDate: formattedDate,
161
- versionQualifier: qualifierLabel ?? undefined,
162
- }
163
- }
164
-
165
- export function getCanonicalModelIdentity(modelInfo: ModelInfo): ParsedModelIdentity {
166
- const namespace = getNamespace(modelInfo)
167
- const rawHandle = getRawHandle(modelInfo, namespace)
168
- const normalizedHandle = normalizeHandle(rawHandle)
169
- const versionParts = splitVersionParts(normalizedHandle)
170
- const familyName = humanizeHandle(versionParts.familySlug)
171
- const familyId = `${namespace}/${versionParts.familySlug}`
172
- const variantDisplayName =
173
- versionParts.variantKey === "base"
174
- ? familyName
175
- : `${familyName} (${versionParts.variantLabel})`
176
-
177
- return {
178
- namespace,
179
- rawHandle,
180
- normalizedHandle,
181
- familySlug: versionParts.familySlug,
182
- familyId,
183
- familyName,
184
- variantKey: versionParts.variantKey,
185
- variantLabel: versionParts.variantLabel,
186
- variantDisplayName,
187
- versionDate: versionParts.versionDate,
188
- versionQualifier: versionParts.versionQualifier,
189
- }
190
- }
191
-
192
- export function getModelFamilyRouteId(model: ModelInfo | string) {
193
- const familyId =
194
- typeof model === "string" ? model.trim() : getCanonicalModelIdentity(model).familyId
195
-
196
- // Use RFC 3986 percent-encoding (matches `models_view.route_id` /
197
- // `model_route_id` produced by Stage J). The legacy `/` → `__`
198
- // transform was a holdover from when model ids were embedded into
199
- // HF-dataset paths and `/` was disallowed; the v2 route lookup
200
- // matches `route_id` directly, so a `__`-formatted slug returns 404.
201
- return encodeURIComponent(familyId)
202
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/model-url-redirects-build.ts ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Redirect map builder (pure, testable).
2
+ //
3
+ // The producer's warehouse makes the GROUP the addressable model (its
4
+ // `model_id` / `model_route_id`). Every variant, dated snapshot, or
5
+ // older-cased spelling the producer folds into a group lives only in that group
6
+ // row's `raw_model_ids` list and has no page of its own. This builds the
7
+ // old-route -> canonical-group-route 301 map straight from `models_view`, so a
8
+ // link to any folded spelling lands on the real group page. Every redirect
9
+ // target is an addressable `model_route_id` by construction.
10
+ //
11
+ // Kept separate from the generated data file (`lib/model-url-redirects.ts`) so
12
+ // the generator script AND unit tests can exercise the build logic without
13
+ // importing the (large) generated map.
14
+ //
15
+ // Route id form: RFC 3986 percent-encoded whole id (`/` -> `%2F`), matching the
16
+ // producer's `route_id` column and middleware's `encodeURIComponent` lookup key.
17
+
18
+ export interface ModelsViewRow {
19
+ /** canonical encoded route for this group — the redirect TARGET. */
20
+ model_route_id: string
21
+ model_id?: string | null
22
+ route_id?: string | null
23
+ model_key?: string | null
24
+ model_group_id?: string | null
25
+ /** raw spellings folded into this group (HF-cased, may contain `/`). */
26
+ raw_model_ids?: string[] | null
27
+ }
28
+
29
+ /** Encode a plain model id into the producer `route_id` form. */
30
+ export function routeId(id: string): string {
31
+ return encodeURIComponent(id.trim())
32
+ }
33
+
34
+ export interface BuildResult {
35
+ /** old encoded route id -> canonical (group) encoded route id. */
36
+ redirects: Map<string, string>
37
+ /**
38
+ * Raw spellings that fan out to MORE THAN ONE group (would be ambiguous to
39
+ * redirect). Excluded from the map and surfaced for review rather than
40
+ * picking one arbitrarily. Expected to be empty for a well-formed warehouse.
41
+ */
42
+ ambiguous: Map<string, Set<string>>
43
+ }
44
+
45
+ /**
46
+ * Build the redirect map from models_view rows. Pure and deterministic.
47
+ *
48
+ * A folded spelling is redirected to its owning group's `model_route_id` unless
49
+ * (a) it already equals that route, or (b) it is itself an addressable page
50
+ * (some row's own id) — we never hijack a working URL. A spelling that maps to
51
+ * multiple distinct groups is recorded as ambiguous and excluded.
52
+ */
53
+ export function buildRedirectsFromModelsView(rows: ModelsViewRow[]): BuildResult {
54
+ // Pass 1: every route form that resolves to a real page (mirrors the
55
+ // getModelSummaryById lookup columns), so we never redirect a live URL.
56
+ const addressable = new Set<string>()
57
+ for (const r of rows) {
58
+ if (r.model_route_id) addressable.add(r.model_route_id)
59
+ if (r.route_id) addressable.add(r.route_id)
60
+ if (r.model_id) addressable.add(routeId(r.model_id))
61
+ if (r.model_group_id) addressable.add(routeId(r.model_group_id))
62
+ if (r.model_key) addressable.add(routeId(r.model_key))
63
+ }
64
+
65
+ // Pass 2: map each folded raw spelling to its owning group route.
66
+ const candidates = new Map<string, Set<string>>()
67
+ for (const r of rows) {
68
+ const target = r.model_route_id
69
+ if (!target) continue
70
+ for (const raw of r.raw_model_ids ?? []) {
71
+ if (!raw) continue
72
+ const old = routeId(raw)
73
+ if (old === target) continue // already the canonical route
74
+ if (addressable.has(old)) continue // has its own page; don't hijack it
75
+ const set = candidates.get(old) ?? new Set<string>()
76
+ set.add(target)
77
+ candidates.set(old, set)
78
+ }
79
+ }
80
+
81
+ const redirects = new Map<string, string>()
82
+ const ambiguous = new Map<string, Set<string>>()
83
+ for (const [old, targets] of candidates) {
84
+ if (targets.size === 1) redirects.set(old, [...targets][0])
85
+ else ambiguous.set(old, targets)
86
+ }
87
+ return { redirects, ambiguous }
88
+ }
89
+
90
+ /** Serialise a redirect map to the generated TS module source. */
91
+ export function serializeRedirectModule(
92
+ redirects: Map<string, string>,
93
+ meta: { source: string },
94
+ ): string {
95
+ const sorted = [...redirects.entries()].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
96
+ const entries = sorted.map(([o, n]) => ` [${JSON.stringify(o)}, ${JSON.stringify(n)}],`).join("\n")
97
+ return `// GENERATED by scripts/generate-model-redirects.ts — do not edit by hand.
98
+ //
99
+ // Derived from the warehouse models_view (${meta.source}). Each entry maps a
100
+ // folded model spelling (a variant / dated snapshot / older-cased id the
101
+ // producer collapses into a group) to that group's canonical route, so a link
102
+ // to any folded spelling 301s to the real group page. Every target is an
103
+ // addressable model_route_id by construction.
104
+ //
105
+ // Entries: ${redirects.size}.
106
+ //
107
+ // Old encoded route id -> canonical encoded route id (RFC 3986, \`/\` -> %2F).
108
+ // middleware.ts looks up the incoming route here and issues a 301 (query params
109
+ // preserved).
110
+
111
+ /** old encoded route id -> canonical (group) encoded route id. */
112
+ export const MODEL_URL_REDIRECTS = new Map<string, string>([
113
+ ${entries}
114
+ ])
115
+
116
+ export function resolveModelRedirect(routeId: string): string | undefined {
117
+ return MODEL_URL_REDIRECTS.get(routeId)
118
+ }
119
+ `
120
+ }
lib/model-url-redirects.ts ADDED
@@ -0,0 +1,1128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // GENERATED by scripts/generate-model-redirects.ts — do not edit by hand.
2
+ //
3
+ // Derived from the warehouse models_view (https://huggingface.co/datasets/evaleval/card_backend/resolve/main/warehouse/2026-06-05T02-06-42Z). Each entry maps a
4
+ // folded model spelling (a variant / dated snapshot / older-cased id the
5
+ // producer collapses into a group) to that group's canonical route, so a link
6
+ // to any folded spelling 301s to the real group page. Every target is an
7
+ // addressable model_route_id by construction.
8
+ //
9
+ // Entries: 1107.
10
+ //
11
+ // Old encoded route id -> canonical encoded route id (RFC 3986, `/` -> %2F).
12
+ // middleware.ts looks up the incoming route here and issues a 301 (query params
13
+ // preserved).
14
+
15
+ /** old encoded route id -> canonical (group) encoded route id. */
16
+ export const MODEL_URL_REDIRECTS = new Map<string, string>([
17
+ ["01-ai%2FYi-1.5-34B-Chat", "01-ai%2FYi-1.5-34B"],
18
+ ["01-ai%2FYi-1.5-6B-Chat", "01-ai%2FYi-1.5-6B"],
19
+ ["01-ai%2FYi-1.5-9B-Chat", "01-ai%2FYi-1.5-9B"],
20
+ ["01-ai%2FYi-34B-Chat", "01-ai%2FYi-34B"],
21
+ ["01-ai%2FYi-6B-Chat", "01-ai%2FYi-6B"],
22
+ ["01-ai%2FYi-Coder-9B-Chat", "01-ai%2Fyi-coder-9b"],
23
+ ["01-ai%2Fyi-1.5-34b-chat", "01-ai%2FYi-1.5-34B"],
24
+ ["01-ai%2Fyi-1.5-6b-chat", "01-ai%2FYi-1.5-6B"],
25
+ ["01-ai%2Fyi-1.5-9b-chat", "01-ai%2FYi-1.5-9B"],
26
+ ["01-ai%2Fyi-34b", "01-ai%2FYi-34B"],
27
+ ["01-ai%2Fyi-34b-chat", "01-ai%2FYi-34B"],
28
+ ["01-ai%2Fyi-6b", "01-ai%2FYi-6B"],
29
+ ["01-ai%2Fyi-6b-chat", "01-ai%2FYi-6B"],
30
+ ["1TuanPham%2FT-VisStar-v0.1", "1TuanPham%2FT-VisStar-7B-v0.1"],
31
+ ["Ahjeong%2FMMPO_Gemma_7b", "Ahjeong%2FMMPO_Gemma_7b_gamma1.1_epoch3"],
32
+ ["Alepach%2FnotHumpback-M1", "Alepach%2FnotHumpback-M1-3b"],
33
+ ["Anthropic%2Fclaude-3-5-sonnet-20240620", "anthropic%2Fclaude-3.5-sonnet"],
34
+ ["Anthropic%2Fclaude-3-haiku-20240307", "anthropic%2Fclaude-3-haiku"],
35
+ ["Anthropic%2Fclaude-3-opus-20240229", "anthropic%2Fclaude-3-opus"],
36
+ ["Anthropic%2Fclaude-3-sonnet-20240229", "anthropic%2Fclaude-3-sonnet"],
37
+ ["Anthropic-LM-v4-s3-52B", "anthropic-lm-v4-s3-52b"],
38
+ ["ArliAI%2FArliAI-RPMax-12B-v1.1", "ArliAI%2FMistral-Nemo-12B-ArliAI-RPMax-v1.1"],
39
+ ["AtlaAI%2FSelene-1", "AtlaAI%2FSelene-1-Llama-3.3-70B"],
40
+ ["BAAI%2FInfinity-Instruct-7M-0729-Llama3_1-8B", "BAAI%2FInfinity-Instruct-7M-Gen-Llama3_1-8B"],
41
+ ["BAAI%2FInfinity-Instruct-7M-0729-mistral-7B", "BAAI%2FInfinity-Instruct-7M-Gen-mistral-7B"],
42
+ ["BEE-spoke-data%2FtFINE-900m-e16-d32-flan-infinity-instruct-7m-T2T_en-1024", "BEE-spoke-data%2FtFINE-900m-e16-d32-instruct"],
43
+ ["CYFRAGOVPL%2FLlama-PLLuM-8B-base", "CYFRAGOVPL%2FLlama-PLLuM-8B-base-2412"],
44
+ ["CYFRAGOVPL%2FLlama-PLLuM-8B-chat", "CYFRAGOVPL%2FLlama-PLLuM-8B-chat-2412"],
45
+ ["CYFRAGOVPL%2FPLLuM-12B-base", "CYFRAGOVPL%2FPLLuM-12B-base-2412"],
46
+ ["CYFRAGOVPL%2FPLLuM-12B-chat", "CYFRAGOVPL%2FPLLuM-12B-chat-2412"],
47
+ ["CYFRAGOVPL%2FPLLuM-12B-nc-base", "CYFRAGOVPL%2FPLLuM-12B-nc-base-2412"],
48
+ ["CYFRAGOVPL%2FPLLuM-12B-nc-chat", "CYFRAGOVPL%2FPLLuM-12B-nc-chat-2412"],
49
+ ["Cohere%20March%202024", "cohere-march-2024"],
50
+ ["Cohere%20May%202024", "cohere-may-2024"],
51
+ ["CohereForAI%2Faya-23-35B", "CohereLabs%2Faya-23-35B"],
52
+ ["CohereForAI%2Faya-23-8B", "CohereLabs%2Faya-23-8B"],
53
+ ["CohereForAI%2Faya-expanse-32b", "CohereLabs%2Faya-expanse-32b"],
54
+ ["CohereForAI%2Faya-expanse-8b", "CohereLabs%2Faya-expanse-8b"],
55
+ ["CohereForAI%2Fc4ai-command-r-plus", "CohereLabs%2Fc4ai-command-r-plus"],
56
+ ["CohereForAI%2Fc4ai-command-r-plus-08-2024", "CohereLabs%2Fc4ai-command-r-plus-08-2024"],
57
+ ["CohereForAI%2Fc4ai-command-r-v01", "CohereLabs%2Fc4ai-command-r-v01"],
58
+ ["CohereForAI%2Fc4ai-command-r7b-12-2024", "CohereLabs%2Fc4ai-command-r7b-12-2024"],
59
+ ["CohereLabs%2Fcommand-a-reasoning-08-2025", "cohere%2Fcommand-a-reasoning"],
60
+ ["DeepAutoAI%2FExplore_Llama-3.1-8B-Inst", "DeepAuto-AI%2FExplore_Llama-3.1-8B-Inst"],
61
+ ["DeepAutoAI%2FExplore_Llama-3.2-1B-Inst", "DeepAuto-AI%2FExplore_Llama-3.2-1B-Inst"],
62
+ ["DeepAutoAI%2FExplore_Llama-3.2-1B-Inst_v0", "DeepAuto-AI%2FExplore_Llama-3.2-1B-Inst_v0"],
63
+ ["DeepAutoAI%2FExplore_Llama-3.2-1B-Inst_v1", "DeepAuto-AI%2FExplore_Llama-3.2-1B-Inst_v1"],
64
+ ["DeepAutoAI%2FExplore_Llama-3.2-1B-Inst_v1.1", "DeepAuto-AI%2FExplore_Llama-3.2-1B-Inst_v1.1"],
65
+ ["DeepAutoAI%2Fcausal_gpt2", "DeepAuto-AI%2Fcausal_gpt2"],
66
+ ["DeepAutoAI%2Fd2nwg_Llama-3.1-8B-Instruct-v0.0", "DeepAuto-AI%2Fd2nwg_Llama-3.1-8B-Instruct-v0.0"],
67
+ ["DeepAutoAI%2Fd2nwg_causal_gpt2", "DeepAuto-AI%2Fd2nwg_causal_gpt2"],
68
+ ["DeepAutoAI%2Fd2nwg_causal_gpt2_v1", "DeepAuto-AI%2Fd2nwg_causal_gpt2_v1"],
69
+ ["DeepAutoAI%2Fldm_soup_Llama-3.1-8B-Inst", "DeepAuto-AI%2Fldm_soup_Llama-3.1-8B-Inst"],
70
+ ["DeepAutoAI%2Fldm_soup_Llama-3.1-8B-Instruct-v0.0", "DeepAuto-AI%2Fldm_soup_Llama-3.1-8B-Instruct-v0.0"],
71
+ ["DeepAutoAI%2Fldm_soup_Llama-3.1-8B-Instruct-v0.1", "DeepAuto-AI%2Fldm_soup_Llama-3.1-8B-Instruct-v0.1"],
72
+ ["DeepMount00%2FLexora-Lite-3B", "DeepMount00%2FLexora-Lite-3B_v2"],
73
+ ["DeepMount00%2FLlama-3.1-8b-Ita", "DeepMount00%2FLlama-3.1-8b-ITA"],
74
+ ["DeepSeek-Coder-V2-Lite-Instruct", "deepseek-coder-v2-lite-instruct"],
75
+ ["DeepSeek-V2-Lite-Chat", "deepseek-ai%2FDeepSeek-V2-Lite"],
76
+ ["Dracarys2-72B-Instruct", "dracarys2-72b-instruct"],
77
+ ["HuggingFaceTB%2FSmolLM-1.7B-Instruct", "HuggingFaceTB%2FSmolLM-1.7B"],
78
+ ["HuggingFaceTB%2FSmolLM-135M-Instruct", "HuggingFaceTB%2FSmolLM-135M"],
79
+ ["HuggingFaceTB%2FSmolLM-360M-Instruct", "HuggingFaceTB%2FSmolLM-360M"],
80
+ ["HuggingFaceTB%2FSmolLM2-1.7B-Instruct", "HuggingFaceTB%2FSmolLM2-1.7B"],
81
+ ["HuggingFaceTB%2FSmolLM2-135M-Instruct", "HuggingFaceTB%2FSmolLM2-135M"],
82
+ ["HuggingFaceTB%2FSmolLM2-360M-Instruct", "HuggingFaceTB%2FSmolLM2-360M"],
83
+ ["Krystalan%2FDRT-o1-14B", "Krystalan%2FDRT-14B"],
84
+ ["Krystalan%2FDRT-o1-7B", "Krystalan%2FDRT-7B"],
85
+ ["LEESM%2Fllama-2-7b-hf-lora-oki100p", "leesm%2Fllama-2-7b-hf-lora-oki100p"],
86
+ ["LEESM%2Fllama-2-7b-hf-lora-oki10p", "leesm%2Fllama-2-7b-hf-lora-oki10p"],
87
+ ["LEESM%2Fllama-3-8b-bnb-4b-kowiki231101", "leesm%2Fllama-3-8b-bnb-4b-kowiki231101"],
88
+ ["LEESM%2Fllama-3-Korean-Bllossom-8B-trexlab-oki10p", "leesm%2Fllama-3-Korean-Bllossom-8B-trexlab-oki10p"],
89
+ ["Llama-2-7b-chat-hf", "meta-llama%2FLlama-2-7b-chat-hf"],
90
+ ["Llama-3.1-Nemotron-70B-Instruct-HF", "nvidia%2FLlama-3.1-Nemotron-70B-Instruct-HF"],
91
+ ["Meta-Llama-3-70B-Instruct", "meta-llama%2Fllama-3"],
92
+ ["Meta-Llama-3-8B-Instruct", "meta-llama%2Fllama-3"],
93
+ ["Mistral-7B-Instruct-v0.2", "mistralai%2FMistral-7B-Instruct-v0.2"],
94
+ ["Mistral-7B-Instruct-v0.3", "mistralai%2Fmistral-7b"],
95
+ ["Mixtral-8x22B-Instruct-v0.1", "mistralai%2FMixtral-8x22B-Instruct-v0.1"],
96
+ ["Mixtral-8x7B-Instruct-v0.1", "mistralai%2FMixtral-8x7B-Instruct-v0.1"],
97
+ ["OpenAssistant%2Foasst-rm-2-pythia-6.9b-epoch-1", "OpenAssistant%2Foasst-rm-pythia"],
98
+ ["OpenAssistant%2Foasst-rm-2.1-pythia-1.4b-epoch-2.5", "OpenAssistant%2Foasst-rm-pythia"],
99
+ ["OpenHermes-2.5-Mistral-7B", "teknium%2FOpenHermes-2.5-Mistral-7B"],
100
+ ["Phi-3-medium-128k-instruct", "microsoft%2Fphi-3-medium-128k"],
101
+ ["Phi-3-medium-4k-instruct", "microsoft%2Fphi-3-medium-4k"],
102
+ ["Phi-3-mini-128k-instruct", "microsoft%2Fphi-3-mini-128k"],
103
+ ["Phi-3-mini-4k-instruct", "microsoft%2Fphi-3-mini-4k"],
104
+ ["Phi-3-small-128k-instruct", "microsoft%2Fphi-3-small-128k"],
105
+ ["Phi-3-small-8k-instruct", "microsoft%2Fphi-3-small-8k"],
106
+ ["Phi-3.5-MoE-instruct", "microsoft%2Fphi-3.5-moe"],
107
+ ["Phi-3.5-mini-instruct", "microsoft%2Fphi-3.5-mini"],
108
+ ["Quazim0t0%2FODB-14b-sce", "Quazim0t0%2FODB-14B-sce"],
109
+ ["Qwen%2FQwen1.5-110B-Chat", "Qwen%2FQwen1.5-110B"],
110
+ ["Qwen%2FQwen1.5-14B-Chat", "Qwen%2FQwen1.5-14B"],
111
+ ["Qwen%2FQwen1.5-32B-Chat", "Qwen%2FQwen1.5-32B"],
112
+ ["Qwen%2FQwen1.5-72B-Chat", "Qwen%2FQwen1.5-72B"],
113
+ ["Qwen%2FQwen1.5-7B-Chat", "Qwen%2FQwen1.5-7B"],
114
+ ["Qwen%2FQwen2-1.5B-Instruct", "Qwen%2FQwen2-1.5B"],
115
+ ["Qwen%2FQwen2-72B-Instruct", "Qwen%2FQwen2-72B"],
116
+ ["Qwen%2FQwen2-7B-Instruct", "Qwen%2FQwen2-7B"],
117
+ ["Qwen%2FQwen2-VL-2B-Instruct", "qwen%2Fqwen2-vl-2b"],
118
+ ["Qwen%2FQwen2-VL-72B-Instruct", "Qwen%2FQwen2-VL-72B"],
119
+ ["Qwen%2FQwen2-VL-7B-Instruct", "qwen%2Fqwen2-vl-7b"],
120
+ ["Qwen%2FQwen2.5-0.5B-Instruct", "Qwen%2FQwen2.5-0.5B"],
121
+ ["Qwen%2FQwen2.5-1.5B-Instruct", "Qwen%2FQwen2.5-1.5B"],
122
+ ["Qwen%2FQwen2.5-14B-Instruct", "Qwen%2FQwen2.5-14B"],
123
+ ["Qwen%2FQwen2.5-32B-Instruct", "Qwen%2FQwen2.5-32B"],
124
+ ["Qwen%2FQwen2.5-3B-Instruct", "Qwen%2FQwen2.5-3B"],
125
+ ["Qwen%2FQwen2.5-72B-Instruct", "Qwen%2FQwen2.5-72B"],
126
+ ["Qwen%2FQwen2.5-72B-Instruct-Turbo", "Qwen%2FQwen2.5-72B"],
127
+ ["Qwen%2FQwen2.5-7B-Instruct", "Qwen%2FQwen2.5-7B"],
128
+ ["Qwen%2FQwen2.5-7B-Instruct-Turbo", "Qwen%2FQwen2.5-7B"],
129
+ ["Qwen%2FQwen2.5-Coder-14B-Instruct", "Qwen%2FQwen2.5-Coder-14B"],
130
+ ["Qwen%2FQwen2.5-Coder-32B-Instruct", "Qwen%2FQwen2.5-Coder-32B"],
131
+ ["Qwen%2FQwen2.5-Coder-7B-Instruct", "Qwen%2FQwen2.5-Coder-7B"],
132
+ ["Qwen%2FQwen2.5-Math-72B-Instruct", "qwen%2Fqwen2.5-math-72b"],
133
+ ["Qwen%2FQwen2.5-Math-7B-Instruct", "Qwen%2FQwen2.5-Math-7B"],
134
+ ["Qwen%2FQwen2.5-VL-72B-Instruct", "alibaba%2Fqwen2.5-vl-72b"],
135
+ ["Qwen%2FQwen3-30B-A3B-Thinking-2507", "Qwen%2FQwen3-30B-A3B"],
136
+ ["Qwen%2FQwen3-Next-80B-A3B-Thinking", "alibaba%2Fqwen3-next-80b-a3b"],
137
+ ["Qwen%2FQwen3-VL-235B-A22B-Thinking-FP8", "alibaba%2Fqwen3-vl-235b-a22b"],
138
+ ["Qwen%2FQwen3.5-122B-A10B", "alibaba%2Fqwen3-5-122b"],
139
+ ["Qwen%2FQwen3.5-35B-A3B", "qwen%2Fqwen3.5-35b"],
140
+ ["Qwen%2FQwen3.5-397B-A17B", "alibaba%2Fqwen3-5-397b"],
141
+ ["Qwen%2FQwen3.6-35B-A3B", "alibaba%2Fqwen3-6-35b"],
142
+ ["Qwen1.5-0.5B-Chat", "Qwen%2FQwen1.5-0.5B-Chat"],
143
+ ["Qwen1.5-1.8B-Chat", "Qwen%2FQwen1.5-1.8B-Chat"],
144
+ ["Qwen1.5-110B-Chat", "Qwen%2FQwen1.5-110B"],
145
+ ["Qwen1.5-4B-Chat", "Qwen%2FQwen1.5-4B-Chat"],
146
+ ["Qwen1.5-72B-Chat", "Qwen%2FQwen1.5-72B"],
147
+ ["Qwen1.5-7B-Chat", "Qwen%2FQwen1.5-7B"],
148
+ ["Qwen2-0.5B-Instruct", "Qwen%2FQwen2-0.5B-Instruct"],
149
+ ["Qwen2-1.5B-Instruct", "Qwen%2FQwen2-1.5B"],
150
+ ["Qwen2-72B-Instruct", "Qwen%2FQwen2-72B"],
151
+ ["Qwen2-7B-Instruct", "Qwen%2FQwen2-7B"],
152
+ ["Ray2333%2FGRM-llama3.2-3B-rewardmodel-ft", "Ray2333%2FGRM-Llama3.2-3B-rewardmodel-ft"],
153
+ ["Reflection-Llama-3.1-70B", "mattshumer%2FReflection-Llama-3.1-70B"],
154
+ ["Sakalti%2FMagro-7B-v1.1", "Sakalti%2FMagro-7b-v1.1"],
155
+ ["Sakalti%2FOxyge1-33B", "Sakalti%2Foxyge1-33B"],
156
+ ["Sakalti%2Flight-3b-beta", "Sakalti%2Flight-3B-beta"],
157
+ ["Sakalti%2Fllama-3-yanyuedao-8b-instruct", "Sakalti%2Fllama-3-yanyuedao-8b-Instruct"],
158
+ ["SkyOrbis%2FSKY-Ko-Qwen2.5-3B-Instruct", "SkyOrbis%2FSKY-Ko-Qwen2.5-3B-Instruct-SFT"],
159
+ ["Skywork%2FSkywork-Reward-V2-Qwen3-0.6B", "Skywork%2Fskywork-reward-v2-qwen3"],
160
+ ["Skywork%2FSkywork-Reward-V2-Qwen3-1.7B", "Skywork%2Fskywork-reward-v2-qwen3"],
161
+ ["Skywork%2FSkywork-Reward-V2-Qwen3-4B", "Skywork%2Fskywork-reward-v2-qwen3"],
162
+ ["Skywork%2FSkywork-Reward-V2-Qwen3-8B", "Skywork%2Fskywork-reward-v2-qwen3"],
163
+ ["Smaug-Qwen2-72B-Instruct", "abacusai%2FSmaug-Qwen2-72B-Instruct"],
164
+ ["Solshine%2FBrimful-merged-replete", "Solshine%2FBrimful-12B-merged-replete"],
165
+ ["Starling-LM-7B-beta", "starling-lm-7b-beta"],
166
+ ["SultanR%2FSmolTulu-1.7b-it-v0", "SultanR%2FSmolTulu-1.7b-Instruct"],
167
+ ["THUDM%2Fcogvlm2-llama3-chat-19B", "zai-org%2Fcogvlm2-llama3-chat-19B"],
168
+ ["THUDM%2Fglm-4-9b", "zai-org%2Fglm-4-9b"],
169
+ ["THUDM%2Fglm-4-9b-chat", "zai-org%2Fglm-4-9b"],
170
+ ["THUDM%2Fglm-4-9b-chat-1m", "zai-org%2Fglm-4-9b-chat-1m"],
171
+ ["THUDM%2Fglm-4-9b-chat-1m-hf", "zai-org%2Fglm-4-9b-chat-1m-hf"],
172
+ ["THUDM%2Fglm-4-9b-chat-hf", "zai-org%2Fglm-4-9b-chat-hf"],
173
+ ["XiaomiMiMo%2FMiMo-V2-Flash", "xiaomi%2Fmimo-v2"],
174
+ ["YOYO-AI%2FQwen2.5-14B-1M-YOYO-V3", "YOYO-AI%2FQwen2.5-14B-YOYO-V3"],
175
+ ["Yi-6B-Chat", "01-ai%2FYi-6B"],
176
+ ["acm_rewrite_qwen2-72B-Chat", "acm-rewrite-qwen2-72b-chat"],
177
+ ["ai2%2Folmo-3-1-32b-instruct", "allenai%2FOlmo-3.1-32B-Instruct"],
178
+ ["ai2%2Folmo-3-1-32b-think", "allenai%2FOlmo-3.1-32B-Think"],
179
+ ["ai21%2Fjamba-1.5-large", "ai21-labs%2Fai21-jamba-1-5-large"],
180
+ ["ai21%2Fjamba-1.5-mini", "ai21-labs%2Fai21-jamba-1-5-mini"],
181
+ ["ai21%2Fjamba-instruct", "ai21%2Fjamba"],
182
+ ["alibaba%2FQwen2.5-72B-Instruct", "Qwen%2FQwen2.5-72B"],
183
+ ["alibaba%2FQwen2.5-Coder-32B-Instruct", "Qwen%2FQwen2.5-Coder-32B"],
184
+ ["alibaba%2FQwen3-Coder-30B-A3B-Instruct", "alibaba%2Fqwen3-coder-30b-a3b"],
185
+ ["alibaba%2Fqwen-1.5-0.5b-chat", "Qwen%2FQwen1.5-0.5B-Chat"],
186
+ ["alibaba%2Fqwen-1.5-1.8b-chat", "Qwen%2FQwen1.5-1.8B-Chat"],
187
+ ["alibaba%2Fqwen-1.5-14b-chat", "Qwen%2FQwen1.5-14B"],
188
+ ["alibaba%2Fqwen-1.5-32b-chat", "Qwen%2FQwen1.5-32B"],
189
+ ["alibaba%2Fqwen-1.5-4b-chat", "Qwen%2FQwen1.5-4B-Chat"],
190
+ ["alibaba%2Fqwen-1.5-7b-chat", "Qwen%2FQwen1.5-7B"],
191
+ ["alibaba%2Fqwen-2-1.5b-instruct", "Qwen%2FQwen2-1.5B"],
192
+ ["alibaba%2Fqwen-2-72b-instruct", "Qwen%2FQwen2-72B"],
193
+ ["alibaba%2Fqwen-2-7b-instruct", "Qwen%2FQwen2-7B"],
194
+ ["alibaba%2Fqwen-2.5-0.5b-instruct", "Qwen%2FQwen2.5-0.5B"],
195
+ ["alibaba%2Fqwen-2.5-1.5b-instruct", "Qwen%2FQwen2.5-1.5B"],
196
+ ["alibaba%2Fqwen-2.5-14b-instruct", "Qwen%2FQwen2.5-14B"],
197
+ ["alibaba%2Fqwen-2.5-32b-instruct", "Qwen%2FQwen2.5-32B"],
198
+ ["alibaba%2Fqwen-2.5-3b-instruct", "Qwen%2FQwen2.5-3B"],
199
+ ["alibaba%2Fqwen-2.5-72b-instruct", "Qwen%2FQwen2.5-72B"],
200
+ ["alibaba%2Fqwen-2.5-7b-instruct", "Qwen%2FQwen2.5-7B"],
201
+ ["alibaba%2Fqwen1.5-110b", "Qwen%2FQwen1.5-110B"],
202
+ ["alibaba%2Fqwen1.5-110b-chat", "Qwen%2FQwen1.5-110B"],
203
+ ["alibaba%2Fqwen1.5-14b-chat", "Qwen%2FQwen1.5-14B"],
204
+ ["alibaba%2Fqwen1.5-72b-chat", "Qwen%2FQwen1.5-72B"],
205
+ ["alibaba%2Fqwen1.5-7b-chat", "Qwen%2FQwen1.5-7B"],
206
+ ["alibaba%2Fqwen2-0.5b", "Qwen%2FQwen2-0.5B"],
207
+ ["alibaba%2Fqwen2-0.5b-instruct", "Qwen%2FQwen2-0.5B-Instruct"],
208
+ ["alibaba%2Fqwen2-1.5b", "Qwen%2FQwen2-1.5B"],
209
+ ["alibaba%2Fqwen2-1.5b-instruct", "Qwen%2FQwen2-1.5B"],
210
+ ["alibaba%2Fqwen2-5-72b-instruct", "Qwen%2FQwen2.5-72B"],
211
+ ["alibaba%2Fqwen2-5-coder-32b-instruct", "Qwen%2FQwen2.5-Coder-32B"],
212
+ ["alibaba%2Fqwen2-5-coder-7b-instruct", "Qwen%2FQwen2.5-Coder-7B"],
213
+ ["alibaba%2Fqwen2-72b-instruct", "Qwen%2FQwen2-72B"],
214
+ ["alibaba%2Fqwen2-7b", "Qwen%2FQwen2-7B"],
215
+ ["alibaba%2Fqwen2-7b-instruct", "Qwen%2FQwen2-7B"],
216
+ ["alibaba%2Fqwen2.5-0.5b", "Qwen%2FQwen2.5-0.5B"],
217
+ ["alibaba%2Fqwen2.5-1.5b", "Qwen%2FQwen2.5-1.5B"],
218
+ ["alibaba%2Fqwen2.5-14b", "Qwen%2FQwen2.5-14B"],
219
+ ["alibaba%2Fqwen2.5-32b", "Qwen%2FQwen2.5-32B"],
220
+ ["alibaba%2Fqwen2.5-32b-instruct", "Qwen%2FQwen2.5-32B"],
221
+ ["alibaba%2Fqwen2.5-3b", "Qwen%2FQwen2.5-3B"],
222
+ ["alibaba%2Fqwen2.5-72b", "Qwen%2FQwen2.5-72B"],
223
+ ["alibaba%2Fqwen2.5-7b", "Qwen%2FQwen2.5-7B"],
224
+ ["alibaba%2Fqwen3-0.6b-instruct", "qwen%2Fqwen3-0.6b"],
225
+ ["alibaba%2Fqwen3-0.6b-instruct-reasoning", "qwen%2Fqwen3-0.6b"],
226
+ ["alibaba%2Fqwen3-1.7b-instruct", "Qwen%2FQwen3-1.7B"],
227
+ ["alibaba%2Fqwen3-1.7b-instruct-reasoning", "Qwen%2FQwen3-1.7B"],
228
+ ["alibaba%2Fqwen3-14b-instruct", "Qwen%2FQwen3-14B"],
229
+ ["alibaba%2Fqwen3-14b-instruct-reasoning", "Qwen%2FQwen3-14B"],
230
+ ["alibaba%2Fqwen3-235b-a22b", "Qwen%2FQwen3-235B-A22B"],
231
+ ["alibaba%2Fqwen3-235b-a22b-instruct", "Qwen%2FQwen3-235B-A22B"],
232
+ ["alibaba%2Fqwen3-235b-a22b-instruct-2507", "Qwen%2FQwen3-235B-A22B"],
233
+ ["alibaba%2Fqwen3-235b-a22b-instruct-2507-reasoning", "Qwen%2FQwen3-235B-A22B"],
234
+ ["alibaba%2Fqwen3-235b-a22b-instruct-reasoning", "Qwen%2FQwen3-235B-A22B"],
235
+ ["alibaba%2Fqwen3-235b-a22b-thinking-2507", "Qwen%2FQwen3-235B-A22B"],
236
+ ["alibaba%2Fqwen3-30b-a3b", "Qwen%2FQwen3-30B-A3B"],
237
+ ["alibaba%2Fqwen3-30b-a3b-2507", "Qwen%2FQwen3-30B-A3B"],
238
+ ["alibaba%2Fqwen3-30b-a3b-2507-reasoning", "Qwen%2FQwen3-30B-A3B"],
239
+ ["alibaba%2Fqwen3-30b-a3b-base", "Qwen%2FQwen3-30B-A3B"],
240
+ ["alibaba%2Fqwen3-30b-a3b-instruct", "Qwen%2FQwen3-30B-A3B"],
241
+ ["alibaba%2Fqwen3-30b-a3b-instruct-reasoning", "Qwen%2FQwen3-30B-A3B"],
242
+ ["alibaba%2Fqwen3-30b-a3b-thinking", "Qwen%2FQwen3-30B-A3B"],
243
+ ["alibaba%2Fqwen3-30b-a3b-thinking-2507", "Qwen%2FQwen3-30B-A3B"],
244
+ ["alibaba%2Fqwen3-32b-instruct", "Qwen%2FQwen3-32B"],
245
+ ["alibaba%2Fqwen3-32b-instruct-reasoning", "Qwen%2FQwen3-32B"],
246
+ ["alibaba%2Fqwen3-4b-2507-instruct", "alibaba%2Fqwen3-4b"],
247
+ ["alibaba%2Fqwen3-4b-2507-instruct-reasoning", "alibaba%2Fqwen3-4b"],
248
+ ["alibaba%2Fqwen3-4b-instruct-reasoning", "alibaba%2Fqwen3-4b"],
249
+ ["alibaba%2Fqwen3-5-0-8b", "Qwen%2FQwen3.5-0.8B"],
250
+ ["alibaba%2Fqwen3-5-122b-a10b", "alibaba%2Fqwen3-5-122b"],
251
+ ["alibaba%2Fqwen3-5-27b", "Qwen%2FQwen3.5-27B"],
252
+ ["alibaba%2Fqwen3-5-2b", "Qwen%2FQwen3.5-2B"],
253
+ ["alibaba%2Fqwen3-5-35b-a3b", "qwen%2Fqwen3.5-35b"],
254
+ ["alibaba%2Fqwen3-5-397b-a17b", "alibaba%2Fqwen3-5-397b"],
255
+ ["alibaba%2Fqwen3-5-4b", "Qwen%2FQwen3.5-4B"],
256
+ ["alibaba%2Fqwen3-5-9b", "Qwen%2FQwen3.5-9B"],
257
+ ["alibaba%2Fqwen3-6-plus", "alibaba%2Fqwen3.6-plus"],
258
+ ["alibaba%2Fqwen3-8b-instruct", "Qwen%2FQwen3-8B"],
259
+ ["alibaba%2Fqwen3-8b-instruct-reasoning", "Qwen%2FQwen3-8B"],
260
+ ["alibaba%2Fqwen3-coder-30b-a3b-instruct", "alibaba%2Fqwen3-coder-30b-a3b"],
261
+ ["alibaba%2Fqwen3-coder-480b-a35b-instruct", "alibaba%2Fqwen3-coder-480b-a35b"],
262
+ ["alibaba%2Fqwen3-max-2026-01-23", "alibaba%2Fqwen3-max"],
263
+ ["alibaba%2Fqwen3-next-80b-a3b-instruct", "alibaba%2Fqwen3-next-80b-a3b"],
264
+ ["alibaba%2Fqwen3-next-80b-a3b-reasoning", "alibaba%2Fqwen3-next-80b-a3b"],
265
+ ["alibaba%2Fqwen3-next-80b-a3b-thinking", "alibaba%2Fqwen3-next-80b-a3b"],
266
+ ["alibaba%2Fqwen3-vl-235b-a22b-instruct", "alibaba%2Fqwen3-vl-235b-a22b"],
267
+ ["alibaba%2Fqwen3-vl-235b-a22b-reasoning", "alibaba%2Fqwen3-vl-235b-a22b"],
268
+ ["alibaba%2Fqwen3-vl-30b-a3b-instruct", "alibaba%2Fqwen3-vl-30b-a3b"],
269
+ ["alibaba%2Fqwen3-vl-30b-a3b-reasoning", "alibaba%2Fqwen3-vl-30b-a3b"],
270
+ ["alibaba%2Fqwen3-vl-32b-instruct", "Qwen%2FQwen3-VL-32B-Thinking"],
271
+ ["alibaba%2Fqwen3-vl-32b-reasoning", "Qwen%2FQwen3-VL-32B-Thinking"],
272
+ ["alibaba%2Fqwen3-vl-4b-instruct", "alibaba%2Fqwen3-vl-4b"],
273
+ ["alibaba%2Fqwen3-vl-4b-reasoning", "alibaba%2Fqwen3-vl-4b"],
274
+ ["alibaba%2Fqwen3-vl-8b-instruct", "Qwen%2FQwen3-VL-8B-Thinking"],
275
+ ["alibaba%2Fqwen3-vl-8b-reasoning", "Qwen%2FQwen3-VL-8B-Thinking"],
276
+ ["alibaba%2Fqwen3.5-0.8b", "Qwen%2FQwen3.5-0.8B"],
277
+ ["alibaba%2Fqwen3.5-122b-a10b", "alibaba%2Fqwen3-5-122b"],
278
+ ["alibaba%2Fqwen3.5-27b", "Qwen%2FQwen3.5-27B"],
279
+ ["alibaba%2Fqwen3.5-2b", "Qwen%2FQwen3.5-2B"],
280
+ ["alibaba%2Fqwen3.5-35b-a3b", "qwen%2Fqwen3.5-35b"],
281
+ ["alibaba%2Fqwen3.5-397b-a17b", "alibaba%2Fqwen3-5-397b"],
282
+ ["alibaba%2Fqwen3.5-4b", "Qwen%2FQwen3.5-4B"],
283
+ ["alibaba%2Fqwen3.5-9b", "Qwen%2FQwen3.5-9B"],
284
+ ["alibaba%2Fqwen3.5-flash", "alibaba%2Fqwen3-5-flash"],
285
+ ["alibaba%2Fqwen3.5-plus-thinking", "alibaba%2Fqwen3.5-plus"],
286
+ ["alibaba%2Fqwq-32b", "Qwen%2FQwQ-32B"],
287
+ ["alibaba%2Fqwq-32b-preview", "Qwen%2FQwQ-32B-Preview"],
288
+ ["allenai%2FOLMo-1.7-7B-hf", "allenai%2FOLMo-7B-hf"],
289
+ ["allenai%2FOLMo-2-1124-7B-Instruct", "allenai%2FOLMo-2-1124-7B"],
290
+ ["allenai%2FOLMo-7B-Instruct", "allenai%2FOLMo-7B"],
291
+ ["allenai%2FOLMoE-1B-7B-0125-Instruct", "allenai%2Folmoe-1b-7b-0125"],
292
+ ["allenai%2Folmo-1.7-7b", "allenai%2FOLMo-7B"],
293
+ ["allenai%2Folmo-2-0325-32b-instruct", "allenai%2Folmo-2-0325-32b"],
294
+ ["allenai%2Folmo-2-1124-13b-instruct", "allenai%2FOLMo-2-1124-13B"],
295
+ ["allenai%2Folmo-2-1124-7b-instruct", "allenai%2FOLMo-2-1124-7B"],
296
+ ["allenai%2Folmo-3.1-32b-instruct", "allenai%2FOlmo-3.1-32B-Instruct"],
297
+ ["allenai%2Folmo-7b", "allenai%2FOLMo-7B"],
298
+ ["allenai%2Folmoe-1b-7b-0125-instruct", "allenai%2Folmoe-1b-7b-0125"],
299
+ ["allknowingroger%2FGemmaSlerp-9B", "allknowingroger%2FGemmaslerp-9B"],
300
+ ["allknowingroger%2FGemmaSlerp2-9B", "allknowingroger%2FGemmaslerp2-9B"],
301
+ ["allknowingroger%2FGemmaSlerp4-10B", "allknowingroger%2FGemmaslerp4-10B"],
302
+ ["allknowingroger%2FQwenSlerp4-14B", "allknowingroger%2FQwenslerp4-14B"],
303
+ ["amazon%2FMegaBeam-Mistral-7B-300k", "aws-prototyping%2FMegaBeam-Mistral-7B-300k"],
304
+ ["anthropic%2Fclaude-3-5-haiku-20241022", "anthropic%2Fclaude-3-5-haiku"],
305
+ ["anthropic%2Fclaude-3-5-sonnet-20240620", "anthropic%2Fclaude-3.5-sonnet"],
306
+ ["anthropic%2Fclaude-3-5-sonnet-20241022", "anthropic%2Fclaude-3.5-sonnet"],
307
+ ["anthropic%2Fclaude-3-7-sonnet-20250219", "anthropic%2FClaude-3.7-Sonnet"],
308
+ ["anthropic%2Fclaude-3-haiku-20240307", "anthropic%2Fclaude-3-haiku"],
309
+ ["anthropic%2Fclaude-3-opus-20240229", "anthropic%2Fclaude-3-opus"],
310
+ ["anthropic%2Fclaude-3-sonnet-20240229", "anthropic%2Fclaude-3-sonnet"],
311
+ ["anthropic%2Fclaude-3.7-sonnet", "anthropic%2FClaude-3.7-Sonnet"],
312
+ ["anthropic%2Fclaude-3.7-sonnet-thinking", "anthropic%2FClaude-3.7-Sonnet"],
313
+ ["anthropic%2Fclaude-35-sonnet", "anthropic%2Fclaude-3.5-sonnet"],
314
+ ["anthropic%2Fclaude-4-sonnet", "anthropic%2FClaude-4-Sonnet"],
315
+ ["anthropic%2Fclaude-haiku-4-5-20251001", "anthropic%2Fclaude-haiku-4.5"],
316
+ ["anthropic%2Fclaude-haiku-4-5-20251001-fc", "anthropic%2Fclaude-haiku-4.5"],
317
+ ["anthropic%2Fclaude-haiku-4-5-20251001-prompt", "anthropic%2Fclaude-haiku-4.5"],
318
+ ["anthropic%2Fclaude-haiku-4-5-20251001-thinking", "anthropic%2Fclaude-haiku-4.5"],
319
+ ["anthropic%2Fclaude-haiku-4-5-20251001-thinking-16k", "anthropic%2Fclaude-haiku-4.5"],
320
+ ["anthropic%2Fclaude-haiku-4-5-20251001-thinking-1k", "anthropic%2Fclaude-haiku-4.5"],
321
+ ["anthropic%2Fclaude-haiku-4-5-20251001-thinking-32k", "anthropic%2Fclaude-haiku-4.5"],
322
+ ["anthropic%2Fclaude-haiku-4-5-20251001-thinking-8k", "anthropic%2Fclaude-haiku-4.5"],
323
+ ["anthropic%2Fclaude-opus-4-1", "anthropic%2Fclaude-opus-4.1"],
324
+ ["anthropic%2Fclaude-opus-4-1-20250805", "anthropic%2Fclaude-opus-4.1"],
325
+ ["anthropic%2Fclaude-opus-4-20250514", "anthropic%2Fclaude-opus-4"],
326
+ ["anthropic%2Fclaude-opus-4-20250514-thinking-10k", "anthropic%2Fclaude-opus-4-20250514-thinking"],
327
+ ["anthropic%2Fclaude-opus-4-20250514-thinking-16k", "anthropic%2Fclaude-opus-4-20250514-thinking"],
328
+ ["anthropic%2Fclaude-opus-4-20250514-thinking-1k", "anthropic%2Fclaude-opus-4-20250514-thinking"],
329
+ ["anthropic%2Fclaude-opus-4-20250514-thinking-8k", "anthropic%2Fclaude-opus-4-20250514-thinking"],
330
+ ["anthropic%2Fclaude-opus-4-5", "anthropic%2Fclaude-opus-4.5"],
331
+ ["anthropic%2Fclaude-opus-4-5-20251101", "anthropic%2Fclaude-opus-4.5"],
332
+ ["anthropic%2Fclaude-opus-4-5-20251101-fc", "anthropic%2Fclaude-opus-4.5"],
333
+ ["anthropic%2Fclaude-opus-4-5-20251101-prompt", "anthropic%2Fclaude-opus-4.5"],
334
+ ["anthropic%2Fclaude-opus-4-5-20251101-thinking-16k", "anthropic%2Fclaude-opus-4-5-20251101-thinking"],
335
+ ["anthropic%2Fclaude-opus-4-5-20251101-thinking-32k", "anthropic%2Fclaude-opus-4-5-20251101-thinking"],
336
+ ["anthropic%2Fclaude-opus-4-5-20251101-thinking-64k", "anthropic%2Fclaude-opus-4-5-20251101-thinking"],
337
+ ["anthropic%2Fclaude-opus-4-5-20251101-thinking-8k", "anthropic%2Fclaude-opus-4-5-20251101-thinking"],
338
+ ["anthropic%2Fclaude-opus-4-5-20251101-thinking-none", "anthropic%2Fclaude-opus-4.5"],
339
+ ["anthropic%2Fclaude-opus-4-5-thinking", "anthropic%2Fclaude-opus-4.5"],
340
+ ["anthropic%2Fclaude-opus-4-6", "anthropic%2Fclaude-opus-4.6"],
341
+ ["anthropic%2Fclaude-opus-4-6-thinking", "anthropic%2Fclaude-opus-4.6"],
342
+ ["anthropic%2Fclaude-opus-4-6-thinking-120k-high", "anthropic%2Fclaude-opus-4.6"],
343
+ ["anthropic%2Fclaude-opus-4-6-thinking-120k-low", "anthropic%2Fclaude-opus-4.6"],
344
+ ["anthropic%2Fclaude-opus-4-6-thinking-120k-medium", "anthropic%2Fclaude-opus-4.6"],
345
+ ["anthropic%2Fclaude-opus-4-7", "anthropic%2Fclaude-opus-4.7"],
346
+ ["anthropic%2Fclaude-opus-4-thinking", "anthropic%2Fclaude-opus-4"],
347
+ ["anthropic%2Fclaude-sonnet-4-20250514", "anthropic%2Fclaude-sonnet-4"],
348
+ ["anthropic%2Fclaude-sonnet-4-20250514-thinking", "anthropic%2Fclaude-sonnet-4"],
349
+ ["anthropic%2Fclaude-sonnet-4-20250514-thinking-10k", "anthropic%2Fclaude-sonnet-4"],
350
+ ["anthropic%2Fclaude-sonnet-4-20250514-thinking-16k-bedrock", "anthropic%2Fclaude-sonnet-4"],
351
+ ["anthropic%2Fclaude-sonnet-4-20250514-thinking-1k", "anthropic%2Fclaude-sonnet-4"],
352
+ ["anthropic%2Fclaude-sonnet-4-20250514-thinking-8k-bedrock", "anthropic%2Fclaude-sonnet-4"],
353
+ ["anthropic%2Fclaude-sonnet-4-5", "anthropic%2Fclaude-sonnet-4.5"],
354
+ ["anthropic%2Fclaude-sonnet-4-5-20250929", "anthropic%2Fclaude-sonnet-4.5"],
355
+ ["anthropic%2Fclaude-sonnet-4-5-20250929-fc", "anthropic%2Fclaude-sonnet-4.5"],
356
+ ["anthropic%2Fclaude-sonnet-4-5-20250929-prompt", "anthropic%2Fclaude-sonnet-4.5"],
357
+ ["anthropic%2Fclaude-sonnet-4-5-20250929-thinking-16k", "anthropic%2Fclaude-sonnet-4-5-20250929-thinking"],
358
+ ["anthropic%2Fclaude-sonnet-4-5-20250929-thinking-1k", "anthropic%2Fclaude-sonnet-4-5-20250929-thinking"],
359
+ ["anthropic%2Fclaude-sonnet-4-5-20250929-thinking-32k", "anthropic%2Fclaude-sonnet-4-5-20250929-thinking"],
360
+ ["anthropic%2Fclaude-sonnet-4-5-20250929-thinking-8k", "anthropic%2Fclaude-sonnet-4-5-20250929-thinking"],
361
+ ["anthropic%2Fclaude-sonnet-4-6", "anthropic%2Fclaude-sonnet-4.6"],
362
+ ["anthropic%2Fclaude-sonnet-4-6-high", "anthropic%2Fclaude-sonnet-4.6"],
363
+ ["anthropic%2Fclaude-sonnet-4-thinking", "anthropic%2Fclaude-sonnet-4"],
364
+ ["anthropic%2Fclaude-sonnet-4.6-high", "anthropic%2Fclaude-sonnet-4.6"],
365
+ ["arcee-ai%2Ftrinity-large-thinking", "arcee-ai%2FTrinity-Large-Thinking"],
366
+ ["avemio%2FGRAG-NEMO-12B-ORPO-HESSIAN-AI", "avemio%2FGerman-RAG-NEMO-12B-ORPO-HESSIAN-AI"],
367
+ ["bytedance%2Fseed-2.0-lite", "bytedance%2Fseed-2-0-lite"],
368
+ ["bytedance-seed%2Fdoubao-seed-code", "bytedance%2Fdoubao-seed-code"],
369
+ ["bytedance-seed%2Fseed-oss-36b-instruct", "ByteDance-Seed%2Fseed-oss-36b"],
370
+ ["claude-opus-4-7", "anthropic%2Fclaude-opus-4.7"],
371
+ ["codegen3_5k-qwen2.5-72b-instruct-2-chk-50", "codegen3-5k-qwen2.5-72b-instruct-2-chk-50"],
372
+ ["cognitivecomputations%2Fdolphin-2.9.2-qwen2-72b", "cognitivecomputations%2Fdolphin-2-9.2-qwen2-72b"],
373
+ ["cohere%2FAyaExpanse-32B", "CohereLabs%2Faya-expanse-32b"],
374
+ ["cohere%2FAyaExpanse-8B", "CohereLabs%2Faya-expanse-8b"],
375
+ ["cohere%2Faya-expanse-32b", "CohereLabs%2Faya-expanse-32b"],
376
+ ["cohere%2Faya-expanse-8b", "CohereLabs%2Faya-expanse-8b"],
377
+ ["cohere%2Fc4ai-command-r-v01", "CohereLabs%2Fc4ai-command-r-v01"],
378
+ ["cohere%2Fcommand-a-03-2025", "cohere%2Fcommand-a"],
379
+ ["cohere%2Fcommand-a-fc", "cohere%2Fcommand-a"],
380
+ ["cohere%2Fcommand-a-reasoning-fc", "cohere%2Fcommand-a-reasoning"],
381
+ ["cohere%2Fcommand-r-03-2024", "cohere%2Fcommand-r"],
382
+ ["cohere%2Fcommand-r-08-2024", "cohere%2Fcommand-r"],
383
+ ["cohere%2Fcommand-r-plus", "CohereLabs%2Fc4ai-command-r-plus"],
384
+ ["cohere%2Fcommand-r-plus-04-2024", "CohereLabs%2Fc4ai-command-r-plus"],
385
+ ["cohere%2Fcommand-r-plus-08-2024", "cohere%2Fcohere-command-r-plus-08-2024"],
386
+ ["cohere%2Fcommand-r7b-fc", "cohere%2Fcommand-r7b"],
387
+ ["cohere%2Ftiny-aya-global", "CohereLabs%2Ftiny-aya-global"],
388
+ ["databricks%2Fdbrx-base", "databricks%2Fdbrx"],
389
+ ["databricks%2Fdbrx-instruct", "databricks%2Fdbrx"],
390
+ ["deepcogito%2Fcogito-v2-1-reasoning", "deepcogito%2Fcogito-v2.1"],
391
+ ["deepseek%2FDeepSeek-R1", "deepseek-ai%2FDeepSeek-R1"],
392
+ ["deepseek%2FDeepSeek-R1-0528", "deepseek-ai%2FDeepSeek-R1"],
393
+ ["deepseek%2FDeepSeek-V3", "deepseek-ai%2FDeepSeek-V3"],
394
+ ["deepseek%2FDeepSeek-V3-0324", "deepseek-ai%2FDeepSeek-V3"],
395
+ ["deepseek%2Fchat-v3-0324", "deepseek%2Fdeepseek-chat-v3-0324"],
396
+ ["deepseek%2Fdeepseek-chat", "deepseek%2Fdeepseek"],
397
+ ["deepseek%2Fdeepseek-coder-v2-instruct", "deepseek%2Fdeepseek-coder-v2"],
398
+ ["deepseek%2Fdeepseek-coder-v2-lite-instruct", "deepseek%2Fdeepseek-coder-v2-lite"],
399
+ ["deepseek%2Fdeepseek-llm-67b-chat", "deepseek%2Fdeepseek-llm-67b"],
400
+ ["deepseek%2Fdeepseek-r1", "deepseek-ai%2FDeepSeek-R1"],
401
+ ["deepseek%2Fdeepseek-r1-0120", "deepseek-ai%2FDeepSeek-R1"],
402
+ ["deepseek%2Fdeepseek-r1-0528", "deepseek-ai%2FDeepSeek-R1"],
403
+ ["deepseek%2Fdeepseek-r1-distill-llama-70b", "deepseek-ai%2FDeepSeek-R1-Distill-Llama-70B"],
404
+ ["deepseek%2Fdeepseek-r1-distill-llama-8b", "deepseek-ai%2FDeepSeek-R1-Distill-Llama-8B"],
405
+ ["deepseek%2Fdeepseek-r1-distill-qwen-1-5b", "deepseek-ai%2FDeepSeek-R1-Distill-Qwen-1.5B"],
406
+ ["deepseek%2Fdeepseek-r1-distill-qwen-14b", "deepseek-ai%2FDeepSeek-R1-Distill-Qwen-14B"],
407
+ ["deepseek%2Fdeepseek-r1-distill-qwen-32b", "deepseek-ai%2FDeepSeek-R1-Distill-Qwen-32B"],
408
+ ["deepseek%2Fdeepseek-v2-chat", "deepseek%2Fdeepseek-v2"],
409
+ ["deepseek%2Fdeepseek-v3", "deepseek-ai%2FDeepSeek-V3"],
410
+ ["deepseek%2Fdeepseek-v3-0324", "deepseek-ai%2FDeepSeek-V3"],
411
+ ["deepseek%2Fdeepseek-v3-1-reasoning", "deepseek%2Fdeepseek-v3-1"],
412
+ ["deepseek%2Fdeepseek-v3-1-terminus", "deepseek-ai%2FDeepSeek-V3.1-Terminus"],
413
+ ["deepseek%2Fdeepseek-v3-2", "deepseek-ai%2FDeepSeek-V3.2"],
414
+ ["deepseek%2Fdeepseek-v3-2-0925", "deepseek-ai%2FDeepSeek-V3.2"],
415
+ ["deepseek%2Fdeepseek-v3-2-exp-fc", "deepseek%2Fdeepseek-v3-2-exp"],
416
+ ["deepseek%2Fdeepseek-v3-2-reasoning", "deepseek-ai%2FDeepSeek-V3.2"],
417
+ ["deepseek%2Fdeepseek-v3-2-reasoning-0925", "deepseek-ai%2FDeepSeek-V3.2"],
418
+ ["deepseek%2Fdeepseek-v3-2-speciale", "deepseek-ai%2FDeepSeek-V3.2-Speciale"],
419
+ ["deepseek%2Fdeepseek-v3.1", "deepseek%2Fdeepseek-v3-1"],
420
+ ["deepseek%2Fdeepseek-v3.2", "deepseek-ai%2FDeepSeek-V3.2"],
421
+ ["deepseek%2Fdeepseek-v3.2-exp", "deepseek%2Fdeepseek-v3-2-exp"],
422
+ ["deepseek%2Fdeepseek-v3.2-speciale", "deepseek-ai%2FDeepSeek-V3.2-Speciale"],
423
+ ["deepseek%2Fdeepseek-v4-flash-fp8", "deepseek%2Fdeepseek-v4-flash"],
424
+ ["deepseek%2Fdeepseek-v4-pro", "deepseek-ai%2FDeepSeek-V4-Pro"],
425
+ ["deepseek%2Fr1", "deepseek-ai%2FDeepSeek-R1"],
426
+ ["deepseek%2Fr1-0528-openrouter", "deepseek-ai%2FDeepSeek-R1"],
427
+ ["deepseek%2Fv3.2", "deepseek-ai%2FDeepSeek-V3.2"],
428
+ ["deepseek-ai%2FDeepSeek-R1-0528", "deepseek-ai%2FDeepSeek-R1"],
429
+ ["deepseek-ai%2Fdeepseek-llm-67b-chat", "deepseek%2Fdeepseek-llm-67b"],
430
+ ["deepseek-ai%2Fdeepseek-llm-7b-chat", "deepseek%2Fdeepseek-llm-7b"],
431
+ ["deepseek-ai%2Fdeepseek-r1", "deepseek-ai%2FDeepSeek-R1"],
432
+ ["deepseek-ai%2Fdeepseek-r1-0528", "deepseek-ai%2FDeepSeek-R1"],
433
+ ["deepseek-ai%2Fdeepseek-v3", "deepseek-ai%2FDeepSeek-V3"],
434
+ ["deepseek-chat", "deepseek%2Fdeepseek"],
435
+ ["deepseek-v3", "deepseek-ai%2FDeepSeek-V3"],
436
+ ["deepseek-v3.1", "deepseek%2Fdeepseek-v3-1"],
437
+ ["dwikitheduck%2Fgemma-2-2b-id", "dwikitheduck%2Fgemma-2-2b-id-inst"],
438
+ ["dwikitheduck%2Fgemma-2-2b-id-instruct", "dwikitheduck%2Fgemma-2-2b-id-inst"],
439
+ ["eleutherai%2FPythia-12B", "EleutherAI%2Fpythia-12b"],
440
+ ["eleutherai%2FPythia-6.9B", "EleutherAI%2Fpythia-6.9b"],
441
+ ["eleutherai%2Fpythia-12b-v0", "EleutherAI%2Fpythia-12b"],
442
+ ["eleutherai%2Fpythia-6.9b", "EleutherAI%2Fpythia-6.9b"],
443
+ ["ewre324%2FThinker-Llama-3.2-3B-Instruct-Reasoning", "ewre324%2Fewre324-Thinker-Llama-3.2-3B-Instruct-Reasoning"],
444
+ ["ewre324%2FThinker-Qwen2.5-0.5B-Instruct-Reasoning", "ewre324%2Fewre324-Thinker-Qwen2.5-0.5B-Instruct-Reasoning"],
445
+ ["ewre324%2FThinker-SmolLM2-135M-Instruct-Reasoning", "ewre324%2Fewre324-Thinker-SmolLM2-135M-Instruct-Reasoning"],
446
+ ["formulae%2Fmita-v1-7b", "formulae%2Fmita-v1.0-7b-2-24-2025"],
447
+ ["gemini-2.5-pro-exp-03-25", "google%2Fgemini-2-5-pro-exp-03-25"],
448
+ ["gemma-1.1-7b-it", "google%2Fgemma-1.1-7b"],
449
+ ["google%2FGemini%202.5%20Flash", "google%2Fgemini-2.5-flash"],
450
+ ["google%2FGemini%202.5%20Pro", "google%2Fgemini-2.5-pro"],
451
+ ["google%2FGemini%203%20Flash", "google%2Fgemini-3-flash"],
452
+ ["google%2FGemini%203%20Pro", "google%2Fgemini-3-pro"],
453
+ ["google%2FGemini%203.1%20Pro", "google%2Fgemini-3.1-pro"],
454
+ ["google%2FGemini-2.5-Pro", "google%2Fgemini-2.5-pro"],
455
+ ["google%2FGemma-3-12B", "google%2Fgemma3-12b"],
456
+ ["google%2FGemma-3-27B", "google%2Fgemma-3-27b"],
457
+ ["google%2Fflan-t5-large", "google%2Ft5-large"],
458
+ ["google%2Fgemini-1.0-pro-001", "google%2Fgemini-1.0-pro"],
459
+ ["google%2Fgemini-1.0-pro-002", "google%2Fgemini-1.0-pro"],
460
+ ["google%2Fgemini-1.5-flash-001", "google%2Fgemini-1.5-flash"],
461
+ ["google%2Fgemini-1.5-flash-002", "google%2Fgemini-1.5-flash"],
462
+ ["google%2Fgemini-1.5-pro-001", "google%2Fgemini-1.5-pro"],
463
+ ["google%2Fgemini-1.5-pro-002", "google%2Fgemini-1.5-pro"],
464
+ ["google%2Fgemini-2-0-flash", "google%2Fgemini-2.0-flash"],
465
+ ["google%2Fgemini-2-0-flash-lite-001", "google%2Fgemini-2.0-flash-lite"],
466
+ ["google%2Fgemini-2-5-flash", "google%2Fgemini-2.5-flash"],
467
+ ["google%2Fgemini-2-5-flash-fc", "google%2Fgemini-2.5-flash"],
468
+ ["google%2Fgemini-2-5-flash-lite", "google%2Fgemini-2.5-flash-lite"],
469
+ ["google%2Fgemini-2-5-flash-lite-fc", "google%2Fgemini-2.5-flash-lite"],
470
+ ["google%2Fgemini-2-5-flash-lite-prompt", "google%2Fgemini-2.5-flash-lite"],
471
+ ["google%2Fgemini-2-5-flash-lite-reasoning", "google%2Fgemini-2.5-flash-lite"],
472
+ ["google%2Fgemini-2-5-flash-prompt", "google%2Fgemini-2.5-flash"],
473
+ ["google%2Fgemini-2-5-flash-reasoning", "google%2Fgemini-2.5-flash"],
474
+ ["google%2Fgemini-2-5-flash-reasoning-04-2025", "google%2Fgemini-2.5-flash"],
475
+ ["google%2Fgemini-2-5-pro", "google%2Fgemini-2.5-pro"],
476
+ ["google%2Fgemini-2.0-flash-001", "google%2Fgemini-2.0-flash"],
477
+ ["google%2Fgemini-2.0-flash-lite-001", "google%2Fgemini-2.0-flash-lite"],
478
+ ["google%2Fgemini-2.0-flash-thinking", "google%2Fgemini-2.0-flash"],
479
+ ["google%2Fgemini-2.0-flash-thinking-exp-01-21", "google%2Fgemini-2-0-flash-thinking-exp-01-21"],
480
+ ["google%2Fgemini-2.0-flash-thinking-exp-1219", "google%2Fgemini-2-0-flash-thinking-exp-1219"],
481
+ ["google%2Fgemini-2.0-pro", "google%2Fgemini-2-0-pro"],
482
+ ["google%2Fgemini-2.0-pro-exp-02-05", "google%2Fgemini-2-0-pro-exp-02-05"],
483
+ ["google%2Fgemini-2.5-flash-lite-preview-09-2025", "google%2Fgemini-2-5-flash-lite-preview-09-2025"],
484
+ ["google%2Fgemini-2.5-flash-preview-04-17", "google%2Fgemini-2-5-flash-preview-04-17"],
485
+ ["google%2Fgemini-2.5-flash-preview-09-2025", "google%2Fgemini-2-5-flash-preview-09-2025"],
486
+ ["google%2Fgemini-2.5-flash-thinking", "google%2Fgemini-2.5-flash"],
487
+ ["google%2Fgemini-2.5-pro-exp-03-25", "google%2Fgemini-2-5-pro-exp-03-25"],
488
+ ["google%2Fgemini-2.5-pro-preview", "google%2Fgemini-2-5-pro-preview"],
489
+ ["google%2Fgemini-3-flash-preview", "google%2Fgemini-3-flash"],
490
+ ["google%2Fgemini-3-pro-low", "google%2Fgemini-3-pro"],
491
+ ["google%2Fgemini-3-pro-preview", "google%2Fgemini-3-pro"],
492
+ ["google%2Fgemini-3-pro-preview-fc", "google%2Fgemini-3-pro"],
493
+ ["google%2Fgemini-3-pro-preview-prompt", "google%2Fgemini-3-pro"],
494
+ ["google%2Fgemini-exp-1206", "google%2Fgemini-exp"],
495
+ ["google%2Fgemma-1.1-2b-it", "google%2Fgemma-1.1-2b"],
496
+ ["google%2Fgemma-1.1-7b-it", "google%2Fgemma-1.1-7b"],
497
+ ["google%2Fgemma-2-27b-it", "google%2Fgemma-2-27b"],
498
+ ["google%2Fgemma-2-2b-it", "google%2Fgemma-2-2b"],
499
+ ["google%2Fgemma-2-9b-it", "google%2Fgemma-2-9b"],
500
+ ["google%2Fgemma-2b-it", "google%2Fgemma-2b"],
501
+ ["google%2Fgemma-3-12b", "google%2Fgemma3-12b"],
502
+ ["google%2Fgemma-3-12b-it", "google%2Fgemma3-12b"],
503
+ ["google%2Fgemma-3-12b-it-prompt", "google%2Fgemma3-12b"],
504
+ ["google%2Fgemma-3-1b-it", "google%2Fgemma-3-1b"],
505
+ ["google%2Fgemma-3-1b-it-prompt", "google%2Fgemma-3-1b"],
506
+ ["google%2Fgemma-3-27b-it", "google%2Fgemma-3-27b"],
507
+ ["google%2Fgemma-3-27b-it-prompt", "google%2Fgemma-3-27b"],
508
+ ["google%2Fgemma-3-27b-nothink", "google%2Fgemma-3-27b"],
509
+ ["google%2Fgemma-3-4b", "google%2Fgemma3-4b"],
510
+ ["google%2Fgemma-3-4b-it", "google%2Fgemma3-4b"],
511
+ ["google%2Fgemma-3-4b-it-prompt", "google%2Fgemma3-4b"],
512
+ ["google%2Fgemma-3n-e2b", "google%2Fgemma-3n-E2B"],
513
+ ["google%2Fgemma-3n-e2b-it", "google%2Fgemma-3n-E2B"],
514
+ ["google%2Fgemma-3n-e2b-it-litert-preview", "google%2Fgemma-3n-E2B"],
515
+ ["google%2Fgemma-3n-e4b", "google%2Fgemma-3n-E4B"],
516
+ ["google%2Fgemma-3n-e4b-it", "google%2Fgemma-3n-E4B"],
517
+ ["google%2Fgemma-3n-e4b-it-litert-preview", "google%2Fgemma-3n-E4B"],
518
+ ["google%2Fgemma-4-26B-A4B-it", "google%2Fgemma4-26b"],
519
+ ["google%2Fgemma-4-26b-a4b", "google%2Fgemma4-26b"],
520
+ ["google%2Fgemma-4-26b-a4b-it", "google%2Fgemma4-26b"],
521
+ ["google%2Fgemma-4-26b-a4b-non-reasoning", "google%2Fgemma4-26b"],
522
+ ["google%2Fgemma-4-31B-it", "google%2Fgemma-4-31B"],
523
+ ["google%2Fgemma-4-31b", "google%2Fgemma-4-31B"],
524
+ ["google%2Fgemma-4-31b-it", "google%2Fgemma-4-31B"],
525
+ ["google%2Fgemma-4-31b-non-reasoning", "google%2Fgemma-4-31B"],
526
+ ["google%2Fgemma-4-E2B-it", "google%2Fgemma-4-E2B"],
527
+ ["google%2Fgemma-4-E4B-it", "google%2Fgemma-4-E4B"],
528
+ ["google%2Fgemma-4-e2b", "google%2Fgemma-4-E2B"],
529
+ ["google%2Fgemma-4-e2b-it", "google%2Fgemma-4-E2B"],
530
+ ["google%2Fgemma-4-e2b-non-reasoning", "google%2Fgemma-4-E2B"],
531
+ ["google%2Fgemma-4-e4b", "google%2Fgemma-4-E4B"],
532
+ ["google%2Fgemma-4-e4b-it", "google%2Fgemma-4-E4B"],
533
+ ["google%2Fgemma-4-e4b-non-reasoning", "google%2Fgemma-4-E4B"],
534
+ ["google%2Fgemma-7b-it", "google%2Fgemma-7b"],
535
+ ["google%2Fgemma4-31b", "google%2Fgemma-4-31B"],
536
+ ["google%2Fgemma4-e2b", "google%2Fgemma-4-E2B"],
537
+ ["google%2Fgemma4-e4b", "google%2Fgemma-4-E4B"],
538
+ ["google%2Flearnlm-1.5-pro-experimental", "google%2Flearnlm-1-5-pro-experimental"],
539
+ ["google%2Fmedgemma-4b-it", "google%2Fmedgemma-4b"],
540
+ ["google%2Ftext-bison%40001", "google%2Ftext-bison"],
541
+ ["google%2Ftext-unicorn%40001", "google%2Ftext-unicorn"],
542
+ ["gpt-4o", "openai%2Fgpt-4o"],
543
+ ["gpt-4o-mini", "openai%2Fgpt-4o-mini"],
544
+ ["gpt-5", "openai%2Fgpt-5"],
545
+ ["grok-3", "xai%2Fgrok-3"],
546
+ ["hermes-3-llama-3.1-70b", "NousResearch%2FHermes-3-Llama-3.1-70B"],
547
+ ["huggingface%2Fsmollm2-1.7b", "HuggingFaceTB%2FSmolLM2-1.7B"],
548
+ ["huggingface%2Fsmollm2-135m", "HuggingFaceTB%2FSmolLM2-135M"],
549
+ ["huggingface%2Fsmollm3-3b-nothink", "huggingface%2Fsmollm3-3b"],
550
+ ["iRyanBell%2FARC1", "iRyanBell%2Fllama3.0-ARC1-8b"],
551
+ ["iRyanBell%2FARC1-II", "iRyanBell%2Fllama3.0-ARC1-II-8b"],
552
+ ["ibm%2FPowerLM-3b", "ibm-research%2FPowerLM-3b"],
553
+ ["ibm%2Fgranite-3.0-2b-base", "ibm-granite%2Fgranite-3.0-2b-base"],
554
+ ["ibm%2Fgranite-3.0-8b-base", "ibm-granite%2Fgranite-3.0-8b-base"],
555
+ ["ibm%2Fgranite-3.1-1b-a400m-base", "ibm-granite%2Fgranite-3.1-1b-a400m-base"],
556
+ ["ibm%2Fgranite-3.1-1b-a400m-instruct", "ibm-granite%2Fgranite-3.1-1b-a400m-instruct"],
557
+ ["ibm%2Fgranite-3.1-2b-base", "ibm-granite%2Fgranite-3.1-2b-base"],
558
+ ["ibm%2Fgranite-3.1-2b-instruct", "ibm-granite%2Fgranite-3.1-2b-instruct"],
559
+ ["ibm%2Fgranite-3.1-3b-a800m-base", "ibm-granite%2Fgranite-3.1-3b-a800m-base"],
560
+ ["ibm%2Fgranite-3.1-3b-a800m-instruct", "ibm-granite%2Fgranite-3.1-3b-a800m-instruct"],
561
+ ["ibm%2Fgranite-3.1-8b-base", "ibm-granite%2Fgranite-3.1-8b-base"],
562
+ ["ibm%2Fgranite-3.1-8b-instruct", "ibm-granite%2Fgranite-3.1-8b"],
563
+ ["ibm%2Fgranite-3.3-8b-base", "ibm-granite%2Fgranite-3.3-8b"],
564
+ ["ibm%2Fgranite-3.3-8b-instruct", "ibm-granite%2Fgranite-3.3-8b"],
565
+ ["ibm%2Fgranite-4-0-micro", "ibm%2Fgranite-4-0-h-micro"],
566
+ ["ibm%2Fgranite-4.0-micro", "ibm%2Fgranite-4-0-h-micro"],
567
+ ["ibm%2Fmerlinite-7b", "ibm-research%2Fmerlinite-7b"],
568
+ ["ibm-granite%2Fgranite-3.0-2b-instruct", "ibm-granite%2Fgranite-3.0-2b"],
569
+ ["ibm-granite%2Fgranite-3.0-8b-instruct", "ibm-granite%2Fgranite-3.0-8b"],
570
+ ["ibm-granite%2Fgranite-3.1-8b-instruct", "ibm-granite%2Fgranite-3.1-8b"],
571
+ ["ibm-granite%2Fgranite-3.2-8b-instruct", "ibm-granite%2Fgranite-3.2-8b"],
572
+ ["inclusionai%2Fling-1t", "inclusionAI%2FLing-1T"],
573
+ ["inclusionai%2Fling-flash-2-0", "inclusionAI%2Fling-flash-2-0"],
574
+ ["inclusionai%2Fling-mini-2-0", "inclusionAI%2Fling-mini-2-0"],
575
+ ["inclusionai%2Fring-1t", "inclusionAI%2FRing-1T"],
576
+ ["inclusionai%2Fring-flash-2-0", "inclusionAI%2Fring-flash-2-0"],
577
+ ["internlm%2Finternlm2_5-20b-chat", "internlm%2Finternlm2.5-20b"],
578
+ ["jeffmeloy%2Fjeffmeloy_Qwen2.5-7B-minperplexity-1", "jeffmeloy%2FQwen2.5-7B-minperplexity-1"],
579
+ ["katanemo%2Farch-agent-32b", "katanemo%2FArch-Agent-32B"],
580
+ ["katanemo%2Farch-agent-3b", "katanemo%2FArch-Agent-3B"],
581
+ ["kimi%2Fkimi-k2-5", "moonshotai%2FKimi-K2.5"],
582
+ ["kimi%2Fkimi-k2-thinking", "moonshotai%2FKimi-K2-Thinking"],
583
+ ["kwaikat%2Fkat-coder-pro-v1", "kwaikat%2Fkat-coder-pro"],
584
+ ["kwaikat%2Fkat-coder-pro-v2", "kwaikat%2Fkat-coder-pro"],
585
+ ["lg%2Fexaone-4-0-1-2b-reasoning", "lg%2Fexaone-4-0-1-2b"],
586
+ ["lg%2Fexaone-4-0-32b-reasoning", "lg%2Fexaone-4-0-32b"],
587
+ ["lg%2Fk-exaone-non-reasoning", "lg%2Fk-exaone"],
588
+ ["liquidai%2Flfm2-24b-a2b", "LiquidAI%2FLFM2-24B-A2B"],
589
+ ["liquidai%2Flfm2-8b-a1b", "LiquidAI%2FLFM2-8B-A1B"],
590
+ ["llama-2-7b-chat-hf", "meta-llama%2FLlama-2-7b-chat-hf"],
591
+ ["llama-3.3-70b-instruct", "meta-llama%2Fllama-3.3"],
592
+ ["llama-4-maverick-17b-128e-instruct", "meta-llama%2Fllama-4"],
593
+ ["lunahr%2Fthea-v2-3b-50r", "lunahr%2Funavailablemodel-thea-v2-3b-50r"],
594
+ ["mathstral-7B-v0.1", "mistralai%2FMathstral-7B-v0.1"],
595
+ ["mattshumer%2FReflection-70B", "mattshumer%2FReflection-Llama-3.1-70B"],
596
+ ["meta%2FLlama-2-13B", "meta-llama%2Fllama-2"],
597
+ ["meta%2FLlama-2-13b-chat", "meta-llama%2Fllama-2"],
598
+ ["meta%2FLlama-2-70B", "meta-llama%2Fllama-2"],
599
+ ["meta%2FLlama-2-70b-chat", "meta-llama%2Fllama-2"],
600
+ ["meta%2FLlama-2-7B", "meta-llama%2Fllama-2"],
601
+ ["meta%2FLlama-2-7b-chat", "meta-llama%2Fllama-2"],
602
+ ["meta%2FLlama-3.1-8B", "meta-llama%2Fllama-3.1"],
603
+ ["meta%2FLlama-4-Maverick", "meta%2Fllama-4-maverick"],
604
+ ["meta%2Fllama-13b", "meta%2FLLaMA-13B"],
605
+ ["meta%2Fllama-2-13b", "meta-llama%2Fllama-2"],
606
+ ["meta%2Fllama-2-13b-hf", "meta-llama%2FLlama-2-13b-hf"],
607
+ ["meta%2Fllama-2-70b", "meta-llama%2Fllama-2"],
608
+ ["meta%2Fllama-2-70b-hf", "meta-llama%2FLlama-2-70b-hf"],
609
+ ["meta%2Fllama-2-7b", "meta-llama%2Fllama-2"],
610
+ ["meta%2Fllama-2-7b-hf", "meta-llama%2FLlama-2-7b-hf"],
611
+ ["meta%2Fllama-3-1-8b-instruct-prompt", "meta-llama%2Fllama-3.1"],
612
+ ["meta%2Fllama-3-2-1b-instruct-fc", "meta-llama%2Fllama-3.2"],
613
+ ["meta%2Fllama-3-2-3b-instruct-fc", "meta-llama%2Fllama-3.2"],
614
+ ["meta%2Fllama-3-3-70b-instruct-fc", "meta-llama%2Fllama-3.3"],
615
+ ["meta%2Fllama-3-70b", "meta-llama%2Fllama-3"],
616
+ ["meta%2Fllama-3-70b-chat", "meta-llama%2Fllama-3"],
617
+ ["meta%2Fllama-3-70b-instruct", "meta-llama%2Fllama-3"],
618
+ ["meta%2Fllama-3-8b", "meta-llama%2Fllama-3"],
619
+ ["meta%2Fllama-3-8b-chat", "meta-llama%2Fllama-3"],
620
+ ["meta%2Fllama-3-8b-instruct", "meta-llama%2Fllama-3"],
621
+ ["meta%2Fllama-3.1-405b", "meta-llama%2Fllama-3.1"],
622
+ ["meta%2Fllama-3.1-405b-instruct", "meta-llama%2Fllama-3.1"],
623
+ ["meta%2Fllama-3.1-405b-instruct-turbo", "meta-llama%2Fllama-3.1"],
624
+ ["meta%2Fllama-3.1-70b", "meta-llama%2Fllama-3.1"],
625
+ ["meta%2Fllama-3.1-70b-instruct", "meta-llama%2Fllama-3.1"],
626
+ ["meta%2Fllama-3.1-70b-instruct-turbo", "meta-llama%2Fllama-3.1"],
627
+ ["meta%2Fllama-3.1-8b", "meta-llama%2Fllama-3.1"],
628
+ ["meta%2Fllama-3.1-8b-instruct", "meta-llama%2Fllama-3.1"],
629
+ ["meta%2Fllama-3.1-8b-instruct-turbo", "meta-llama%2Fllama-3.1"],
630
+ ["meta%2Fllama-3.2-11b-instruct", "meta%2Fllama-3-2-11b"],
631
+ ["meta%2Fllama-3.2-11b-vision-instruct-turbo", "meta-llama%2Fllama-3.2"],
632
+ ["meta%2Fllama-3.2-1b", "meta-llama%2Fllama-3.2"],
633
+ ["meta%2Fllama-3.2-3b", "meta-llama%2Fllama-3.2"],
634
+ ["meta%2Fllama-3.2-3b-instruct", "meta-llama%2Fllama-3.2"],
635
+ ["meta%2Fllama-3.2-90b-instruct", "meta-llama%2Fllama-3.2"],
636
+ ["meta%2Fllama-3.2-90b-vision-instruct-turbo", "meta-llama%2Fllama-3.2"],
637
+ ["meta%2Fllama-3.3-70b-instruct", "meta-llama%2Fllama-3.3"],
638
+ ["meta%2Fllama-3.3-70b-instruct-turbo", "meta-llama%2Fllama-3.3"],
639
+ ["meta%2Fllama-30b", "meta%2FLLaMA-30B"],
640
+ ["meta%2Fllama-4-maverick-17b-128e-instruct-fp8", "meta-llama%2Fllama-4"],
641
+ ["meta%2Fllama-4-maverick-17b-128e-instruct-fp8-fc", "meta-llama%2Fllama-4"],
642
+ ["meta%2Fllama-4-maverick-17b-128e-instruct-fp8-together", "meta-llama%2Fllama-4"],
643
+ ["meta%2Fllama-4-scout-17b-16e-instruct", "meta-llama%2Fllama-4"],
644
+ ["meta%2Fllama-4-scout-17b-16e-instruct-fc", "meta-llama%2Fllama-4"],
645
+ ["meta%2Fllama-4-scout-17b-16e-instruct-together", "meta-llama%2Fllama-4"],
646
+ ["meta%2Fllama-65b", "meta%2FLLaMA-65B"],
647
+ ["meta%2Fllama-7b", "meta%2FLLaMA-7B"],
648
+ ["meta%2Fllama3.1-8b", "meta-llama%2Fllama-3.1"],
649
+ ["meta-llama%2FLlama-3.1-70B", "meta-llama%2Fllama-3.1"],
650
+ ["meta-llama%2FLlama-3.1-70B-Instruct", "meta-llama%2Fllama-3.1"],
651
+ ["meta-llama%2FLlama-3.1-8B", "meta-llama%2Fllama-3.1"],
652
+ ["meta-llama%2FLlama-3.1-8B-Instruct", "meta-llama%2Fllama-3.1"],
653
+ ["meta-llama%2FLlama-3.2-1B", "meta-llama%2Fllama-3.2"],
654
+ ["meta-llama%2FLlama-3.2-1B-Instruct", "meta-llama%2Fllama-3.2"],
655
+ ["meta-llama%2FLlama-3.2-3B", "meta-llama%2Fllama-3.2"],
656
+ ["meta-llama%2FLlama-3.2-3B-Instruct", "meta-llama%2Fllama-3.2"],
657
+ ["meta-llama%2FLlama-3.2-90B-Vision-Instruct", "meta-llama%2Fllama-3.2"],
658
+ ["meta-llama%2FLlama-3.3-70B-Instruct", "meta-llama%2Fllama-3.3"],
659
+ ["meta-llama%2FLlama-4-Maverick-17B-128E-Instruct-FP8", "meta-llama%2Fllama-4"],
660
+ ["meta-llama%2FLlama-4-Scout-17B-16E-Instruct", "meta-llama%2Fllama-4"],
661
+ ["meta-llama%2FMeta-Llama-3-70B", "meta-llama%2Fllama-3"],
662
+ ["meta-llama%2FMeta-Llama-3-70B-Instruct", "meta-llama%2Fllama-3"],
663
+ ["meta-llama%2FMeta-Llama-3-8B", "meta-llama%2Fllama-3"],
664
+ ["meta-llama%2FMeta-Llama-3-8B-Instruct", "meta-llama%2Fllama-3"],
665
+ ["meta-llama%2FMeta-Llama-3.1-70B-Instruct", "meta-llama%2Fllama-3.1"],
666
+ ["meta-llama%2FMeta-Llama-3.1-8B", "meta-llama%2Fllama-3.1"],
667
+ ["meta-llama%2FMeta-Llama-3.1-8B-Instruct", "meta-llama%2Fllama-3.1"],
668
+ ["meta-llama%2Fllama-3.2-90b-vision-instruct", "meta-llama%2Fllama-3.2"],
669
+ ["meta-llama%2Fllama-3.3-70b-instruct", "meta-llama%2Fllama-3.3"],
670
+ ["meta-llama%2Fllama3.1-8b", "meta-llama%2Fllama-3.1"],
671
+ ["meta-llama-3-70b-instruct", "meta-llama%2Fllama-3"],
672
+ ["meta-llama-3-8b-instruct", "meta-llama%2Fllama-3"],
673
+ ["microsoft%2FPhi-3-medium-128k-instruct", "microsoft%2Fphi-3-medium-128k"],
674
+ ["microsoft%2FPhi-3-medium-4k-instruct", "microsoft%2Fphi-3-medium-4k"],
675
+ ["microsoft%2FPhi-3-mini-128k-instruct", "microsoft%2Fphi-3-mini-128k"],
676
+ ["microsoft%2FPhi-3-mini-4k-instruct", "microsoft%2Fphi-3-mini-4k"],
677
+ ["microsoft%2FPhi-3-small-128k-instruct", "microsoft%2Fphi-3-small-128k"],
678
+ ["microsoft%2FPhi-3-small-8k-instruct", "microsoft%2Fphi-3-small-8k"],
679
+ ["microsoft%2FPhi-3.5-MoE-instruct", "microsoft%2Fphi-3.5-moe"],
680
+ ["microsoft%2FPhi-3.5-mini-instruct", "microsoft%2Fphi-3.5-mini"],
681
+ ["microsoft%2FPhi-4", "microsoft%2Fphi-4"],
682
+ ["microsoft%2FPhi-4-mini-instruct", "microsoft%2FPhi-4-mini-reasoning"],
683
+ ["microsoft%2FPhi-4-reasoning", "microsoft%2Fphi-4"],
684
+ ["microsoft%2Fphi-3-medium-4k-instruct", "microsoft%2Fphi-3-medium-4k"],
685
+ ["microsoft%2Fphi-3-small-8k-instruct", "microsoft%2Fphi-3-small-8k"],
686
+ ["microsoft%2Fphi-3.5-mini-instruct", "microsoft%2Fphi-3.5-mini"],
687
+ ["microsoft%2Fphi-3.5-moe-instruct", "microsoft%2Fphi-3.5-moe"],
688
+ ["microsoft%2Fphi-3.5-vision-instruct", "microsoft%2Fphi-3.5-vision"],
689
+ ["microsoft%2Fphi-4-mini", "microsoft%2FPhi-4-mini-reasoning"],
690
+ ["microsoft%2Fphi-4-mini-reasoning", "microsoft%2FPhi-4-mini-reasoning"],
691
+ ["microsoft%2Fphi-4-multimodal-instruct", "microsoft%2Fphi-4-multimodal"],
692
+ ["microsoft%2Fphi-4-prompt", "microsoft%2Fphi-4"],
693
+ ["microsoft%2Fphi-4-reasoning", "microsoft%2Fphi-4"],
694
+ ["microsoft%2Fphi-4-reasoning-plus", "microsoft%2Fphi-4"],
695
+ ["microsoft%2Fphi3-medium-128k", "microsoft%2Fphi-3-medium-128k"],
696
+ ["microsoft%2Fphi3-medium-4k", "microsoft%2Fphi-3-medium-4k"],
697
+ ["microsoft%2Fphi3-mini-128k", "microsoft%2Fphi-3-mini-128k"],
698
+ ["microsoft%2Fphi3-mini-4k", "microsoft%2Fphi-3-mini-4k"],
699
+ ["microsoft%2Fphi4-reasoning-plus", "microsoft%2Fphi-4"],
700
+ ["minimax%2FMiniMax-M2.1", "MiniMaxAI%2FMiniMax-M2.1"],
701
+ ["minimax%2FMiniMax-M2.5", "MiniMaxAI%2FMiniMax-M2.5"],
702
+ ["minimax%2FMiniMax-M2.5-Lightning", "minimax%2Fminimax-m2-5-lightning"],
703
+ ["minimax%2FMiniMax-M2.7", "minimax%2Fminimax-m2.7"],
704
+ ["minimax%2Fm2.5", "MiniMaxAI%2FMiniMax-M2.5"],
705
+ ["minimax%2Fminimax-m2-1", "MiniMaxAI%2FMiniMax-M2.1"],
706
+ ["minimax%2Fminimax-m2-5", "MiniMaxAI%2FMiniMax-M2.5"],
707
+ ["minimax%2Fminimax-m2-7", "minimax%2Fminimax-m2.7"],
708
+ ["minimax%2Fminimax-m2.1", "MiniMaxAI%2FMiniMax-M2.1"],
709
+ ["minimax%2Fminimax-m2.5", "MiniMaxAI%2FMiniMax-M2.5"],
710
+ ["mistral%2Fdevstral-2", "mistralai%2Fdevstral-2"],
711
+ ["mistral%2Fdevstral-medium", "mistralai%2Fdevstral-medium"],
712
+ ["mistral%2Fdevstral-small", "mistralai%2Fdevstral-small"],
713
+ ["mistral%2Fdevstral-small-2", "mistralai%2Fdevstral-small-2"],
714
+ ["mistral%2Fdevstral-small-2505", "mistralai%2Fdevstral-small"],
715
+ ["mistral%2Fmagistral-medium", "mistralai%2Fmagistral-medium"],
716
+ ["mistral%2Fmagistral-medium-2509", "mistralai%2Fmagistral-medium"],
717
+ ["mistral%2Fmagistral-small", "mistralai%2Fmagistral-small"],
718
+ ["mistral%2Fmagistral-small-2509", "mistralai%2Fmagistral-small"],
719
+ ["mistral%2Fministral-3-14b", "mistralai%2Fministral-3-14b"],
720
+ ["mistral%2Fministral-3-3b", "mistralai%2Fministral-3-3b"],
721
+ ["mistral%2Fministral-3-8b", "mistralai%2Fministral-3-8b"],
722
+ ["mistral%2Fministral-3-8b-base-2512", "mistralai%2Fministral-3-8b"],
723
+ ["mistral%2Fministral-3-8b-instruct-2512", "mistralai%2Fministral-3-8b"],
724
+ ["mistral%2Fministral-8b-instruct-2410", "mistralai%2Fministral-8b"],
725
+ ["mistral%2Fministral-8b-latest", "mistralai%2Fministral-8b"],
726
+ ["mistral%2Fmistral-7b-instruct", "mistralai%2Fmistral-7b"],
727
+ ["mistral%2Fmistral-large", "mistralai%2Fmistral-large"],
728
+ ["mistral%2Fmistral-large-2402", "mistralai%2Fmistral-large"],
729
+ ["mistral%2Fmistral-large-2407", "mistralai%2Fmistral-large"],
730
+ ["mistral%2Fmistral-large-3", "mistralai%2Fmistral-large-3"],
731
+ ["mistral%2Fmistral-large-3-2509", "mistralai%2Fmistral-large-3"],
732
+ ["mistral%2Fmistral-large-latest", "mistralai%2Fmistral-large"],
733
+ ["mistral%2Fmistral-medium", "mistralai%2Fmistral-medium"],
734
+ ["mistral%2Fmistral-medium-3", "mistralai%2Fmistral-medium-3"],
735
+ ["mistral%2Fmistral-medium-3-1", "mistralai%2Fmistral-medium-3.1"],
736
+ ["mistral%2Fmistral-nemo-instruct-2407", "mistralai%2Fmistral-nemo"],
737
+ ["mistral%2Fmistral-saba", "mistralai%2Fmistral-saba"],
738
+ ["mistral%2Fmistral-small", "mistralai%2Fmistral-small"],
739
+ ["mistral%2Fmistral-small-24b-base-2501", "mistralai%2FMistral-Small-24B-Base-2501"],
740
+ ["mistral%2Fmistral-small-24b-instruct-2501", "mistralai%2Fmistral-small-24b-instruct-2501"],
741
+ ["mistral%2Fmistral-small-2503", "mistralai%2Fmistral-small-2503"],
742
+ ["mistral%2Fmistral-small-3", "mistralai%2Fmistral-small-24b-instruct-2501"],
743
+ ["mistral%2Fmistral-small-3-1", "mistralai%2Fmistral-small-24b-instruct-2501"],
744
+ ["mistral%2Fmistral-small-3-2", "mistralai%2Fmistral-small-3.2"],
745
+ ["mistral%2Fmistral-small-3.2-24b-instruct-2506", "mistralai%2Fmistral-small-3-2-24b-instruct-2506"],
746
+ ["mistral%2Fmistral-small-4", "mistralai%2Fmistral-small-4"],
747
+ ["mistral%2Fmistral-small-4-non-reasoning", "mistralai%2Fmistral-small-4"],
748
+ ["mistral%2Fmistral-small-latest", "mistralai%2Fmistral-small"],
749
+ ["mistral%2Fmixtral-8x7b-instruct", "mistralai%2Fmixtral-8x7b"],
750
+ ["mistral%2Fpixtral-12b-2409", "mistralai%2Fpixtral-12b"],
751
+ ["mistral%2Fpixtral-large", "mistralai%2Fpixtral-large"],
752
+ ["mistral%2Fpixtral-large-2411", "mistralai%2Fpixtral-large"],
753
+ ["mistral-7b-instruct-v0.2", "mistralai%2FMistral-7B-Instruct-v0.2"],
754
+ ["mistralai%2FDevstral-Small-2505", "mistralai%2Fdevstral-small"],
755
+ ["mistralai%2FMinistral-8B-Instruct-2410", "mistralai%2Fministral-8b"],
756
+ ["mistralai%2FMistral-7B", "mistralai%2Fmistral-7b"],
757
+ ["mistralai%2FMistral-7B-Instruct-v0.3", "mistralai%2Fmistral-7b"],
758
+ ["mistralai%2FMistral-Medium", "mistralai%2Fmistral-medium"],
759
+ ["mistralai%2FMistral-Nemo-Base-2407", "mistralai%2Fmistral-nemo"],
760
+ ["mistralai%2FMistral-Nemo-Instruct-2407", "mistralai%2Fmistral-nemo"],
761
+ ["mistralai%2Fdevstral-small-2507", "mistralai%2Fdevstral-small"],
762
+ ["mistralai%2Fmagistral-small-2506", "mistralai%2FMagistral-Small-2506"],
763
+ ["mistralai%2Fmagistral-small-2509", "mistralai%2Fmagistral-small"],
764
+ ["mistralai%2Fministral-8b-instruct-2410-fc", "mistralai%2Fministral-8b"],
765
+ ["mistralai%2Fmistral-7b-instruct-v0.1", "mistralai%2FMistral-7B-Instruct-v0.1"],
766
+ ["mistralai%2Fmistral-7b-instruct-v0.2", "mistralai%2FMistral-7B-Instruct-v0.2"],
767
+ ["mistralai%2Fmistral-7b-instruct-v0.3", "mistralai%2Fmistral-7b"],
768
+ ["mistralai%2Fmistral-7b-v0.1", "mistralai%2FMistral-7B-v0.1"],
769
+ ["mistralai%2Fmistral-large-2407", "mistralai%2Fmistral-large"],
770
+ ["mistralai%2Fmistral-large-2411", "mistralai%2Fmistral-large"],
771
+ ["mistralai%2Fmistral-large-instruct-2407", "mistralai%2FMistral-Large-Instruct-2407"],
772
+ ["mistralai%2Fmistral-large-instruct-2411", "mistralai%2FMistral-Large-Instruct-2411"],
773
+ ["mistralai%2Fmistral-medium-2505", "mistralai%2Fmistral-medium"],
774
+ ["mistralai%2Fmistral-nemo-base-2407", "mistralai%2Fmistral-nemo"],
775
+ ["mistralai%2Fmistral-nemo-instruct-2407", "mistralai%2Fmistral-nemo"],
776
+ ["mistralai%2Fmistral-small-instruct", "mistralai%2Fmistral-small"],
777
+ ["mistralai%2Fmistral-small-instruct-2409", "mistralai%2FMistral-Small-Instruct-2409"],
778
+ ["mistralai%2Fmixtral-8x22b-instruct-v0.1", "mistralai%2FMixtral-8x22B-Instruct-v0.1"],
779
+ ["mistralai%2Fmixtral-8x7b-instruct-v0.1", "mistralai%2FMixtral-8x7B-Instruct-v0.1"],
780
+ ["mistralai%2Fmixtral-8x7b-v0.1", "mistralai%2FMixtral-8x7B-v0.1"],
781
+ ["mixtral-8x22b-instruct-v0.1", "mistralai%2FMixtral-8x22B-Instruct-v0.1"],
782
+ ["mixtral-8x7b-instruct-v0.1", "mistralai%2FMixtral-8x7B-Instruct-v0.1"],
783
+ ["moonshot%2FKimi%20K2%20Thinking", "moonshotai%2FKimi-K2-Thinking"],
784
+ ["moonshot%2FKimi%20K2.5", "moonshotai%2FKimi-K2.5"],
785
+ ["moonshot%2Fkimi-k2-0711-preview", "moonshotai%2Fkimi-k2"],
786
+ ["moonshot%2Fkimi-k2.5", "moonshotai%2FKimi-K2.5"],
787
+ ["moonshot-ai%2Fkimi-k2-instruct", "moonshotai%2Fkimi-k2"],
788
+ ["moonshot-ai%2Fkimi-k2-thinking", "moonshotai%2FKimi-K2-Thinking"],
789
+ ["moonshot-ai%2Fkimi-k2.5", "moonshotai%2FKimi-K2.5"],
790
+ ["moonshotai%2FKimi-K2-Instruct", "moonshotai%2Fkimi-k2"],
791
+ ["moonshotai%2Fkimi-k2-0905", "moonshotai%2Fkimi-k2"],
792
+ ["moonshotai%2Fkimi-k2-0905-preview", "moonshotai%2Fkimi-k2"],
793
+ ["moonshotai%2Fkimi-k2-base", "moonshotai%2Fkimi-k2"],
794
+ ["moonshotai%2Fkimi-k2-instruct", "moonshotai%2Fkimi-k2"],
795
+ ["moonshotai%2Fkimi-k2-instruct-0905", "moonshotai%2Fkimi-k2"],
796
+ ["moonshotai%2Fkimi-k2-thinking", "moonshotai%2FKimi-K2-Thinking"],
797
+ ["moonshotai%2Fkimi-k2.5", "moonshotai%2FKimi-K2.5"],
798
+ ["moonshotai%2Fkimi-k2.5-thinking", "moonshotai%2FKimi-K2.5"],
799
+ ["moonshotai%2Fkimi-k2.6", "moonshotai%2FKimi-K2.6"],
800
+ ["moonshotai%2Fkimi-k2.6-thinking", "moonshotai%2FKimi-K2.6"],
801
+ ["moonshotai%2Fmoonshotai-kimi-k2-instruct-fc", "moonshotai%2Fkimi-k2"],
802
+ ["mosaicml%2Fmpt-30b", "mosaicml%2FMPT-30B"],
803
+ ["mosaicml%2Fmpt-30b-instruct", "mosaicml%2FMPT-30B"],
804
+ ["mosaicml%2Fmpt-instruct-30b", "mosaicml%2FMPT-Instruct-30B"],
805
+ ["my_model%2F", "my-model"],
806
+ ["nexusflow%2Fathene-v2-chat", "Nexusflow%2FAthene-V2-Chat"],
807
+ ["nhyha%2Fmerge_Qwen2.5-7B-Instruct_20241023_0314", "nhyha%2FN3N_Qwen2.5-7B-Instruct_20241023_0314"],
808
+ ["nous-research%2Fdeephermes-3-mistral-24b-preview", "NousResearch%2FDeepHermes-3-Mistral-24B-Preview"],
809
+ ["nous-research%2Fhermes-3-llama-3-1-70b", "NousResearch%2FHermes-3-Llama-3.1-70B"],
810
+ ["nous-research%2Fhermes-4-llama-3-1-405b-reasoning", "nous-research%2Fhermes-4-llama-3-1-405b"],
811
+ ["nous-research%2Fhermes-4-llama-3-1-70b-reasoning", "nous-research%2Fhermes-4-llama-3-1-70b"],
812
+ ["nousresearch%2Fnous-hermes-13b", "NousResearch%2FNous-Hermes-13b"],
813
+ ["nvidia%2Fllama-3-1-nemotron-nano-4b-reasoning", "nvidia%2Fllama-3.1-nemotron-nano-4b"],
814
+ ["nvidia%2Fllama-3-1-nemotron-ultra-253b-v1-reasoning", "nvidia%2Fllama-3-1-nemotron-ultra-253b"],
815
+ ["nvidia%2Fllama-3-3-nemotron-super-49b-reasoning", "nvidia%2Fllama-3-3-nemotron-super-49b"],
816
+ ["nvidia%2Fllama-3.1-nemotron-70b-instruct", "nvidia%2Fllama-3.1-nemotron-70b"],
817
+ ["nvidia%2Fllama-3.1-nemotron-nano-8b-v1", "nvidia%2Fllama-3.1-nemotron-nano-8b"],
818
+ ["nvidia%2Fllama-3.1-nemotron-ultra-253b-v1", "nvidia%2Fllama-3-1-nemotron-ultra-253b"],
819
+ ["nvidia%2Fllama-3.3-nemotron-super-49b-v1", "nvidia%2FLlama-3_3-Nemotron-Super-49B-v1"],
820
+ ["nvidia%2Fnemotron-3-nano-30b-a3b", "nvidia%2Fnemotron-3-nano-30b"],
821
+ ["nvidia%2Fnemotron-3-super-120b-a12b", "nvidia%2FNVIDIA-Nemotron-3-Super-120B-A12B-BF16"],
822
+ ["nvidia%2Fnemotron-cascade-2-30b-a3b", "nvidia%2FNemotron-Cascade-2-30B-A3B"],
823
+ ["nvidia%2Fnvidia-nemotron-3-nano-30b-a3b", "nvidia%2Fnemotron-3-nano-30b"],
824
+ ["nvidia%2Fnvidia-nemotron-3-super-120b-a12b", "nvidia%2FNVIDIA-Nemotron-3-Super-120B-A12B-FP8"],
825
+ ["nvidia%2Fnvidia-nemotron-nano-12b-v2-vl", "nvidia%2Fnemotron-nano-12b-vl"],
826
+ ["nvidia%2Fnvidia-nemotron-nano-9b-v2", "nvidia%2FNVIDIA-Nemotron-Nano-9B-v2"],
827
+ ["olmo-2-1124-13b-instruct", "allenai%2FOLMo-2-1124-13B"],
828
+ ["oopere%2Fpruned20-llama-1b", "oopere%2FLlama-3.2-1B-pruned-20pct"],
829
+ ["oopere%2Fpruned40-llama-1b", "oopere%2FLlama-3.2-1B-pruned-40pct"],
830
+ ["oopere%2Fpruned40-llama-3.2-1B", "oopere%2FLlama-3.2-1B-pruned-40pct"],
831
+ ["oopere%2Fpruned60-llama-1b", "oopere%2Fpruned60-llama-3.2-1B"],
832
+ ["openai%2FGPT%204o", "openai%2Fgpt-4o"],
833
+ ["openai%2FGPT%205", "openai%2Fgpt-5"],
834
+ ["openai%2FGPT%205%20Codex", "openai%2Fgpt-5-codex"],
835
+ ["openai%2FGPT%205.1", "openai%2Fgpt-5.1"],
836
+ ["openai%2FGPT%205.1%20Codex", "openai%2Fgpt-5.1-codex"],
837
+ ["openai%2FGPT%205.2", "openai%2Fgpt-5.2"],
838
+ ["openai%2FGPT%205.2%20Codex", "openai%2Fgpt-5.2-codex"],
839
+ ["openai%2FGPT%205.2%20Pro", "openai%2Fgpt-5.2-pro"],
840
+ ["openai%2FGPT%205.3%20Codex", "openai%2Fgpt-5.3-codex"],
841
+ ["openai%2FGPT%20OSS%20120B", "openai%2Fgpt-oss-120b"],
842
+ ["openai%2FGPT-4.1", "openai%2Fgpt-4.1"],
843
+ ["openai%2FGPT-4o-1120", "openai%2Fgpt-4o"],
844
+ ["openai%2FGPT-5", "openai%2Fgpt-5"],
845
+ ["openai%2FGPT-5.2", "openai%2Fgpt-5.2"],
846
+ ["openai%2FGPT-J-6B", "EleutherAI%2Fgpt-j-6b"],
847
+ ["openai%2FGPT-NeoX-20B", "EleutherAI%2Fgpt-neox-20b"],
848
+ ["openai%2FOpenAI-o1", "openai%2Fo1"],
849
+ ["openai%2FOpenAI-o3-mini-high", "openai%2Fo3-mini"],
850
+ ["openai%2Fgpt-3-5-turbo-0613", "openai%2Fgpt-3.5-turbo"],
851
+ ["openai%2Fgpt-3.5-turbo-0125", "openai%2Fgpt-3.5-turbo"],
852
+ ["openai%2Fgpt-3.5-turbo-0301", "openai%2Fgpt-3.5-turbo"],
853
+ ["openai%2Fgpt-3.5-turbo-0613", "openai%2Fgpt-3.5-turbo"],
854
+ ["openai%2Fgpt-3.5-turbo-1106", "openai%2Fgpt-3.5-turbo"],
855
+ ["openai%2Fgpt-4-0314", "openai%2Fgpt-4"],
856
+ ["openai%2Fgpt-4-0613", "openai%2Fgpt-4"],
857
+ ["openai%2Fgpt-4-1", "openai%2Fgpt-4.1"],
858
+ ["openai%2Fgpt-4-1-mini", "openai%2Fgpt-4.1"],
859
+ ["openai%2Fgpt-4-1-nano", "openai%2Fgpt-4.1"],
860
+ ["openai%2Fgpt-4.1-mini", "openai%2Fgpt-4.1"],
861
+ ["openai%2Fgpt-4.1-mini-2025-04-14", "openai%2Fgpt-4.1"],
862
+ ["openai%2Fgpt-4o-2024-05-13", "openai%2Fgpt-4o"],
863
+ ["openai%2Fgpt-4o-2024-08-06", "openai%2Fgpt-4o"],
864
+ ["openai%2Fgpt-4o-2024-11-20", "openai%2Fgpt-4o"],
865
+ ["openai%2Fgpt-4o-mini-2024-07-18", "openai%2Fgpt-4o-mini"],
866
+ ["openai%2Fgpt-5-1", "openai%2Fgpt-5.1"],
867
+ ["openai%2Fgpt-5-1-codex", "openai%2Fgpt-5.1-codex"],
868
+ ["openai%2Fgpt-5-1-codex-mini", "openai%2Fgpt-5.1-codex-mini"],
869
+ ["openai%2Fgpt-5-1-non-reasoning", "openai%2Fgpt-5.1"],
870
+ ["openai%2Fgpt-5-2", "openai%2Fgpt-5.2"],
871
+ ["openai%2Fgpt-5-2-codex", "openai%2Fgpt-5.2-codex"],
872
+ ["openai%2Fgpt-5-2-medium", "openai%2Fgpt-5.2"],
873
+ ["openai%2Fgpt-5-3-codex", "openai%2Fgpt-5.3-codex"],
874
+ ["openai%2Fgpt-5-4", "openai%2Fgpt-5.4"],
875
+ ["openai%2Fgpt-5-4-high", "openai%2Fgpt-5.4"],
876
+ ["openai%2Fgpt-5-4-low", "openai%2Fgpt-5.4"],
877
+ ["openai%2Fgpt-5-4-medium", "openai%2Fgpt-5.4"],
878
+ ["openai%2Fgpt-5-4-mini", "openai%2Fgpt-5.4-mini"],
879
+ ["openai%2Fgpt-5-4-mini-high", "openai%2Fgpt-5.4-mini"],
880
+ ["openai%2Fgpt-5-4-mini-low", "openai%2Fgpt-5.4-mini"],
881
+ ["openai%2Fgpt-5-4-mini-medium", "openai%2Fgpt-5.4-mini"],
882
+ ["openai%2Fgpt-5-4-mini-non-reasoning", "openai%2Fgpt-5.4-mini"],
883
+ ["openai%2Fgpt-5-4-nano", "openai%2Fgpt-5.4-nano"],
884
+ ["openai%2Fgpt-5-4-nano-high", "openai%2Fgpt-5.4-nano"],
885
+ ["openai%2Fgpt-5-4-nano-low", "openai%2Fgpt-5.4-nano"],
886
+ ["openai%2Fgpt-5-4-nano-medium", "openai%2Fgpt-5.4-nano"],
887
+ ["openai%2Fgpt-5-4-nano-non-reasoning", "openai%2Fgpt-5.4-nano"],
888
+ ["openai%2Fgpt-5-4-non-reasoning", "openai%2Fgpt-5.4"],
889
+ ["openai%2Fgpt-5-4-pro", "openai%2Fgpt-5.4-pro"],
890
+ ["openai%2Fgpt-5-high", "openai%2Fgpt-5"],
891
+ ["openai%2Fgpt-5-low", "openai%2Fgpt-5"],
892
+ ["openai%2Fgpt-5-medium", "openai%2Fgpt-5"],
893
+ ["openai%2Fgpt-5-mini-medium", "openai%2Fgpt-5-mini"],
894
+ ["openai%2Fgpt-5-mini-minimal", "openai%2Fgpt-5-mini"],
895
+ ["openai%2Fgpt-5-minimal", "openai%2Fgpt-5"],
896
+ ["openai%2Fgpt-5-nano-medium", "openai%2Fgpt-5-nano"],
897
+ ["openai%2Fgpt-5-nano-minimal", "openai%2Fgpt-5-nano"],
898
+ ["openai%2Fgpt-5.1-2025-11-13", "openai%2Fgpt-5.1"],
899
+ ["openai%2Fgpt-5.1-codex-high", "openai%2Fgpt-5.1-codex"],
900
+ ["openai%2Fgpt-5.1-instant", "openai%2Fgpt-5-1-instant"],
901
+ ["openai%2Fgpt-5.1-thinking", "openai%2Fgpt-5.1"],
902
+ ["openai%2Fgpt-5.4-high", "openai%2Fgpt-5.4"],
903
+ ["openai%2Fgpt-5.4-mini-2026-03-17", "openai%2Fgpt-5.4-mini"],
904
+ ["openai%2Fgpt-5.4-nano-2026-03-17", "openai%2Fgpt-5.4-nano"],
905
+ ["openai%2Fgpt-oss-120b-high", "openai%2Fgpt-oss-120b"],
906
+ ["openai%2Fgpt-oss-120b-low", "openai%2Fgpt-oss-120b"],
907
+ ["openai%2Fgpt-oss-20b-high", "openai%2Fgpt-oss-20b"],
908
+ ["openai%2Fgpt-oss-20b-low", "openai%2Fgpt-oss-20b"],
909
+ ["openai%2Fgpt-oss-20b-medium", "openai%2Fgpt-oss-20b"],
910
+ ["openai%2Fo1-preview", "openai%2Fo1"],
911
+ ["openai%2Fo3%20Pro", "openai%2Fo3-pro"],
912
+ ["openbmb%2Fminicpm-sala", "openbmb%2FMiniCPM-SALA"],
913
+ ["openchat%2Fopenchat-3.5-0106", "openchat%2Fopenchat_3.5"],
914
+ ["openchat%2Fopenchat-3.5-1210", "openchat%2Fopenchat_3.5"],
915
+ ["openchat%2Fopenchat-35", "openchat%2Fopenchat_3.5"],
916
+ ["perplexity-sonar-reasoning", "unknown%2Fperplexity-sonar-reasoning"],
917
+ ["phi-3-medium-4k-instruct", "microsoft%2Fphi-3-medium-4k"],
918
+ ["phi-3-mini-128k-instruct", "microsoft%2Fphi-3-mini-128k"],
919
+ ["phi-3-mini-4k-instruct", "microsoft%2Fphi-3-mini-4k"],
920
+ ["phi-3-small-8k-instruct", "microsoft%2Fphi-3-small-8k"],
921
+ ["phi-4", "microsoft%2Fphi-4"],
922
+ ["prithivMLmods%2FPhi4-Super", "prithivMLmods%2FPhi-4-Super"],
923
+ ["qwen%2FQwen2.5-7B", "Qwen%2FQwen2.5-7B"],
924
+ ["qwen%2FQwen3-235B", "Qwen%2FQwen3-235B-A22B"],
925
+ ["qwen%2Fqvq-72b-preview", "Qwen%2FQVQ-72B-Preview"],
926
+ ["qwen%2Fqwen-2.5-14b-instruct", "Qwen%2FQwen2.5-14B"],
927
+ ["qwen%2Fqwen-2.5-32b-instruct", "Qwen%2FQwen2.5-32B"],
928
+ ["qwen%2Fqwen-2.5-72b-instruct", "Qwen%2FQwen2.5-72B"],
929
+ ["qwen%2Fqwen-2.5-7b-instruct", "Qwen%2FQwen2.5-7B"],
930
+ ["qwen%2Fqwen-2.5-coder-32b-instruct", "Qwen%2FQwen2.5-Coder-32B"],
931
+ ["qwen%2Fqwen-2.5-coder-7b-instruct", "Qwen%2FQwen2.5-Coder-7B"],
932
+ ["qwen%2Fqwen-2.5-vl-72b-instruct", "alibaba%2Fqwen2.5-vl-72b"],
933
+ ["qwen%2Fqwen1.5-110b-chat", "Qwen%2FQwen1.5-110B"],
934
+ ["qwen%2Fqwen1.5-14b", "Qwen%2FQwen1.5-14B"],
935
+ ["qwen%2Fqwen1.5-32b", "Qwen%2FQwen1.5-32B"],
936
+ ["qwen%2Fqwen1.5-72b", "Qwen%2FQwen1.5-72B"],
937
+ ["qwen%2Fqwen1.5-72b-chat", "Qwen%2FQwen1.5-72B"],
938
+ ["qwen%2Fqwen1.5-7b", "Qwen%2FQwen1.5-7B"],
939
+ ["qwen%2Fqwen2-72b-instruct", "Qwen%2FQwen2-72B"],
940
+ ["qwen%2Fqwen2-7b-instruct", "Qwen%2FQwen2-7B"],
941
+ ["qwen%2Fqwen2-vl-72b", "Qwen%2FQwen2-VL-72B"],
942
+ ["qwen%2Fqwen2.5-72b-instruct-turbo", "Qwen%2FQwen2.5-72B"],
943
+ ["qwen%2Fqwen2.5-7b-instruct-turbo", "Qwen%2FQwen2.5-7B"],
944
+ ["qwen%2Fqwen2.5-omni-7b", "Qwen%2FQwen2.5-Omni-7B"],
945
+ ["qwen%2Fqwen2.5-vl-72b", "alibaba%2Fqwen2.5-vl-72b"],
946
+ ["qwen%2Fqwen2.5-vl-7b", "alibaba%2Fqwen2.5-vl-7b"],
947
+ ["qwen%2Fqwen3-14b", "Qwen%2FQwen3-14B"],
948
+ ["qwen%2Fqwen3-14b-fc", "Qwen%2FQwen3-14B"],
949
+ ["qwen%2Fqwen3-14b-prompt", "Qwen%2FQwen3-14B"],
950
+ ["qwen%2Fqwen3-235b-a22b", "Qwen%2FQwen3-235B-A22B"],
951
+ ["qwen%2Fqwen3-235b-a22b-2507", "Qwen%2FQwen3-235B-A22B"],
952
+ ["qwen%2Fqwen3-235b-a22b-instruct-2507", "Qwen%2FQwen3-235B-A22B"],
953
+ ["qwen%2Fqwen3-235b-a22b-instruct-2507-fc", "Qwen%2FQwen3-235B-A22B"],
954
+ ["qwen%2Fqwen3-235b-a22b-instruct-2507-fp8", "Qwen%2FQwen3-235B-A22B"],
955
+ ["qwen%2Fqwen3-235b-a22b-instruct-2507-prompt", "Qwen%2FQwen3-235B-A22B"],
956
+ ["qwen%2Fqwen3-235b-a22b-thinking-2507", "Qwen%2FQwen3-235B-A22B"],
957
+ ["qwen%2Fqwen3-30b-a3b", "Qwen%2FQwen3-30B-A3B"],
958
+ ["qwen%2Fqwen3-30b-a3b-instruct-2507", "Qwen%2FQwen3-30B-A3B"],
959
+ ["qwen%2Fqwen3-30b-a3b-instruct-2507-fc", "Qwen%2FQwen3-30B-A3B"],
960
+ ["qwen%2Fqwen3-30b-a3b-instruct-2507-prompt", "Qwen%2FQwen3-30B-A3B"],
961
+ ["qwen%2Fqwen3-32b", "Qwen%2FQwen3-32B"],
962
+ ["qwen%2Fqwen3-32b-fc", "Qwen%2FQwen3-32B"],
963
+ ["qwen%2Fqwen3-32b-nothink", "Qwen%2FQwen3-32B"],
964
+ ["qwen%2Fqwen3-32b-prompt", "Qwen%2FQwen3-32B"],
965
+ ["qwen%2Fqwen3-4b-instruct", "alibaba%2Fqwen3-4b"],
966
+ ["qwen%2Fqwen3-8b", "Qwen%2FQwen3-8B"],
967
+ ["qwen%2Fqwen3-8b-fc", "Qwen%2FQwen3-8B"],
968
+ ["qwen%2Fqwen3-8b-nothink", "Qwen%2FQwen3-8B"],
969
+ ["qwen%2Fqwen3-8b-prompt", "Qwen%2FQwen3-8B"],
970
+ ["qwen%2Fqwen3-coder-480b-a35b-instruct", "alibaba%2Fqwen3-coder-480b-a35b"],
971
+ ["qwen%2Fqwen3-max", "alibaba%2Fqwen3-max"],
972
+ ["qwen%2Fqwen3-next-80b-a3b-instruct", "alibaba%2Fqwen3-next-80b-a3b"],
973
+ ["qwen%2Fqwen3-next-80b-a3b-thinking", "alibaba%2Fqwen3-next-80b-a3b"],
974
+ ["qwen%2Fqwen3-vl-235b-a22b-instruct", "alibaba%2Fqwen3-vl-235b-a22b"],
975
+ ["qwen%2Fqwen3-vl-235b-a22b-thinking", "alibaba%2Fqwen3-vl-235b-a22b"],
976
+ ["qwen%2Fqwen3-vl-30b-a3b-instruct", "alibaba%2Fqwen3-vl-30b-a3b"],
977
+ ["qwen%2Fqwen3-vl-30b-a3b-thinking", "alibaba%2Fqwen3-vl-30b-a3b"],
978
+ ["qwen%2Fqwen3-vl-32b-instruct", "Qwen%2FQwen3-VL-32B-Thinking"],
979
+ ["qwen%2Fqwen3-vl-32b-thinking", "Qwen%2FQwen3-VL-32B-Thinking"],
980
+ ["qwen%2Fqwen3-vl-4b-instruct", "alibaba%2Fqwen3-vl-4b"],
981
+ ["qwen%2Fqwen3-vl-4b-thinking", "alibaba%2Fqwen3-vl-4b"],
982
+ ["qwen%2Fqwen3-vl-8b-instruct", "Qwen%2FQwen3-VL-8B-Thinking"],
983
+ ["qwen%2Fqwen3-vl-8b-thinking", "Qwen%2FQwen3-VL-8B-Thinking"],
984
+ ["qwen%2Fqwen3.5-0.8b", "Qwen%2FQwen3.5-0.8B"],
985
+ ["qwen%2Fqwen3.5-122b", "alibaba%2Fqwen3-5-122b"],
986
+ ["qwen%2Fqwen3.5-122b-a10b", "alibaba%2Fqwen3-5-122b"],
987
+ ["qwen%2Fqwen3.5-122b-nothink", "alibaba%2Fqwen3-5-122b"],
988
+ ["qwen%2Fqwen3.5-27b", "Qwen%2FQwen3.5-27B"],
989
+ ["qwen%2Fqwen3.5-27b-nothink", "Qwen%2FQwen3.5-27B"],
990
+ ["qwen%2Fqwen3.5-2b", "Qwen%2FQwen3.5-2B"],
991
+ ["qwen%2Fqwen3.5-35b-a3b", "qwen%2Fqwen3.5-35b"],
992
+ ["qwen%2Fqwen3.5-397b-a17b", "alibaba%2Fqwen3-5-397b"],
993
+ ["qwen%2Fqwen3.5-4b", "Qwen%2FQwen3.5-4B"],
994
+ ["qwen%2Fqwen3.5-9b", "Qwen%2FQwen3.5-9B"],
995
+ ["qwen%2Fqwen3.6-27b", "Qwen%2FQwen3.6-27B"],
996
+ ["qwen%2Fqwen3.6-35b-a3b", "alibaba%2Fqwen3-6-35b"],
997
+ ["qwen%2Fqwen3.6-plus", "alibaba%2Fqwen3.6-plus"],
998
+ ["qwen%2Fqwq-32b", "Qwen%2FQwQ-32B"],
999
+ ["qwen1.5-0.5b-chat", "Qwen%2FQwen1.5-0.5B-Chat"],
1000
+ ["qwen1.5-1.8b-chat", "Qwen%2FQwen1.5-1.8B-Chat"],
1001
+ ["qwen1.5-110b-chat", "Qwen%2FQwen1.5-110B"],
1002
+ ["qwen1.5-4b-chat", "Qwen%2FQwen1.5-4B-Chat"],
1003
+ ["qwen1.5-72b-chat", "Qwen%2FQwen1.5-72B"],
1004
+ ["qwen1.5-7b-chat", "Qwen%2FQwen1.5-7B"],
1005
+ ["qwen2-72b-instruct", "Qwen%2FQwen2-72B"],
1006
+ ["qwen2-math-72b-instruct", "Qwen%2FQwen2-Math-72B-Instruct"],
1007
+ ["reflection-llama-3.1-70b", "mattshumer%2FReflection-Llama-3.1-70B"],
1008
+ ["sarvam%2Fsarvam-105b", "sarvamai%2Fsarvam-105b"],
1009
+ ["sarvam%2Fsarvam-30b", "sarvamai%2Fsarvam-30b"],
1010
+ ["sarvam%2Fsarvam-m-reasoning", "sarvam%2Fsarvam-m"],
1011
+ ["snowflake%2Farctic-instruct", "Snowflake%2Fsnowflake-arctic-instruct"],
1012
+ ["snowflake%2Fsnowflake-arctic-instruct", "Snowflake%2Fsnowflake-arctic-instruct"],
1013
+ ["sometimesanotion%2FLamarck-14B-v0.7-rc4", "sometimesanotion%2FLamarck-14B-v0.7"],
1014
+ ["stabilityai%2Fstablelm-2-12b-chat", "stabilityai%2Fstablelm-2-12b"],
1015
+ ["stabilityai%2Fstablelm-2-1_6b-chat", "stabilityai%2Fstablelm-2-1_6b"],
1016
+ ["stanford%2Falpaca-7b", "stanford%2FAlpaca-7B"],
1017
+ ["stepfun%2Fstep-3-5-flash", "stepfun-ai%2FStep-3.5-Flash"],
1018
+ ["stepfun%2Fstep-3.5-flash", "stepfun-ai%2FStep-3.5-Flash"],
1019
+ ["swiss-ai-initiative%2Fapertus-70b-instruct", "swiss-ai-initiative%2Fapertus-70b"],
1020
+ ["swiss-ai-initiative%2Fapertus-8b-instruct", "swiss-ai-initiative%2Fapertus-8b"],
1021
+ ["thudm%2Fchatglm-6b", "zai-org%2Fchatglm-6b"],
1022
+ ["tiger-lab%2Fmammoth2-7b-plus", "TIGER-Lab%2FMAmmoTH2-7B-Plus"],
1023
+ ["tiger-lab%2Fmammoth2-8b-plus", "TIGER-Lab%2FMAmmoTH2-8B-Plus"],
1024
+ ["tiger-lab%2Fmammoth2-8x7b-plus", "TIGER-Lab%2FMAmmoTH2-8x7B-Plus"],
1025
+ ["tii-uae%2Ffalcon3-10b-instruct-fc", "tiiuae%2Ffalcon3-10b"],
1026
+ ["tii-uae%2Ffalcon3-1b-instruct-fc", "tii-uae%2Ffalcon3-1b"],
1027
+ ["tii-uae%2Ffalcon3-3b-instruct-fc", "tii-uae%2Ffalcon3-3b"],
1028
+ ["tii-uae%2Ffalcon3-7b-instruct-fc", "tiiuae%2Ffalcon3-7b"],
1029
+ ["tiiuae%2FFalcon-40B", "tiiuae%2Ffalcon-40b"],
1030
+ ["tiiuae%2FFalcon-7B", "tiiuae%2Ffalcon-7b"],
1031
+ ["tiiuae%2FFalcon3-10B-Instruct", "tiiuae%2Ffalcon3-10b"],
1032
+ ["tiiuae%2FFalcon3-1B-Instruct", "tii-uae%2Ffalcon3-1b"],
1033
+ ["tiiuae%2FFalcon3-3B-Instruct", "tii-uae%2Ffalcon3-3b"],
1034
+ ["tiiuae%2FFalcon3-7B-Instruct", "tiiuae%2Ffalcon3-7b"],
1035
+ ["tiiuae%2Ffalcon-40b-instruct", "tiiuae%2Ffalcon-40b"],
1036
+ ["tiiuae%2Ffalcon-7b-instruct", "tiiuae%2Ffalcon-7b"],
1037
+ ["tinycompany%2FSigmaBoi-bgem3", "tinycompany%2FSigmaBoi-bge-m3"],
1038
+ ["tinycompany%2FSigmaBoi-nomic-moe", "tinycompany%2FSigmaBoi-nomic-moe-bf16"],
1039
+ ["together%2Fredpajama-incite-base-7b", "together%2FRedPajama-INCITE-Base-7B"],
1040
+ ["together%2Fredpajama-incite-instruct-7b", "together%2FRedPajama-INCITE-Instruct-7B"],
1041
+ ["togethercomputer%2FRedPajama-INCITE-7B-Base", "togethercomputer%2Fredpajama-incite-7b"],
1042
+ ["togethercomputer%2FRedPajama-INCITE-7B-Chat", "togethercomputer%2Fredpajama-incite-7b"],
1043
+ ["togethercomputer%2FRedPajama-INCITE-7B-Instruct", "togethercomputer%2Fredpajama-incite-7b"],
1044
+ ["trillionlabs%2Ftri-21b-think-preview", "trillionlabs%2FTri-21B-Think-Preview"],
1045
+ ["unknown%2Fdoubao-1.5-pro", "unknown%2FDoubao-1.5-pro"],
1046
+ ["unknown%2Fiswe_agent", "unknown%2FiSWE_Agent"],
1047
+ ["upstage%2Fsolar-open-100b-reasoning", "upstage%2Fsolar-open-100b"],
1048
+ ["upstage%2Fsolar-pro-2", "upstage%2Fsolar-pro2"],
1049
+ ["vicgalle%2FHumanish-RP-Llama-3.1-8B", "vicgalle%2FHumanish-Roleplay-Llama-3.1-8B"],
1050
+ ["vicuna-7b-v1.5", "lmsys%2Fvicuna-7b-v1.5"],
1051
+ ["wbot-4%3A347b_no_s", "wbot-4347b-no-s"],
1052
+ ["weqweasdas%2FRM-Gemma-7B-4096", "weqweasdas%2FRM-Gemma-7B"],
1053
+ ["writer%2Fpalmyra-x-004", "writer%2Fpalmyra-x"],
1054
+ ["writer%2Fpalmyra-x-v2", "writer%2Fpalmyra-x"],
1055
+ ["writer%2Fpalmyra-x-v3", "writer%2Fpalmyra-x"],
1056
+ ["xai%2FGrok%204", "xai%2Fgrok-4"],
1057
+ ["xai%2Fgrok-1", "xai-org%2Fgrok-1"],
1058
+ ["xai%2Fgrok-2-1212", "xai%2Fgrok-2"],
1059
+ ["xai%2Fgrok-2-mini", "xai%2Fgrok-2"],
1060
+ ["xai%2Fgrok-2-vision-1212", "xai%2Fgrok-2"],
1061
+ ["xai%2Fgrok-3-beta", "xai%2Fgrok-3"],
1062
+ ["xai%2Fgrok-3-mini-beta", "xai%2Fgrok-3-mini"],
1063
+ ["xai%2Fgrok-3-mini-beta-low-openrouter", "xai%2Fgrok-3-mini"],
1064
+ ["xai%2Fgrok-3-mini-reasoning", "xai%2Fgrok-3-mini"],
1065
+ ["xai%2Fgrok-3-openrouter", "xai%2Fgrok-3"],
1066
+ ["xai%2Fgrok-4-0709", "xai%2Fgrok-4"],
1067
+ ["xai%2Fgrok-4-0709-fc", "xai%2Fgrok-4"],
1068
+ ["xai%2Fgrok-4-0709-prompt", "xai%2Fgrok-4"],
1069
+ ["xai%2Fgrok-4-20-0309", "xai%2Fgrok-4"],
1070
+ ["xai%2Fgrok-4-20-non-reasoning", "xai%2Fgrok-4.20-non-reasoning"],
1071
+ ["xai%2Fgrok-4-heavy", "xai%2Fgrok-4"],
1072
+ ["xiaomi%2Fmimo-v2-0206", "xiaomi%2Fmimo-v2"],
1073
+ ["xiaomi%2Fmimo-v2-flash", "xiaomi%2Fmimo-v2"],
1074
+ ["xiaomi%2Fmimo-v2-pro", "xiaomi%2Fmimo-v2"],
1075
+ ["yandex%2FYaLM-100B", "yandex%2Fyalm-100b"],
1076
+ ["yi-6b-chat", "01-ai%2FYi-6B"],
1077
+ ["z-ai%2Fglm-4.5", "zai-org%2FGLM-4.5"],
1078
+ ["zai%2Fglm-4-5-air", "zai-org%2FGLM-4.5-Air"],
1079
+ ["zai%2Fglm-4-5v", "zai%2Fglm-4.5v"],
1080
+ ["zai%2Fglm-4-5v-reasoning", "zai%2Fglm-4.5v"],
1081
+ ["zai%2Fglm-4-6", "zai-org%2FGLM-4.6"],
1082
+ ["zai%2Fglm-4-6-reasoning", "zai-org%2FGLM-4.6"],
1083
+ ["zai%2Fglm-4-6v", "zai%2Fglm-4.6v"],
1084
+ ["zai%2Fglm-4-6v-reasoning", "zai%2Fglm-4.6v"],
1085
+ ["zai%2Fglm-4-7", "zai-org%2FGLM-4.7"],
1086
+ ["zai%2Fglm-4-7-flash", "zai-org%2FGLM-4.7-Flash"],
1087
+ ["zai%2Fglm-4-7-non-reasoning", "zai-org%2FGLM-4.7"],
1088
+ ["zai%2Fglm-4.5", "zai-org%2FGLM-4.5"],
1089
+ ["zai%2Fglm-4.6", "zai-org%2FGLM-4.6"],
1090
+ ["zai%2Fglm-4.7", "zai-org%2FGLM-4.7"],
1091
+ ["zai%2Fglm-5", "zai-org%2FGLM-5"],
1092
+ ["zai%2Fglm-5-1", "zai-org%2FGLM-5.1"],
1093
+ ["zai%2Fglm-5-1-non-reasoning", "zai-org%2FGLM-5.1"],
1094
+ ["zai%2Fglm-5-non-reasoning", "zai-org%2FGLM-5"],
1095
+ ["zai%2Fglm-5-thinking", "zai-org%2FGLM-5"],
1096
+ ["zai%2Fglm-5.1-thinking", "zai-org%2FGLM-5.1"],
1097
+ ["zai-org%2FGLM-4.6-FP8", "zai-org%2FGLM-4.6"],
1098
+ ["zai-org%2FGLM-4.7-FP8", "zai-org%2FGLM-4.7"],
1099
+ ["zai-org%2FGLM-5-FP8", "zai-org%2FGLM-5"],
1100
+ ["zai-org%2FGLM-5.1-FP8", "zai-org%2FGLM-5.1"],
1101
+ ["zai-org%2Fglm-4.5", "zai-org%2FGLM-4.5"],
1102
+ ["zai-org%2Fglm-4.5-air", "zai-org%2FGLM-4.5-Air"],
1103
+ ["zai-org%2Fglm-4.5-air-fp8", "zai-org%2FGLM-4.5-Air"],
1104
+ ["zai-org%2Fglm-4.6", "zai-org%2FGLM-4.6"],
1105
+ ["zai-org%2Fglm-4.7", "zai-org%2FGLM-4.7"],
1106
+ ["zai-org%2Fglm-4.7-flash", "zai-org%2FGLM-4.7-Flash"],
1107
+ ["zai-org%2Fglm-5", "zai-org%2FGLM-5"],
1108
+ ["zai-org%2Fglm-5.1", "zai-org%2FGLM-5.1"],
1109
+ ["zai-org%2Fglm-5v-turbo", "zai%2Fglm-5v-turbo"],
1110
+ ["zelk12%2FTest01012025155054", "zelk12%2FTest01012025155054_gemma-2"],
1111
+ ["zephyr-7b-alpha", "HuggingFaceH4%2Fzephyr-7b-alpha"],
1112
+ ["zephyr-7b-beta", "HuggingFaceH4%2Fzephyr-7b-beta"],
1113
+ ["zhipu%2FGLM%204.6", "zai-org%2FGLM-4.6"],
1114
+ ["zhipu%2FGLM%204.7", "zai-org%2FGLM-4.7"],
1115
+ ["zhipu%2Fglm-4.5", "zai-org%2FGLM-4.5"],
1116
+ ["zhipu%2Fglm-5", "zai-org%2FGLM-5"],
1117
+ ["zhipu-ai%2Fglm-4-9b", "zai-org%2Fglm-4-9b"],
1118
+ ["zhipu-ai%2Fglm-4-9b-chat", "zai-org%2Fglm-4-9b"],
1119
+ ["zhipu-ai%2Fglm-4.5", "zai-org%2FGLM-4.5"],
1120
+ ["zhipu-ai%2Fglm-4.5-air", "zai-org%2FGLM-4.5-Air"],
1121
+ ["zhipu-ai%2Fglm-4.6", "zai-org%2FGLM-4.6"],
1122
+ ["zhipu-ai%2Fglm-4.7", "zai-org%2FGLM-4.7"],
1123
+ ["zhipu-ai%2Fglm-5", "zai-org%2FGLM-5"],
1124
+ ])
1125
+
1126
+ export function resolveModelRedirect(routeId: string): string | undefined {
1127
+ return MODEL_URL_REDIRECTS.get(routeId)
1128
+ }
lib/utils.ts CHANGED
@@ -30,6 +30,22 @@ export function routeIdFromSegments(value: string | string[] | undefined): strin
30
  return joined.replace(/\//g, "%2F")
31
  }
32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  /**
34
  * Title-case a benchmark / family / eval label that arrives in slug or
35
  * snake-case form. Example inputs and outputs:
 
30
  return joined.replace(/\//g, "%2F")
31
  }
32
 
33
+ /**
34
+ * Build the backend `%2F`-encoded route id form from a plain canonical
35
+ * model id (`org/name`). Matches the producer's `route_id` /
36
+ * `model_route_id` encoding (RFC 3986 percent-encoding of the whole id),
37
+ * so the result can be fed straight into `routeIdToPath` for a URL path.
38
+ *
39
+ * This is the model-resolution-rework replacement for the old
40
+ * client-side family-route computation (since removed): the
41
+ * group/leaf id is now server-provided (`model_group_id`), and we only
42
+ * need to encode it for routing — never re-derive it.
43
+ */
44
+ export function routeIdFromModelId(id: string | null | undefined): string {
45
+ if (!id) return ""
46
+ return encodeURIComponent(id.trim())
47
+ }
48
+
49
  /**
50
  * Title-case a benchmark / family / eval label that arrives in slug or
51
  * snake-case form. Example inputs and outputs:
lib/view-data.ts CHANGED
@@ -551,6 +551,8 @@ function reshapeCellToModelResult(row: Row): ModelResultForBenchmark {
551
  return {
552
  model_info: (modelInfo ?? modelInfoFromModelRow(row)) as ModelInfo,
553
  model_route_id: optionalString(row.model_route_id),
 
 
554
  score: scoreDetails.score,
555
  score_details: scoreDetails,
556
  evaluation_timestamp: asString(row.evaluation_timestamp, ""),
@@ -631,10 +633,16 @@ function modelSummaryFromRows(modelRow: Row, cellRows: Row[]): ModelEvaluationSu
631
  ...variant,
632
  variant_id: asString(variant.variant_id ?? variant.variant_key, `variant-${index}`),
633
  variant_key: asString(variant.variant_key, `variant-${index}`),
 
 
 
 
 
 
634
  variant_label: asString(variant.variant_label ?? variant.variant_display_name, "Default"),
635
  variant_display_name: asString(variant.variant_display_name ?? variant.variant_label ?? modelRow.model_name, modelRow.model_name),
636
  raw_model_ids: asArray<string>(variant.raw_model_ids),
637
- family_id: asString(variant.family_id ?? modelRow.model_family_id, modelRow.model_family_id),
638
  family_name: asString(variant.family_name ?? modelRow.model_family_name, modelRow.model_family_name),
639
  total_evaluations: asNumber(variant.total_evaluations ?? totalEvaluations),
640
  last_updated: asString(variant.last_updated ?? lastUpdated, lastUpdated),
@@ -649,10 +657,17 @@ function modelSummaryFromRows(modelRow: Row, cellRows: Row[]): ModelEvaluationSu
649
 
650
  return {
651
  ...core,
652
- model_family_id: asString(modelRow.model_family_id ?? modelRow.model_key ?? modelRow.model_id, modelRow.model_key ?? modelRow.model_id),
653
  model_route_id: asString(modelRow.model_route_id ?? modelRow.route_id, modelRow.route_id),
654
  model_family_name: asString(modelRow.model_family_name ?? modelRow.model_name, modelRow.model_name),
655
  raw_model_ids: rawModelIds.length > 0 ? rawModelIds : [asString(modelRow.model_key ?? modelRow.model_id, "")].filter(Boolean),
 
 
 
 
 
 
 
656
  variants,
657
  }
658
  }
@@ -751,7 +766,7 @@ export async function getDashboardData() {
751
 
752
  export async function getModelSummaryById(routeId: string): Promise<ModelEvaluationSummary | null> {
753
  // Lookups use the addressable identifier (`model_key`/`route_id`/
754
- // `model_route_id`/`model_family_id`) so unresolved models — whose
755
  // `model_id` is NULL — are still findable. `model_id` is kept in the
756
  // OR chain as a back-compat fallback for old links.
757
  //
@@ -760,20 +775,38 @@ export async function getModelSummaryById(routeId: string): Promise<ModelEvaluat
760
  // Next.js already decodes path params before they reach here, so
761
  // `routeId` lands as `google/gemini-3-pro`.
762
  // - Plain canonical id with `/` (same shape after Next.js decode).
763
- // - Legacy `__`-separated form (e.g. `google__gemini-3-pro`) — old
764
- // `getModelFamilyRouteId` emitted this; bookmarks may still use
765
- // it. Convert `__` → `/` for lookup.
766
  const dunder = routeId.includes("__") ? routeId.replace(/__/g, "/") : routeId
767
  const rows = await readRows<Row>(
768
  `SELECT *
769
  FROM models_view
770
- WHERE model_key = ? OR route_id = ? OR model_route_id = ? OR model_family_id = ? OR model_id = ?
771
  OR model_key = ? OR model_id = ?
772
  LIMIT 1`,
773
  [routeId, routeId, routeId, routeId, routeId, dunder, dunder],
774
  { contextLabel: `model_lookup=${routeId}` }
775
  )
776
- const modelRow = rows[0]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
777
  if (!modelRow) return null
778
 
779
  const cellRows = await getModelEvaluationRows(asString(modelRow.model_key ?? modelRow.model_id, routeId))
@@ -888,6 +921,7 @@ export async function getEvalSummaryById(evalId: string): Promise<BenchmarkEvalS
888
  return {
889
  model_info: base.model_info,
890
  model_route_id: row.model_route_id,
 
891
  evaluation_timestamp: base.evaluation_timestamp,
892
  source_metadata: base.source_metadata,
893
  source_data: base.source_data,
@@ -939,6 +973,7 @@ export async function getEvalSummaryById(evalId: string): Promise<BenchmarkEvalS
939
  .map((mr) => ({
940
  model_info: mr.model_info,
941
  model_route_id: mr.model_route_id,
 
942
  evaluation_timestamp: mr.evaluation_timestamp,
943
  source_metadata: mr.source_metadata,
944
  source_data: mr.source_data,
@@ -962,9 +997,25 @@ export async function getDeveloperList(): Promise<DeveloperListEntry[]> {
962
  return [...(headline.developers ?? [])].sort((a, b) => a.developer.localeCompare(b.developer))
963
  }
964
 
 
 
 
 
 
 
 
 
965
  export async function getDeveloperSummaryById(routeId: string) {
966
  const developers = await getDeveloperList()
967
- const developer = developers.find((entry) => entry.route_id === routeId)
 
 
 
 
 
 
 
 
968
  if (!developer) return null
969
 
970
  const modelRows = await readRows<Row>(
 
551
  return {
552
  model_info: (modelInfo ?? modelInfoFromModelRow(row)) as ModelInfo,
553
  model_route_id: optionalString(row.model_route_id),
554
+ // model-resolution-rework: server-provided group id for routing fallback.
555
+ model_group_id: optionalString(row.model_group_id),
556
  score: scoreDetails.score,
557
  score_details: scoreDetails,
558
  evaluation_timestamp: asString(row.evaluation_timestamp, ""),
 
633
  ...variant,
634
  variant_id: asString(variant.variant_id ?? variant.variant_key, `variant-${index}`),
635
  variant_key: asString(variant.variant_key, `variant-${index}`),
636
+ // Carry the GROUP's encoded route onto every variant. comparison-index /
637
+ // peer-ranks are keyed by the group route_id, and the model detail page
638
+ // renders the selected VARIANT as its summary — without this the variant
639
+ // has no model_route_id and the page falls back to a non-matching id, so
640
+ // peer-comparison charts find no scores.
641
+ model_route_id: asString(modelRow.model_route_id ?? modelRow.route_id, modelRow.route_id),
642
  variant_label: asString(variant.variant_label ?? variant.variant_display_name, "Default"),
643
  variant_display_name: asString(variant.variant_display_name ?? variant.variant_label ?? modelRow.model_name, modelRow.model_name),
644
  raw_model_ids: asArray<string>(variant.raw_model_ids),
645
+ family_id: asString(variant.family_id ?? modelRow.model_group_id, modelRow.model_group_id),
646
  family_name: asString(variant.family_name ?? modelRow.model_family_name, modelRow.model_family_name),
647
  total_evaluations: asNumber(variant.total_evaluations ?? totalEvaluations),
648
  last_updated: asString(variant.last_updated ?? lastUpdated, lastUpdated),
 
657
 
658
  return {
659
  ...core,
660
+ model_group_id: asString(modelRow.model_group_id ?? modelRow.model_key ?? modelRow.model_id, modelRow.model_key ?? modelRow.model_id),
661
  model_route_id: asString(modelRow.model_route_id ?? modelRow.route_id, modelRow.route_id),
662
  model_family_name: asString(modelRow.model_family_name ?? modelRow.model_name, modelRow.model_name),
663
  raw_model_ids: rawModelIds.length > 0 ? rawModelIds : [asString(modelRow.model_key ?? modelRow.model_id, "")].filter(Boolean),
664
+ // model-resolution-rework (additive, nullable). The summary builder
665
+ // reads `SELECT *` from models_view, so these columns flow through
666
+ // once the producer view layer emits them (post-M9). Until then
667
+ // optionalString yields undefined and the UI conditionally omits them.
668
+ lineage_origin_model_id: optionalString(modelRow.lineage_origin_model_id),
669
+ resolution_source: optionalString(modelRow.resolution_source),
670
+ resolution_granularity: optionalString(modelRow.resolution_granularity),
671
  variants,
672
  }
673
  }
 
766
 
767
  export async function getModelSummaryById(routeId: string): Promise<ModelEvaluationSummary | null> {
768
  // Lookups use the addressable identifier (`model_key`/`route_id`/
769
+ // `model_route_id`/`model_group_id`) so unresolved models — whose
770
  // `model_id` is NULL — are still findable. `model_id` is kept in the
771
  // OR chain as a back-compat fallback for old links.
772
  //
 
775
  // Next.js already decodes path params before they reach here, so
776
  // `routeId` lands as `google/gemini-3-pro`.
777
  // - Plain canonical id with `/` (same shape after Next.js decode).
778
+ // - Legacy `__`-separated form (e.g. `google__gemini-3-pro`) — the
779
+ // old client-side family route computation emitted this; bookmarks
780
+ // may still use it. Convert `__` → `/` for lookup.
781
  const dunder = routeId.includes("__") ? routeId.replace(/__/g, "/") : routeId
782
  const rows = await readRows<Row>(
783
  `SELECT *
784
  FROM models_view
785
+ WHERE model_key = ? OR route_id = ? OR model_route_id = ? OR model_group_id = ? OR model_id = ?
786
  OR model_key = ? OR model_id = ?
787
  LIMIT 1`,
788
  [routeId, routeId, routeId, routeId, routeId, dunder, dunder],
789
  { contextLabel: `model_lookup=${routeId}` }
790
  )
791
+ let modelRow = rows[0]
792
+ if (!modelRow) {
793
+ // The id may be a FOLDED raw id — a dated snapshot / older-cased / variant
794
+ // spelling the producer collapsed into a group (e.g.
795
+ // `mistralai/mistral-medium-2505` folds into `mistralai/mistral-medium`).
796
+ // Such ids exist only inside the owning group row's `raw_model_ids` list,
797
+ // never as their own row, so resolve them to that group. Case-insensitive,
798
+ // since raw_model_ids preserve HF casing. This makes every inbound model
799
+ // link/bookmark resolve to a real page instead of 404-ing.
800
+ const byRaw = await readRows<Row>(
801
+ `SELECT *
802
+ FROM models_view
803
+ WHERE list_contains(list_transform(raw_model_ids, x -> lower(x)), lower(?))
804
+ OR list_contains(list_transform(raw_model_ids, x -> lower(x)), lower(?))
805
+ LIMIT 1`,
806
+ [routeId, dunder]
807
+ )
808
+ modelRow = byRaw[0]
809
+ }
810
  if (!modelRow) return null
811
 
812
  const cellRows = await getModelEvaluationRows(asString(modelRow.model_key ?? modelRow.model_id, routeId))
 
921
  return {
922
  model_info: base.model_info,
923
  model_route_id: row.model_route_id,
924
+ model_group_id: base.model_group_id,
925
  evaluation_timestamp: base.evaluation_timestamp,
926
  source_metadata: base.source_metadata,
927
  source_data: base.source_data,
 
973
  .map((mr) => ({
974
  model_info: mr.model_info,
975
  model_route_id: mr.model_route_id,
976
+ model_group_id: mr.model_group_id,
977
  evaluation_timestamp: mr.evaluation_timestamp,
978
  source_metadata: mr.source_metadata,
979
  source_data: mr.source_data,
 
997
  return [...(headline.developers ?? [])].sort((a, b) => a.developer.localeCompare(b.developer))
998
  }
999
 
1000
+ function decodeLoose(value: string): string {
1001
+ try {
1002
+ return decodeURIComponent(value)
1003
+ } catch {
1004
+ return value
1005
+ }
1006
+ }
1007
+
1008
  export async function getDeveloperSummaryById(routeId: string) {
1009
  const developers = await getDeveloperList()
1010
+ // `route_id` is stored pre-percent-encoded for names with spaces/parens
1011
+ // (e.g. "Mistral AI" -> "Mistral%20AI"), but the incoming routeId arrives
1012
+ // already decoded (routeIdFromSegments only round-trips %2F). Compare on the
1013
+ // decoded form so those developers' detail pages resolve. Exact match is
1014
+ // tried first as a fast path.
1015
+ const target = decodeLoose(routeId)
1016
+ const developer =
1017
+ developers.find((entry) => entry.route_id === routeId) ??
1018
+ developers.find((entry) => decodeLoose(entry.route_id) === target)
1019
  if (!developer) return null
1020
 
1021
  const modelRows = await readRows<Row>(
middleware.ts ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // model-resolution-rework — 301 redirects for changed model URLs.
2
+ //
3
+ // ~297 model routes change at the registry re-baseline (204 canonical_id
4
+ // flips root->leaf + 93 casing re-keys). Bookmarked / inbound links to the
5
+ // OLD route must 301 to the NEW one.
6
+ //
7
+ // WHY middleware and not `app/models/[...id]/route.ts`: a Route Handler
8
+ // (`route.ts`) cannot coexist with the existing `page.tsx` in the same App
9
+ // Router segment — Next.js treats that as a conflict. Middleware runs before
10
+ // routing, sees every `/models/*` request, and can issue a 301 with full
11
+ // control over the URL and query string. (Deviation from the playbook's
12
+ // suggested file path; same behaviour, correct Next.js mechanism.)
13
+ //
14
+ // CONTRACT (tested in tests/transformations/model-url-redirects.test.ts):
15
+ // - 301 (permanent) so caches/search engines update.
16
+ // - QUERY PARAMS PRESERVED across the redirect (e.g. ?version=...), so a
17
+ // deep link to a specific variant survives.
18
+ // - the model id round-trips through percent-encoding: the incoming path
19
+ // segment is decoded by Next.js, we re-encode to the producer `route_id`
20
+ // form (whole-id encoding, `/` -> %2F) to match the redirect-map keys.
21
+
22
+ import { NextResponse } from "next/server"
23
+ import type { NextRequest } from "next/server"
24
+
25
+ import { resolveModelRedirect } from "@/lib/model-url-redirects"
26
+
27
+ export const config = {
28
+ // Only run on model detail routes. Static assets / api are untouched.
29
+ matcher: ["/models/:path*"],
30
+ }
31
+
32
+ const MODELS_PREFIX = "/models/"
33
+
34
+ export function middleware(req: NextRequest): NextResponse | undefined {
35
+ const { pathname } = req.nextUrl
36
+ if (!pathname.startsWith(MODELS_PREFIX)) return undefined
37
+
38
+ // Everything after `/models/`. Next.js has already percent-DECODED the
39
+ // path, so `/models/anthropic%2Fclaude-3-haiku` arrives here as the
40
+ // decoded `anthropic/claude-3-haiku`. Re-encode the whole id to the
41
+ // producer `route_id` form to look up the map.
42
+ const rawTail = pathname.slice(MODELS_PREFIX.length)
43
+ if (!rawTail) return undefined
44
+
45
+ const decoded = safeDecode(rawTail)
46
+ const routeId = encodeURIComponent(decoded)
47
+
48
+ const newRouteId = resolveModelRedirect(routeId)
49
+ if (!newRouteId) return undefined
50
+
51
+ // Build the target. encodeURIComponent gives the whole-id `%2F` form;
52
+ // keep it as a single path segment under the [...id] catch-all so the
53
+ // page resolves it exactly like a freshly-minted link.
54
+ const target = req.nextUrl.clone()
55
+ target.pathname = `${MODELS_PREFIX}${newRouteId}`
56
+ // PRESERVE query params (e.g. ?version=20240620). `clone()` already copies
57
+ // search; set it explicitly so intent is obvious and robust to refactors.
58
+ target.search = req.nextUrl.search
59
+
60
+ return NextResponse.redirect(target, 301)
61
+ }
62
+
63
+ function safeDecode(value: string): string {
64
+ try {
65
+ return decodeURIComponent(value)
66
+ } catch {
67
+ // Malformed percent-encoding — treat the raw value as already-decoded.
68
+ return value
69
+ }
70
+ }
package.json CHANGED
@@ -12,6 +12,8 @@
12
  "start": "next start",
13
  "test": "vitest",
14
  "test:drift": "RUN_DRIFT=1 vitest --run tests/upstream-drift.test.ts",
 
 
15
  "compare-data-backends": "node scripts/compare-data-backends.mjs",
16
  "refresh-fixtures": "node scripts/refresh-fixtures.mjs",
17
  "audit-adapters": "tsx scripts/audit-adapters.mjs"
@@ -72,10 +74,12 @@
72
  "zod": "3.25.67"
73
  },
74
  "devDependencies": {
 
75
  "@tailwindcss/postcss": "^4.1.9",
76
  "@types/node": "^22",
77
  "@types/react": "^19",
78
  "@types/react-dom": "^19",
 
79
  "postcss": "^8.5",
80
  "puppeteer-core": "^24.42.0",
81
  "tailwindcss": "^4.1.9",
 
12
  "start": "next start",
13
  "test": "vitest",
14
  "test:drift": "RUN_DRIFT=1 vitest --run tests/upstream-drift.test.ts",
15
+ "test:integrity": "vitest --run tests/redirect-integrity.test.ts",
16
+ "test:e2e": "playwright test",
17
  "compare-data-backends": "node scripts/compare-data-backends.mjs",
18
  "refresh-fixtures": "node scripts/refresh-fixtures.mjs",
19
  "audit-adapters": "tsx scripts/audit-adapters.mjs"
 
74
  "zod": "3.25.67"
75
  },
76
  "devDependencies": {
77
+ "@playwright/test": "^1.60.0",
78
  "@tailwindcss/postcss": "^4.1.9",
79
  "@types/node": "^22",
80
  "@types/react": "^19",
81
  "@types/react-dom": "^19",
82
+ "playwright": "^1.60.0",
83
  "postcss": "^8.5",
84
  "puppeteer-core": "^24.42.0",
85
  "tailwindcss": "^4.1.9",
playwright.config.ts ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { defineConfig } from "@playwright/test"
2
+
3
+ // Frontend preflight e2e (opt-in): `SNAPSHOT_URL=<warehouse> pnpm test:e2e`.
4
+ // NOT part of `pnpm test` and nothing gates deploy — it's a correctness check you
5
+ // run before shipping a meaningful frontend change. See tests/PREFLIGHT.md.
6
+ //
7
+ // The webServer block boots `pnpm dev` against the snapshot itself (and reuses an
8
+ // already-running one), so there's no manual server dance. When SNAPSHOT_URL is
9
+ // unset the spec self-skips.
10
+ const SNAPSHOT_URL = process.env.SNAPSHOT_URL
11
+ const PORT = Number(process.env.PORT || 3211)
12
+
13
+ export default defineConfig({
14
+ testDir: "tests/e2e",
15
+ // Each test sweeps many pages; give them room (they parallelize internally).
16
+ timeout: 300_000,
17
+ fullyParallel: false,
18
+ workers: 1,
19
+ reporter: "list",
20
+ use: { baseURL: `http://localhost:${PORT}` },
21
+ webServer: SNAPSHOT_URL
22
+ ? {
23
+ command: `pnpm dev -p ${PORT}`,
24
+ url: `http://localhost:${PORT}`,
25
+ timeout: 120_000,
26
+ reuseExistingServer: true,
27
+ env: { DATA_BACKEND: "v2", SNAPSHOT_URL },
28
+ }
29
+ : undefined,
30
+ })
pnpm-lock.yaml CHANGED
@@ -115,7 +115,7 @@ importers:
115
  version: 8.5.1(react@19.1.1)
116
  geist:
117
  specifier: ^1.3.1
118
- version: 1.4.2(next@15.2.8(react-dom@19.1.1(react@19.1.1))(react@19.1.1))
119
  input-otp:
120
  specifier: 1.4.1
121
  version: 1.4.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
@@ -124,7 +124,7 @@ importers:
124
  version: 0.454.0(react@19.1.1)
125
  next:
126
  specifier: 15.2.8
127
- version: 15.2.8(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
128
  next-themes:
129
  specifier: latest
130
  version: 0.4.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
@@ -168,6 +168,9 @@ importers:
168
  specifier: 3.25.67
169
  version: 3.25.67
170
  devDependencies:
 
 
 
171
  '@tailwindcss/postcss':
172
  specifier: ^4.1.9
173
  version: 4.1.12
@@ -180,6 +183,9 @@ importers:
180
  '@types/react-dom':
181
  specifier: ^19
182
  version: 19.1.7(@types/react@19.1.10)
 
 
 
183
  postcss:
184
  specifier: ^8.5
185
  version: 8.5.6
@@ -754,6 +760,11 @@ packages:
754
  cpu: [x64]
755
  os: [win32]
756
 
 
 
 
 
 
757
  '@puppeteer/browsers@2.13.0':
758
  resolution: {integrity: sha512-46BZJYJjc/WwmKjsvDFykHtXrtomsCIrwYQPOP7VfMJoZY2bsDF9oROBABR3paDjDcmkUye1Pb1BqdcdiipaWA==}
759
  engines: {node: '>=18'}
@@ -2058,6 +2069,11 @@ packages:
2058
  fraction.js@4.3.7:
2059
  resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
2060
 
 
 
 
 
 
2061
  fsevents@2.3.3:
2062
  resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
2063
  engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -2351,6 +2367,16 @@ packages:
2351
  pkg-types@1.3.1:
2352
  resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
2353
 
 
 
 
 
 
 
 
 
 
 
2354
  postcss-value-parser@4.2.0:
2355
  resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
2356
 
@@ -3177,6 +3203,10 @@ snapshots:
3177
  '@next/swc-win32-x64-msvc@15.2.5':
3178
  optional: true
3179
 
 
 
 
 
3180
  '@puppeteer/browsers@2.13.0':
3181
  dependencies:
3182
  debug: 4.4.3
@@ -4495,12 +4525,15 @@ snapshots:
4495
 
4496
  fraction.js@4.3.7: {}
4497
 
 
 
 
4498
  fsevents@2.3.3:
4499
  optional: true
4500
 
4501
- geist@1.4.2(next@15.2.8(react-dom@19.1.1(react@19.1.1))(react@19.1.1)):
4502
  dependencies:
4503
- next: 15.2.8(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
4504
 
4505
  get-caller-file@2.0.5: {}
4506
 
@@ -4664,7 +4697,7 @@ snapshots:
4664
  react: 19.1.1
4665
  react-dom: 19.1.1(react@19.1.1)
4666
 
4667
- next@15.2.8(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
4668
  dependencies:
4669
  '@next/env': 15.2.8
4670
  '@swc/counter': 0.1.3
@@ -4684,6 +4717,7 @@ snapshots:
4684
  '@next/swc-linux-x64-musl': 15.2.5
4685
  '@next/swc-win32-arm64-msvc': 15.2.5
4686
  '@next/swc-win32-x64-msvc': 15.2.5
 
4687
  sharp: 0.33.5
4688
  transitivePeerDependencies:
4689
  - '@babel/core'
@@ -4747,6 +4781,14 @@ snapshots:
4747
  mlly: 1.7.4
4748
  pathe: 2.0.3
4749
 
 
 
 
 
 
 
 
 
4750
  postcss-value-parser@4.2.0: {}
4751
 
4752
  postcss@8.4.31:
 
115
  version: 8.5.1(react@19.1.1)
116
  geist:
117
  specifier: ^1.3.1
118
+ version: 1.4.2(next@15.2.8(@playwright/test@1.60.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))
119
  input-otp:
120
  specifier: 1.4.1
121
  version: 1.4.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
 
124
  version: 0.454.0(react@19.1.1)
125
  next:
126
  specifier: 15.2.8
127
+ version: 15.2.8(@playwright/test@1.60.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
128
  next-themes:
129
  specifier: latest
130
  version: 0.4.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
 
168
  specifier: 3.25.67
169
  version: 3.25.67
170
  devDependencies:
171
+ '@playwright/test':
172
+ specifier: ^1.60.0
173
+ version: 1.60.0
174
  '@tailwindcss/postcss':
175
  specifier: ^4.1.9
176
  version: 4.1.12
 
183
  '@types/react-dom':
184
  specifier: ^19
185
  version: 19.1.7(@types/react@19.1.10)
186
+ playwright:
187
+ specifier: ^1.60.0
188
+ version: 1.60.0
189
  postcss:
190
  specifier: ^8.5
191
  version: 8.5.6
 
760
  cpu: [x64]
761
  os: [win32]
762
 
763
+ '@playwright/test@1.60.0':
764
+ resolution: {integrity: sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==}
765
+ engines: {node: '>=18'}
766
+ hasBin: true
767
+
768
  '@puppeteer/browsers@2.13.0':
769
  resolution: {integrity: sha512-46BZJYJjc/WwmKjsvDFykHtXrtomsCIrwYQPOP7VfMJoZY2bsDF9oROBABR3paDjDcmkUye1Pb1BqdcdiipaWA==}
770
  engines: {node: '>=18'}
 
2069
  fraction.js@4.3.7:
2070
  resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
2071
 
2072
+ fsevents@2.3.2:
2073
+ resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
2074
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
2075
+ os: [darwin]
2076
+
2077
  fsevents@2.3.3:
2078
  resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
2079
  engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
 
2367
  pkg-types@1.3.1:
2368
  resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
2369
 
2370
+ playwright-core@1.60.0:
2371
+ resolution: {integrity: sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==}
2372
+ engines: {node: '>=18'}
2373
+ hasBin: true
2374
+
2375
+ playwright@1.60.0:
2376
+ resolution: {integrity: sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==}
2377
+ engines: {node: '>=18'}
2378
+ hasBin: true
2379
+
2380
  postcss-value-parser@4.2.0:
2381
  resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
2382
 
 
3203
  '@next/swc-win32-x64-msvc@15.2.5':
3204
  optional: true
3205
 
3206
+ '@playwright/test@1.60.0':
3207
+ dependencies:
3208
+ playwright: 1.60.0
3209
+
3210
  '@puppeteer/browsers@2.13.0':
3211
  dependencies:
3212
  debug: 4.4.3
 
4525
 
4526
  fraction.js@4.3.7: {}
4527
 
4528
+ fsevents@2.3.2:
4529
+ optional: true
4530
+
4531
  fsevents@2.3.3:
4532
  optional: true
4533
 
4534
+ geist@1.4.2(next@15.2.8(@playwright/test@1.60.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)):
4535
  dependencies:
4536
+ next: 15.2.8(@playwright/test@1.60.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
4537
 
4538
  get-caller-file@2.0.5: {}
4539
 
 
4697
  react: 19.1.1
4698
  react-dom: 19.1.1(react@19.1.1)
4699
 
4700
+ next@15.2.8(@playwright/test@1.60.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
4701
  dependencies:
4702
  '@next/env': 15.2.8
4703
  '@swc/counter': 0.1.3
 
4717
  '@next/swc-linux-x64-musl': 15.2.5
4718
  '@next/swc-win32-arm64-msvc': 15.2.5
4719
  '@next/swc-win32-x64-msvc': 15.2.5
4720
+ '@playwright/test': 1.60.0
4721
  sharp: 0.33.5
4722
  transitivePeerDependencies:
4723
  - '@babel/core'
 
4781
  mlly: 1.7.4
4782
  pathe: 2.0.3
4783
 
4784
+ playwright-core@1.60.0: {}
4785
+
4786
+ playwright@1.60.0:
4787
+ dependencies:
4788
+ playwright-core: 1.60.0
4789
+ optionalDependencies:
4790
+ fsevents: 2.3.2
4791
+
4792
  postcss-value-parser@4.2.0: {}
4793
 
4794
  postcss@8.4.31:
scripts/generate-model-redirects.ts ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env tsx
2
+ // Generate lib/model-url-redirects.ts from the warehouse models_view.
3
+ //
4
+ // The producer folds variants / dated snapshots / older-cased spellings into a
5
+ // group whose model_route_id is the addressable page; the folded spellings live
6
+ // only in raw_model_ids. This emits a folded-spelling -> group-route 301 map so
7
+ // a link to any folded spelling lands on the real group page. Targets are always
8
+ // addressable by construction, so the map can never 301 to a dead URL.
9
+ //
10
+ // Usage:
11
+ // DATA_BACKEND=v2 SNAPSHOT_URL=<warehouse-url-or-file> tsx scripts/generate-model-redirects.ts
12
+ // tsx scripts/generate-model-redirects.ts --snapshot <warehouse-url> --out lib/model-url-redirects.ts
13
+
14
+ import { writeFileSync } from "node:fs"
15
+ import { dirname, resolve } from "node:path"
16
+ import { fileURLToPath } from "node:url"
17
+
18
+ import { DuckDBConnection } from "@duckdb/node-api"
19
+
20
+ import {
21
+ buildRedirectsFromModelsView,
22
+ serializeRedirectModule,
23
+ type ModelsViewRow,
24
+ } from "../lib/model-url-redirects-build"
25
+
26
+ const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..")
27
+ const DEFAULT_OUT = resolve(REPO_ROOT, "lib/model-url-redirects.ts")
28
+
29
+ function parseArg(flag: string): string | undefined {
30
+ const i = process.argv.indexOf(flag)
31
+ return i >= 0 ? process.argv[i + 1] : undefined
32
+ }
33
+
34
+ // @duckdb/node-api returns LIST columns as { items: [...] }.
35
+ function listItems(v: unknown): string[] {
36
+ if (Array.isArray(v)) return v.map(String)
37
+ if (v && typeof v === "object" && Array.isArray((v as { items?: unknown[] }).items)) {
38
+ return (v as { items: unknown[] }).items.map(String)
39
+ }
40
+ return []
41
+ }
42
+
43
+ function str(v: unknown): string | null {
44
+ return v == null ? null : String(v)
45
+ }
46
+
47
+ async function main() {
48
+ const snapshot = (parseArg("--snapshot") ?? process.env.SNAPSHOT_URL ?? "").replace(/\/+$/, "")
49
+ if (!snapshot) {
50
+ throw new Error("need --snapshot <warehouse-url> or SNAPSHOT_URL set")
51
+ }
52
+ const outPath = resolve(parseArg("--out") ?? DEFAULT_OUT)
53
+
54
+ const con = await DuckDBConnection.create()
55
+ await con.run("INSTALL httpfs; LOAD httpfs;")
56
+ const reader = await con.runAndReadAll(
57
+ `SELECT model_route_id, model_id, route_id, model_key, model_group_id, raw_model_ids
58
+ FROM read_parquet('${snapshot}/models_view.parquet')`,
59
+ )
60
+ const raw = reader.getRowObjects() as Array<Record<string, unknown>>
61
+ const rows: ModelsViewRow[] = raw.map((r) => ({
62
+ model_route_id: String(r.model_route_id ?? ""),
63
+ model_id: str(r.model_id),
64
+ route_id: str(r.route_id),
65
+ model_key: str(r.model_key),
66
+ model_group_id: str(r.model_group_id),
67
+ raw_model_ids: listItems(r.raw_model_ids),
68
+ }))
69
+
70
+ const { redirects, ambiguous } = buildRedirectsFromModelsView(rows)
71
+ writeFileSync(outPath, serializeRedirectModule(redirects, { source: snapshot }))
72
+
73
+ // eslint-disable-next-line no-console
74
+ console.log(
75
+ `wrote ${outPath}: ${redirects.size} redirects from ${rows.length} models_view rows` +
76
+ (ambiguous.size ? `; ${ambiguous.size} ambiguous spellings excluded` : ""),
77
+ )
78
+ if (ambiguous.size > 0) {
79
+ // eslint-disable-next-line no-console
80
+ console.log(` ambiguous (fan out to >1 group): ${[...ambiguous.keys()].slice(0, 10).join(", ")}`)
81
+ }
82
+ }
83
+
84
+ main().catch((e) => {
85
+ // eslint-disable-next-line no-console
86
+ console.error(e)
87
+ process.exit(1)
88
+ })
scripts/verify-identity.mjs DELETED
@@ -1,36 +0,0 @@
1
- import "./server-only-shim.mjs"
2
- import fs from "fs"
3
-
4
- const { getCanonicalModelIdentity, getModelFamilyRouteId } = await import("../lib/model-family.ts")
5
-
6
- const cards = JSON.parse(fs.readFileSync(".cache/hf-data/model-cards.json", "utf8"))
7
-
8
- let total = 0
9
- let familyIdMismatch = 0
10
- let familyNameMismatch = 0
11
- const familyIdExamples = []
12
- const familyNameExamples = []
13
-
14
- for (const c of cards) {
15
- total++
16
- const computed = getCanonicalModelIdentity({
17
- id: c.model_family_id,
18
- name: c.model_family_name,
19
- })
20
- if (computed.familyId !== c.model_family_id) {
21
- familyIdMismatch++
22
- if (familyIdExamples.length < 5) familyIdExamples.push({pipeline: c.model_family_id, computed: computed.familyId})
23
- }
24
- if (computed.familyName !== c.model_family_name) {
25
- familyNameMismatch++
26
- if (familyNameExamples.length < 10) familyNameExamples.push({pipeline: c.model_family_name, computed: computed.familyName})
27
- }
28
- const computedRoute = getModelFamilyRouteId(computed.familyId)
29
- if (computedRoute !== c.model_route_id) {
30
- console.error("ROUTE MISMATCH", c.model_family_id, "->", computedRoute, "vs", c.model_route_id)
31
- }
32
- }
33
-
34
- console.log({total, familyIdMismatch, familyNameMismatch})
35
- if (familyIdExamples.length) console.log("familyId examples:", familyIdExamples)
36
- if (familyNameExamples.length) console.log("familyName examples:", familyNameExamples.slice(0, 10))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
scripts/verify-setup-alias.mjs DELETED
@@ -1,99 +0,0 @@
1
- import "./server-only-shim.mjs"
2
- import fs from "fs"
3
-
4
- const { getCanonicalModelIdentity, getModelFamilyRouteId } = await import("../lib/model-family.ts")
5
-
6
- // Replicate the runtime normalizer's logic exactly (lib/hf-data.ts:750-812)
7
- // so we can trace what it would do against any real card.
8
- function normalizeSetupAliasQualifier(value) {
9
- return value?.trim().toLowerCase().replace(/[_\s]+/g, "-") ?? ""
10
- }
11
- function isSetupAliasQualifier(value) {
12
- const normalized = normalizeSetupAliasQualifier(value)
13
- return (
14
- normalized === "prompt" ||
15
- normalized === "fc" ||
16
- normalized === "function-calling" ||
17
- normalized.startsWith("thinking")
18
- )
19
- }
20
- function getLatestTimestamp(a, b) {
21
- if (!a) return b
22
- if (!b) return a
23
- const aTime = new Date(a).getTime()
24
- const bTime = new Date(b).getTime()
25
- if (!Number.isFinite(aTime)) return b
26
- if (!Number.isFinite(bTime)) return a
27
- return bTime > aTime ? b : a
28
- }
29
- function normalizeSingleModelCardEntry(entry) {
30
- const familyIdentity = getCanonicalModelIdentity({ id: entry.model_family_id, name: entry.model_family_name })
31
- const variantsByKey = new Map()
32
- for (const variant of entry.variants ?? []) {
33
- let normalizedVariantKey = variant.variant_key
34
- let normalizedVariantLabel = variant.variant_label
35
- if (variant.variant_key === "base") {
36
- normalizedVariantKey = "default"
37
- normalizedVariantLabel = "Default"
38
- } else if (variant.variant_key !== "default") {
39
- const syntheticIdentity = getCanonicalModelIdentity({
40
- id: `${familyIdentity.familyId}-${variant.variant_key}`,
41
- name: `${familyIdentity.familyId}-${variant.variant_key}`,
42
- })
43
- if (syntheticIdentity.versionDate && isSetupAliasQualifier(syntheticIdentity.versionQualifier)) {
44
- normalizedVariantKey = syntheticIdentity.versionDate
45
- normalizedVariantLabel = syntheticIdentity.versionDate
46
- } else {
47
- normalizedVariantKey = syntheticIdentity.variantKey
48
- normalizedVariantLabel = syntheticIdentity.variantLabel
49
- }
50
- }
51
- const existing = variantsByKey.get(normalizedVariantKey)
52
- if (existing) {
53
- existing.evaluation_count += variant.evaluation_count
54
- existing.last_updated = getLatestTimestamp(existing.last_updated, variant.last_updated)
55
- existing.raw_model_ids = Array.from(new Set([...(existing.raw_model_ids ?? []), ...(variant.raw_model_ids ?? [])])).sort()
56
- continue
57
- }
58
- variantsByKey.set(normalizedVariantKey, {
59
- ...variant,
60
- variant_key: normalizedVariantKey,
61
- variant_label: normalizedVariantLabel,
62
- raw_model_ids: [...(variant.raw_model_ids ?? [])].sort(),
63
- })
64
- }
65
- const normalizedVariants = Array.from(variantsByKey.values())
66
- return { ...entry, variants: normalizedVariants }
67
- }
68
-
69
- const cards = JSON.parse(fs.readFileSync(".cache/hf-data/model-cards.json", "utf8"))
70
-
71
- // === Sample a known-affected card to show pre/post ===
72
- console.log("=== Sample: openai/gpt-5.2 (multi-variant flagship, known to exercise the rule) ===")
73
- const gpt52 = cards.find(c => c.model_family_id === "openai/gpt-5.2")
74
- if (gpt52) {
75
- console.log("Input variants (cache state):")
76
- for (const v of gpt52.variants) console.log(` '${v.variant_key}'`)
77
- console.log("After runtime normalize (what /api/model-cards returns):")
78
- const normalized = normalizeSingleModelCardEntry(gpt52)
79
- for (const v of normalized.variants) console.log(` '${v.variant_key}' / raw_ids count=${v.raw_model_ids?.length ?? 0}`)
80
- }
81
-
82
- // === Aggregate audit: across all cards, does normalizer change anything? ===
83
- console.log("\n=== Aggregate: cards where normalizer would CHANGE the variants list ===")
84
- let changedCount = 0
85
- const changedExamples = []
86
- for (const c of cards) {
87
- const beforeKeys = (c.variants ?? []).map(v => v.variant_key).sort().join("|")
88
- const afterKeys = normalizeSingleModelCardEntry(c).variants.map(v => v.variant_key).sort().join("|")
89
- if (beforeKeys !== afterKeys) {
90
- changedCount++
91
- if (changedExamples.length < 5) changedExamples.push({ family: c.model_family_id, before: beforeKeys, after: afterKeys })
92
- }
93
- }
94
- console.log(` ${changedCount} of ${cards.length} cards would have variants changed by runtime normalizer`)
95
- for (const ex of changedExamples) {
96
- console.log(` ${ex.family}:`)
97
- console.log(` before: ${ex.before}`)
98
- console.log(` after: ${ex.after}`)
99
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
test_redirects_manual.mjs ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { resolveModelRedirect } from "./lib/model-url-redirects.ts"
2
+ import { routeIdFromModelId } from "./lib/utils.ts"
3
+
4
+ // Test 1: Percent-encoding round-trip with special characters
5
+ const testIds = [
6
+ "anthropic/claude-3.5-sonnet-20240620", // has '.'
7
+ "meta/Llama-3.1-8B", // has '.' and mixed case
8
+ "01-ai/Yi-1.5-34B", // has '.' and '-' and digits
9
+ "openai/gpt-4o", // lowercase with special combo
10
+ ]
11
+
12
+ console.log("=== TEST 1: Percent-encoding round-trip ===")
13
+ testIds.forEach(id => {
14
+ const encoded = routeIdFromModelId(id)
15
+ const decoded = decodeURIComponent(encoded)
16
+ const roundtrip = encoded.includes("%2F") ? "✓" : "✗ (slash not encoded!)"
17
+ const match = decoded === id ? "✓" : "✗ (mismatch!)"
18
+ console.log(` ${id}`)
19
+ console.log(` -> encoded: ${encoded} ${roundtrip}`)
20
+ console.log(` -> decoded: ${decoded} ${match}`)
21
+ })
22
+
23
+ console.log("\n=== All manual tests completed ===")
tests/PREFLIGHT.md ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Frontend preflight — correctness checks before a deploy
2
+
3
+ Run these before pushing a meaningful frontend change to the HF Space. They are
4
+ **opt-in and non-blocking** — nothing here gates `git push` or the default
5
+ `pnpm test`; they're a correctness gate you choose to run.
6
+
7
+ Guiding principle, learned the hard way: **"the page returns 200 / an SVG exists /
8
+ no console error" ≠ correct.** A model page can render cleanly and still be wrong —
9
+ a folded id 404s its data, a redirect points the wrong way, or a chart silently
10
+ shows only the current model. These checks assert content, not just liveness.
11
+
12
+ ## How to run
13
+
14
+ ```bash
15
+ export SNAPSHOT_URL="https://huggingface.co/datasets/evaleval/card_backend/resolve/main/warehouse/<id>"
16
+ # (or file:///abs/path/to/warehouse/<id>)
17
+
18
+ # 1. Server-free data-contract checks (fast). Self-skips if SNAPSHOT_URL is unset.
19
+ pnpm test:integrity # tests/redirect-integrity.test.ts (vitest)
20
+
21
+ # 2. Live-server page + chart-content checks (Playwright boots its own dev server).
22
+ pnpm test:e2e # tests/e2e/frontend-preflight.spec.ts
23
+ ```
24
+
25
+ Both need `SNAPSHOT_URL`. `test:e2e` starts `pnpm dev` against it automatically
26
+ (and reuses an already-running server on the port). Set `PORT` to override 3211.
27
+
28
+ ## What each layer asserts
29
+
30
+ **`test:integrity` (vitest, server-free)** — against the warehouse snapshot:
31
+ - redirect map has 0 self-redirects, 0 chains/loops, every target is an addressable route;
32
+ - direction is folded→group (no addressable id is a redirect KEY — catches an inverted map);
33
+ - every redirect key is a known folded `raw_model_id`;
34
+ - no `raw_model_id` belongs to >1 group (the resolver's `LIMIT 1` fallback is unambiguous);
35
+ - no `models_view` row has NULL `raw_model_ids`.
36
+
37
+ **`test:e2e` (Playwright, live server)** — against the running app + `/api/comparison-index` ground truth:
38
+ - model / eval / developer pages render with HTTP <400, no client error text, no console errors —
39
+ **including the regression sets**: folded model ids (from `raw_model_ids`) and percent-encoded
40
+ developer names (e.g. `Mistral AI`);
41
+ - **100% of folded model ids resolve** (cheap status sweep — not a sample);
42
+ - comparison charts render real **peer bars** — counted in the DOM via `data-model-bar` /
43
+ `data-bar-current`, NOT via `#n/m` badges (those come from the peer-*ranks* sidecar) or the
44
+ "No peer scores" string (which never fires on the silent single-bar failure). Catches the
45
+ "chart shows only the current model" bug. Also: 0 "Unknown Model" labels; the current model is
46
+ not rendered as its own peer.
47
+
48
+ ## The bug taxonomy these were built to catch
49
+
50
+ 1. Folded model ids 404 — lookup didn't match `raw_model_ids`.
51
+ 2. Inverted redirect map 301'd working group URLs to dead leaves.
52
+ 3. Percent-encoded developer names (`Mistral%20AI`) 404'd (decoded-vs-encoded mismatch).
53
+ 4. Comparison charts showed only the current model (peer lookup keyed on a non-matching id).
54
+ 5. Peer bars showed "Unknown Model" (label read a field that isn't on the score rows).
55
+
56
+ The chart checks depend on the inert `data-model-bar` / `data-bar-current` attributes in
57
+ `components/benchmark-detail.tsx` — keep them.
58
+
59
+ ## Known limitations (spot-check manually when touching the relevant code)
60
+
61
+ - The chart check only inspects the DEFAULT-rendered metric tab; a break isolated to a
62
+ non-default tab isn't exercised.
63
+ - Eval/benchmark detail pages get liveness-only checking — their leaderboard content isn't
64
+ asserted against the index the way model charts are.
65
+ - Error-marker strings are hardcoded UI copy; update them here if the copy changes.
66
+ - **Redirect preservation** (old/pre-rework URLs that used to redirect): not gated here. The
67
+ warehouse-derived map only covers ids the producer folds into `raw_model_ids`; old base→variant
68
+ spellings that aren't folded will 404. That's a stale-bookmark concern with an upstream
69
+ (registry alias) fix, not a frontend gate.
70
+
71
+ ## Enforcement
72
+
73
+ There is no CI today (this repo is an HF Space — deploy is `git push`; HF builds the Dockerfile
74
+ and does not run tests). So these are run by hand, by convention, like the `scripts/verify-*.mjs`
75
+ family. If we add hosted CI later (e.g. mirror to GitHub), `test:integrity` is the cheap,
76
+ server-free job to wire in first; `test:e2e` belongs in a separate, explicitly-owned job.
tests/e2e/frontend-preflight.spec.ts ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { DuckDBConnection } from "@duckdb/node-api"
2
+ import { expect, test, type Browser } from "@playwright/test"
3
+
4
+ // Frontend correctness e2e — the layer naive "page returns 200" checks miss.
5
+ // Opt-in via `SNAPSHOT_URL=<warehouse> pnpm test:e2e`; self-skips otherwise.
6
+ // Verifies, against the live server + comparison-index ground truth:
7
+ // - model/eval/developer pages render (incl. folded-id + encoded-name regressions),
8
+ // - 100% of folded model ids resolve (the raw_model_ids fallback),
9
+ // - comparison charts render real PEER bars (not just the current model), 0 "Unknown Model".
10
+ // Full bug taxonomy + known limitations: tests/PREFLIGHT.md.
11
+
12
+ const BASE = `http://localhost:${process.env.PORT || 3211}`
13
+ const SNAPSHOT = (process.env.SNAPSHOT_URL || "").replace(/\/+$/, "")
14
+ const SAMPLE = 30
15
+ const CHART_MODELS = 16
16
+
17
+ test.describe.configure({ mode: "serial" })
18
+ test.skip(!SNAPSHOT, "set SNAPSHOT_URL to run the frontend preflight e2e")
19
+
20
+ const ERROR_MARKERS = [
21
+ "Model not found", "Failed to load model data", "Eval not found",
22
+ "Benchmark not found", "Failed to load", "Application error",
23
+ "Something went wrong", "This page could not be found",
24
+ ]
25
+
26
+ const enc = (s: unknown) => encodeURIComponent(String(s))
27
+ const norm = (s: unknown) => String(s || "").toLowerCase().replace(/[^a-z0-9]/g, "")
28
+ const asArray = (j: any): any[] => Array.isArray(j) ? j
29
+ : (j && typeof j === "object" && ["models", "evals", "developers", "items", "rows", "cards", "data"].map((k) => j[k]).find(Array.isArray)) || []
30
+ const sample = <T>(a: T[], n: number): T[] => a.length <= n ? a.slice() : Array.from({ length: n }, (_, i) => a[Math.floor(i * (a.length / n))])
31
+ const listItems = (v: any): string[] => Array.isArray(v) ? v.map(String) : Array.isArray(v?.items) ? v.items.map(String) : []
32
+ const getJson = async (p: string) => { const r = await fetch(`${BASE}${p}`); return r.ok ? r.json() : null }
33
+ const getJsonT = async (p: string, ms = 90_000, tries = 2) => {
34
+ for (let i = 0; i < tries; i++) {
35
+ try { const r = await fetch(`${BASE}${p}`, { signal: AbortSignal.timeout(ms) }); if (r.ok) return r.json() } catch { /* retry */ }
36
+ }
37
+ return null
38
+ }
39
+ async function mapLimit<T, R>(items: T[], limit: number, fn: (x: T) => Promise<R>): Promise<R[]> {
40
+ const out = new Array<R>(items.length)
41
+ let i = 0
42
+ await Promise.all(Array.from({ length: Math.min(limit, items.length) }, async () => {
43
+ while (i < items.length) { const idx = i++; out[idx] = await fn(items[idx]) }
44
+ }))
45
+ return out
46
+ }
47
+
48
+ let models: any[] = []
49
+ let evals: any[] = []
50
+ let devs: any[] = []
51
+ let folded: string[] = []
52
+ let expectedPeersFor: (routeId: string) => string[]
53
+
54
+ test.beforeAll(async () => {
55
+ models = asArray(await getJson("/api/model-cards-lite"))
56
+ evals = asArray(await getJson("/api/eval-list-lite"))
57
+ devs = asArray(await getJson("/api/developers"))
58
+ expect(models.length, "model-cards-lite empty").toBeGreaterThan(0)
59
+
60
+ // Comparison-index is the chart ground truth — unavailable must be a HARD fail,
61
+ // not a silent skip (else the whole chart layer disables itself).
62
+ const ci = await getJsonT("/api/comparison-index")
63
+ expect(ci?.by_model && ci?.evals, "comparison-index unavailable — chart check cannot run").toBeTruthy()
64
+ expectedPeersFor = (routeId: string) => {
65
+ const byModel = ci.by_model[routeId]
66
+ if (!byModel) return []
67
+ const peers = new Set<string>()
68
+ for (const evalId of Object.keys(byModel)) {
69
+ for (const metric of (ci.evals[evalId]?.metrics ?? [])) {
70
+ for (const s of metric.scores) {
71
+ if (s.model_route_id === routeId) continue
72
+ const n = norm(s.model_family_name || s.model_family_id)
73
+ if (n) peers.add(n)
74
+ }
75
+ }
76
+ }
77
+ return [...peers]
78
+ }
79
+
80
+ // Discover folded ids exhaustively from the warehouse (not a tiny stride).
81
+ const con = await DuckDBConnection.create()
82
+ await con.run("INSTALL httpfs; LOAD httpfs;")
83
+ const rows = (await con.runAndReadAll(
84
+ `SELECT model_id, raw_model_ids FROM read_parquet('${SNAPSHOT}/models_view.parquet') WHERE len(raw_model_ids) > 0`,
85
+ )).getRowObjects()
86
+ const set = new Set<string>()
87
+ for (const r of rows) for (const raw of listItems(r.raw_model_ids)) {
88
+ if (String(raw).toLowerCase() !== String(r.model_id).toLowerCase()) set.add(String(raw))
89
+ }
90
+ folded = [...set]
91
+ expect(folded.length, "folded-id regression set empty — discovery/data path changed").toBeGreaterThan(0)
92
+ })
93
+
94
+ test("100% of folded model ids resolve (raw_model_ids fallback)", async () => {
95
+ const statuses = await mapLimit(folded, 12, async (raw) => {
96
+ const r = await fetch(`${BASE}/api/model-summary?id=${enc(raw)}`, { signal: AbortSignal.timeout(30_000) }).catch(() => null)
97
+ return { raw, status: r ? r.status : 0 }
98
+ })
99
+ const bad = statuses.filter((s) => s.status !== 200).map((s) => s.raw)
100
+ expect(bad, `${bad.length}/${folded.length} folded ids do not resolve: ${bad.slice(0, 8)}`).toEqual([])
101
+ })
102
+
103
+ test("model / eval / developer pages render (incl. regression sets)", async ({ browser }) => {
104
+ const seen = new Set<string>()
105
+ const targets: { cls: string; url: string }[] = []
106
+ const add = (cls: string, url: string) => { if (url && !seen.has(url)) { seen.add(url); targets.push({ cls, url }) } }
107
+
108
+ for (const m of sample(models, SAMPLE)) add("model", `/models/${enc(m.model_id || m.id || m.model_key)}`)
109
+ for (const raw of sample(folded, 25)) add("model(folded)", `/models/${enc(raw)}`)
110
+ for (const e of sample(evals, SAMPLE)) { const id = e.evaluation_id || e.id || e.benchmark_id; if (id) add("eval", `/evals/${String(id).replace(/%2F/g, "/")}`) }
111
+ const devUrl = (rid: string) => `/developers/${String(rid).replace(/%2F/g, "/")}`
112
+ const encodedDevs = devs.filter((d) => /[^A-Za-z0-9._/-]/.test(String(d.developer || "")) || /%(?!2F)/i.test(String(d.route_id || "")))
113
+ expect(encodedDevs.length, "encoded-developer regression set empty — data path changed").toBeGreaterThan(0)
114
+ for (const d of encodedDevs) if (d.route_id) add("developer(encoded)", devUrl(d.route_id))
115
+ for (const d of sample(devs, 20)) if (d.route_id) add("developer", devUrl(d.route_id))
116
+
117
+ const results = await mapLimit(targets, 6, async (t) => {
118
+ const page = await browser.newPage()
119
+ const errs: string[] = []
120
+ page.on("console", (m) => { if (m.type() === "error") errs.push(m.text().slice(0, 120)) })
121
+ page.on("pageerror", (e) => errs.push(String(e).slice(0, 120)))
122
+ let status = 0
123
+ try { const r = await page.goto(`${BASE}${t.url}`, { waitUntil: "networkidle", timeout: 45_000 }); status = r ? r.status() : 0; await page.waitForTimeout(700) }
124
+ catch (e) { errs.push(`goto: ${String(e).slice(0, 120)}`) }
125
+ const text = await page.evaluate(() => document.body?.innerText || "").catch(() => "")
126
+ const marker = ERROR_MARKERS.find((m) => text.includes(m))
127
+ await page.close()
128
+ return (!(status > 0 && status < 400) || marker || errs.length)
129
+ ? `${t.url} [${status}]${marker ? ` "${marker}"` : ""}${errs.length ? ` ${JSON.stringify(errs.slice(0, 2))}` : ""}`
130
+ : null
131
+ })
132
+ const broken = results.filter(Boolean) as string[]
133
+ expect(broken, `${broken.length} broken pages:\n ${broken.slice(0, 15).join("\n ")}`).toEqual([])
134
+ })
135
+
136
+ test("comparison charts render real peer bars (not only the current model)", async ({ browser }) => {
137
+ const candidates = sample(models, CHART_MODELS)
138
+ .map((m) => enc(m.model_id || m.id || m.model_key))
139
+ .filter((routeId) => expectedPeersFor(routeId).length > 0)
140
+ const per = await mapLimit(candidates, 6, async (routeId) => {
141
+ const expected = expectedPeersFor(routeId)
142
+ const page = await browser.newPage()
143
+ const problems: string[] = []
144
+ let bars: { id: string | null; cur: boolean }[] = []
145
+ try {
146
+ await page.goto(`${BASE}/models/${routeId}`, { waitUntil: "networkidle", timeout: 45_000 })
147
+ await page.waitForTimeout(1000)
148
+ const unknown = ((await page.evaluate(() => document.body?.innerText || "")).match(/Unknown Model/g) || []).length
149
+ bars = await page.$$eval("[data-model-bar]", (els) =>
150
+ els.map((e) => ({ id: e.getAttribute("data-model-bar"), cur: e.getAttribute("data-bar-current") === "1" })))
151
+ const peerBars = bars.filter((b) => !b.cur)
152
+ const currentBars = bars.length - peerBars.length
153
+ // MULTIPLE charts rendered but NONE show a peer bar => the only-current-model bug.
154
+ if (currentBars >= 2 && peerBars.length === 0) problems.push(`${routeId}: ${expected.length} peers in index but 0 peer bars`)
155
+ if (peerBars.some((b) => b.id === routeId)) problems.push(`${routeId}: current model rendered as its own peer (double-count)`)
156
+ if (unknown > 0) problems.push(`${routeId}: ${unknown} "Unknown Model" labels`)
157
+ } catch (e) { problems.push(`${routeId}: ${String(e).slice(0, 80)}`) }
158
+ await page.close()
159
+ return { bars: bars.length, problems }
160
+ })
161
+ const charted = candidates.length
162
+ const totalBars = per.reduce((n, r) => n + r.bars, 0)
163
+ const broken = per.flatMap((r) => r.problems)
164
+ expect(charted, "no sampled model had expected peers — chart check was vacuous").toBeGreaterThan(0)
165
+ // Suite-wide: peers expected but NO bars anywhere => the data-model-bar hook was
166
+ // dropped or charts don't render — the chart check silently disabled itself.
167
+ expect(totalBars, "0 chart bars across charted pages — data-model-bar hook missing or charts broken").toBeGreaterThan(0)
168
+ expect(broken, `chart problems:\n ${broken.slice(0, 15).join("\n ")}`).toEqual([])
169
+ })
tests/fixtures/developers/01-ai.json CHANGED
@@ -2,7 +2,7 @@
2
  "developer": "01-ai",
3
  "models": [
4
  {
5
- "model_family_id": "01-ai/yi-34b",
6
  "model_route_id": "01-ai__yi-34b",
7
  "model_family_name": "Yi 34B",
8
  "developer": "01-ai",
@@ -215,7 +215,7 @@
215
  ]
216
  },
217
  {
218
- "model_family_id": "01-ai/yi-6b",
219
  "model_route_id": "01-ai__yi-6b",
220
  "model_family_name": "Yi 6B",
221
  "developer": "01-ai",
@@ -428,7 +428,7 @@
428
  ]
429
  },
430
  {
431
- "model_family_id": "01-ai/yi-34b-chat",
432
  "model_route_id": "01-ai__yi-34b-chat",
433
  "model_family_name": "Yi Chat 34B",
434
  "developer": "01-ai",
@@ -560,7 +560,7 @@
560
  ]
561
  },
562
  {
563
- "model_family_id": "01-ai/yi-large-preview",
564
  "model_route_id": "01-ai__yi-large-preview",
565
  "model_family_name": "Yi Large Preview",
566
  "developer": "01-ai",
@@ -730,7 +730,7 @@
730
  ]
731
  },
732
  {
733
- "model_family_id": "01-ai/yi-1-5-34b",
734
  "model_route_id": "01-ai__yi-1-5-34b",
735
  "model_family_name": "Yi-1.5-34B",
736
  "developer": "01-ai",
@@ -851,7 +851,7 @@
851
  ]
852
  },
853
  {
854
- "model_family_id": "01-ai/yi-1-5-34b-32k",
855
  "model_route_id": "01-ai__yi-1-5-34b-32k",
856
  "model_family_name": "Yi-1.5-34B-32K",
857
  "developer": "01-ai",
@@ -972,7 +972,7 @@
972
  ]
973
  },
974
  {
975
- "model_family_id": "01-ai/yi-1-5-34b-chat",
976
  "model_route_id": "01-ai__yi-1-5-34b-chat",
977
  "model_family_name": "Yi-1.5-34B-Chat",
978
  "developer": "01-ai",
@@ -1093,7 +1093,7 @@
1093
  ]
1094
  },
1095
  {
1096
- "model_family_id": "01-ai/yi-1-5-34b-chat-16k",
1097
  "model_route_id": "01-ai__yi-1-5-34b-chat-16k",
1098
  "model_family_name": "Yi-1.5-34B-Chat-16K",
1099
  "developer": "01-ai",
@@ -1214,7 +1214,7 @@
1214
  ]
1215
  },
1216
  {
1217
- "model_family_id": "01-ai/yi-1-5-6b",
1218
  "model_route_id": "01-ai__yi-1-5-6b",
1219
  "model_family_name": "Yi-1.5-6B",
1220
  "developer": "01-ai",
@@ -1335,7 +1335,7 @@
1335
  ]
1336
  },
1337
  {
1338
- "model_family_id": "01-ai/yi-1-5-6b-chat",
1339
  "model_route_id": "01-ai__yi-1-5-6b-chat",
1340
  "model_family_name": "Yi-1.5-6B-Chat",
1341
  "developer": "01-ai",
@@ -1456,7 +1456,7 @@
1456
  ]
1457
  },
1458
  {
1459
- "model_family_id": "01-ai/yi-1-5-9b",
1460
  "model_route_id": "01-ai__yi-1-5-9b",
1461
  "model_family_name": "Yi-1.5-9B",
1462
  "developer": "01-ai",
@@ -1577,7 +1577,7 @@
1577
  ]
1578
  },
1579
  {
1580
- "model_family_id": "01-ai/yi-1-5-9b-32k",
1581
  "model_route_id": "01-ai__yi-1-5-9b-32k",
1582
  "model_family_name": "Yi-1.5-9B-32K",
1583
  "developer": "01-ai",
@@ -1698,7 +1698,7 @@
1698
  ]
1699
  },
1700
  {
1701
- "model_family_id": "01-ai/yi-1-5-9b-chat",
1702
  "model_route_id": "01-ai__yi-1-5-9b-chat",
1703
  "model_family_name": "Yi-1.5-9B-Chat",
1704
  "developer": "01-ai",
@@ -1819,7 +1819,7 @@
1819
  ]
1820
  },
1821
  {
1822
- "model_family_id": "01-ai/yi-1-5-9b-chat-16k",
1823
  "model_route_id": "01-ai__yi-1-5-9b-chat-16k",
1824
  "model_family_name": "Yi-1.5-9B-Chat-16K",
1825
  "developer": "01-ai",
@@ -1940,7 +1940,7 @@
1940
  ]
1941
  },
1942
  {
1943
- "model_family_id": "01-ai/yi-34b-200k",
1944
  "model_route_id": "01-ai__yi-34b-200k",
1945
  "model_family_name": "Yi-34B-200K",
1946
  "developer": "01-ai",
@@ -2061,7 +2061,7 @@
2061
  ]
2062
  },
2063
  {
2064
- "model_family_id": "01-ai/yi-6b-200k",
2065
  "model_route_id": "01-ai__yi-6b-200k",
2066
  "model_family_name": "Yi-6B-200K",
2067
  "developer": "01-ai",
@@ -2182,7 +2182,7 @@
2182
  ]
2183
  },
2184
  {
2185
- "model_family_id": "01-ai/yi-6b-chat",
2186
  "model_route_id": "01-ai__yi-6b-chat",
2187
  "model_family_name": "Yi-6B-Chat",
2188
  "developer": "01-ai",
@@ -2303,7 +2303,7 @@
2303
  ]
2304
  },
2305
  {
2306
- "model_family_id": "01-ai/yi-9b",
2307
  "model_route_id": "01-ai__yi-9b",
2308
  "model_family_name": "Yi-9B",
2309
  "developer": "01-ai",
@@ -2424,7 +2424,7 @@
2424
  ]
2425
  },
2426
  {
2427
- "model_family_id": "01-ai/yi-9b-200k",
2428
  "model_route_id": "01-ai__yi-9b-200k",
2429
  "model_family_name": "Yi-9B-200K",
2430
  "developer": "01-ai",
@@ -2545,7 +2545,7 @@
2545
  ]
2546
  },
2547
  {
2548
- "model_family_id": "01-ai/yi-coder-9b-chat",
2549
  "model_route_id": "01-ai__yi-coder-9b-chat",
2550
  "model_family_name": "Yi-Coder-9B-Chat",
2551
  "developer": "01-ai",
 
2
  "developer": "01-ai",
3
  "models": [
4
  {
5
+ "model_group_id": "01-ai/yi-34b",
6
  "model_route_id": "01-ai__yi-34b",
7
  "model_family_name": "Yi 34B",
8
  "developer": "01-ai",
 
215
  ]
216
  },
217
  {
218
+ "model_group_id": "01-ai/yi-6b",
219
  "model_route_id": "01-ai__yi-6b",
220
  "model_family_name": "Yi 6B",
221
  "developer": "01-ai",
 
428
  ]
429
  },
430
  {
431
+ "model_group_id": "01-ai/yi-34b-chat",
432
  "model_route_id": "01-ai__yi-34b-chat",
433
  "model_family_name": "Yi Chat 34B",
434
  "developer": "01-ai",
 
560
  ]
561
  },
562
  {
563
+ "model_group_id": "01-ai/yi-large-preview",
564
  "model_route_id": "01-ai__yi-large-preview",
565
  "model_family_name": "Yi Large Preview",
566
  "developer": "01-ai",
 
730
  ]
731
  },
732
  {
733
+ "model_group_id": "01-ai/yi-1-5-34b",
734
  "model_route_id": "01-ai__yi-1-5-34b",
735
  "model_family_name": "Yi-1.5-34B",
736
  "developer": "01-ai",
 
851
  ]
852
  },
853
  {
854
+ "model_group_id": "01-ai/yi-1-5-34b-32k",
855
  "model_route_id": "01-ai__yi-1-5-34b-32k",
856
  "model_family_name": "Yi-1.5-34B-32K",
857
  "developer": "01-ai",
 
972
  ]
973
  },
974
  {
975
+ "model_group_id": "01-ai/yi-1-5-34b-chat",
976
  "model_route_id": "01-ai__yi-1-5-34b-chat",
977
  "model_family_name": "Yi-1.5-34B-Chat",
978
  "developer": "01-ai",
 
1093
  ]
1094
  },
1095
  {
1096
+ "model_group_id": "01-ai/yi-1-5-34b-chat-16k",
1097
  "model_route_id": "01-ai__yi-1-5-34b-chat-16k",
1098
  "model_family_name": "Yi-1.5-34B-Chat-16K",
1099
  "developer": "01-ai",
 
1214
  ]
1215
  },
1216
  {
1217
+ "model_group_id": "01-ai/yi-1-5-6b",
1218
  "model_route_id": "01-ai__yi-1-5-6b",
1219
  "model_family_name": "Yi-1.5-6B",
1220
  "developer": "01-ai",
 
1335
  ]
1336
  },
1337
  {
1338
+ "model_group_id": "01-ai/yi-1-5-6b-chat",
1339
  "model_route_id": "01-ai__yi-1-5-6b-chat",
1340
  "model_family_name": "Yi-1.5-6B-Chat",
1341
  "developer": "01-ai",
 
1456
  ]
1457
  },
1458
  {
1459
+ "model_group_id": "01-ai/yi-1-5-9b",
1460
  "model_route_id": "01-ai__yi-1-5-9b",
1461
  "model_family_name": "Yi-1.5-9B",
1462
  "developer": "01-ai",
 
1577
  ]
1578
  },
1579
  {
1580
+ "model_group_id": "01-ai/yi-1-5-9b-32k",
1581
  "model_route_id": "01-ai__yi-1-5-9b-32k",
1582
  "model_family_name": "Yi-1.5-9B-32K",
1583
  "developer": "01-ai",
 
1698
  ]
1699
  },
1700
  {
1701
+ "model_group_id": "01-ai/yi-1-5-9b-chat",
1702
  "model_route_id": "01-ai__yi-1-5-9b-chat",
1703
  "model_family_name": "Yi-1.5-9B-Chat",
1704
  "developer": "01-ai",
 
1819
  ]
1820
  },
1821
  {
1822
+ "model_group_id": "01-ai/yi-1-5-9b-chat-16k",
1823
  "model_route_id": "01-ai__yi-1-5-9b-chat-16k",
1824
  "model_family_name": "Yi-1.5-9B-Chat-16K",
1825
  "developer": "01-ai",
 
1940
  ]
1941
  },
1942
  {
1943
+ "model_group_id": "01-ai/yi-34b-200k",
1944
  "model_route_id": "01-ai__yi-34b-200k",
1945
  "model_family_name": "Yi-34B-200K",
1946
  "developer": "01-ai",
 
2061
  ]
2062
  },
2063
  {
2064
+ "model_group_id": "01-ai/yi-6b-200k",
2065
  "model_route_id": "01-ai__yi-6b-200k",
2066
  "model_family_name": "Yi-6B-200K",
2067
  "developer": "01-ai",
 
2182
  ]
2183
  },
2184
  {
2185
+ "model_group_id": "01-ai/yi-6b-chat",
2186
  "model_route_id": "01-ai__yi-6b-chat",
2187
  "model_family_name": "Yi-6B-Chat",
2188
  "developer": "01-ai",
 
2303
  ]
2304
  },
2305
  {
2306
+ "model_group_id": "01-ai/yi-9b",
2307
  "model_route_id": "01-ai__yi-9b",
2308
  "model_family_name": "Yi-9B",
2309
  "developer": "01-ai",
 
2424
  ]
2425
  },
2426
  {
2427
+ "model_group_id": "01-ai/yi-9b-200k",
2428
  "model_route_id": "01-ai__yi-9b-200k",
2429
  "model_family_name": "Yi-9B-200K",
2430
  "developer": "01-ai",
 
2545
  ]
2546
  },
2547
  {
2548
+ "model_group_id": "01-ai/yi-coder-9b-chat",
2549
  "model_route_id": "01-ai__yi-coder-9b-chat",
2550
  "model_family_name": "Yi-Coder-9B-Chat",
2551
  "developer": "01-ai",
tests/fixtures/developers/anthropic.json CHANGED
@@ -2,7 +2,7 @@
2
  "developer": "Anthropic",
3
  "models": [
4
  {
5
- "model_family_id": "anthropic/claude-v1-3",
6
  "model_route_id": "anthropic__claude-v1-3",
7
  "model_family_name": "Anthropic Claude v1.3",
8
  "developer": "anthropic",
@@ -217,7 +217,7 @@
217
  ]
218
  },
219
  {
220
- "model_family_id": "anthropic/claude-2-0",
221
  "model_route_id": "anthropic__claude-2-0",
222
  "model_family_name": "Claude 2.0",
223
  "developer": "anthropic",
@@ -378,7 +378,7 @@
378
  ]
379
  },
380
  {
381
- "model_family_id": "anthropic/claude-2-1",
382
  "model_route_id": "anthropic__claude-2-1",
383
  "model_family_name": "Claude 2.1",
384
  "developer": "anthropic",
@@ -548,7 +548,7 @@
548
  ]
549
  },
550
  {
551
- "model_family_id": "anthropic/claude-21",
552
  "model_route_id": "anthropic__claude-21",
553
  "model_family_name": "Claude 2.1",
554
  "developer": "anthropic",
@@ -743,7 +743,7 @@
743
  ]
744
  },
745
  {
746
- "model_family_id": "anthropic/claude-3-haiku",
747
  "model_route_id": "anthropic__claude-3-haiku",
748
  "model_family_name": "Claude 3 Haiku",
749
  "developer": "anthropic",
@@ -978,7 +978,7 @@
978
  ]
979
  },
980
  {
981
- "model_family_id": "anthropic/claude-3-opus",
982
  "model_route_id": "anthropic__claude-3-opus",
983
  "model_family_name": "Claude 3 Opus",
984
  "developer": "anthropic",
@@ -1214,7 +1214,7 @@
1214
  ]
1215
  },
1216
  {
1217
- "model_family_id": "anthropic/claude-3-sonnet",
1218
  "model_route_id": "anthropic__claude-3-sonnet",
1219
  "model_family_name": "Claude 3 Sonnet",
1220
  "developer": "anthropic",
@@ -1446,7 +1446,7 @@
1446
  ]
1447
  },
1448
  {
1449
- "model_family_id": "anthropic/claude-3-5-haiku",
1450
  "model_route_id": "anthropic__claude-3-5-haiku",
1451
  "model_family_name": "Claude 3.5 Haiku",
1452
  "developer": "anthropic",
@@ -1672,7 +1672,7 @@
1672
  ]
1673
  },
1674
  {
1675
- "model_family_id": "anthropic/claude-3-5-sonnet",
1676
  "model_route_id": "anthropic__claude-3-5-sonnet",
1677
  "model_family_name": "Claude 3.5 Sonnet",
1678
  "developer": "anthropic",
@@ -1920,7 +1920,7 @@
1920
  ]
1921
  },
1922
  {
1923
- "model_family_id": "anthropic/claude-35-sonnet-june-24",
1924
  "model_route_id": "anthropic__claude-35-sonnet-june-24",
1925
  "model_family_name": "Claude 3.5 Sonnet (June '24)",
1926
  "developer": "anthropic",
@@ -2106,7 +2106,7 @@
2106
  ]
2107
  },
2108
  {
2109
- "model_family_id": "anthropic/claude-35-sonnet",
2110
  "model_route_id": "anthropic__claude-35-sonnet",
2111
  "model_family_name": "Claude 3.5 Sonnet (Oct '24)",
2112
  "developer": "anthropic",
@@ -2301,7 +2301,7 @@
2301
  ]
2302
  },
2303
  {
2304
- "model_family_id": "anthropic/claude-3-7",
2305
  "model_route_id": "anthropic__claude-3-7",
2306
  "model_family_name": "Claude 3.7",
2307
  "developer": "anthropic",
@@ -2370,7 +2370,7 @@
2370
  ]
2371
  },
2372
  {
2373
- "model_family_id": "anthropic/claude-3-7-sonnet",
2374
  "model_route_id": "anthropic__claude-3-7-sonnet",
2375
  "model_family_name": "Claude 3.7 Sonnet",
2376
  "developer": "anthropic",
@@ -2613,7 +2613,7 @@
2613
  ]
2614
  },
2615
  {
2616
- "model_family_id": "anthropic/claude-3-7-sonnet-thinking",
2617
  "model_route_id": "anthropic__claude-3-7-sonnet-thinking",
2618
  "model_family_name": "Claude 3.7 Sonnet (Reasoning)",
2619
  "developer": "anthropic",
@@ -2808,7 +2808,7 @@
2808
  ]
2809
  },
2810
  {
2811
- "model_family_id": "anthropic/claude-3-7-thinking-16k",
2812
  "model_route_id": "anthropic__claude-3-7-thinking-16k",
2813
  "model_family_name": "Claude 3.7 Thinking 16K",
2814
  "developer": "anthropic",
@@ -2877,7 +2877,7 @@
2877
  ]
2878
  },
2879
  {
2880
- "model_family_id": "anthropic/claude-3-7-thinking-1k",
2881
  "model_route_id": "anthropic__claude-3-7-thinking-1k",
2882
  "model_family_name": "Claude 3.7 Thinking 1K",
2883
  "developer": "anthropic",
@@ -2946,7 +2946,7 @@
2946
  ]
2947
  },
2948
  {
2949
- "model_family_id": "anthropic/claude-3-7-thinking-8k",
2950
  "model_route_id": "anthropic__claude-3-7-thinking-8k",
2951
  "model_family_name": "Claude 3.7 Thinking 8K",
2952
  "developer": "anthropic",
@@ -3015,7 +3015,7 @@
3015
  ]
3016
  },
3017
  {
3018
- "model_family_id": "anthropic/claude-4-opus-thinking",
3019
  "model_route_id": "anthropic__claude-4-opus-thinking",
3020
  "model_family_name": "Claude 4 Opus (Reasoning)",
3021
  "developer": "anthropic",
@@ -3210,7 +3210,7 @@
3210
  ]
3211
  },
3212
  {
3213
- "model_family_id": "anthropic/claude-4-sonnet-thinking",
3214
  "model_route_id": "anthropic__claude-4-sonnet-thinking",
3215
  "model_family_name": "Claude 4 Sonnet (Reasoning)",
3216
  "developer": "anthropic",
@@ -3405,7 +3405,7 @@
3405
  ]
3406
  },
3407
  {
3408
- "model_family_id": "anthropic/claude-4-1-opus",
3409
  "model_route_id": "anthropic__claude-4-1-opus",
3410
  "model_family_name": "Claude 4.1 Opus (Non-reasoning)",
3411
  "developer": "anthropic",
@@ -3540,7 +3540,7 @@
3540
  ]
3541
  },
3542
  {
3543
- "model_family_id": "anthropic/claude-4-1-opus-thinking",
3544
  "model_route_id": "anthropic__claude-4-1-opus-thinking",
3545
  "model_family_name": "Claude 4.1 Opus (Reasoning)",
3546
  "developer": "anthropic",
@@ -3735,7 +3735,7 @@
3735
  ]
3736
  },
3737
  {
3738
- "model_family_id": "anthropic/claude-4-5-haiku",
3739
  "model_route_id": "anthropic__claude-4-5-haiku",
3740
  "model_family_name": "Claude 4.5 Haiku (Non-reasoning)",
3741
  "developer": "anthropic",
@@ -3930,7 +3930,7 @@
3930
  ]
3931
  },
3932
  {
3933
- "model_family_id": "anthropic/claude-4-5-haiku-reasoning",
3934
  "model_route_id": "anthropic__claude-4-5-haiku-reasoning",
3935
  "model_family_name": "Claude 4.5 Haiku (Reasoning)",
3936
  "developer": "anthropic",
@@ -4125,7 +4125,7 @@
4125
  ]
4126
  },
4127
  {
4128
- "model_family_id": "anthropic/claude-4-5-sonnet-thinking",
4129
  "model_route_id": "anthropic__claude-4-5-sonnet-thinking",
4130
  "model_family_name": "Claude 4.5 Sonnet (Reasoning)",
4131
  "developer": "anthropic",
@@ -4320,7 +4320,7 @@
4320
  ]
4321
  },
4322
  {
4323
- "model_family_id": "anthropic/claude-code",
4324
  "model_route_id": "anthropic__claude-code",
4325
  "model_family_name": "Claude Code",
4326
  "developer": "Anthropic",
@@ -4389,7 +4389,7 @@
4389
  ]
4390
  },
4391
  {
4392
- "model_family_id": "anthropic/claude-instant",
4393
  "model_route_id": "anthropic__claude-instant",
4394
  "model_family_name": "Claude Instant",
4395
  "developer": "anthropic",
@@ -4575,7 +4575,7 @@
4575
  ]
4576
  },
4577
  {
4578
- "model_family_id": "anthropic/claude-instant-1-2",
4579
  "model_route_id": "anthropic__claude-instant-1-2",
4580
  "model_family_name": "Claude Instant 1.2",
4581
  "developer": "anthropic",
@@ -4745,7 +4745,7 @@
4745
  ]
4746
  },
4747
  {
4748
- "model_family_id": "anthropic/claude-mythos-preview",
4749
  "model_route_id": "anthropic__claude-mythos-preview",
4750
  "model_family_name": "Claude Mythos Preview",
4751
  "developer": "anthropic",
@@ -4955,7 +4955,7 @@
4955
  ]
4956
  },
4957
  {
4958
- "model_family_id": "anthropic/claude-opus-4",
4959
  "model_route_id": "anthropic__claude-opus-4",
4960
  "model_family_name": "Claude Opus 4",
4961
  "developer": "anthropic",
@@ -5220,7 +5220,7 @@
5220
  ]
5221
  },
5222
  {
5223
- "model_family_id": "anthropic/claude-opus-4-5-thinking",
5224
  "model_route_id": "anthropic__claude-opus-4-5-thinking",
5225
  "model_family_name": "Claude Opus 4.5 (Reasoning)",
5226
  "developer": "anthropic",
@@ -5415,7 +5415,7 @@
5415
  ]
5416
  },
5417
  {
5418
- "model_family_id": "anthropic/claude-opus-4-6-adaptive",
5419
  "model_route_id": "anthropic__claude-opus-4-6-adaptive",
5420
  "model_family_name": "Claude Opus 4.6 (Adaptive Reasoning, Max Effort)",
5421
  "developer": "anthropic",
@@ -5610,7 +5610,7 @@
5610
  ]
5611
  },
5612
  {
5613
- "model_family_id": "anthropic/claude-sonnet-4",
5614
  "model_route_id": "anthropic__claude-sonnet-4",
5615
  "model_family_name": "Claude Sonnet 4",
5616
  "developer": "anthropic",
@@ -5875,7 +5875,7 @@
5875
  ]
5876
  },
5877
  {
5878
- "model_family_id": "anthropic/claude-sonnet-4-6-adaptive",
5879
  "model_route_id": "anthropic__claude-sonnet-4-6-adaptive",
5880
  "model_family_name": "Claude Sonnet 4.6 (Adaptive Reasoning, Max Effort)",
5881
  "developer": "anthropic",
@@ -6070,7 +6070,7 @@
6070
  ]
6071
  },
6072
  {
6073
- "model_family_id": "anthropic/claude-sonnet-4-6-non-reasoning-low-effort",
6074
  "model_route_id": "anthropic__claude-sonnet-4-6-non-reasoning-low-effort",
6075
  "model_family_name": "Claude Sonnet 4.6 (Non-reasoning, Low Effort)",
6076
  "developer": "anthropic",
@@ -6265,7 +6265,7 @@
6265
  ]
6266
  },
6267
  {
6268
- "model_family_id": "anthropic/claude-3-5-sonnet-oct",
6269
  "model_route_id": "anthropic__claude-3-5-sonnet-oct",
6270
  "model_family_name": "Claude-3.5-Sonnet(Oct)",
6271
  "developer": "anthropic",
@@ -6385,7 +6385,7 @@
6385
  ]
6386
  },
6387
  {
6388
- "model_family_id": "anthropic/claude-4-sonnet",
6389
  "model_route_id": "anthropic__claude-4-sonnet",
6390
  "model_family_name": "Claude-4-Sonnet",
6391
  "developer": "anthropic",
@@ -6604,7 +6604,7 @@
6604
  ]
6605
  },
6606
  {
6607
- "model_family_id": "anthropic/claude-4-5-sonnet",
6608
  "model_route_id": "anthropic__claude-4-5-sonnet",
6609
  "model_family_name": "Claude-4.5-Sonnet",
6610
  "developer": "anthropic",
@@ -6802,7 +6802,7 @@
6802
  ]
6803
  },
6804
  {
6805
- "model_family_id": "anthropic/claude-sonnet-4-6-high",
6806
  "model_route_id": "anthropic__claude-sonnet-4-6-high",
6807
  "model_family_name": "Claude-Sonnet-4.6-high",
6808
  "developer": "Anthropic",
@@ -6883,7 +6883,7 @@
6883
  ]
6884
  },
6885
  {
6886
- "model_family_id": "anthropic/opus-4-1",
6887
  "model_route_id": "anthropic__opus-4-1",
6888
  "model_family_name": "Opus 4.1",
6889
  "developer": "anthropic",
@@ -6952,7 +6952,7 @@
6952
  ]
6953
  },
6954
  {
6955
- "model_family_id": "anthropic/opus-4-5",
6956
  "model_route_id": "anthropic__opus-4-5",
6957
  "model_family_name": "Opus 4.5",
6958
  "developer": "anthropic",
@@ -7043,7 +7043,7 @@
7043
  ]
7044
  },
7045
  {
7046
- "model_family_id": "anthropic/opus-4-6",
7047
  "model_route_id": "anthropic__opus-4-6",
7048
  "model_family_name": "Opus 4.6",
7049
  "developer": "anthropic",
@@ -7112,7 +7112,7 @@
7112
  ]
7113
  },
7114
  {
7115
- "model_family_id": "anthropic/sonnet-4-5",
7116
  "model_route_id": "anthropic__sonnet-4-5",
7117
  "model_family_name": "Sonnet 4.5",
7118
  "developer": "anthropic",
@@ -7181,7 +7181,7 @@
7181
  ]
7182
  },
7183
  {
7184
- "model_family_id": "anthropic/opus-4-6-max-effort",
7185
  "model_route_id": "anthropic__opus-4-6-max-effort",
7186
  "model_family_name": "anthropic-opus-4-6-max-effort",
7187
  "developer": "anthropic",
@@ -7250,7 +7250,7 @@
7250
  ]
7251
  },
7252
  {
7253
- "model_family_id": "anthropic/anthropic-claude-3-7-sonnet",
7254
  "model_route_id": "anthropic__anthropic-claude-3-7-sonnet",
7255
  "model_family_name": "anthropic/anthropic/claude-3-7-sonnet-20250219",
7256
  "developer": "anthropic",
@@ -7319,7 +7319,7 @@
7319
  ]
7320
  },
7321
  {
7322
- "model_family_id": "anthropic/claude-2",
7323
  "model_route_id": "anthropic__claude-2",
7324
  "model_family_name": "claude-2",
7325
  "developer": "anthropic",
@@ -7507,7 +7507,7 @@
7507
  ]
7508
  },
7509
  {
7510
- "model_family_id": "anthropic/claude-4-opus",
7511
  "model_route_id": "anthropic__claude-4-opus",
7512
  "model_family_name": "claude-4-opus-20250514",
7513
  "developer": "anthropic",
@@ -7715,7 +7715,7 @@
7715
  ]
7716
  },
7717
  {
7718
- "model_family_id": "anthropic/claude-haiku-4-5",
7719
  "model_route_id": "anthropic__claude-haiku-4-5",
7720
  "model_family_name": "claude-haiku-4-5-20251001",
7721
  "developer": "anthropic",
@@ -7985,7 +7985,7 @@
7985
  ]
7986
  },
7987
  {
7988
- "model_family_id": "anthropic/claude-opus-4-1",
7989
  "model_route_id": "anthropic__claude-opus-4-1",
7990
  "model_family_name": "claude-opus-4-1-20250805",
7991
  "developer": "anthropic",
@@ -8125,7 +8125,7 @@
8125
  ]
8126
  },
8127
  {
8128
- "model_family_id": "anthropic/claude-opus-4-5",
8129
  "model_route_id": "anthropic__claude-opus-4-5",
8130
  "model_family_name": "claude-opus-4-5-20251101",
8131
  "developer": "anthropic",
@@ -8398,7 +8398,7 @@
8398
  ]
8399
  },
8400
  {
8401
- "model_family_id": "anthropic/claude-opus-4-6",
8402
  "model_route_id": "anthropic__claude-opus-4-6",
8403
  "model_family_name": "claude-opus-4-6",
8404
  "developer": "anthropic",
@@ -8624,7 +8624,7 @@
8624
  ]
8625
  },
8626
  {
8627
- "model_family_id": "anthropic/claude-opus-4-6-thinking-120k-high",
8628
  "model_route_id": "anthropic__claude-opus-4-6-thinking-120k-high",
8629
  "model_family_name": "claude-opus-4-6-thinking-120K-high",
8630
  "developer": "anthropic",
@@ -8693,7 +8693,7 @@
8693
  ]
8694
  },
8695
  {
8696
- "model_family_id": "anthropic/claude-opus-4-6-thinking-120k-low",
8697
  "model_route_id": "anthropic__claude-opus-4-6-thinking-120k-low",
8698
  "model_family_name": "claude-opus-4-6-thinking-120K-low",
8699
  "developer": "anthropic",
@@ -8762,7 +8762,7 @@
8762
  ]
8763
  },
8764
  {
8765
- "model_family_id": "anthropic/claude-opus-4-6-thinking-120k-max",
8766
  "model_route_id": "anthropic__claude-opus-4-6-thinking-120k-max",
8767
  "model_family_name": "claude-opus-4-6-thinking-120K-max",
8768
  "developer": "anthropic",
@@ -8831,7 +8831,7 @@
8831
  ]
8832
  },
8833
  {
8834
- "model_family_id": "anthropic/claude-opus-4-6-thinking-120k-medium",
8835
  "model_route_id": "anthropic__claude-opus-4-6-thinking-120k-medium",
8836
  "model_family_name": "claude-opus-4-6-thinking-120K-medium",
8837
  "developer": "anthropic",
@@ -8900,7 +8900,7 @@
8900
  ]
8901
  },
8902
  {
8903
- "model_family_id": "anthropic/claude-opus-4-7",
8904
  "model_route_id": "anthropic__claude-opus-4-7",
8905
  "model_family_name": "claude-opus-4-7",
8906
  "developer": "anthropic",
@@ -9079,7 +9079,7 @@
9079
  ]
9080
  },
9081
  {
9082
- "model_family_id": "anthropic/claude-sonnet-4-5",
9083
  "model_route_id": "anthropic__claude-sonnet-4-5",
9084
  "model_family_name": "claude-sonnet-4-5-20250929",
9085
  "developer": "anthropic",
@@ -9353,7 +9353,7 @@
9353
  ]
9354
  },
9355
  {
9356
- "model_family_id": "anthropic/claude-sonnet-4-6",
9357
  "model_route_id": "anthropic__claude-sonnet-4-6",
9358
  "model_family_name": "claude-sonnet-4-6",
9359
  "developer": "anthropic",
@@ -9563,7 +9563,7 @@
9563
  ]
9564
  },
9565
  {
9566
- "model_family_id": "anthropic/claude-sonnet-4-6-max",
9567
  "model_route_id": "anthropic__claude-sonnet-4-6-max",
9568
  "model_family_name": "claude_sonnet_4_6_max",
9569
  "developer": "anthropic",
 
2
  "developer": "Anthropic",
3
  "models": [
4
  {
5
+ "model_group_id": "anthropic/claude-v1-3",
6
  "model_route_id": "anthropic__claude-v1-3",
7
  "model_family_name": "Anthropic Claude v1.3",
8
  "developer": "anthropic",
 
217
  ]
218
  },
219
  {
220
+ "model_group_id": "anthropic/claude-2-0",
221
  "model_route_id": "anthropic__claude-2-0",
222
  "model_family_name": "Claude 2.0",
223
  "developer": "anthropic",
 
378
  ]
379
  },
380
  {
381
+ "model_group_id": "anthropic/claude-2-1",
382
  "model_route_id": "anthropic__claude-2-1",
383
  "model_family_name": "Claude 2.1",
384
  "developer": "anthropic",
 
548
  ]
549
  },
550
  {
551
+ "model_group_id": "anthropic/claude-21",
552
  "model_route_id": "anthropic__claude-21",
553
  "model_family_name": "Claude 2.1",
554
  "developer": "anthropic",
 
743
  ]
744
  },
745
  {
746
+ "model_group_id": "anthropic/claude-3-haiku",
747
  "model_route_id": "anthropic__claude-3-haiku",
748
  "model_family_name": "Claude 3 Haiku",
749
  "developer": "anthropic",
 
978
  ]
979
  },
980
  {
981
+ "model_group_id": "anthropic/claude-3-opus",
982
  "model_route_id": "anthropic__claude-3-opus",
983
  "model_family_name": "Claude 3 Opus",
984
  "developer": "anthropic",
 
1214
  ]
1215
  },
1216
  {
1217
+ "model_group_id": "anthropic/claude-3-sonnet",
1218
  "model_route_id": "anthropic__claude-3-sonnet",
1219
  "model_family_name": "Claude 3 Sonnet",
1220
  "developer": "anthropic",
 
1446
  ]
1447
  },
1448
  {
1449
+ "model_group_id": "anthropic/claude-3-5-haiku",
1450
  "model_route_id": "anthropic__claude-3-5-haiku",
1451
  "model_family_name": "Claude 3.5 Haiku",
1452
  "developer": "anthropic",
 
1672
  ]
1673
  },
1674
  {
1675
+ "model_group_id": "anthropic/claude-3-5-sonnet",
1676
  "model_route_id": "anthropic__claude-3-5-sonnet",
1677
  "model_family_name": "Claude 3.5 Sonnet",
1678
  "developer": "anthropic",
 
1920
  ]
1921
  },
1922
  {
1923
+ "model_group_id": "anthropic/claude-35-sonnet-june-24",
1924
  "model_route_id": "anthropic__claude-35-sonnet-june-24",
1925
  "model_family_name": "Claude 3.5 Sonnet (June '24)",
1926
  "developer": "anthropic",
 
2106
  ]
2107
  },
2108
  {
2109
+ "model_group_id": "anthropic/claude-35-sonnet",
2110
  "model_route_id": "anthropic__claude-35-sonnet",
2111
  "model_family_name": "Claude 3.5 Sonnet (Oct '24)",
2112
  "developer": "anthropic",
 
2301
  ]
2302
  },
2303
  {
2304
+ "model_group_id": "anthropic/claude-3-7",
2305
  "model_route_id": "anthropic__claude-3-7",
2306
  "model_family_name": "Claude 3.7",
2307
  "developer": "anthropic",
 
2370
  ]
2371
  },
2372
  {
2373
+ "model_group_id": "anthropic/claude-3-7-sonnet",
2374
  "model_route_id": "anthropic__claude-3-7-sonnet",
2375
  "model_family_name": "Claude 3.7 Sonnet",
2376
  "developer": "anthropic",
 
2613
  ]
2614
  },
2615
  {
2616
+ "model_group_id": "anthropic/claude-3-7-sonnet-thinking",
2617
  "model_route_id": "anthropic__claude-3-7-sonnet-thinking",
2618
  "model_family_name": "Claude 3.7 Sonnet (Reasoning)",
2619
  "developer": "anthropic",
 
2808
  ]
2809
  },
2810
  {
2811
+ "model_group_id": "anthropic/claude-3-7-thinking-16k",
2812
  "model_route_id": "anthropic__claude-3-7-thinking-16k",
2813
  "model_family_name": "Claude 3.7 Thinking 16K",
2814
  "developer": "anthropic",
 
2877
  ]
2878
  },
2879
  {
2880
+ "model_group_id": "anthropic/claude-3-7-thinking-1k",
2881
  "model_route_id": "anthropic__claude-3-7-thinking-1k",
2882
  "model_family_name": "Claude 3.7 Thinking 1K",
2883
  "developer": "anthropic",
 
2946
  ]
2947
  },
2948
  {
2949
+ "model_group_id": "anthropic/claude-3-7-thinking-8k",
2950
  "model_route_id": "anthropic__claude-3-7-thinking-8k",
2951
  "model_family_name": "Claude 3.7 Thinking 8K",
2952
  "developer": "anthropic",
 
3015
  ]
3016
  },
3017
  {
3018
+ "model_group_id": "anthropic/claude-4-opus-thinking",
3019
  "model_route_id": "anthropic__claude-4-opus-thinking",
3020
  "model_family_name": "Claude 4 Opus (Reasoning)",
3021
  "developer": "anthropic",
 
3210
  ]
3211
  },
3212
  {
3213
+ "model_group_id": "anthropic/claude-4-sonnet-thinking",
3214
  "model_route_id": "anthropic__claude-4-sonnet-thinking",
3215
  "model_family_name": "Claude 4 Sonnet (Reasoning)",
3216
  "developer": "anthropic",
 
3405
  ]
3406
  },
3407
  {
3408
+ "model_group_id": "anthropic/claude-4-1-opus",
3409
  "model_route_id": "anthropic__claude-4-1-opus",
3410
  "model_family_name": "Claude 4.1 Opus (Non-reasoning)",
3411
  "developer": "anthropic",
 
3540
  ]
3541
  },
3542
  {
3543
+ "model_group_id": "anthropic/claude-4-1-opus-thinking",
3544
  "model_route_id": "anthropic__claude-4-1-opus-thinking",
3545
  "model_family_name": "Claude 4.1 Opus (Reasoning)",
3546
  "developer": "anthropic",
 
3735
  ]
3736
  },
3737
  {
3738
+ "model_group_id": "anthropic/claude-4-5-haiku",
3739
  "model_route_id": "anthropic__claude-4-5-haiku",
3740
  "model_family_name": "Claude 4.5 Haiku (Non-reasoning)",
3741
  "developer": "anthropic",
 
3930
  ]
3931
  },
3932
  {
3933
+ "model_group_id": "anthropic/claude-4-5-haiku-reasoning",
3934
  "model_route_id": "anthropic__claude-4-5-haiku-reasoning",
3935
  "model_family_name": "Claude 4.5 Haiku (Reasoning)",
3936
  "developer": "anthropic",
 
4125
  ]
4126
  },
4127
  {
4128
+ "model_group_id": "anthropic/claude-4-5-sonnet-thinking",
4129
  "model_route_id": "anthropic__claude-4-5-sonnet-thinking",
4130
  "model_family_name": "Claude 4.5 Sonnet (Reasoning)",
4131
  "developer": "anthropic",
 
4320
  ]
4321
  },
4322
  {
4323
+ "model_group_id": "anthropic/claude-code",
4324
  "model_route_id": "anthropic__claude-code",
4325
  "model_family_name": "Claude Code",
4326
  "developer": "Anthropic",
 
4389
  ]
4390
  },
4391
  {
4392
+ "model_group_id": "anthropic/claude-instant",
4393
  "model_route_id": "anthropic__claude-instant",
4394
  "model_family_name": "Claude Instant",
4395
  "developer": "anthropic",
 
4575
  ]
4576
  },
4577
  {
4578
+ "model_group_id": "anthropic/claude-instant-1-2",
4579
  "model_route_id": "anthropic__claude-instant-1-2",
4580
  "model_family_name": "Claude Instant 1.2",
4581
  "developer": "anthropic",
 
4745
  ]
4746
  },
4747
  {
4748
+ "model_group_id": "anthropic/claude-mythos-preview",
4749
  "model_route_id": "anthropic__claude-mythos-preview",
4750
  "model_family_name": "Claude Mythos Preview",
4751
  "developer": "anthropic",
 
4955
  ]
4956
  },
4957
  {
4958
+ "model_group_id": "anthropic/claude-opus-4",
4959
  "model_route_id": "anthropic__claude-opus-4",
4960
  "model_family_name": "Claude Opus 4",
4961
  "developer": "anthropic",
 
5220
  ]
5221
  },
5222
  {
5223
+ "model_group_id": "anthropic/claude-opus-4-5-thinking",
5224
  "model_route_id": "anthropic__claude-opus-4-5-thinking",
5225
  "model_family_name": "Claude Opus 4.5 (Reasoning)",
5226
  "developer": "anthropic",
 
5415
  ]
5416
  },
5417
  {
5418
+ "model_group_id": "anthropic/claude-opus-4-6-adaptive",
5419
  "model_route_id": "anthropic__claude-opus-4-6-adaptive",
5420
  "model_family_name": "Claude Opus 4.6 (Adaptive Reasoning, Max Effort)",
5421
  "developer": "anthropic",
 
5610
  ]
5611
  },
5612
  {
5613
+ "model_group_id": "anthropic/claude-sonnet-4",
5614
  "model_route_id": "anthropic__claude-sonnet-4",
5615
  "model_family_name": "Claude Sonnet 4",
5616
  "developer": "anthropic",
 
5875
  ]
5876
  },
5877
  {
5878
+ "model_group_id": "anthropic/claude-sonnet-4-6-adaptive",
5879
  "model_route_id": "anthropic__claude-sonnet-4-6-adaptive",
5880
  "model_family_name": "Claude Sonnet 4.6 (Adaptive Reasoning, Max Effort)",
5881
  "developer": "anthropic",
 
6070
  ]
6071
  },
6072
  {
6073
+ "model_group_id": "anthropic/claude-sonnet-4-6-non-reasoning-low-effort",
6074
  "model_route_id": "anthropic__claude-sonnet-4-6-non-reasoning-low-effort",
6075
  "model_family_name": "Claude Sonnet 4.6 (Non-reasoning, Low Effort)",
6076
  "developer": "anthropic",
 
6265
  ]
6266
  },
6267
  {
6268
+ "model_group_id": "anthropic/claude-3-5-sonnet-oct",
6269
  "model_route_id": "anthropic__claude-3-5-sonnet-oct",
6270
  "model_family_name": "Claude-3.5-Sonnet(Oct)",
6271
  "developer": "anthropic",
 
6385
  ]
6386
  },
6387
  {
6388
+ "model_group_id": "anthropic/claude-4-sonnet",
6389
  "model_route_id": "anthropic__claude-4-sonnet",
6390
  "model_family_name": "Claude-4-Sonnet",
6391
  "developer": "anthropic",
 
6604
  ]
6605
  },
6606
  {
6607
+ "model_group_id": "anthropic/claude-4-5-sonnet",
6608
  "model_route_id": "anthropic__claude-4-5-sonnet",
6609
  "model_family_name": "Claude-4.5-Sonnet",
6610
  "developer": "anthropic",
 
6802
  ]
6803
  },
6804
  {
6805
+ "model_group_id": "anthropic/claude-sonnet-4-6-high",
6806
  "model_route_id": "anthropic__claude-sonnet-4-6-high",
6807
  "model_family_name": "Claude-Sonnet-4.6-high",
6808
  "developer": "Anthropic",
 
6883
  ]
6884
  },
6885
  {
6886
+ "model_group_id": "anthropic/opus-4-1",
6887
  "model_route_id": "anthropic__opus-4-1",
6888
  "model_family_name": "Opus 4.1",
6889
  "developer": "anthropic",
 
6952
  ]
6953
  },
6954
  {
6955
+ "model_group_id": "anthropic/opus-4-5",
6956
  "model_route_id": "anthropic__opus-4-5",
6957
  "model_family_name": "Opus 4.5",
6958
  "developer": "anthropic",
 
7043
  ]
7044
  },
7045
  {
7046
+ "model_group_id": "anthropic/opus-4-6",
7047
  "model_route_id": "anthropic__opus-4-6",
7048
  "model_family_name": "Opus 4.6",
7049
  "developer": "anthropic",
 
7112
  ]
7113
  },
7114
  {
7115
+ "model_group_id": "anthropic/sonnet-4-5",
7116
  "model_route_id": "anthropic__sonnet-4-5",
7117
  "model_family_name": "Sonnet 4.5",
7118
  "developer": "anthropic",
 
7181
  ]
7182
  },
7183
  {
7184
+ "model_group_id": "anthropic/opus-4-6-max-effort",
7185
  "model_route_id": "anthropic__opus-4-6-max-effort",
7186
  "model_family_name": "anthropic-opus-4-6-max-effort",
7187
  "developer": "anthropic",
 
7250
  ]
7251
  },
7252
  {
7253
+ "model_group_id": "anthropic/anthropic-claude-3-7-sonnet",
7254
  "model_route_id": "anthropic__anthropic-claude-3-7-sonnet",
7255
  "model_family_name": "anthropic/anthropic/claude-3-7-sonnet-20250219",
7256
  "developer": "anthropic",
 
7319
  ]
7320
  },
7321
  {
7322
+ "model_group_id": "anthropic/claude-2",
7323
  "model_route_id": "anthropic__claude-2",
7324
  "model_family_name": "claude-2",
7325
  "developer": "anthropic",
 
7507
  ]
7508
  },
7509
  {
7510
+ "model_group_id": "anthropic/claude-4-opus",
7511
  "model_route_id": "anthropic__claude-4-opus",
7512
  "model_family_name": "claude-4-opus-20250514",
7513
  "developer": "anthropic",
 
7715
  ]
7716
  },
7717
  {
7718
+ "model_group_id": "anthropic/claude-haiku-4-5",
7719
  "model_route_id": "anthropic__claude-haiku-4-5",
7720
  "model_family_name": "claude-haiku-4-5-20251001",
7721
  "developer": "anthropic",
 
7985
  ]
7986
  },
7987
  {
7988
+ "model_group_id": "anthropic/claude-opus-4-1",
7989
  "model_route_id": "anthropic__claude-opus-4-1",
7990
  "model_family_name": "claude-opus-4-1-20250805",
7991
  "developer": "anthropic",
 
8125
  ]
8126
  },
8127
  {
8128
+ "model_group_id": "anthropic/claude-opus-4-5",
8129
  "model_route_id": "anthropic__claude-opus-4-5",
8130
  "model_family_name": "claude-opus-4-5-20251101",
8131
  "developer": "anthropic",
 
8398
  ]
8399
  },
8400
  {
8401
+ "model_group_id": "anthropic/claude-opus-4-6",
8402
  "model_route_id": "anthropic__claude-opus-4-6",
8403
  "model_family_name": "claude-opus-4-6",
8404
  "developer": "anthropic",
 
8624
  ]
8625
  },
8626
  {
8627
+ "model_group_id": "anthropic/claude-opus-4-6-thinking-120k-high",
8628
  "model_route_id": "anthropic__claude-opus-4-6-thinking-120k-high",
8629
  "model_family_name": "claude-opus-4-6-thinking-120K-high",
8630
  "developer": "anthropic",
 
8693
  ]
8694
  },
8695
  {
8696
+ "model_group_id": "anthropic/claude-opus-4-6-thinking-120k-low",
8697
  "model_route_id": "anthropic__claude-opus-4-6-thinking-120k-low",
8698
  "model_family_name": "claude-opus-4-6-thinking-120K-low",
8699
  "developer": "anthropic",
 
8762
  ]
8763
  },
8764
  {
8765
+ "model_group_id": "anthropic/claude-opus-4-6-thinking-120k-max",
8766
  "model_route_id": "anthropic__claude-opus-4-6-thinking-120k-max",
8767
  "model_family_name": "claude-opus-4-6-thinking-120K-max",
8768
  "developer": "anthropic",
 
8831
  ]
8832
  },
8833
  {
8834
+ "model_group_id": "anthropic/claude-opus-4-6-thinking-120k-medium",
8835
  "model_route_id": "anthropic__claude-opus-4-6-thinking-120k-medium",
8836
  "model_family_name": "claude-opus-4-6-thinking-120K-medium",
8837
  "developer": "anthropic",
 
8900
  ]
8901
  },
8902
  {
8903
+ "model_group_id": "anthropic/claude-opus-4-7",
8904
  "model_route_id": "anthropic__claude-opus-4-7",
8905
  "model_family_name": "claude-opus-4-7",
8906
  "developer": "anthropic",
 
9079
  ]
9080
  },
9081
  {
9082
+ "model_group_id": "anthropic/claude-sonnet-4-5",
9083
  "model_route_id": "anthropic__claude-sonnet-4-5",
9084
  "model_family_name": "claude-sonnet-4-5-20250929",
9085
  "developer": "anthropic",
 
9353
  ]
9354
  },
9355
  {
9356
+ "model_group_id": "anthropic/claude-sonnet-4-6",
9357
  "model_route_id": "anthropic__claude-sonnet-4-6",
9358
  "model_family_name": "claude-sonnet-4-6",
9359
  "developer": "anthropic",
 
9563
  ]
9564
  },
9565
  {
9566
+ "model_group_id": "anthropic/claude-sonnet-4-6-max",
9567
  "model_route_id": "anthropic__claude-sonnet-4-6-max",
9568
  "model_family_name": "claude_sonnet_4_6_max",
9569
  "developer": "anthropic",
tests/fixtures/developers/openai.json CHANGED
@@ -2,7 +2,7 @@
2
  "developer": "OpenAI",
3
  "models": [
4
  {
5
- "model_family_id": "openai/chatgpt-agent",
6
  "model_route_id": "openai__chatgpt-agent",
7
  "model_family_name": "ChatGPT Agent",
8
  "developer": "OpenAI",
@@ -71,7 +71,7 @@
71
  ]
72
  },
73
  {
74
- "model_family_id": "openai/codex",
75
  "model_route_id": "openai__codex",
76
  "model_family_name": "CodeX",
77
  "developer": "OpenAI",
@@ -140,7 +140,7 @@
140
  ]
141
  },
142
  {
143
- "model_family_id": "openai/gpt-oss-20b-high",
144
  "model_route_id": "openai__gpt-oss-20b-high",
145
  "model_family_name": "GPT OSS 20B High",
146
  "developer": "openai",
@@ -209,7 +209,7 @@
209
  ]
210
  },
211
  {
212
- "model_family_id": "openai/gpt-3-5-turbo-0125",
213
  "model_route_id": "openai__gpt-3-5-turbo-0125",
214
  "model_family_name": "GPT-3.5 Turbo",
215
  "developer": "openai",
@@ -380,7 +380,7 @@
380
  ]
381
  },
382
  {
383
- "model_family_id": "openai/gpt-35-turbo",
384
  "model_route_id": "openai__gpt-35-turbo",
385
  "model_family_name": "GPT-3.5 Turbo",
386
  "developer": "openai",
@@ -539,7 +539,7 @@
539
  ]
540
  },
541
  {
542
- "model_family_id": "openai/gpt-3-5-turbo-0301",
543
  "model_route_id": "openai__gpt-3-5-turbo-0301",
544
  "model_family_name": "GPT-3.5 Turbo 0301",
545
  "developer": "openai",
@@ -753,7 +753,7 @@
753
  ]
754
  },
755
  {
756
- "model_family_id": "openai/gpt-3-5-turbo-0613",
757
  "model_route_id": "openai__gpt-3-5-turbo-0613",
758
  "model_family_name": "GPT-3.5 Turbo 0613",
759
  "developer": "openai",
@@ -990,7 +990,7 @@
990
  ]
991
  },
992
  {
993
- "model_family_id": "openai/gpt-3-5-turbo-1106",
994
  "model_route_id": "openai__gpt-3-5-turbo-1106",
995
  "model_family_name": "GPT-3.5 Turbo 1106",
996
  "developer": "openai",
@@ -1119,7 +1119,7 @@
1119
  ]
1120
  },
1121
  {
1122
- "model_family_id": "openai/gpt-4-0613",
1123
  "model_route_id": "openai__gpt-4-0613",
1124
  "model_family_name": "GPT-4",
1125
  "developer": "openai",
@@ -1334,7 +1334,7 @@
1334
  ]
1335
  },
1336
  {
1337
- "model_family_id": "openai/gpt-4",
1338
  "model_route_id": "openai__gpt-4",
1339
  "model_family_name": "GPT-4",
1340
  "developer": "openai",
@@ -1466,7 +1466,7 @@
1466
  ]
1467
  },
1468
  {
1469
- "model_family_id": "openai/gpt-4-0314",
1470
  "model_route_id": "openai__gpt-4-0314",
1471
  "model_family_name": "GPT-4 0314",
1472
  "developer": "openai",
@@ -1598,7 +1598,7 @@
1598
  ]
1599
  },
1600
  {
1601
- "model_family_id": "openai/gpt-4-turbo",
1602
  "model_route_id": "openai__gpt-4-turbo",
1603
  "model_family_name": "GPT-4 Turbo",
1604
  "developer": "openai",
@@ -1825,7 +1825,7 @@
1825
  ]
1826
  },
1827
  {
1828
- "model_family_id": "openai/gpt-4-1-nano",
1829
  "model_route_id": "openai__gpt-4-1-nano",
1830
  "model_family_name": "GPT-4.1 nano",
1831
  "developer": "openai",
@@ -2064,7 +2064,7 @@
2064
  ]
2065
  },
2066
  {
2067
- "model_family_id": "openai/gpt-4-5",
2068
  "model_route_id": "openai__gpt-4-5",
2069
  "model_family_name": "GPT-4.5",
2070
  "developer": "openai",
@@ -2290,7 +2290,7 @@
2290
  ]
2291
  },
2292
  {
2293
- "model_family_id": "openai/gpt-4-5-preview",
2294
  "model_route_id": "openai__gpt-4-5-preview",
2295
  "model_family_name": "GPT-4.5 2025-02-27 preview",
2296
  "developer": "openai",
@@ -2419,7 +2419,7 @@
2419
  ]
2420
  },
2421
  {
2422
- "model_family_id": "openai/gpt-4o",
2423
  "model_route_id": "openai__gpt-4o",
2424
  "model_family_name": "GPT-4o",
2425
  "developer": "openai",
@@ -2709,7 +2709,7 @@
2709
  ]
2710
  },
2711
  {
2712
- "model_family_id": "openai/gpt-4o-chatgpt",
2713
  "model_route_id": "openai__gpt-4o-chatgpt",
2714
  "model_family_name": "GPT-4o (ChatGPT)",
2715
  "developer": "openai",
@@ -2895,7 +2895,7 @@
2895
  ]
2896
  },
2897
  {
2898
- "model_family_id": "openai/gpt-4o-chatgpt-03-25",
2899
  "model_route_id": "openai__gpt-4o-chatgpt-03-25",
2900
  "model_family_name": "GPT-4o (March 2025, chatgpt-4o-latest)",
2901
  "developer": "openai",
@@ -3090,7 +3090,7 @@
3090
  ]
3091
  },
3092
  {
3093
- "model_family_id": "openai/gpt-4o-realtime-dec-2024",
3094
  "model_route_id": "openai__gpt-4o-realtime-dec-2024",
3095
  "model_family_name": "GPT-4o Realtime (Dec '24)",
3096
  "developer": "openai",
@@ -3204,7 +3204,7 @@
3204
  ]
3205
  },
3206
  {
3207
- "model_family_id": "openai/gpt-4o-mini",
3208
  "model_route_id": "openai__gpt-4o-mini",
3209
  "model_family_name": "GPT-4o mini",
3210
  "developer": "openai",
@@ -3444,7 +3444,7 @@
3444
  ]
3445
  },
3446
  {
3447
- "model_family_id": "openai/gpt-4o-mini-realtime-dec-2024",
3448
  "model_route_id": "openai__gpt-4o-mini-realtime-dec-2024",
3449
  "model_family_name": "GPT-4o mini Realtime (Dec '24)",
3450
  "developer": "openai",
@@ -3558,7 +3558,7 @@
3558
  ]
3559
  },
3560
  {
3561
- "model_family_id": "openai/gpt-4o-1120",
3562
  "model_route_id": "openai__gpt-4o-1120",
3563
  "model_family_name": "GPT-4o-1120",
3564
  "developer": "openai",
@@ -3678,7 +3678,7 @@
3678
  ]
3679
  },
3680
  {
3681
- "model_family_id": "openai/gpt-5",
3682
  "model_route_id": "openai__gpt-5",
3683
  "model_family_name": "GPT-5",
3684
  "developer": "openai",
@@ -3985,7 +3985,7 @@
3985
  ]
3986
  },
3987
  {
3988
- "model_family_id": "openai/gpt-5-chatgpt",
3989
  "model_route_id": "openai__gpt-5-chatgpt",
3990
  "model_family_name": "GPT-5 (ChatGPT)",
3991
  "developer": "openai",
@@ -4180,7 +4180,7 @@
4180
  ]
4181
  },
4182
  {
4183
- "model_family_id": "openai/gpt-5-low",
4184
  "model_route_id": "openai__gpt-5-low",
4185
  "model_family_name": "GPT-5 (low)",
4186
  "developer": "openai",
@@ -4375,7 +4375,7 @@
4375
  ]
4376
  },
4377
  {
4378
- "model_family_id": "openai/gpt-5-medium",
4379
  "model_route_id": "openai__gpt-5-medium",
4380
  "model_family_name": "GPT-5 (medium)",
4381
  "developer": "openai",
@@ -4570,7 +4570,7 @@
4570
  ]
4571
  },
4572
  {
4573
- "model_family_id": "openai/gpt-5-minimal",
4574
  "model_route_id": "openai__gpt-5-minimal",
4575
  "model_family_name": "GPT-5 (minimal)",
4576
  "developer": "openai",
@@ -4765,7 +4765,7 @@
4765
  ]
4766
  },
4767
  {
4768
- "model_family_id": "openai/gpt-5-codex",
4769
  "model_route_id": "openai__gpt-5-codex",
4770
  "model_family_name": "GPT-5 Codex (high)",
4771
  "developer": "openai",
@@ -4964,7 +4964,7 @@
4964
  ]
4965
  },
4966
  {
4967
- "model_family_id": "openai/gpt-5-mini-medium",
4968
  "model_route_id": "openai__gpt-5-mini-medium",
4969
  "model_family_name": "GPT-5 mini (medium)",
4970
  "developer": "openai",
@@ -5159,7 +5159,7 @@
5159
  ]
5160
  },
5161
  {
5162
- "model_family_id": "openai/gpt-5-mini-minimal",
5163
  "model_route_id": "openai__gpt-5-mini-minimal",
5164
  "model_family_name": "GPT-5 mini (minimal)",
5165
  "developer": "openai",
@@ -5354,7 +5354,7 @@
5354
  ]
5355
  },
5356
  {
5357
- "model_family_id": "openai/gpt-5-nano",
5358
  "model_route_id": "openai__gpt-5-nano",
5359
  "model_family_name": "GPT-5 nano",
5360
  "developer": "openai",
@@ -5618,7 +5618,7 @@
5618
  ]
5619
  },
5620
  {
5621
- "model_family_id": "openai/gpt-5-nano-medium",
5622
  "model_route_id": "openai__gpt-5-nano-medium",
5623
  "model_family_name": "GPT-5 nano (medium)",
5624
  "developer": "openai",
@@ -5813,7 +5813,7 @@
5813
  ]
5814
  },
5815
  {
5816
- "model_family_id": "openai/gpt-5-nano-minimal",
5817
  "model_route_id": "openai__gpt-5-nano-minimal",
5818
  "model_family_name": "GPT-5 nano (minimal)",
5819
  "developer": "openai",
@@ -6008,7 +6008,7 @@
6008
  ]
6009
  },
6010
  {
6011
- "model_family_id": "openai/gpt-5-1-non-reasoning",
6012
  "model_route_id": "openai__gpt-5-1-non-reasoning",
6013
  "model_family_name": "GPT-5.1 (Non-reasoning)",
6014
  "developer": "openai",
@@ -6203,7 +6203,7 @@
6203
  ]
6204
  },
6205
  {
6206
- "model_family_id": "openai/gpt-5-1-codex-mini",
6207
  "model_route_id": "openai__gpt-5-1-codex-mini",
6208
  "model_family_name": "GPT-5.1 Codex mini (high)",
6209
  "developer": "openai",
@@ -6401,7 +6401,7 @@
6401
  ]
6402
  },
6403
  {
6404
- "model_family_id": "openai/gpt-5-1-high",
6405
  "model_route_id": "openai__gpt-5-1-high",
6406
  "model_family_name": "GPT-5.1 High",
6407
  "developer": "openai",
@@ -6470,7 +6470,7 @@
6470
  ]
6471
  },
6472
  {
6473
- "model_family_id": "openai/gpt-5-1-codex-max",
6474
  "model_route_id": "openai__gpt-5-1-codex-max",
6475
  "model_family_name": "GPT-5.1-Codex-Max",
6476
  "developer": "OpenAI",
@@ -6539,7 +6539,7 @@
6539
  ]
6540
  },
6541
  {
6542
- "model_family_id": "openai/gpt-5-2-non-reasoning",
6543
  "model_route_id": "openai__gpt-5-2-non-reasoning",
6544
  "model_family_name": "GPT-5.2 (Non-reasoning)",
6545
  "developer": "openai",
@@ -6734,7 +6734,7 @@
6734
  ]
6735
  },
6736
  {
6737
- "model_family_id": "openai/gpt-5-2-medium",
6738
  "model_route_id": "openai__gpt-5-2-medium",
6739
  "model_family_name": "GPT-5.2 (medium)",
6740
  "developer": "openai",
@@ -6929,7 +6929,7 @@
6929
  ]
6930
  },
6931
  {
6932
- "model_family_id": "openai/gpt-5-2-pro",
6933
  "model_route_id": "openai__gpt-5-2-pro",
6934
  "model_family_name": "GPT-5.2 Pro",
6935
  "developer": "openai",
@@ -7106,7 +7106,7 @@
7106
  ]
7107
  },
7108
  {
7109
- "model_family_id": "openai/gpt-5-4-non-reasoning",
7110
  "model_route_id": "openai__gpt-5-4-non-reasoning",
7111
  "model_family_name": "GPT-5.4 (Non-reasoning)",
7112
  "developer": "openai",
@@ -7301,7 +7301,7 @@
7301
  ]
7302
  },
7303
  {
7304
- "model_family_id": "openai/gpt-5-4-pro",
7305
  "model_route_id": "openai__gpt-5-4-pro",
7306
  "model_family_name": "GPT-5.4 Pro (xhigh)",
7307
  "developer": "openai",
@@ -7415,7 +7415,7 @@
7415
  ]
7416
  },
7417
  {
7418
- "model_family_id": "openai/gpt-5-4-mini-non-reasoning",
7419
  "model_route_id": "openai__gpt-5-4-mini-non-reasoning",
7420
  "model_family_name": "GPT-5.4 mini (Non-Reasoning)",
7421
  "developer": "openai",
@@ -7610,7 +7610,7 @@
7610
  ]
7611
  },
7612
  {
7613
- "model_family_id": "openai/gpt-5-4-mini-medium",
7614
  "model_route_id": "openai__gpt-5-4-mini-medium",
7615
  "model_family_name": "GPT-5.4 mini (medium)",
7616
  "developer": "openai",
@@ -7807,7 +7807,7 @@
7807
  ]
7808
  },
7809
  {
7810
- "model_family_id": "openai/gpt-5-4-nano-non-reasoning",
7811
  "model_route_id": "openai__gpt-5-4-nano-non-reasoning",
7812
  "model_family_name": "GPT-5.4 nano (Non-Reasoning)",
7813
  "developer": "openai",
@@ -8002,7 +8002,7 @@
8002
  ]
8003
  },
8004
  {
8005
- "model_family_id": "openai/gpt-5-4-nano-medium",
8006
  "model_route_id": "openai__gpt-5-4-nano-medium",
8007
  "model_family_name": "GPT-5.4 nano (medium)",
8008
  "developer": "openai",
@@ -8199,7 +8199,7 @@
8199
  ]
8200
  },
8201
  {
8202
- "model_family_id": "openai/gpt-5-4-high",
8203
  "model_route_id": "openai__gpt-5-4-high",
8204
  "model_family_name": "GPT-5.4-high",
8205
  "developer": "OpenAI",
@@ -8280,7 +8280,7 @@
8280
  ]
8281
  },
8282
  {
8283
- "model_family_id": "openai/gpt-5-5",
8284
  "model_route_id": "openai__gpt-5-5",
8285
  "model_family_name": "GPT-5.5",
8286
  "developer": "openai",
@@ -8497,7 +8497,7 @@
8497
  ]
8498
  },
8499
  {
8500
- "model_family_id": "openai/gpt-5-5-pro",
8501
  "model_route_id": "openai__gpt-5-5-pro",
8502
  "model_family_name": "GPT-5.5 Pro",
8503
  "developer": "openai",
@@ -8606,7 +8606,7 @@
8606
  ]
8607
  },
8608
  {
8609
- "model_family_id": "openai/gpt-j-6b",
8610
  "model_route_id": "openai__gpt-j-6b",
8611
  "model_family_name": "GPT-J 6B",
8612
  "developer": "openai",
@@ -8818,7 +8818,7 @@
8818
  ]
8819
  },
8820
  {
8821
- "model_family_id": "openai/gpt-neox-20b",
8822
  "model_route_id": "openai__gpt-neox-20b",
8823
  "model_family_name": "GPT-NeoX 20B",
8824
  "developer": "openai",
@@ -9030,7 +9030,7 @@
9030
  ]
9031
  },
9032
  {
9033
- "model_family_id": "openai/lingma-agent-lingma-swe-gpt-72b-v0918",
9034
  "model_route_id": "openai__lingma-agent-lingma-swe-gpt-72b-v0918",
9035
  "model_family_name": "Lingma Agent + Lingma SWE-GPT 72b (v0918)",
9036
  "developer": "openai",
@@ -9099,7 +9099,7 @@
9099
  ]
9100
  },
9101
  {
9102
- "model_family_id": "openai/lingma-agent-lingma-swe-gpt-72b-v0925",
9103
  "model_route_id": "openai__lingma-agent-lingma-swe-gpt-72b-v0925",
9104
  "model_family_name": "Lingma Agent + Lingma SWE-GPT 72b (v0925)",
9105
  "developer": "openai",
@@ -9168,7 +9168,7 @@
9168
  ]
9169
  },
9170
  {
9171
- "model_family_id": "openai/lingma-agent-lingma-swe-gpt-7b-v0918",
9172
  "model_route_id": "openai__lingma-agent-lingma-swe-gpt-7b-v0918",
9173
  "model_family_name": "Lingma Agent + Lingma SWE-GPT 7b (v0918)",
9174
  "developer": "openai",
@@ -9237,7 +9237,7 @@
9237
  ]
9238
  },
9239
  {
9240
- "model_family_id": "openai/lingma-agent-lingma-swe-gpt-7b-v0925",
9241
  "model_route_id": "openai__lingma-agent-lingma-swe-gpt-7b-v0925",
9242
  "model_family_name": "Lingma Agent + Lingma SWE-GPT 7b (v0925)",
9243
  "developer": "openai",
@@ -9306,7 +9306,7 @@
9306
  ]
9307
  },
9308
  {
9309
- "model_family_id": "openai/deep-research",
9310
  "model_route_id": "openai__deep-research",
9311
  "model_family_name": "OpenAI Deep Research",
9312
  "developer": "OpenAI",
@@ -9375,7 +9375,7 @@
9375
  ]
9376
  },
9377
  {
9378
- "model_family_id": "openai/openai-o1",
9379
  "model_route_id": "openai__openai-o1",
9380
  "model_family_name": "OpenAI-o1",
9381
  "developer": "openai",
@@ -9495,7 +9495,7 @@
9495
  ]
9496
  },
9497
  {
9498
- "model_family_id": "openai/openai-o3-mini-high",
9499
  "model_route_id": "openai__openai-o3-mini-high",
9500
  "model_family_name": "OpenAI-o3-mini-high",
9501
  "developer": "openai",
@@ -9615,7 +9615,7 @@
9615
  ]
9616
  },
9617
  {
9618
- "model_family_id": "openai/ada-350m",
9619
  "model_route_id": "openai__ada-350m",
9620
  "model_family_name": "ada 350M",
9621
  "developer": "openai",
@@ -9827,7 +9827,7 @@
9827
  ]
9828
  },
9829
  {
9830
- "model_family_id": "openai/babbage-1-3b",
9831
  "model_route_id": "openai__babbage-1-3b",
9832
  "model_family_name": "babbage 1.3B",
9833
  "developer": "openai",
@@ -10039,7 +10039,7 @@
10039
  ]
10040
  },
10041
  {
10042
- "model_family_id": "openai/codex-mini-latest",
10043
  "model_route_id": "openai__codex-mini-latest",
10044
  "model_family_name": "codex-mini-latest",
10045
  "developer": "openai",
@@ -10108,7 +10108,7 @@
10108
  ]
10109
  },
10110
  {
10111
- "model_family_id": "openai/curie-6-7b",
10112
  "model_route_id": "openai__curie-6-7b",
10113
  "model_family_name": "curie 6.7B",
10114
  "developer": "openai",
@@ -10320,7 +10320,7 @@
10320
  ]
10321
  },
10322
  {
10323
- "model_family_id": "openai/davinci-175b",
10324
  "model_route_id": "openai__davinci-175b",
10325
  "model_family_name": "davinci 175B",
10326
  "developer": "openai",
@@ -10532,7 +10532,7 @@
10532
  ]
10533
  },
10534
  {
10535
- "model_family_id": "openai/gpt-4-1106-preview",
10536
  "model_route_id": "openai__gpt-4-1106-preview",
10537
  "model_family_name": "gpt-4-1106-preview",
10538
  "developer": "openai",
@@ -10713,7 +10713,7 @@
10713
  ]
10714
  },
10715
  {
10716
- "model_family_id": "openai/gpt-4-1",
10717
  "model_route_id": "openai__gpt-4-1",
10718
  "model_family_name": "gpt-4.1-2025-04-14",
10719
  "developer": "openai",
@@ -10967,7 +10967,7 @@
10967
  ]
10968
  },
10969
  {
10970
- "model_family_id": "openai/gpt-4-1-mini",
10971
  "model_route_id": "openai__gpt-4-1-mini",
10972
  "model_family_name": "gpt-4.1-mini-2025-04-14",
10973
  "developer": "openai",
@@ -11211,7 +11211,7 @@
11211
  ]
11212
  },
11213
  {
11214
- "model_family_id": "openai/gpt-5-4-low",
11215
  "model_route_id": "openai__gpt-5-4-low",
11216
  "model_family_name": "gpt-5-4-low",
11217
  "developer": "openai",
@@ -11280,7 +11280,7 @@
11280
  ]
11281
  },
11282
  {
11283
- "model_family_id": "openai/gpt-5-4-medium",
11284
  "model_route_id": "openai__gpt-5-4-medium",
11285
  "model_family_name": "gpt-5-4-medium",
11286
  "developer": "openai",
@@ -11349,7 +11349,7 @@
11349
  ]
11350
  },
11351
  {
11352
- "model_family_id": "openai/gpt-5-4-mini-high",
11353
  "model_route_id": "openai__gpt-5-4-mini-high",
11354
  "model_family_name": "gpt-5-4-mini-high",
11355
  "developer": "openai",
@@ -11418,7 +11418,7 @@
11418
  ]
11419
  },
11420
  {
11421
- "model_family_id": "openai/gpt-5-4-mini-low",
11422
  "model_route_id": "openai__gpt-5-4-mini-low",
11423
  "model_family_name": "gpt-5-4-mini-low",
11424
  "developer": "openai",
@@ -11487,7 +11487,7 @@
11487
  ]
11488
  },
11489
  {
11490
- "model_family_id": "openai/gpt-5-4-mini-xhigh",
11491
  "model_route_id": "openai__gpt-5-4-mini-xhigh",
11492
  "model_family_name": "gpt-5-4-mini-xhigh",
11493
  "developer": "openai",
@@ -11556,7 +11556,7 @@
11556
  ]
11557
  },
11558
  {
11559
- "model_family_id": "openai/gpt-5-4-nano-high",
11560
  "model_route_id": "openai__gpt-5-4-nano-high",
11561
  "model_family_name": "gpt-5-4-nano-high",
11562
  "developer": "openai",
@@ -11625,7 +11625,7 @@
11625
  ]
11626
  },
11627
  {
11628
- "model_family_id": "openai/gpt-5-4-nano-low",
11629
  "model_route_id": "openai__gpt-5-4-nano-low",
11630
  "model_family_name": "gpt-5-4-nano-low",
11631
  "developer": "openai",
@@ -11694,7 +11694,7 @@
11694
  ]
11695
  },
11696
  {
11697
- "model_family_id": "openai/gpt-5-4-nano-xhigh",
11698
  "model_route_id": "openai__gpt-5-4-nano-xhigh",
11699
  "model_family_name": "gpt-5-4-nano-xhigh",
11700
  "developer": "openai",
@@ -11763,7 +11763,7 @@
11763
  ]
11764
  },
11765
  {
11766
- "model_family_id": "openai/gpt-5-4-pro-xhigh",
11767
  "model_route_id": "openai__gpt-5-4-pro-xhigh",
11768
  "model_family_name": "gpt-5-4-pro-xhigh",
11769
  "developer": "openai",
@@ -11832,7 +11832,7 @@
11832
  ]
11833
  },
11834
  {
11835
- "model_family_id": "openai/gpt-5-4-xhigh",
11836
  "model_route_id": "openai__gpt-5-4-xhigh",
11837
  "model_family_name": "gpt-5-4-xhigh",
11838
  "developer": "openai",
@@ -11901,7 +11901,7 @@
11901
  ]
11902
  },
11903
  {
11904
- "model_family_id": "openai/gpt-5-mini",
11905
  "model_route_id": "openai__gpt-5-mini",
11906
  "model_family_name": "gpt-5-mini-2025-08-07",
11907
  "developer": "openai",
@@ -12168,7 +12168,7 @@
12168
  ]
12169
  },
12170
  {
12171
- "model_family_id": "openai/gpt-5-pro",
12172
  "model_route_id": "openai__gpt-5-pro",
12173
  "model_family_name": "gpt-5-pro-2025-10-06",
12174
  "developer": "openai",
@@ -12237,7 +12237,7 @@
12237
  ]
12238
  },
12239
  {
12240
- "model_family_id": "openai/gpt-5-1",
12241
  "model_route_id": "openai__gpt-5-1",
12242
  "model_family_name": "gpt-5.1-2025-11-13",
12243
  "developer": "openai",
@@ -12508,7 +12508,7 @@
12508
  ]
12509
  },
12510
  {
12511
- "model_family_id": "openai/gpt-5-1-codex",
12512
  "model_route_id": "openai__gpt-5-1-codex",
12513
  "model_family_name": "gpt-5.1-codex",
12514
  "developer": "openai",
@@ -12709,7 +12709,7 @@
12709
  ]
12710
  },
12711
  {
12712
- "model_family_id": "openai/gpt-5-1-codex-high",
12713
  "model_route_id": "openai__gpt-5-1-codex-high",
12714
  "model_family_name": "gpt-5.1-codex-high",
12715
  "developer": "openai",
@@ -12778,7 +12778,7 @@
12778
  ]
12779
  },
12780
  {
12781
- "model_family_id": "openai/gpt-5-1-instant",
12782
  "model_route_id": "openai__gpt-5-1-instant",
12783
  "model_family_name": "gpt-5.1-instant-2025-11-12",
12784
  "developer": "openai",
@@ -12897,7 +12897,7 @@
12897
  ]
12898
  },
12899
  {
12900
- "model_family_id": "openai/gpt-5-1-medium",
12901
  "model_route_id": "openai__gpt-5-1-medium",
12902
  "model_family_name": "gpt-5.1-medium-2025-11-12",
12903
  "developer": "openai",
@@ -12966,7 +12966,7 @@
12966
  ]
12967
  },
12968
  {
12969
- "model_family_id": "openai/gpt-5-1-thinking",
12970
  "model_route_id": "openai__gpt-5-1-thinking",
12971
  "model_family_name": "gpt-5.1-thinking-2025-11-12",
12972
  "developer": "openai",
@@ -13085,7 +13085,7 @@
13085
  ]
13086
  },
13087
  {
13088
- "model_family_id": "openai/gpt-5-2",
13089
  "model_route_id": "openai__gpt-5-2",
13090
  "model_family_name": "gpt-5.2-2025-12-11",
13091
  "developer": "openai",
@@ -13378,7 +13378,7 @@
13378
  ]
13379
  },
13380
  {
13381
- "model_family_id": "openai/gpt-5-2-codex",
13382
  "model_route_id": "openai__gpt-5-2-codex",
13383
  "model_family_name": "gpt-5.2-codex",
13384
  "developer": "openai",
@@ -13580,7 +13580,7 @@
13580
  ]
13581
  },
13582
  {
13583
- "model_family_id": "openai/gpt-5-3-chat-latest",
13584
  "model_route_id": "openai__gpt-5-3-chat-latest",
13585
  "model_family_name": "gpt-5.3-chat-latest",
13586
  "developer": "openai",
@@ -13659,7 +13659,7 @@
13659
  ]
13660
  },
13661
  {
13662
- "model_family_id": "openai/gpt-5-3-codex",
13663
  "model_route_id": "openai__gpt-5-3-codex",
13664
  "model_family_name": "gpt-5.3-codex",
13665
  "developer": "openai",
@@ -13864,7 +13864,7 @@
13864
  ]
13865
  },
13866
  {
13867
- "model_family_id": "openai/gpt-5-4",
13868
  "model_route_id": "openai__gpt-5-4",
13869
  "model_family_name": "gpt-5.4",
13870
  "developer": "openai",
@@ -14089,7 +14089,7 @@
14089
  ]
14090
  },
14091
  {
14092
- "model_family_id": "openai/gpt-5-4-mini",
14093
  "model_route_id": "openai__gpt-5-4-mini",
14094
  "model_family_name": "gpt-5.4-mini",
14095
  "developer": "openai",
@@ -14296,7 +14296,7 @@
14296
  ]
14297
  },
14298
  {
14299
- "model_family_id": "openai/gpt-5-4-nano",
14300
  "model_route_id": "openai__gpt-5-4-nano",
14301
  "model_family_name": "gpt-5.4-nano",
14302
  "developer": "openai",
@@ -14501,7 +14501,7 @@
14501
  ]
14502
  },
14503
  {
14504
- "model_family_id": "openai/gpt-oss-120b-low",
14505
  "model_route_id": "openai__gpt-oss-120b-low",
14506
  "model_family_name": "gpt-oss-120B (low)",
14507
  "developer": "openai",
@@ -14696,7 +14696,7 @@
14696
  ]
14697
  },
14698
  {
14699
- "model_family_id": "openai/gpt-oss-120b",
14700
  "model_route_id": "openai__gpt-oss-120b",
14701
  "model_family_name": "gpt-oss-120b",
14702
  "developer": "openai",
@@ -14921,7 +14921,7 @@
14921
  ]
14922
  },
14923
  {
14924
- "model_family_id": "openai/gpt-oss-120b-high",
14925
  "model_route_id": "openai__gpt-oss-120b-high",
14926
  "model_family_name": "gpt-oss-120b-high",
14927
  "developer": "openai",
@@ -15011,7 +15011,7 @@
15011
  ]
15012
  },
15013
  {
15014
- "model_family_id": "openai/gpt-oss-20b-low",
15015
  "model_route_id": "openai__gpt-oss-20b-low",
15016
  "model_family_name": "gpt-oss-20B (low)",
15017
  "developer": "openai",
@@ -15206,7 +15206,7 @@
15206
  ]
15207
  },
15208
  {
15209
- "model_family_id": "openai/gpt-oss-20b",
15210
  "model_route_id": "openai__gpt-oss-20b",
15211
  "model_family_name": "gpt-oss-20b",
15212
  "developer": "openai",
@@ -15427,7 +15427,7 @@
15427
  ]
15428
  },
15429
  {
15430
- "model_family_id": "openai/o1",
15431
  "model_route_id": "openai__o1",
15432
  "model_family_name": "o1",
15433
  "developer": "openai",
@@ -15655,7 +15655,7 @@
15655
  ]
15656
  },
15657
  {
15658
- "model_family_id": "openai/o1-mini",
15659
  "model_route_id": "openai__o1-mini",
15660
  "model_family_name": "o1-mini",
15661
  "developer": "openai",
@@ -15872,7 +15872,7 @@
15872
  ]
15873
  },
15874
  {
15875
- "model_family_id": "openai/o1-preview",
15876
  "model_route_id": "openai__o1-preview",
15877
  "model_family_name": "o1-preview",
15878
  "developer": "openai",
@@ -16075,7 +16075,7 @@
16075
  ]
16076
  },
16077
  {
16078
- "model_family_id": "openai/o1-pro",
16079
  "model_route_id": "openai__o1-pro",
16080
  "model_family_name": "o1-pro",
16081
  "developer": "openai",
@@ -16208,7 +16208,7 @@
16208
  ]
16209
  },
16210
  {
16211
- "model_family_id": "openai/o3",
16212
  "model_route_id": "openai__o3",
16213
  "model_family_name": "o3",
16214
  "developer": "openai",
@@ -16482,7 +16482,7 @@
16482
  ]
16483
  },
16484
  {
16485
- "model_family_id": "openai/o3-mini",
16486
  "model_route_id": "openai__o3-mini",
16487
  "model_family_name": "o3-mini",
16488
  "developer": "openai",
@@ -16746,7 +16746,7 @@
16746
  ]
16747
  },
16748
  {
16749
- "model_family_id": "openai/o3-mini-high",
16750
  "model_route_id": "openai__o3-mini-high",
16751
  "model_family_name": "o3-mini (high)",
16752
  "developer": "openai",
@@ -16941,7 +16941,7 @@
16941
  ]
16942
  },
16943
  {
16944
- "model_family_id": "openai/o3-pro",
16945
  "model_route_id": "openai__o3-pro",
16946
  "model_family_name": "o3-pro",
16947
  "developer": "openai",
@@ -17122,7 +17122,7 @@
17122
  ]
17123
  },
17124
  {
17125
- "model_family_id": "openai/o4-mini",
17126
  "model_route_id": "openai__o4-mini",
17127
  "model_family_name": "o4-mini",
17128
  "developer": "openai",
@@ -17390,7 +17390,7 @@
17390
  ]
17391
  },
17392
  {
17393
- "model_family_id": "openai/gpt-4-0125-preview",
17394
  "model_route_id": "openai__gpt-4-0125-preview",
17395
  "model_family_name": "openai/gpt-4-0125-preview",
17396
  "developer": "openai",
@@ -17468,7 +17468,7 @@
17468
  ]
17469
  },
17470
  {
17471
- "model_family_id": "openai/text-ada-001",
17472
  "model_route_id": "openai__text-ada-001",
17473
  "model_family_name": "text-ada-001",
17474
  "developer": "openai",
@@ -17680,7 +17680,7 @@
17680
  ]
17681
  },
17682
  {
17683
- "model_family_id": "openai/text-babbage-001",
17684
  "model_route_id": "openai__text-babbage-001",
17685
  "model_family_name": "text-babbage-001",
17686
  "developer": "openai",
@@ -17892,7 +17892,7 @@
17892
  ]
17893
  },
17894
  {
17895
- "model_family_id": "openai/text-curie-001",
17896
  "model_route_id": "openai__text-curie-001",
17897
  "model_family_name": "text-curie-001",
17898
  "developer": "openai",
@@ -18104,7 +18104,7 @@
18104
  ]
18105
  },
18106
  {
18107
- "model_family_id": "openai/text-davinci-002",
18108
  "model_route_id": "openai__text-davinci-002",
18109
  "model_family_name": "text-davinci-002",
18110
  "developer": "openai",
@@ -18324,7 +18324,7 @@
18324
  ]
18325
  },
18326
  {
18327
- "model_family_id": "openai/text-davinci-003",
18328
  "model_route_id": "openai__text-davinci-003",
18329
  "model_family_name": "text-davinci-003",
18330
  "developer": "openai",
 
2
  "developer": "OpenAI",
3
  "models": [
4
  {
5
+ "model_group_id": "openai/chatgpt-agent",
6
  "model_route_id": "openai__chatgpt-agent",
7
  "model_family_name": "ChatGPT Agent",
8
  "developer": "OpenAI",
 
71
  ]
72
  },
73
  {
74
+ "model_group_id": "openai/codex",
75
  "model_route_id": "openai__codex",
76
  "model_family_name": "CodeX",
77
  "developer": "OpenAI",
 
140
  ]
141
  },
142
  {
143
+ "model_group_id": "openai/gpt-oss-20b-high",
144
  "model_route_id": "openai__gpt-oss-20b-high",
145
  "model_family_name": "GPT OSS 20B High",
146
  "developer": "openai",
 
209
  ]
210
  },
211
  {
212
+ "model_group_id": "openai/gpt-3-5-turbo-0125",
213
  "model_route_id": "openai__gpt-3-5-turbo-0125",
214
  "model_family_name": "GPT-3.5 Turbo",
215
  "developer": "openai",
 
380
  ]
381
  },
382
  {
383
+ "model_group_id": "openai/gpt-35-turbo",
384
  "model_route_id": "openai__gpt-35-turbo",
385
  "model_family_name": "GPT-3.5 Turbo",
386
  "developer": "openai",
 
539
  ]
540
  },
541
  {
542
+ "model_group_id": "openai/gpt-3-5-turbo-0301",
543
  "model_route_id": "openai__gpt-3-5-turbo-0301",
544
  "model_family_name": "GPT-3.5 Turbo 0301",
545
  "developer": "openai",
 
753
  ]
754
  },
755
  {
756
+ "model_group_id": "openai/gpt-3-5-turbo-0613",
757
  "model_route_id": "openai__gpt-3-5-turbo-0613",
758
  "model_family_name": "GPT-3.5 Turbo 0613",
759
  "developer": "openai",
 
990
  ]
991
  },
992
  {
993
+ "model_group_id": "openai/gpt-3-5-turbo-1106",
994
  "model_route_id": "openai__gpt-3-5-turbo-1106",
995
  "model_family_name": "GPT-3.5 Turbo 1106",
996
  "developer": "openai",
 
1119
  ]
1120
  },
1121
  {
1122
+ "model_group_id": "openai/gpt-4-0613",
1123
  "model_route_id": "openai__gpt-4-0613",
1124
  "model_family_name": "GPT-4",
1125
  "developer": "openai",
 
1334
  ]
1335
  },
1336
  {
1337
+ "model_group_id": "openai/gpt-4",
1338
  "model_route_id": "openai__gpt-4",
1339
  "model_family_name": "GPT-4",
1340
  "developer": "openai",
 
1466
  ]
1467
  },
1468
  {
1469
+ "model_group_id": "openai/gpt-4-0314",
1470
  "model_route_id": "openai__gpt-4-0314",
1471
  "model_family_name": "GPT-4 0314",
1472
  "developer": "openai",
 
1598
  ]
1599
  },
1600
  {
1601
+ "model_group_id": "openai/gpt-4-turbo",
1602
  "model_route_id": "openai__gpt-4-turbo",
1603
  "model_family_name": "GPT-4 Turbo",
1604
  "developer": "openai",
 
1825
  ]
1826
  },
1827
  {
1828
+ "model_group_id": "openai/gpt-4-1-nano",
1829
  "model_route_id": "openai__gpt-4-1-nano",
1830
  "model_family_name": "GPT-4.1 nano",
1831
  "developer": "openai",
 
2064
  ]
2065
  },
2066
  {
2067
+ "model_group_id": "openai/gpt-4-5",
2068
  "model_route_id": "openai__gpt-4-5",
2069
  "model_family_name": "GPT-4.5",
2070
  "developer": "openai",
 
2290
  ]
2291
  },
2292
  {
2293
+ "model_group_id": "openai/gpt-4-5-preview",
2294
  "model_route_id": "openai__gpt-4-5-preview",
2295
  "model_family_name": "GPT-4.5 2025-02-27 preview",
2296
  "developer": "openai",
 
2419
  ]
2420
  },
2421
  {
2422
+ "model_group_id": "openai/gpt-4o",
2423
  "model_route_id": "openai__gpt-4o",
2424
  "model_family_name": "GPT-4o",
2425
  "developer": "openai",
 
2709
  ]
2710
  },
2711
  {
2712
+ "model_group_id": "openai/gpt-4o-chatgpt",
2713
  "model_route_id": "openai__gpt-4o-chatgpt",
2714
  "model_family_name": "GPT-4o (ChatGPT)",
2715
  "developer": "openai",
 
2895
  ]
2896
  },
2897
  {
2898
+ "model_group_id": "openai/gpt-4o-chatgpt-03-25",
2899
  "model_route_id": "openai__gpt-4o-chatgpt-03-25",
2900
  "model_family_name": "GPT-4o (March 2025, chatgpt-4o-latest)",
2901
  "developer": "openai",
 
3090
  ]
3091
  },
3092
  {
3093
+ "model_group_id": "openai/gpt-4o-realtime-dec-2024",
3094
  "model_route_id": "openai__gpt-4o-realtime-dec-2024",
3095
  "model_family_name": "GPT-4o Realtime (Dec '24)",
3096
  "developer": "openai",
 
3204
  ]
3205
  },
3206
  {
3207
+ "model_group_id": "openai/gpt-4o-mini",
3208
  "model_route_id": "openai__gpt-4o-mini",
3209
  "model_family_name": "GPT-4o mini",
3210
  "developer": "openai",
 
3444
  ]
3445
  },
3446
  {
3447
+ "model_group_id": "openai/gpt-4o-mini-realtime-dec-2024",
3448
  "model_route_id": "openai__gpt-4o-mini-realtime-dec-2024",
3449
  "model_family_name": "GPT-4o mini Realtime (Dec '24)",
3450
  "developer": "openai",
 
3558
  ]
3559
  },
3560
  {
3561
+ "model_group_id": "openai/gpt-4o-1120",
3562
  "model_route_id": "openai__gpt-4o-1120",
3563
  "model_family_name": "GPT-4o-1120",
3564
  "developer": "openai",
 
3678
  ]
3679
  },
3680
  {
3681
+ "model_group_id": "openai/gpt-5",
3682
  "model_route_id": "openai__gpt-5",
3683
  "model_family_name": "GPT-5",
3684
  "developer": "openai",
 
3985
  ]
3986
  },
3987
  {
3988
+ "model_group_id": "openai/gpt-5-chatgpt",
3989
  "model_route_id": "openai__gpt-5-chatgpt",
3990
  "model_family_name": "GPT-5 (ChatGPT)",
3991
  "developer": "openai",
 
4180
  ]
4181
  },
4182
  {
4183
+ "model_group_id": "openai/gpt-5-low",
4184
  "model_route_id": "openai__gpt-5-low",
4185
  "model_family_name": "GPT-5 (low)",
4186
  "developer": "openai",
 
4375
  ]
4376
  },
4377
  {
4378
+ "model_group_id": "openai/gpt-5-medium",
4379
  "model_route_id": "openai__gpt-5-medium",
4380
  "model_family_name": "GPT-5 (medium)",
4381
  "developer": "openai",
 
4570
  ]
4571
  },
4572
  {
4573
+ "model_group_id": "openai/gpt-5-minimal",
4574
  "model_route_id": "openai__gpt-5-minimal",
4575
  "model_family_name": "GPT-5 (minimal)",
4576
  "developer": "openai",
 
4765
  ]
4766
  },
4767
  {
4768
+ "model_group_id": "openai/gpt-5-codex",
4769
  "model_route_id": "openai__gpt-5-codex",
4770
  "model_family_name": "GPT-5 Codex (high)",
4771
  "developer": "openai",
 
4964
  ]
4965
  },
4966
  {
4967
+ "model_group_id": "openai/gpt-5-mini-medium",
4968
  "model_route_id": "openai__gpt-5-mini-medium",
4969
  "model_family_name": "GPT-5 mini (medium)",
4970
  "developer": "openai",
 
5159
  ]
5160
  },
5161
  {
5162
+ "model_group_id": "openai/gpt-5-mini-minimal",
5163
  "model_route_id": "openai__gpt-5-mini-minimal",
5164
  "model_family_name": "GPT-5 mini (minimal)",
5165
  "developer": "openai",
 
5354
  ]
5355
  },
5356
  {
5357
+ "model_group_id": "openai/gpt-5-nano",
5358
  "model_route_id": "openai__gpt-5-nano",
5359
  "model_family_name": "GPT-5 nano",
5360
  "developer": "openai",
 
5618
  ]
5619
  },
5620
  {
5621
+ "model_group_id": "openai/gpt-5-nano-medium",
5622
  "model_route_id": "openai__gpt-5-nano-medium",
5623
  "model_family_name": "GPT-5 nano (medium)",
5624
  "developer": "openai",
 
5813
  ]
5814
  },
5815
  {
5816
+ "model_group_id": "openai/gpt-5-nano-minimal",
5817
  "model_route_id": "openai__gpt-5-nano-minimal",
5818
  "model_family_name": "GPT-5 nano (minimal)",
5819
  "developer": "openai",
 
6008
  ]
6009
  },
6010
  {
6011
+ "model_group_id": "openai/gpt-5-1-non-reasoning",
6012
  "model_route_id": "openai__gpt-5-1-non-reasoning",
6013
  "model_family_name": "GPT-5.1 (Non-reasoning)",
6014
  "developer": "openai",
 
6203
  ]
6204
  },
6205
  {
6206
+ "model_group_id": "openai/gpt-5-1-codex-mini",
6207
  "model_route_id": "openai__gpt-5-1-codex-mini",
6208
  "model_family_name": "GPT-5.1 Codex mini (high)",
6209
  "developer": "openai",
 
6401
  ]
6402
  },
6403
  {
6404
+ "model_group_id": "openai/gpt-5-1-high",
6405
  "model_route_id": "openai__gpt-5-1-high",
6406
  "model_family_name": "GPT-5.1 High",
6407
  "developer": "openai",
 
6470
  ]
6471
  },
6472
  {
6473
+ "model_group_id": "openai/gpt-5-1-codex-max",
6474
  "model_route_id": "openai__gpt-5-1-codex-max",
6475
  "model_family_name": "GPT-5.1-Codex-Max",
6476
  "developer": "OpenAI",
 
6539
  ]
6540
  },
6541
  {
6542
+ "model_group_id": "openai/gpt-5-2-non-reasoning",
6543
  "model_route_id": "openai__gpt-5-2-non-reasoning",
6544
  "model_family_name": "GPT-5.2 (Non-reasoning)",
6545
  "developer": "openai",
 
6734
  ]
6735
  },
6736
  {
6737
+ "model_group_id": "openai/gpt-5-2-medium",
6738
  "model_route_id": "openai__gpt-5-2-medium",
6739
  "model_family_name": "GPT-5.2 (medium)",
6740
  "developer": "openai",
 
6929
  ]
6930
  },
6931
  {
6932
+ "model_group_id": "openai/gpt-5-2-pro",
6933
  "model_route_id": "openai__gpt-5-2-pro",
6934
  "model_family_name": "GPT-5.2 Pro",
6935
  "developer": "openai",
 
7106
  ]
7107
  },
7108
  {
7109
+ "model_group_id": "openai/gpt-5-4-non-reasoning",
7110
  "model_route_id": "openai__gpt-5-4-non-reasoning",
7111
  "model_family_name": "GPT-5.4 (Non-reasoning)",
7112
  "developer": "openai",
 
7301
  ]
7302
  },
7303
  {
7304
+ "model_group_id": "openai/gpt-5-4-pro",
7305
  "model_route_id": "openai__gpt-5-4-pro",
7306
  "model_family_name": "GPT-5.4 Pro (xhigh)",
7307
  "developer": "openai",
 
7415
  ]
7416
  },
7417
  {
7418
+ "model_group_id": "openai/gpt-5-4-mini-non-reasoning",
7419
  "model_route_id": "openai__gpt-5-4-mini-non-reasoning",
7420
  "model_family_name": "GPT-5.4 mini (Non-Reasoning)",
7421
  "developer": "openai",
 
7610
  ]
7611
  },
7612
  {
7613
+ "model_group_id": "openai/gpt-5-4-mini-medium",
7614
  "model_route_id": "openai__gpt-5-4-mini-medium",
7615
  "model_family_name": "GPT-5.4 mini (medium)",
7616
  "developer": "openai",
 
7807
  ]
7808
  },
7809
  {
7810
+ "model_group_id": "openai/gpt-5-4-nano-non-reasoning",
7811
  "model_route_id": "openai__gpt-5-4-nano-non-reasoning",
7812
  "model_family_name": "GPT-5.4 nano (Non-Reasoning)",
7813
  "developer": "openai",
 
8002
  ]
8003
  },
8004
  {
8005
+ "model_group_id": "openai/gpt-5-4-nano-medium",
8006
  "model_route_id": "openai__gpt-5-4-nano-medium",
8007
  "model_family_name": "GPT-5.4 nano (medium)",
8008
  "developer": "openai",
 
8199
  ]
8200
  },
8201
  {
8202
+ "model_group_id": "openai/gpt-5-4-high",
8203
  "model_route_id": "openai__gpt-5-4-high",
8204
  "model_family_name": "GPT-5.4-high",
8205
  "developer": "OpenAI",
 
8280
  ]
8281
  },
8282
  {
8283
+ "model_group_id": "openai/gpt-5-5",
8284
  "model_route_id": "openai__gpt-5-5",
8285
  "model_family_name": "GPT-5.5",
8286
  "developer": "openai",
 
8497
  ]
8498
  },
8499
  {
8500
+ "model_group_id": "openai/gpt-5-5-pro",
8501
  "model_route_id": "openai__gpt-5-5-pro",
8502
  "model_family_name": "GPT-5.5 Pro",
8503
  "developer": "openai",
 
8606
  ]
8607
  },
8608
  {
8609
+ "model_group_id": "openai/gpt-j-6b",
8610
  "model_route_id": "openai__gpt-j-6b",
8611
  "model_family_name": "GPT-J 6B",
8612
  "developer": "openai",
 
8818
  ]
8819
  },
8820
  {
8821
+ "model_group_id": "openai/gpt-neox-20b",
8822
  "model_route_id": "openai__gpt-neox-20b",
8823
  "model_family_name": "GPT-NeoX 20B",
8824
  "developer": "openai",
 
9030
  ]
9031
  },
9032
  {
9033
+ "model_group_id": "openai/lingma-agent-lingma-swe-gpt-72b-v0918",
9034
  "model_route_id": "openai__lingma-agent-lingma-swe-gpt-72b-v0918",
9035
  "model_family_name": "Lingma Agent + Lingma SWE-GPT 72b (v0918)",
9036
  "developer": "openai",
 
9099
  ]
9100
  },
9101
  {
9102
+ "model_group_id": "openai/lingma-agent-lingma-swe-gpt-72b-v0925",
9103
  "model_route_id": "openai__lingma-agent-lingma-swe-gpt-72b-v0925",
9104
  "model_family_name": "Lingma Agent + Lingma SWE-GPT 72b (v0925)",
9105
  "developer": "openai",
 
9168
  ]
9169
  },
9170
  {
9171
+ "model_group_id": "openai/lingma-agent-lingma-swe-gpt-7b-v0918",
9172
  "model_route_id": "openai__lingma-agent-lingma-swe-gpt-7b-v0918",
9173
  "model_family_name": "Lingma Agent + Lingma SWE-GPT 7b (v0918)",
9174
  "developer": "openai",
 
9237
  ]
9238
  },
9239
  {
9240
+ "model_group_id": "openai/lingma-agent-lingma-swe-gpt-7b-v0925",
9241
  "model_route_id": "openai__lingma-agent-lingma-swe-gpt-7b-v0925",
9242
  "model_family_name": "Lingma Agent + Lingma SWE-GPT 7b (v0925)",
9243
  "developer": "openai",
 
9306
  ]
9307
  },
9308
  {
9309
+ "model_group_id": "openai/deep-research",
9310
  "model_route_id": "openai__deep-research",
9311
  "model_family_name": "OpenAI Deep Research",
9312
  "developer": "OpenAI",
 
9375
  ]
9376
  },
9377
  {
9378
+ "model_group_id": "openai/openai-o1",
9379
  "model_route_id": "openai__openai-o1",
9380
  "model_family_name": "OpenAI-o1",
9381
  "developer": "openai",
 
9495
  ]
9496
  },
9497
  {
9498
+ "model_group_id": "openai/openai-o3-mini-high",
9499
  "model_route_id": "openai__openai-o3-mini-high",
9500
  "model_family_name": "OpenAI-o3-mini-high",
9501
  "developer": "openai",
 
9615
  ]
9616
  },
9617
  {
9618
+ "model_group_id": "openai/ada-350m",
9619
  "model_route_id": "openai__ada-350m",
9620
  "model_family_name": "ada 350M",
9621
  "developer": "openai",
 
9827
  ]
9828
  },
9829
  {
9830
+ "model_group_id": "openai/babbage-1-3b",
9831
  "model_route_id": "openai__babbage-1-3b",
9832
  "model_family_name": "babbage 1.3B",
9833
  "developer": "openai",
 
10039
  ]
10040
  },
10041
  {
10042
+ "model_group_id": "openai/codex-mini-latest",
10043
  "model_route_id": "openai__codex-mini-latest",
10044
  "model_family_name": "codex-mini-latest",
10045
  "developer": "openai",
 
10108
  ]
10109
  },
10110
  {
10111
+ "model_group_id": "openai/curie-6-7b",
10112
  "model_route_id": "openai__curie-6-7b",
10113
  "model_family_name": "curie 6.7B",
10114
  "developer": "openai",
 
10320
  ]
10321
  },
10322
  {
10323
+ "model_group_id": "openai/davinci-175b",
10324
  "model_route_id": "openai__davinci-175b",
10325
  "model_family_name": "davinci 175B",
10326
  "developer": "openai",
 
10532
  ]
10533
  },
10534
  {
10535
+ "model_group_id": "openai/gpt-4-1106-preview",
10536
  "model_route_id": "openai__gpt-4-1106-preview",
10537
  "model_family_name": "gpt-4-1106-preview",
10538
  "developer": "openai",
 
10713
  ]
10714
  },
10715
  {
10716
+ "model_group_id": "openai/gpt-4-1",
10717
  "model_route_id": "openai__gpt-4-1",
10718
  "model_family_name": "gpt-4.1-2025-04-14",
10719
  "developer": "openai",
 
10967
  ]
10968
  },
10969
  {
10970
+ "model_group_id": "openai/gpt-4-1-mini",
10971
  "model_route_id": "openai__gpt-4-1-mini",
10972
  "model_family_name": "gpt-4.1-mini-2025-04-14",
10973
  "developer": "openai",
 
11211
  ]
11212
  },
11213
  {
11214
+ "model_group_id": "openai/gpt-5-4-low",
11215
  "model_route_id": "openai__gpt-5-4-low",
11216
  "model_family_name": "gpt-5-4-low",
11217
  "developer": "openai",
 
11280
  ]
11281
  },
11282
  {
11283
+ "model_group_id": "openai/gpt-5-4-medium",
11284
  "model_route_id": "openai__gpt-5-4-medium",
11285
  "model_family_name": "gpt-5-4-medium",
11286
  "developer": "openai",
 
11349
  ]
11350
  },
11351
  {
11352
+ "model_group_id": "openai/gpt-5-4-mini-high",
11353
  "model_route_id": "openai__gpt-5-4-mini-high",
11354
  "model_family_name": "gpt-5-4-mini-high",
11355
  "developer": "openai",
 
11418
  ]
11419
  },
11420
  {
11421
+ "model_group_id": "openai/gpt-5-4-mini-low",
11422
  "model_route_id": "openai__gpt-5-4-mini-low",
11423
  "model_family_name": "gpt-5-4-mini-low",
11424
  "developer": "openai",
 
11487
  ]
11488
  },
11489
  {
11490
+ "model_group_id": "openai/gpt-5-4-mini-xhigh",
11491
  "model_route_id": "openai__gpt-5-4-mini-xhigh",
11492
  "model_family_name": "gpt-5-4-mini-xhigh",
11493
  "developer": "openai",
 
11556
  ]
11557
  },
11558
  {
11559
+ "model_group_id": "openai/gpt-5-4-nano-high",
11560
  "model_route_id": "openai__gpt-5-4-nano-high",
11561
  "model_family_name": "gpt-5-4-nano-high",
11562
  "developer": "openai",
 
11625
  ]
11626
  },
11627
  {
11628
+ "model_group_id": "openai/gpt-5-4-nano-low",
11629
  "model_route_id": "openai__gpt-5-4-nano-low",
11630
  "model_family_name": "gpt-5-4-nano-low",
11631
  "developer": "openai",
 
11694
  ]
11695
  },
11696
  {
11697
+ "model_group_id": "openai/gpt-5-4-nano-xhigh",
11698
  "model_route_id": "openai__gpt-5-4-nano-xhigh",
11699
  "model_family_name": "gpt-5-4-nano-xhigh",
11700
  "developer": "openai",
 
11763
  ]
11764
  },
11765
  {
11766
+ "model_group_id": "openai/gpt-5-4-pro-xhigh",
11767
  "model_route_id": "openai__gpt-5-4-pro-xhigh",
11768
  "model_family_name": "gpt-5-4-pro-xhigh",
11769
  "developer": "openai",
 
11832
  ]
11833
  },
11834
  {
11835
+ "model_group_id": "openai/gpt-5-4-xhigh",
11836
  "model_route_id": "openai__gpt-5-4-xhigh",
11837
  "model_family_name": "gpt-5-4-xhigh",
11838
  "developer": "openai",
 
11901
  ]
11902
  },
11903
  {
11904
+ "model_group_id": "openai/gpt-5-mini",
11905
  "model_route_id": "openai__gpt-5-mini",
11906
  "model_family_name": "gpt-5-mini-2025-08-07",
11907
  "developer": "openai",
 
12168
  ]
12169
  },
12170
  {
12171
+ "model_group_id": "openai/gpt-5-pro",
12172
  "model_route_id": "openai__gpt-5-pro",
12173
  "model_family_name": "gpt-5-pro-2025-10-06",
12174
  "developer": "openai",
 
12237
  ]
12238
  },
12239
  {
12240
+ "model_group_id": "openai/gpt-5-1",
12241
  "model_route_id": "openai__gpt-5-1",
12242
  "model_family_name": "gpt-5.1-2025-11-13",
12243
  "developer": "openai",
 
12508
  ]
12509
  },
12510
  {
12511
+ "model_group_id": "openai/gpt-5-1-codex",
12512
  "model_route_id": "openai__gpt-5-1-codex",
12513
  "model_family_name": "gpt-5.1-codex",
12514
  "developer": "openai",
 
12709
  ]
12710
  },
12711
  {
12712
+ "model_group_id": "openai/gpt-5-1-codex-high",
12713
  "model_route_id": "openai__gpt-5-1-codex-high",
12714
  "model_family_name": "gpt-5.1-codex-high",
12715
  "developer": "openai",
 
12778
  ]
12779
  },
12780
  {
12781
+ "model_group_id": "openai/gpt-5-1-instant",
12782
  "model_route_id": "openai__gpt-5-1-instant",
12783
  "model_family_name": "gpt-5.1-instant-2025-11-12",
12784
  "developer": "openai",
 
12897
  ]
12898
  },
12899
  {
12900
+ "model_group_id": "openai/gpt-5-1-medium",
12901
  "model_route_id": "openai__gpt-5-1-medium",
12902
  "model_family_name": "gpt-5.1-medium-2025-11-12",
12903
  "developer": "openai",
 
12966
  ]
12967
  },
12968
  {
12969
+ "model_group_id": "openai/gpt-5-1-thinking",
12970
  "model_route_id": "openai__gpt-5-1-thinking",
12971
  "model_family_name": "gpt-5.1-thinking-2025-11-12",
12972
  "developer": "openai",
 
13085
  ]
13086
  },
13087
  {
13088
+ "model_group_id": "openai/gpt-5-2",
13089
  "model_route_id": "openai__gpt-5-2",
13090
  "model_family_name": "gpt-5.2-2025-12-11",
13091
  "developer": "openai",
 
13378
  ]
13379
  },
13380
  {
13381
+ "model_group_id": "openai/gpt-5-2-codex",
13382
  "model_route_id": "openai__gpt-5-2-codex",
13383
  "model_family_name": "gpt-5.2-codex",
13384
  "developer": "openai",
 
13580
  ]
13581
  },
13582
  {
13583
+ "model_group_id": "openai/gpt-5-3-chat-latest",
13584
  "model_route_id": "openai__gpt-5-3-chat-latest",
13585
  "model_family_name": "gpt-5.3-chat-latest",
13586
  "developer": "openai",
 
13659
  ]
13660
  },
13661
  {
13662
+ "model_group_id": "openai/gpt-5-3-codex",
13663
  "model_route_id": "openai__gpt-5-3-codex",
13664
  "model_family_name": "gpt-5.3-codex",
13665
  "developer": "openai",
 
13864
  ]
13865
  },
13866
  {
13867
+ "model_group_id": "openai/gpt-5-4",
13868
  "model_route_id": "openai__gpt-5-4",
13869
  "model_family_name": "gpt-5.4",
13870
  "developer": "openai",
 
14089
  ]
14090
  },
14091
  {
14092
+ "model_group_id": "openai/gpt-5-4-mini",
14093
  "model_route_id": "openai__gpt-5-4-mini",
14094
  "model_family_name": "gpt-5.4-mini",
14095
  "developer": "openai",
 
14296
  ]
14297
  },
14298
  {
14299
+ "model_group_id": "openai/gpt-5-4-nano",
14300
  "model_route_id": "openai__gpt-5-4-nano",
14301
  "model_family_name": "gpt-5.4-nano",
14302
  "developer": "openai",
 
14501
  ]
14502
  },
14503
  {
14504
+ "model_group_id": "openai/gpt-oss-120b-low",
14505
  "model_route_id": "openai__gpt-oss-120b-low",
14506
  "model_family_name": "gpt-oss-120B (low)",
14507
  "developer": "openai",
 
14696
  ]
14697
  },
14698
  {
14699
+ "model_group_id": "openai/gpt-oss-120b",
14700
  "model_route_id": "openai__gpt-oss-120b",
14701
  "model_family_name": "gpt-oss-120b",
14702
  "developer": "openai",
 
14921
  ]
14922
  },
14923
  {
14924
+ "model_group_id": "openai/gpt-oss-120b-high",
14925
  "model_route_id": "openai__gpt-oss-120b-high",
14926
  "model_family_name": "gpt-oss-120b-high",
14927
  "developer": "openai",
 
15011
  ]
15012
  },
15013
  {
15014
+ "model_group_id": "openai/gpt-oss-20b-low",
15015
  "model_route_id": "openai__gpt-oss-20b-low",
15016
  "model_family_name": "gpt-oss-20B (low)",
15017
  "developer": "openai",
 
15206
  ]
15207
  },
15208
  {
15209
+ "model_group_id": "openai/gpt-oss-20b",
15210
  "model_route_id": "openai__gpt-oss-20b",
15211
  "model_family_name": "gpt-oss-20b",
15212
  "developer": "openai",
 
15427
  ]
15428
  },
15429
  {
15430
+ "model_group_id": "openai/o1",
15431
  "model_route_id": "openai__o1",
15432
  "model_family_name": "o1",
15433
  "developer": "openai",
 
15655
  ]
15656
  },
15657
  {
15658
+ "model_group_id": "openai/o1-mini",
15659
  "model_route_id": "openai__o1-mini",
15660
  "model_family_name": "o1-mini",
15661
  "developer": "openai",
 
15872
  ]
15873
  },
15874
  {
15875
+ "model_group_id": "openai/o1-preview",
15876
  "model_route_id": "openai__o1-preview",
15877
  "model_family_name": "o1-preview",
15878
  "developer": "openai",
 
16075
  ]
16076
  },
16077
  {
16078
+ "model_group_id": "openai/o1-pro",
16079
  "model_route_id": "openai__o1-pro",
16080
  "model_family_name": "o1-pro",
16081
  "developer": "openai",
 
16208
  ]
16209
  },
16210
  {
16211
+ "model_group_id": "openai/o3",
16212
  "model_route_id": "openai__o3",
16213
  "model_family_name": "o3",
16214
  "developer": "openai",
 
16482
  ]
16483
  },
16484
  {
16485
+ "model_group_id": "openai/o3-mini",
16486
  "model_route_id": "openai__o3-mini",
16487
  "model_family_name": "o3-mini",
16488
  "developer": "openai",
 
16746
  ]
16747
  },
16748
  {
16749
+ "model_group_id": "openai/o3-mini-high",
16750
  "model_route_id": "openai__o3-mini-high",
16751
  "model_family_name": "o3-mini (high)",
16752
  "developer": "openai",
 
16941
  ]
16942
  },
16943
  {
16944
+ "model_group_id": "openai/o3-pro",
16945
  "model_route_id": "openai__o3-pro",
16946
  "model_family_name": "o3-pro",
16947
  "developer": "openai",
 
17122
  ]
17123
  },
17124
  {
17125
+ "model_group_id": "openai/o4-mini",
17126
  "model_route_id": "openai__o4-mini",
17127
  "model_family_name": "o4-mini",
17128
  "developer": "openai",
 
17390
  ]
17391
  },
17392
  {
17393
+ "model_group_id": "openai/gpt-4-0125-preview",
17394
  "model_route_id": "openai__gpt-4-0125-preview",
17395
  "model_family_name": "openai/gpt-4-0125-preview",
17396
  "developer": "openai",
 
17468
  ]
17469
  },
17470
  {
17471
+ "model_group_id": "openai/text-ada-001",
17472
  "model_route_id": "openai__text-ada-001",
17473
  "model_family_name": "text-ada-001",
17474
  "developer": "openai",
 
17680
  ]
17681
  },
17682
  {
17683
+ "model_group_id": "openai/text-babbage-001",
17684
  "model_route_id": "openai__text-babbage-001",
17685
  "model_family_name": "text-babbage-001",
17686
  "developer": "openai",
 
17892
  ]
17893
  },
17894
  {
17895
+ "model_group_id": "openai/text-curie-001",
17896
  "model_route_id": "openai__text-curie-001",
17897
  "model_family_name": "text-curie-001",
17898
  "developer": "openai",
 
18104
  ]
18105
  },
18106
  {
18107
+ "model_group_id": "openai/text-davinci-002",
18108
  "model_route_id": "openai__text-davinci-002",
18109
  "model_family_name": "text-davinci-002",
18110
  "developer": "openai",
 
18324
  ]
18325
  },
18326
  {
18327
+ "model_group_id": "openai/text-davinci-003",
18328
  "model_route_id": "openai__text-davinci-003",
18329
  "model_family_name": "text-davinci-003",
18330
  "developer": "openai",
tests/fixtures/model-cards/01-ai__yi-34b.json CHANGED
@@ -1,5 +1,5 @@
1
  {
2
- "model_family_id": "01-ai/yi-34b",
3
  "model_route_id": "01-ai__yi-34b",
4
  "model_family_name": "Yi 34B",
5
  "developer": "01-ai",
 
1
  {
2
+ "model_group_id": "01-ai/yi-34b",
3
  "model_route_id": "01-ai__yi-34b",
4
  "model_family_name": "Yi 34B",
5
  "developer": "01-ai",
tests/fixtures/model-cards/anthropic__claude-opus-4.5.json CHANGED
@@ -1,5 +1,5 @@
1
  {
2
- "model_family_id": "anthropic/claude-opus-4.5",
3
  "model_route_id": "anthropic__claude-opus-4.5",
4
  "model_family_name": "Claude Opus 4.5",
5
  "developer": "anthropic",
 
1
  {
2
+ "model_group_id": "anthropic/claude-opus-4.5",
3
  "model_route_id": "anthropic__claude-opus-4.5",
4
  "model_family_name": "Claude Opus 4.5",
5
  "developer": "anthropic",
tests/fixtures/model-cards/openai__gpt-5.json CHANGED
@@ -1,5 +1,5 @@
1
  {
2
- "model_family_id": "openai/gpt-5",
3
  "model_route_id": "openai__gpt-5",
4
  "model_family_name": "GPT 5",
5
  "developer": "openai",
 
1
  {
2
+ "model_group_id": "openai/gpt-5",
3
  "model_route_id": "openai__gpt-5",
4
  "model_family_name": "GPT 5",
5
  "developer": "openai",
tests/fixtures/models/ai21__j1-grande-v1-17b.json CHANGED
@@ -13,7 +13,7 @@
13
  "model_route_id": "ai21__j1-grande-v1-17b",
14
  "model_version": null
15
  },
16
- "model_family_id": "ai21/j1-grande-v1-17b",
17
  "model_route_id": "ai21__j1-grande-v1-17b",
18
  "model_family_name": "J1-Grande v1 17B",
19
  "raw_model_ids": [
 
13
  "model_route_id": "ai21__j1-grande-v1-17b",
14
  "model_version": null
15
  },
16
+ "model_group_id": "ai21/j1-grande-v1-17b",
17
  "model_route_id": "ai21__j1-grande-v1-17b",
18
  "model_family_name": "J1-Grande v1 17B",
19
  "raw_model_ids": [
tests/fixtures/models/bytedance__seed-2-0-lite.json CHANGED
@@ -25,7 +25,7 @@
25
  "model_route_id": "bytedance__seed-2-0-lite",
26
  "model_version": null
27
  },
28
- "model_family_id": "bytedance/seed-2-0-lite",
29
  "model_route_id": "bytedance__seed-2-0-lite",
30
  "model_family_name": "Seed 2.0 Lite",
31
  "raw_model_ids": [
 
25
  "model_route_id": "bytedance__seed-2-0-lite",
26
  "model_version": null
27
  },
28
+ "model_group_id": "bytedance/seed-2-0-lite",
29
  "model_route_id": "bytedance__seed-2-0-lite",
30
  "model_family_name": "Seed 2.0 Lite",
31
  "raw_model_ids": [
tests/fixtures/models/google__gemini-3-flash.json CHANGED
@@ -22,7 +22,7 @@
22
  "model_route_id": "google__gemini-3-flash",
23
  "model_version": null
24
  },
25
- "model_family_id": "google/gemini-3-flash",
26
  "model_route_id": "google__gemini-3-flash",
27
  "model_family_name": "Gemini 3 Flash Preview (Non-reasoning)",
28
  "raw_model_ids": [
 
22
  "model_route_id": "google__gemini-3-flash",
23
  "model_version": null
24
  },
25
+ "model_group_id": "google/gemini-3-flash",
26
  "model_route_id": "google__gemini-3-flash",
27
  "model_family_name": "Gemini 3 Flash Preview (Non-reasoning)",
28
  "raw_model_ids": [
tests/fixtures/models/openai__gpt-5-2-pro.json CHANGED
@@ -28,7 +28,7 @@
28
  "model_route_id": "openai__gpt-5-2-pro",
29
  "model_version": null
30
  },
31
- "model_family_id": "openai/gpt-5-2-pro",
32
  "model_route_id": "openai__gpt-5-2-pro",
33
  "model_family_name": "GPT-5.2 Pro",
34
  "raw_model_ids": [
 
28
  "model_route_id": "openai__gpt-5-2-pro",
29
  "model_version": null
30
  },
31
+ "model_group_id": "openai/gpt-5-2-pro",
32
  "model_route_id": "openai__gpt-5-2-pro",
33
  "model_family_name": "GPT-5.2 Pro",
34
  "raw_model_ids": [
tests/pipeline-contract.test.ts CHANGED
@@ -100,19 +100,19 @@ describe("Tier A — pipeline contracts (model files)", () => {
100
  expect(violations, formatViolations(violations)).toEqual([])
101
  })
102
 
103
- it("model card has model_family_id matching pipelineSlugify(model_family_id) → model_route_id", () => {
104
  const violations: Violation[] = []
105
  for (const { id, data } of models) {
106
- if (!data.model_family_id) {
107
- violations.push({ fixture: id, path: "model_family_id", detail: "missing" })
108
  continue
109
  }
110
- const expected = data.model_family_id.replace(/\//g, "__")
111
  if (data.model_route_id !== expected) {
112
  violations.push({
113
  fixture: id,
114
  path: "model_route_id",
115
- detail: `${data.model_route_id} !== ${expected} (derived from ${data.model_family_id})`,
116
  })
117
  }
118
  }
 
100
  expect(violations, formatViolations(violations)).toEqual([])
101
  })
102
 
103
+ it("model card has model_group_id matching pipelineSlugify(model_group_id) → model_route_id", () => {
104
  const violations: Violation[] = []
105
  for (const { id, data } of models) {
106
+ if (!data.model_group_id) {
107
+ violations.push({ fixture: id, path: "model_group_id", detail: "missing" })
108
  continue
109
  }
110
+ const expected = data.model_group_id.replace(/\//g, "__")
111
  if (data.model_route_id !== expected) {
112
  violations.push({
113
  fixture: id,
114
  path: "model_route_id",
115
+ detail: `${data.model_route_id} !== ${expected} (derived from ${data.model_group_id})`,
116
  })
117
  }
118
  }
tests/redirect-integrity.test.ts ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { readFileSync } from "fs"
2
+ import { fileURLToPath } from "url"
3
+
4
+ import { DuckDBConnection } from "@duckdb/node-api"
5
+ import { beforeAll, describe, expect, it } from "vitest"
6
+
7
+ // Server-FREE data-contract checks for the model URL redirect map + resolver
8
+ // fallback invariants, asserted against a Stage-J warehouse snapshot. Opt-in via
9
+ // `SNAPSHOT_URL=<warehouse> pnpm test:integrity` — self-skips in the default
10
+ // `pnpm test` run (mirrors the RUN_DRIFT pattern in upstream-drift.test.ts) so it
11
+ // never blocks a dev or a deploy. The server-BOUND checks (page rendering, chart
12
+ // content, redirect-preservation) live in the Playwright e2e suite + PREFLIGHT.md.
13
+ //
14
+ // Why this exists: "the page returns 200" != "correct". The redirect map and the
15
+ // raw_model_ids fallback have invariants that, if violated, silently break model
16
+ // pages (folded ids 404, an inverted map 301s working URLs to dead leaves). See
17
+ // tests/PREFLIGHT.md for the full bug taxonomy.
18
+
19
+ const SNAPSHOT = (process.env.SNAPSHOT_URL || "").replace(/\/+$/, "")
20
+ const shouldRun = !!SNAPSHOT
21
+ const MAP_PATH = fileURLToPath(new URL("../lib/model-url-redirects.ts", import.meta.url))
22
+
23
+ const dec = (s: string) => { try { return decodeURIComponent(s) } catch { return s } }
24
+ const listItems = (v: unknown): string[] =>
25
+ Array.isArray(v) ? v.map(String)
26
+ : v && typeof v === "object" && Array.isArray((v as { items?: unknown[] }).items)
27
+ ? (v as { items: unknown[] }).items.map(String)
28
+ : []
29
+
30
+ describe.skipIf(!shouldRun)("redirect-map + fallback integrity (vs SNAPSHOT_URL)", () => {
31
+ let map: [string, string][] = []
32
+ let keys: Set<string>
33
+ const addressable = new Set<string>() // decoded route forms that resolve to a page
34
+ const rawOwners = new Map<string, Set<string>>() // raw spelling -> owning group(s)
35
+ let nullRaw = 0
36
+ let rowCount = 0
37
+
38
+ beforeAll(async () => {
39
+ map = [...readFileSync(MAP_PATH, "utf8").matchAll(/\["([^"]+)",\s*"([^"]+)"\]/g)].map((m) => [m[1], m[2]])
40
+ keys = new Set(map.map(([k]) => k))
41
+ const con = await DuckDBConnection.create()
42
+ await con.run("INSTALL httpfs; LOAD httpfs;")
43
+ const rows = (await con.runAndReadAll(
44
+ `SELECT model_id, route_id, model_route_id, model_group_id, model_key, raw_model_ids
45
+ FROM read_parquet('${SNAPSHOT}/models_view.parquet')`,
46
+ )).getRowObjects()
47
+ rowCount = rows.length
48
+ for (const r of rows) {
49
+ for (const v of [r.model_id, r.model_route_id, r.route_id, r.model_group_id, r.model_key]) {
50
+ if (v) addressable.add(dec(String(v)))
51
+ }
52
+ if (r.raw_model_ids == null) nullRaw++
53
+ for (const raw of listItems(r.raw_model_ids)) {
54
+ const set = rawOwners.get(String(raw)) ?? rawOwners.set(String(raw), new Set()).get(String(raw))!
55
+ set.add(String(r.model_id))
56
+ }
57
+ }
58
+ }, 120_000)
59
+
60
+ it("redirect map has no self-redirects", () => {
61
+ expect(map.filter(([k, v]) => k === v)).toEqual([])
62
+ })
63
+
64
+ it("redirect map has no chains/loops (no target is also a key)", () => {
65
+ expect(map.filter(([, v]) => keys.has(v)).map(([, v]) => v)).toEqual([])
66
+ })
67
+
68
+ it("every redirect target is an addressable route", () => {
69
+ const dead = [...new Set(map.map(([, v]) => v))].filter((t) => !addressable.has(dec(t)))
70
+ expect(dead, `dead targets: ${dead.slice(0, 8)}`).toEqual([])
71
+ })
72
+
73
+ it("direction is folded->group (no addressable id is a redirect KEY)", () => {
74
+ // An addressable key means the map redirects a WORKING page away — the
75
+ // inverted-map bug.
76
+ const inverted = map.filter(([k]) => addressable.has(dec(k))).map(([k]) => k)
77
+ expect(inverted, `addressable ids used as redirect keys: ${inverted.slice(0, 8)}`).toEqual([])
78
+ })
79
+
80
+ it("every redirect key is a known folded raw_model_id", () => {
81
+ const orphan = map.filter(([k]) => !addressable.has(dec(k)) && !rawOwners.has(dec(k))).map(([k]) => k)
82
+ expect(orphan, `orphan keys (neither addressable nor a raw id): ${orphan.slice(0, 8)}`).toEqual([])
83
+ })
84
+
85
+ it("no raw_model_id belongs to >1 group (LIMIT 1 fallback is unambiguous)", () => {
86
+ const multi = [...rawOwners].filter(([, s]) => s.size > 1).map(([k]) => k)
87
+ expect(multi, `raw ids in >1 group: ${multi.slice(0, 8)}`).toEqual([])
88
+ })
89
+
90
+ it("no models_view row has NULL raw_model_ids (the fallback scans this column)", () => {
91
+ expect(nullRaw, `${nullRaw}/${rowCount} rows have NULL raw_model_ids`).toBe(0)
92
+ })
93
+ })
tests/transformations/identity-canonicalization.test.ts DELETED
@@ -1,251 +0,0 @@
1
- import { describe, expect, it } from "vitest"
2
-
3
- import { getCanonicalModelIdentity, getModelFamilyRouteId } from "../../lib/model-family"
4
-
5
- // Executable spec for the identity-canonicalization transformation.
6
- //
7
- // Each table here corresponds to a Group in the spec. Pipeline-side
8
- // implementation must produce identical outputs for every row. Adding a row
9
- // here is the way to extend the spec.
10
- //
11
- // When the pipeline ships matching values, run `scripts/verify-identity.mjs`
12
- // against the full live cache to confirm cross-corpus equivalence before
13
- // deleting the TS implementation.
14
-
15
- interface Case {
16
- id: string
17
- name?: string
18
- developer?: string
19
- expected: {
20
- namespace?: string
21
- familySlug?: string
22
- familyId?: string
23
- familyName?: string
24
- variantKey?: string
25
- variantLabel?: string
26
- versionDate?: string | undefined
27
- versionQualifier?: string | undefined
28
- variantDisplayName?: string
29
- }
30
- why: string
31
- }
32
-
33
- // ---------------------------------------------------------------------------
34
- // Group A — Token case map
35
- // ---------------------------------------------------------------------------
36
- //
37
- // Each token in the map below must title-case to the canonical form. The full
38
- // case map is captured in the spec doc (Group A table). Here we exercise a
39
- // representative subset plus the "no map entry" baseline.
40
-
41
- describe("Group A — token case map", () => {
42
- const cases: Case[] = [
43
- { id: "anthropic/claude-opus-4.5", name: "Claude Opus 4.5", expected: { familyName: "Claude Opus 4.5" }, why: "claude → Claude, opus → Opus" },
44
- { id: "openai/gpt-5", name: "GPT 5", expected: { familyName: "GPT 5" }, why: "gpt → GPT (uppercased)" },
45
- { id: "google/gemini-2.5-pro", name: "Gemini 2.5 Pro", expected: { familyName: "Gemini 2.5 Pro" }, why: "gemini, pro" },
46
- { id: "meta/llama-3", name: "Llama 3", expected: { familyName: "Llama 3" }, why: "llama" },
47
- { id: "01-ai/yi-large", name: "Yi Large", expected: { familyName: "Yi Large" }, why: "yi (lowercase letter, but in map)" },
48
- { id: "anthropic/claude-haiku", name: "Claude Haiku", expected: { familyName: "Claude Haiku" }, why: "haiku" },
49
- { id: "anthropic/claude-instant", name: "Claude Instant", expected: { familyName: "Claude Instant" }, why: "instant" },
50
- { id: "x/x-ai", name: "x AI", expected: { familyName: "X AI" }, why: "ai → AI (a deliberate capitalization)" },
51
- { id: "test/turbo-mini", name: "Turbo Mini", expected: { familyName: "Turbo Mini" }, why: "turbo, mini" },
52
- { id: "qwen/qwen-coder", name: "Qwen Coder", expected: { familyName: "Qwen Coder" }, why: "qwen, coder" },
53
- { id: "test/foo-bar", name: "Foo Bar", expected: { familyName: "Foo Bar" }, why: "tokens not in map → naive title-case" },
54
- ]
55
- it.each(cases)("$why → familyName=$expected.familyName", ({ id, name, expected }) => {
56
- const result = getCanonicalModelIdentity({ id, name: name ?? "" })
57
- expect(result.familyName).toBe(expected.familyName)
58
- })
59
- })
60
-
61
- // ---------------------------------------------------------------------------
62
- // Group B — v/V version-token rule
63
- // ---------------------------------------------------------------------------
64
- //
65
- // Tokens matching /^v\d/i are lowercased. This rule is the source of the
66
- // 1,253-card pipeline disagreement documented in the spec; pipeline must
67
- // apply this rule before emitting model_family_name.
68
-
69
- describe("Group B — v/V version-token rule", () => {
70
- const cases: Case[] = [
71
- { id: "deepseek-ai/deepseek-v3", name: "Deepseek V3", expected: { familyName: "Deepseek v3" }, why: "trailing v3" },
72
- { id: "deepseek-ai/deepseek-v3.1", name: "Deepseek V3.1", expected: { familyName: "Deepseek v3.1" }, why: "v3.1 with patch" },
73
- { id: "mistralai/mistral-7b-instruct-v0.3", name: "Mistral 7B Instruct v0.3", expected: { familyName: "Mistral 7B Instruct v0.3" }, why: "v0.3 in middle, mistral & instruct in case map" },
74
- { id: "mistralai/mixtral-8x22b-instruct-v0.1", name: "Mixtral 8x22b Instruct v0.1", expected: { familyName: "Mixtral 8x22b Instruct v0.1" }, why: "v0.1 with size token" },
75
- { id: "amazon/nova-lite-v1.0", name: "Nova Lite v1.0", expected: { familyName: "Nova Lite v1.0" }, why: "v1.0" },
76
- { id: "test/foo-bar", name: "Foo Bar", expected: { familyName: "Foo Bar" }, why: "no v rule applies" },
77
- ]
78
- it.each(cases)("$why → familyName=$expected.familyName", ({ id, name, expected }) => {
79
- expect(getCanonicalModelIdentity({ id, name: name ?? "" }).familyName).toBe(expected.familyName)
80
- })
81
- })
82
-
83
- // ---------------------------------------------------------------------------
84
- // Group C — Date and qualifier extraction
85
- // ---------------------------------------------------------------------------
86
-
87
- describe("Group C — date and qualifier extraction", () => {
88
- const cases: Case[] = [
89
- {
90
- id: "anthropic/claude-3-5-sonnet",
91
- name: "Claude 3.5 Sonnet",
92
- expected: { familySlug: "claude-3.5-sonnet", versionDate: undefined, versionQualifier: undefined, variantKey: "base", variantLabel: "Current" },
93
- why: "no date in handle → base variant, Current label",
94
- },
95
- {
96
- id: "anthropic/claude-3-5-sonnet-20240620",
97
- name: "Claude 3.5 Sonnet 20240620",
98
- expected: { familySlug: "claude-3.5-sonnet", versionDate: "2024-06-20", versionQualifier: undefined, variantKey: "20240620", variantLabel: "2024-06-20" },
99
- why: "YYYYMMDD date present, no qualifier",
100
- },
101
- {
102
- id: "anthropic/claude-3-5-sonnet-20240620-thinking",
103
- name: "Claude 3.5 Sonnet 20240620 Thinking",
104
- expected: {
105
- familySlug: "claude-3.5-sonnet",
106
- versionDate: "2024-06-20",
107
- versionQualifier: "Thinking",
108
- variantKey: "20240620-thinking",
109
- variantLabel: "2024-06-20 · Thinking",
110
- },
111
- why: "date plus single-token qualifier",
112
- },
113
- {
114
- id: "openai/gpt-5-2025-12-11-thinking-high",
115
- name: "GPT 5 2025-12-11 thinking high",
116
- expected: {
117
- // The (?:19|20)\d{6} regex requires 8 contiguous digits; "2025-12-11" has dashes, so
118
- // it doesn't match as a date. NormalizeHandle's digit-dash-digit collapse only fires when
119
- // the right side is followed by a dash or end, so "2025-12-11" stays as is.
120
- // → no date matched → base variant.
121
- variantKey: "base",
122
- versionDate: undefined,
123
- },
124
- why: "dashed date is NOT recognized — only YYYYMMDD form",
125
- },
126
- ]
127
- it.each(cases)("$why", ({ id, name, expected }) => {
128
- const result = getCanonicalModelIdentity({ id, name: name ?? "" })
129
- if (expected.familySlug !== undefined) expect(result.familySlug).toBe(expected.familySlug)
130
- if (expected.versionDate !== undefined || "versionDate" in expected) expect(result.versionDate).toBe(expected.versionDate)
131
- if (expected.versionQualifier !== undefined || "versionQualifier" in expected) expect(result.versionQualifier).toBe(expected.versionQualifier)
132
- if (expected.variantKey !== undefined) expect(result.variantKey).toBe(expected.variantKey)
133
- if (expected.variantLabel !== undefined) expect(result.variantLabel).toBe(expected.variantLabel)
134
- })
135
- })
136
-
137
- // ---------------------------------------------------------------------------
138
- // Group D — Handle normalization
139
- // ---------------------------------------------------------------------------
140
-
141
- describe("Group D — handle normalization (familyId derivation)", () => {
142
- const cases: Case[] = [
143
- { id: "anthropic/Claude_Opus_4.5", name: "Claude Opus 4.5", expected: { familyId: "anthropic/claude-opus-4.5" }, why: "underscore → dash, lowercase" },
144
- { id: "anthropic/claude opus 4.5", name: "Claude Opus 4.5", expected: { familyId: "anthropic/claude-opus-4.5" }, why: "space → dash" },
145
- { id: "anthropic/--claude--opus--", name: "Claude Opus", expected: { familyId: "anthropic/claude-opus" }, why: "leading/trailing/repeated dashes collapsed" },
146
- { id: "anthropic/claude-3-5-sonnet", name: "Claude 3.5 Sonnet", expected: { familyId: "anthropic/claude-3.5-sonnet" }, why: "digit-dash-digit-dash collapses to digit-dot-digit-dash" },
147
- { id: "anthropic/claude-3-5", name: "Claude 3.5", expected: { familyId: "anthropic/claude-3.5" }, why: "digit-dash-digit at end collapses to digit-dot-digit" },
148
- { id: "google/gemini-1.5-pro", name: "Gemini 1.5 Pro", expected: { familyId: "google/gemini-1.5-pro" }, why: "existing dot is preserved" },
149
- ]
150
- it.each(cases)("$why", ({ id, name, expected }) => {
151
- expect(getCanonicalModelIdentity({ id, name: name ?? "" }).familyId).toBe(expected.familyId)
152
- })
153
- })
154
-
155
- // ---------------------------------------------------------------------------
156
- // Group E — Namespace and rawHandle extraction
157
- // ---------------------------------------------------------------------------
158
-
159
- describe("Group E — namespace + rawHandle extraction", () => {
160
- it("id with slash → first segment is namespace, rest is handle", () => {
161
- const r = getCanonicalModelIdentity({ id: "anthropic/claude-opus-4-5", name: "Claude Opus 4.5" })
162
- expect(r.namespace).toBe("anthropic")
163
- expect(r.rawHandle).toBe("claude-opus-4-5")
164
- })
165
- it("id without slash falls back to developer field for namespace", () => {
166
- const r = getCanonicalModelIdentity({ id: "gpt-5", name: "GPT 5", developer: "OpenAI" })
167
- expect(r.namespace).toBe("openai")
168
- })
169
- it("developer with spaces is slug-cased for namespace", () => {
170
- const r = getCanonicalModelIdentity({ id: "claude-opus-4.5", name: "Claude Opus 4.5", developer: "Anthropic AI" })
171
- expect(r.namespace).toBe("anthropic-ai")
172
- })
173
- it("namespace prefix in name is stripped when computing rawHandle from name", () => {
174
- // Falls into getRawHandle's strippedName path when id has no '/' segment
175
- const r = getCanonicalModelIdentity({ id: "", name: "anthropic/Claude Opus 4.5", developer: "Anthropic" })
176
- expect(r.namespace).toBe("anthropic")
177
- // Note: with id empty, idHandle is empty, so falls back to stripNamespace(name, namespace)
178
- expect(r.rawHandle).toBe("Claude Opus 4.5")
179
- })
180
- })
181
-
182
- // ---------------------------------------------------------------------------
183
- // Group F — familyId and model_route_id contract
184
- // ---------------------------------------------------------------------------
185
-
186
- describe("Group F — familyId and model_route_id", () => {
187
- // KNOWN-RED (intentional): getModelFamilyRouteId now returns percent-encoded
188
- // ids (e.g. `openai%2Fgpt-5`) — the correct v2 form, matching the warehouse
189
- // `route_id`. These cases still assert the legacy `__` form.
190
- // TODO: clean up route_id upstream (Stage J producer) to slash-form, then
191
- // align getModelFamilyRouteId + these expectations.
192
- const cases = [
193
- { familyId: "anthropic/claude-opus-4.5", routeId: "anthropic__claude-opus-4.5" },
194
- { familyId: "openai/gpt-5", routeId: "openai__gpt-5" },
195
- { familyId: "01-ai/yi-large", routeId: "01-ai__yi-large" },
196
- { familyId: "google/gemini-2.5-pro", routeId: "google__gemini-2.5-pro" },
197
- ]
198
- it.each(cases)("getModelFamilyRouteId($familyId) === $routeId", ({ familyId, routeId }) => {
199
- expect(getModelFamilyRouteId(familyId)).toBe(routeId)
200
- })
201
- })
202
-
203
- // ---------------------------------------------------------------------------
204
- // Integration cases — full-tuple round-trips for fixtures we ship
205
- // ---------------------------------------------------------------------------
206
- //
207
- // These mirror real model cards in tests/fixtures/model-cards/ (where the
208
- // pipeline ALSO emits the canonical fields). When pipeline matches across the
209
- // full corpus, this section is a sanity check that the spec stays in sync
210
- // with the fixtures.
211
-
212
- describe("integration — fixtures round-trip", () => {
213
- const fixtures = [
214
- {
215
- id: "anthropic/claude-opus-4.5",
216
- name: "Claude Opus 4.5",
217
- expected: {
218
- familyId: "anthropic/claude-opus-4.5",
219
- familyName: "Claude Opus 4.5",
220
- variantKey: "base",
221
- },
222
- why: "anthropic__claude-opus-4.5 fixture (dotted route_id quirk)",
223
- },
224
- {
225
- id: "openai/gpt-5",
226
- name: "GPT 5",
227
- expected: {
228
- familyId: "openai/gpt-5",
229
- familyName: "GPT 5",
230
- variantKey: "base",
231
- },
232
- why: "openai__gpt-5 fixture",
233
- },
234
- {
235
- id: "01-ai/yi-34b",
236
- name: "Yi 34B",
237
- expected: {
238
- familyId: "01-ai/yi-34b",
239
- familyName: "Yi 34B",
240
- variantKey: "base",
241
- },
242
- why: "01-ai__yi-34b fixture (dash-prefix slug)",
243
- },
244
- ]
245
- it.each(fixtures)("$why", ({ id, name, expected }) => {
246
- const result = getCanonicalModelIdentity({ id, name })
247
- expect(result.familyId).toBe(expected.familyId)
248
- expect(result.familyName).toBe(expected.familyName)
249
- expect(result.variantKey).toBe(expected.variantKey)
250
- })
251
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/transformations/model-url-redirects.test.ts ADDED
@@ -0,0 +1,219 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { describe, expect, it } from "vitest"
2
+
3
+ import {
4
+ buildRedirectsFromModelsView,
5
+ routeId,
6
+ serializeRedirectModule,
7
+ type ModelsViewRow,
8
+ } from "@/lib/model-url-redirects-build"
9
+ import { resolveModelRedirect } from "@/lib/model-url-redirects"
10
+ import { routeIdFromModelId, routeIdToPath } from "@/lib/utils"
11
+
12
+ // model-resolution-rework — executable spec for the model-URL redirect layer.
13
+ //
14
+ // Covers the two CRITICAL contract points the playbook calls out:
15
+ // 1. percent-encode / round-trip of model ids (RFC 3986, `/` -> %2F).
16
+ // 2. query-param preservation across the 301 (asserted via the middleware's
17
+ // target-URL construction, replicated here to stay node-only / no Next
18
+ // runtime).
19
+
20
+ // ---------------------------------------------------------------------------
21
+ // Percent-encoding round-trip
22
+ // ---------------------------------------------------------------------------
23
+
24
+ describe("route id percent-encoding round-trips", () => {
25
+ const ids = [
26
+ "anthropic/claude-3.5-sonnet-20240620",
27
+ "meta/Llama-3.1-8B",
28
+ "01-ai/Yi-1.5-34B",
29
+ "EleutherAI/pythia-6.9b",
30
+ "ByteDance-Seed/Seed-OSS-36B-Instruct",
31
+ "openai/gpt-4o",
32
+ "mistralai/mistral-7b-instruct-v0.3",
33
+ ]
34
+
35
+ it.each(ids)("encode→decode is identity for %s", (id) => {
36
+ const encoded = routeIdFromModelId(id)
37
+ // No bare slashes survive encoding (single-segment route id form).
38
+ expect(encoded).not.toContain("/")
39
+ // Full round-trip back to the original id.
40
+ expect(decodeURIComponent(encoded)).toBe(id)
41
+ })
42
+
43
+ it("routeIdToPath turns the encoded form back into a slash path", () => {
44
+ const encoded = routeIdFromModelId("anthropic/claude-3.5-sonnet-20240620")
45
+ expect(encoded).toBe("anthropic%2Fclaude-3.5-sonnet-20240620")
46
+ expect(routeIdToPath(encoded)).toBe("anthropic/claude-3.5-sonnet-20240620")
47
+ })
48
+
49
+ it("routeIdFromModelId trims and tolerates empty/nullish", () => {
50
+ expect(routeIdFromModelId(" openai/gpt-4o ")).toBe("openai%2Fgpt-4o")
51
+ expect(routeIdFromModelId(undefined)).toBe("")
52
+ expect(routeIdFromModelId(null)).toBe("")
53
+ expect(routeIdFromModelId("")).toBe("")
54
+ })
55
+
56
+ it("casing is preserved through encoding (HF ids are case-sensitive)", () => {
57
+ expect(routeIdFromModelId("meta/Llama-3.1-8B")).toBe("meta%2FLlama-3.1-8B")
58
+ expect(routeIdFromModelId("meta/llama-3.1-8b")).not.toBe(
59
+ routeIdFromModelId("meta/Llama-3.1-8B"),
60
+ )
61
+ })
62
+ })
63
+
64
+ // ---------------------------------------------------------------------------
65
+ // buildRedirectsFromModelsView — map construction from the warehouse
66
+ // ---------------------------------------------------------------------------
67
+
68
+ describe("buildRedirectsFromModelsView", () => {
69
+ it("maps a folded raw spelling to its owning group route", () => {
70
+ const rows: ModelsViewRow[] = [
71
+ {
72
+ model_route_id: "mistralai%2Fmistral-medium",
73
+ model_id: "mistralai/mistral-medium",
74
+ route_id: "mistralai%2Fmistral-medium",
75
+ model_key: "mistralai/mistral-medium",
76
+ model_group_id: "mistralai/mistral-medium",
77
+ raw_model_ids: ["mistralai/mistral-medium-2505", "mistralai/Mistral-Medium"],
78
+ },
79
+ ]
80
+ const { redirects } = buildRedirectsFromModelsView(rows)
81
+ expect(redirects.get(routeId("mistralai/mistral-medium-2505"))).toBe("mistralai%2Fmistral-medium")
82
+ expect(redirects.get(routeId("mistralai/Mistral-Medium"))).toBe("mistralai%2Fmistral-medium")
83
+ })
84
+
85
+ it("never redirects an addressable id — no group->leaf hijack", () => {
86
+ const rows: ModelsViewRow[] = [
87
+ {
88
+ model_route_id: "mistralai%2Fmistral-medium",
89
+ model_id: "mistralai/mistral-medium",
90
+ // the group's own id appearing in its raw list must NOT become a redirect
91
+ raw_model_ids: ["mistralai/mistral-medium"],
92
+ },
93
+ ]
94
+ const { redirects } = buildRedirectsFromModelsView(rows)
95
+ expect(redirects.size).toBe(0)
96
+ })
97
+
98
+ it("every redirect target is an addressable model_route_id", () => {
99
+ const rows: ModelsViewRow[] = [
100
+ { model_route_id: "org%2Fgroup-a", model_id: "org/group-a", raw_model_ids: ["org/snap-1"] },
101
+ { model_route_id: "org%2Fgroup-b", model_id: "org/group-b", raw_model_ids: ["org/snap-2"] },
102
+ ]
103
+ const { redirects } = buildRedirectsFromModelsView(rows)
104
+ const addressable = new Set(rows.map((r) => r.model_route_id))
105
+ for (const target of redirects.values()) {
106
+ expect(addressable.has(target)).toBe(true)
107
+ }
108
+ })
109
+
110
+ it("excludes (does not arbitrarily pick) a spelling that fans out to multiple groups", () => {
111
+ const rows: ModelsViewRow[] = [
112
+ { model_route_id: "org%2Fgroup-a", model_id: "org/group-a", raw_model_ids: ["org/shared"] },
113
+ { model_route_id: "org%2Fgroup-b", model_id: "org/group-b", raw_model_ids: ["org/shared"] },
114
+ ]
115
+ const { redirects, ambiguous } = buildRedirectsFromModelsView(rows)
116
+ expect(redirects.has(routeId("org/shared"))).toBe(false)
117
+ expect(ambiguous.get(routeId("org/shared"))?.size).toBe(2)
118
+ })
119
+
120
+ it("is idempotent when the same spelling appears repeatedly", () => {
121
+ const rows: ModelsViewRow[] = [
122
+ { model_route_id: "org%2Fg", model_id: "org/g", raw_model_ids: ["org/snap", "org/snap"] },
123
+ ]
124
+ const { redirects } = buildRedirectsFromModelsView(rows)
125
+ expect(redirects.size).toBe(1)
126
+ expect(redirects.get(routeId("org/snap"))).toBe("org%2Fg")
127
+ })
128
+ })
129
+
130
+ // ---------------------------------------------------------------------------
131
+ // serializeRedirectModule — generated source shape
132
+ // ---------------------------------------------------------------------------
133
+
134
+ describe("serializeRedirectModule", () => {
135
+ it("produces sorted, parseable entries derived from the warehouse", () => {
136
+ const map = new Map([
137
+ ["b%2Fx", "b%2Fy"],
138
+ ["a%2Fx", "a%2Fy"],
139
+ ])
140
+ const src = serializeRedirectModule(map, { source: "warehouse/test" })
141
+ expect(src).toContain("Derived from the warehouse")
142
+ // sorted: a before b
143
+ expect(src.indexOf('"a%2Fx"')).toBeLessThan(src.indexOf('"b%2Fx"'))
144
+ expect(src).toContain("export const MODEL_URL_REDIRECTS")
145
+ expect(src).toContain("export function resolveModelRedirect")
146
+ })
147
+ })
148
+
149
+ // ---------------------------------------------------------------------------
150
+ // Redirect target construction — query-param preservation (CRITICAL)
151
+ // ---------------------------------------------------------------------------
152
+ //
153
+ // Replicates the middleware target-building logic against a real URL so we
154
+ // assert the 301 contract without booting the Next runtime.
155
+
156
+ function buildRedirectTarget(incomingUrl: string): { url: URL; status: number } | null {
157
+ const url = new URL(incomingUrl)
158
+ const MODELS_PREFIX = "/models/"
159
+ if (!url.pathname.startsWith(MODELS_PREFIX)) return null
160
+ const rawTail = url.pathname.slice(MODELS_PREFIX.length)
161
+ if (!rawTail) return null
162
+ let decoded: string
163
+ try {
164
+ decoded = decodeURIComponent(rawTail)
165
+ } catch {
166
+ decoded = rawTail
167
+ }
168
+ const incomingRouteId = encodeURIComponent(decoded)
169
+ const newRouteId = resolveModelRedirect(incomingRouteId)
170
+ if (!newRouteId) return null
171
+ const target = new URL(url.toString())
172
+ target.pathname = `${MODELS_PREFIX}${newRouteId}`
173
+ target.search = url.search // preserve query params
174
+ return { url: target, status: 301 }
175
+ }
176
+
177
+ describe("redirect target construction (middleware contract)", () => {
178
+ // A known folded-spelling -> group redirect from the generated map: the dated
179
+ // snapshot folds into the moving group pointer.
180
+ const OLD = "anthropic/claude-3-haiku-20240307"
181
+ const NEW = "anthropic/claude-3-haiku"
182
+
183
+ it("the generated map contains the expected folded-spelling -> group redirect", () => {
184
+ expect(resolveModelRedirect(routeId(OLD))).toBe(routeId(NEW))
185
+ })
186
+
187
+ it("redirects an old encoded URL to the new one with 301", () => {
188
+ const res = buildRedirectTarget(`https://x.test/models/${routeId(OLD)}`)
189
+ expect(res).not.toBeNull()
190
+ expect(res!.status).toBe(301)
191
+ expect(res!.url.pathname).toBe(`/models/${routeId(NEW)}`)
192
+ })
193
+
194
+ it("PRESERVES query params across the redirect", () => {
195
+ const res = buildRedirectTarget(`https://x.test/models/${routeId(OLD)}?version=20240307&foo=bar`)
196
+ expect(res).not.toBeNull()
197
+ expect(res!.url.search).toBe("?version=20240307&foo=bar")
198
+ expect(res!.url.searchParams.get("version")).toBe("20240307")
199
+ expect(res!.url.searchParams.get("foo")).toBe("bar")
200
+ })
201
+
202
+ it("preserves an empty query string (no spurious ?)", () => {
203
+ const res = buildRedirectTarget(`https://x.test/models/${routeId(OLD)}`)
204
+ expect(res!.url.search).toBe("")
205
+ })
206
+
207
+ it("handles the decoded (slash) path form too — round-trips to the same redirect", () => {
208
+ // Next.js may hand us the decoded path; the lookup re-encodes it.
209
+ const res = buildRedirectTarget(`https://x.test/models/${OLD}?version=v1`)
210
+ expect(res).not.toBeNull()
211
+ expect(res!.url.pathname).toBe(`/models/${routeId(NEW)}`)
212
+ expect(res!.url.searchParams.get("version")).toBe("v1")
213
+ })
214
+
215
+ it("returns null (no redirect) for an unknown model route", () => {
216
+ const res = buildRedirectTarget("https://x.test/models/unknown%2Fmodel-xyz")
217
+ expect(res).toBeNull()
218
+ })
219
+ })
tests/transformations/setup-alias-merging.test.ts DELETED
@@ -1,268 +0,0 @@
1
- import { describe, expect, it } from "vitest"
2
-
3
- import { getCanonicalModelIdentity } from "../../lib/model-family"
4
-
5
- // Executable spec for the setup-alias-merging transformation.
6
- //
7
- // These tests describe TS as it currently runs in production. Quirks are
8
- // preserved on purpose — the migration target is to move the computation
9
- // upstream without changing what users see, not to fix transformation
10
- // decisions. If a test below looks "wrong" to product sense, that's a
11
- // future product decision; fixing it is out of scope here.
12
- //
13
- // Pipeline-side implementation must produce identical outputs for every
14
- // row. Verify cross-corpus equivalence with `scripts/verify-setup-alias.mjs`
15
- // once pipeline ships.
16
-
17
- // ---------------------------------------------------------------------------
18
- // Group A — isSetupAliasQualifier truth table
19
- // ---------------------------------------------------------------------------
20
- //
21
- // Reproduces the function from lib/hf-data.ts (and its identical
22
- // twin in scripts/cache-hf-data.mjs). Pipeline must match exactly.
23
-
24
- function normalizeSetupAliasQualifier(value: string | null | undefined): string {
25
- return value?.trim().toLowerCase().replace(/[_\s]+/g, "-") ?? ""
26
- }
27
-
28
- function isSetupAliasQualifier(value: string | null | undefined): boolean {
29
- const normalized = normalizeSetupAliasQualifier(value)
30
- return (
31
- normalized === "prompt" ||
32
- normalized === "fc" ||
33
- normalized === "function-calling" ||
34
- normalized.startsWith("thinking")
35
- )
36
- }
37
-
38
- describe("Group A — isSetupAliasQualifier", () => {
39
- const cases = [
40
- { input: "prompt", expected: true, why: "exact: prompt" },
41
- { input: "Prompt", expected: true, why: "case-insensitive" },
42
- { input: "PROMPT", expected: true, why: "case-insensitive" },
43
- { input: "fc", expected: true, why: "exact: fc" },
44
- { input: "FC", expected: true, why: "case-insensitive" },
45
- { input: "function-calling", expected: true, why: "exact" },
46
- { input: "function calling", expected: true, why: "space → dash" },
47
- { input: "function_calling", expected: true, why: "underscore → dash" },
48
- { input: "thinking", expected: true, why: "exact thinking" },
49
- { input: "thinking-1k", expected: true, why: "starts with thinking" },
50
- { input: "thinking-medium", expected: true, why: "starts with thinking" },
51
- { input: "thinking-none", expected: true, why: "starts with thinking" },
52
- { input: "thinking_xhigh", expected: true, why: "underscore → dash, then prefix" },
53
- { input: "Thinking 1K", expected: true, why: "case + space normalized → starts with thinking" },
54
- { input: "high", expected: false, why: "non-alias inference qualifier" },
55
- { input: "medium", expected: false, why: "non-alias" },
56
- { input: "low", expected: false, why: "non-alias" },
57
- { input: "minimal", expected: false, why: "non-alias" },
58
- { input: "8k", expected: false, why: "context-length without thinking prefix" },
59
- { input: "16k", expected: false, why: "context-length without thinking prefix" },
60
- { input: "", expected: false, why: "empty" },
61
- { input: null, expected: false, why: "null" },
62
- { input: undefined, expected: false, why: "undefined" },
63
- { input: " prompt ", expected: true, why: "leading/trailing whitespace trimmed" },
64
- { input: "prompts", expected: false, why: "trailing s — not exact prompt and no thinking prefix" },
65
- { input: "fcfc", expected: false, why: "doesn't match exact fc" },
66
- ]
67
- it.each(cases)("'$input' → $expected ($why)", ({ input, expected }) => {
68
- expect(isSetupAliasQualifier(input)).toBe(expected)
69
- })
70
- })
71
-
72
- // ---------------------------------------------------------------------------
73
- // Group B — End-to-end variant normalization (TS-as-is)
74
- // ---------------------------------------------------------------------------
75
- //
76
- // Replicates lib/hf-data.ts verbatim, NO date-format fix applied.
77
- // Documents the dashed-date fall-through behaviour as the canonical spec.
78
-
79
- interface VariantInput {
80
- variant_key: string
81
- variant_label?: string
82
- }
83
-
84
- function normalizeOne(familyId: string, variant: VariantInput): { variant_key: string; variant_label: string } {
85
- if (variant.variant_key === "base") {
86
- return { variant_key: "default", variant_label: "Default" }
87
- }
88
- if (variant.variant_key === "default") {
89
- return { variant_key: "default", variant_label: variant.variant_label ?? "Default" }
90
- }
91
-
92
- const synth = getCanonicalModelIdentity({
93
- id: `${familyId}-${variant.variant_key}`,
94
- name: `${familyId}-${variant.variant_key}`,
95
- })
96
-
97
- if (synth.versionDate && isSetupAliasQualifier(synth.versionQualifier)) {
98
- return { variant_key: synth.versionDate, variant_label: synth.versionDate }
99
- }
100
- return { variant_key: synth.variantKey, variant_label: synth.variantLabel }
101
- }
102
-
103
- describe("Group B — End-to-end variant normalization", () => {
104
- const familyId = "openai/gpt-5.2"
105
- const cases = [
106
- { variant_key: "default", expected: { variant_key: "default", variant_label: "Default" }, why: "default passes through" },
107
- { variant_key: "base", expected: { variant_key: "default", variant_label: "Default" }, why: "base renamed to default" },
108
- { variant_key: "20251101", expected: { variant_key: "20251101", variant_label: "2025-11-01" }, why: "YYYYMMDD date-only — preserved as raw token, ISO label" },
109
- {
110
- variant_key: "2025-11-01",
111
- expected: { variant_key: "base", variant_label: "Current" },
112
- why: "DASHED date-only falls through to base — TS quirk, preserved as canonical for this migration",
113
- },
114
- {
115
- variant_key: "20240620-thinking",
116
- expected: { variant_key: "2024-06-20", variant_label: "2024-06-20" },
117
- why: "YYYYMMDD + thinking → merge to ISO date",
118
- },
119
- {
120
- variant_key: "20240620-thinking-1k",
121
- expected: { variant_key: "2024-06-20", variant_label: "2024-06-20" },
122
- why: "YYYYMMDD + thinking-1k → merge (startsWith match aggregates all thinking budgets)",
123
- },
124
- {
125
- variant_key: "20240620-thinking-medium",
126
- expected: { variant_key: "2024-06-20", variant_label: "2024-06-20" },
127
- why: "all thinking-N variants for this YYYYMMDD date collapse together",
128
- },
129
- { variant_key: "20240620-fc", expected: { variant_key: "2024-06-20", variant_label: "2024-06-20" }, why: "YYYYMMDD + fc → merge" },
130
- { variant_key: "20240620-prompt", expected: { variant_key: "2024-06-20", variant_label: "2024-06-20" }, why: "YYYYMMDD + prompt → merge" },
131
- {
132
- variant_key: "20240620-high",
133
- expected: { variant_key: "20240620-high", variant_label: "2024-06-20 · High" },
134
- why: "non-alias qualifier preserved with date",
135
- },
136
- {
137
- variant_key: "2025-12-11-thinking-medium",
138
- expected: { variant_key: "base", variant_label: "Current" },
139
- why: "DASHED date — regex doesn't match, falls through to base (TS quirk)",
140
- },
141
- {
142
- variant_key: "2025-12-11-thinking-1k",
143
- expected: { variant_key: "base", variant_label: "Current" },
144
- why: "DASHED date with thinking-1k — same fall-through",
145
- },
146
- {
147
- variant_key: "2025-12-11-fc",
148
- expected: { variant_key: "base", variant_label: "Current" },
149
- why: "DASHED date + fc — fall-through",
150
- },
151
- {
152
- variant_key: "2025-12-11-high",
153
- expected: { variant_key: "base", variant_label: "Current" },
154
- why: "DASHED date + non-alias qualifier — fall-through",
155
- },
156
- { variant_key: "gpt-foo-bar", expected: { variant_key: "base", variant_label: "Current" }, why: "no date detected" },
157
- ]
158
- it.each(cases)("'$variant_key' → '$expected.variant_key' ($why)", ({ variant_key, expected }) => {
159
- const result = normalizeOne(familyId, { variant_key })
160
- expect(result.variant_key).toBe(expected.variant_key)
161
- expect(result.variant_label).toBe(expected.variant_label)
162
- })
163
- })
164
-
165
- // ---------------------------------------------------------------------------
166
- // Group C — Multi-variant deduplication after normalization
167
- // ---------------------------------------------------------------------------
168
- //
169
- // Documents the user-visible aggregation effect: cards with multiple
170
- // dashed-date variants all collapse into a single "base" entry. This is
171
- // TS as-is. If the team later decides users would benefit from
172
- // disaggregation, that's a separate product call (see the spec doc).
173
-
174
- function normalizeVariants(
175
- familyId: string,
176
- variants: Array<VariantInput & { evaluation_count?: number; raw_model_ids?: string[]; last_updated?: string }>
177
- ) {
178
- const byKey = new Map<
179
- string,
180
- { variant_key: string; variant_label: string; evaluation_count: number; raw_model_ids: string[]; last_updated?: string }
181
- >()
182
- for (const v of variants) {
183
- const norm = normalizeOne(familyId, v)
184
- const existing = byKey.get(norm.variant_key)
185
- if (existing) {
186
- existing.evaluation_count += v.evaluation_count ?? 0
187
- existing.raw_model_ids = Array.from(new Set([...existing.raw_model_ids, ...(v.raw_model_ids ?? [])])).sort()
188
- if (v.last_updated && (!existing.last_updated || new Date(v.last_updated) > new Date(existing.last_updated))) {
189
- existing.last_updated = v.last_updated
190
- }
191
- } else {
192
- byKey.set(norm.variant_key, {
193
- variant_key: norm.variant_key,
194
- variant_label: norm.variant_label,
195
- evaluation_count: v.evaluation_count ?? 0,
196
- raw_model_ids: [...(v.raw_model_ids ?? [])].sort(),
197
- last_updated: v.last_updated,
198
- })
199
- }
200
- }
201
- return [...byKey.values()]
202
- }
203
-
204
- describe("Group C — Multi-variant deduplication (TS-as-is)", () => {
205
- it("openai/gpt-5.2: 7 dashed-date variants collapse into default + base", () => {
206
- const result = normalizeVariants("openai/gpt-5.2", [
207
- { variant_key: "default", evaluation_count: 1, raw_model_ids: ["openai/gpt-5.2"] },
208
- {
209
- variant_key: "2025-12-11",
210
- evaluation_count: 3,
211
- raw_model_ids: ["openai/gpt-5.2-2025-12-11", "openai/gpt-5-2-2025-12-11-fc", "openai/gpt-5-2-2025-12-11-prompt"],
212
- },
213
- { variant_key: "2025-12-11-thinking-medium", evaluation_count: 1, raw_model_ids: ["openai/gpt-5-2-2025-12-11-thinking-medium"] },
214
- { variant_key: "2025-12-11-thinking-low", evaluation_count: 1, raw_model_ids: ["openai/gpt-5-2-2025-12-11-thinking-low"] },
215
- { variant_key: "2025-12-11-thinking-high", evaluation_count: 1, raw_model_ids: ["openai/gpt-5-2-2025-12-11-thinking-high"] },
216
- { variant_key: "2025-12-11-thinking-none", evaluation_count: 1, raw_model_ids: ["openai/gpt-5-2-2025-12-11-thinking-none"] },
217
- { variant_key: "2025-12-11-thinking-xhigh", evaluation_count: 1, raw_model_ids: ["openai/gpt-5-2-2025-12-11-thinking-xhigh"] },
218
- ])
219
- // All 6 dashed-date variants collapse into a single "base" entry. This
220
- // is TS-as-is behaviour and the canonical spec for this migration.
221
- expect(result.map((v) => v.variant_key).sort()).toEqual(["base", "default"])
222
- const base = result.find((v) => v.variant_key === "base")!
223
- expect(base.evaluation_count).toBe(8)
224
- expect(base.raw_model_ids.length).toBe(8)
225
- })
226
-
227
- it("anthropic/claude-haiku-4.5: YYYYMMDD-thinking-Nk variants merge into ISO date (startsWith match fires)", () => {
228
- const result = normalizeVariants("anthropic/claude-haiku-4.5", [
229
- { variant_key: "default", evaluation_count: 1, raw_model_ids: ["anthropic/claude-haiku-4.5"] },
230
- { variant_key: "20251001", evaluation_count: 2, raw_model_ids: ["anthropic/claude-haiku-4-5-20251001", "anthropic/claude-haiku-4-5-20251001-fc"] },
231
- { variant_key: "20251001-thinking-1k", evaluation_count: 1, raw_model_ids: ["anthropic/claude-haiku-4-5-20251001-thinking-1k"] },
232
- { variant_key: "20251001-thinking-8k", evaluation_count: 1, raw_model_ids: ["anthropic/claude-haiku-4-5-20251001-thinking-8k"] },
233
- { variant_key: "20251001-thinking-16k", evaluation_count: 1, raw_model_ids: ["anthropic/claude-haiku-4-5-20251001-thinking-16k"] },
234
- { variant_key: "20251001-thinking-32k", evaluation_count: 1, raw_model_ids: ["anthropic/claude-haiku-4-5-20251001-thinking-32k"] },
235
- ])
236
- // YYYYMMDD-thinking-Nk variants merge into "2024-10-01" (ISO) via the
237
- // startsWith("thinking") match. The base "20251001" stays as YYYYMMDD
238
- // because it has no qualifier. So they DON'T merge with each other —
239
- // different normalized keys ("20251001" vs "2025-10-01"). TS quirk.
240
- const keys = result.map((v) => v.variant_key).sort()
241
- expect(keys).toContain("default")
242
- expect(keys).toContain("20251001")
243
- expect(keys).toContain("2025-10-01")
244
- expect(keys.length).toBe(3)
245
- const merged = result.find((v) => v.variant_key === "2025-10-01")!
246
- expect(merged.evaluation_count).toBe(4)
247
- expect(merged.raw_model_ids.length).toBe(4)
248
- })
249
-
250
- it("non-alias qualifiers with YYYYMMDD dates preserved as separate variants", () => {
251
- const result = normalizeVariants("openai/gpt-5", [
252
- { variant_key: "default", evaluation_count: 1, raw_model_ids: [] },
253
- { variant_key: "20250807", evaluation_count: 1, raw_model_ids: [] },
254
- { variant_key: "20250807-high", evaluation_count: 1, raw_model_ids: [] },
255
- { variant_key: "20250807-low", evaluation_count: 1, raw_model_ids: [] },
256
- { variant_key: "20250807-medium", evaluation_count: 1, raw_model_ids: [] },
257
- { variant_key: "20250807-minimal", evaluation_count: 1, raw_model_ids: [] },
258
- ])
259
- expect(result.map((v) => v.variant_key).sort()).toEqual([
260
- "20250807",
261
- "20250807-high",
262
- "20250807-low",
263
- "20250807-medium",
264
- "20250807-minimal",
265
- "default",
266
- ])
267
- })
268
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/upstream-drift.test.ts CHANGED
@@ -95,13 +95,13 @@ describe.skipIf(!shouldRun)(`Tier A drift — live cache contracts (${modelFiles
95
  expect(violations, `${violations} eval-details without category. Examples: ${examples.join(", ")}`).toBe(0)
96
  })
97
 
98
- it("every model card has model_route_id === pipelineSlugify(model_family_id)", () => {
99
  let violations = 0
100
  const examples: string[] = []
101
  const cardsPath = path.resolve(import.meta.dirname, "..", ".cache", "hf-data", "model-cards.json")
102
  const cards = JSON.parse(fs.readFileSync(cardsPath, "utf8"))
103
  for (const card of cards) {
104
- const expected = (card.model_family_id || "").replace(/\//g, "__")
105
  if (card.model_route_id !== expected) {
106
  violations += 1
107
  if (examples.length < 5) examples.push(`${card.model_route_id} (expected ${expected})`)
 
95
  expect(violations, `${violations} eval-details without category. Examples: ${examples.join(", ")}`).toBe(0)
96
  })
97
 
98
+ it("every model card has model_route_id === pipelineSlugify(model_group_id)", () => {
99
  let violations = 0
100
  const examples: string[] = []
101
  const cardsPath = path.resolve(import.meta.dirname, "..", ".cache", "hf-data", "model-cards.json")
102
  const cards = JSON.parse(fs.readFileSync(cardsPath, "utf8"))
103
  for (const card of cards) {
104
+ const expected = (card.model_group_id || "").replace(/\//g, "__")
105
  if (card.model_route_id !== expected) {
106
  violations += 1
107
  if (examples.length < 5) examples.push(`${card.model_route_id} (expected ${expected})`)
tests/view-data.test.ts CHANGED
@@ -27,7 +27,7 @@ async function writeSyntheticStageJSnapshot(snapshotDir: string) {
27
  'openai/gpt-5' AS id,
28
  'openai%2Fgpt-5' AS route_id,
29
  'openai%2Fgpt-5' AS model_route_id,
30
- 'openai/gpt-5' AS model_family_id,
31
  'GPT 5' AS model_name,
32
  'GPT 5' AS canonical_model_name,
33
  'GPT 5' AS model_family_name,
 
27
  'openai/gpt-5' AS id,
28
  'openai%2Fgpt-5' AS route_id,
29
  'openai%2Fgpt-5' AS model_route_id,
30
+ 'openai/gpt-5' AS model_group_id,
31
  'GPT 5' AS model_name,
32
  'GPT 5' AS canonical_model_name,
33
  'GPT 5' AS model_family_name,
vitest.config.ts CHANGED
@@ -15,6 +15,8 @@ export default defineConfig({
15
  // self-skips otherwise. We DON'T exclude the path here because vitest's
16
  // exclude wins over an explicit path arg, which would silently make
17
  // `pnpm test:drift` find zero tests.
18
- exclude: ["**/node_modules/**", "**/dist/**"],
 
 
19
  },
20
  })
 
15
  // self-skips otherwise. We DON'T exclude the path here because vitest's
16
  // exclude wins over an explicit path arg, which would silently make
17
  // `pnpm test:drift` find zero tests.
18
+ // tests/e2e/ is the Playwright (@playwright/test) suite — a different runner.
19
+ // Excluded so `pnpm test` (vitest) doesn't try to collect its *.spec.ts.
20
+ exclude: ["**/node_modules/**", "**/dist/**", "**/tests/e2e/**"],
21
  },
22
  })