Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions validator/src/eval_backend/api/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
JobQueueResponse,
LeaderboardEntry,
LeaderboardResponse,
ProviderBenchmarkEntry,
ProviderBenchmarkResponse,
ProviderEvaluationCreateRequest,
ProviderEvaluationCreateResponse,
ReviewControlOut,
Expand Down Expand Up @@ -855,6 +857,43 @@ def leaderboard(request: Request, limit: int = 100, include_deleted: bool = Fals
session.close()


@router.get("/api/provider-benchmarks", response_model=ProviderBenchmarkResponse)
def provider_benchmarks(request: Request, limit: int = 200) -> ProviderBenchmarkResponse:
session = get_session(request)
try:
stmt = (
select(EvaluationRun)
.where(
EvaluationRun.submission_id.is_(None),
EvaluationRun.status == "completed",
EvaluationRun.deleted_at.is_(None),
)
.order_by(EvaluationRun.created_at.desc())
.limit(max(1, min(limit, 500)))
)
runs = session.execute(stmt).scalars().all()
items: list[ProviderBenchmarkEntry] = []
for run in runs:
metrics = json.loads(run.metrics_json) if run.metrics_json else {}
route = metrics.get("provider_route") or metrics.get("pool_model") or "unknown"
repeat = metrics.get("repeat")
items.append(
ProviderBenchmarkEntry(
id=run.id,
route=str(route),
benchmark=run.benchmark,
score=run.score,
repeat=int(repeat) if isinstance(repeat, (int, float)) else None,
cost_usd=run.cost_usd,
duration_seconds=run.duration_seconds,
finished_at=run.finished_at,
)
)
return ProviderBenchmarkResponse(items=items)
finally:
session.close()


@router.get("/api/jobs", response_model=JobQueueResponse)
def list_jobs(
request: Request,
Expand Down
15 changes: 15 additions & 0 deletions validator/src/eval_backend/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,21 @@ class LeaderboardResponse(BaseModel):
items: list[LeaderboardEntry]


class ProviderBenchmarkEntry(BaseModel):
id: int
route: str
benchmark: str
score: float | None = None
repeat: int | None = None
cost_usd: float | None = None
duration_seconds: float | None = None
finished_at: datetime | None = None


class ProviderBenchmarkResponse(BaseModel):
items: list[ProviderBenchmarkEntry]


class JobQueueOut(BaseModel):
model_config = ConfigDict(from_attributes=True)

Expand Down
146 changes: 146 additions & 0 deletions web/src/components/ProviderBenchmarkChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import type { ProviderBenchmarkEntry } from '../lib/api'

// Grouped bar chart, faceted by benchmark. Hand-rolled SVG, no charting
// library. Mirrors the admin dashboard's chart 1:1 (same palette, same
// layout) so the two surfaces show the same picture -- see
// mini-router.github.io admin/src/components/ProviderBenchmarkChart.tsx.

// Validated dark-mode categorical palette (8 slots, fixed order -- never
// reassigned by rank/filter). Passes CVD + contrast checks against this
// site's dark surface (#060b14).
const ROUTE_PALETTE = [
'#3987e5', // blue
'#d95926', // orange
'#199e70', // aqua
'#c98500', // yellow
'#d55181', // magenta
'#008300', // green
'#9085e9', // violet
'#e66767', // red
]

function buildRouteColors(points: ProviderBenchmarkEntry[]): Map<string, string> {
const routes = Array.from(new Set(points.map((p) => p.route))).sort()
const map = new Map<string, string>()
routes.forEach((route, idx) => {
map.set(route, ROUTE_PALETTE[idx % ROUTE_PALETTE.length])
})
return map
}

function fmtPercent(value: number | null): string {
return value == null || Number.isNaN(value) ? '—' : `${(value * 100).toFixed(1)}%`
}

const FACET_WIDTH = 420
const FACET_HEIGHT = 220
const PLOT_TOP = 16
const PLOT_BOTTOM = 36
const BAR_GAP = 6

export default function ProviderBenchmarkChart({ points }: { points: ProviderBenchmarkEntry[] }) {
const scored = points.filter((p) => p.score != null)
if (!scored.length) {
return (
<div className="panel-soft rounded-xl border border-dashed border-white/10 px-5 py-6 text-sm text-text-dim">
No provider benchmark results yet.
</div>
)
}

const routeColors = buildRouteColors(scored)
const benchmarks = Array.from(new Set(scored.map((p) => p.benchmark))).sort()

return (
<div className="space-y-5">
<div className="flex flex-wrap items-center gap-x-4 gap-y-2">
{Array.from(routeColors.entries()).map(([route, color]) => (
<div key={route} className="flex items-center gap-1.5 text-xs text-text-dim">
<span className="h-2.5 w-2.5 rounded-full" style={{ backgroundColor: color }} />
{route}
</div>
))}
</div>
<div className="flex flex-wrap gap-4">
{benchmarks.map((benchmark) => {
const rows = scored
.filter((p) => p.benchmark === benchmark)
.sort((a, b) => (b.score ?? 0) - (a.score ?? 0))
const plotHeight = FACET_HEIGHT - PLOT_TOP - PLOT_BOTTOM
const barWidth = Math.max(28, (FACET_WIDTH - BAR_GAP * (rows.length - 1)) / rows.length - 4)

return (
<div key={benchmark} className="panel rounded-xl p-4">
<div className="mb-2 text-xs font-medium uppercase tracking-[0.18em] text-text-dim">
{benchmark}
</div>
<svg
viewBox={`0 0 ${FACET_WIDTH} ${FACET_HEIGHT}`}
width={FACET_WIDTH}
height={FACET_HEIGHT}
role="img"
aria-label={`Score by model for ${benchmark}`}
>
{[0, 0.25, 0.5, 0.75, 1].map((frac) => {
const y = PLOT_TOP + plotHeight * (1 - frac)
return (
<line
key={frac}
x1={0}
x2={FACET_WIDTH}
y1={y}
y2={y}
stroke="rgba(255,255,255,0.08)"
strokeWidth={1}
/>
)
})}
{rows.map((row, idx) => {
const score = row.score ?? 0
const barHeight = plotHeight * Math.max(0, Math.min(1, score))
const x = idx * (barWidth + BAR_GAP)
const y = PLOT_TOP + (plotHeight - barHeight)
const color = routeColors.get(row.route) ?? ROUTE_PALETTE[0]
return (
<g key={`${row.id}-${row.route}`}>
<title>
{row.route} — {benchmark}: {fmtPercent(row.score)}
</title>
<rect x={x} y={y} width={barWidth} height={Math.max(1, barHeight)} rx={4} fill={color} />
<text
x={x + barWidth / 2}
y={y - 6}
textAnchor="middle"
fontSize={11}
fill="#e8eaf0"
>
{fmtPercent(row.score)}
</text>
<text
x={x + barWidth / 2}
y={FACET_HEIGHT - PLOT_BOTTOM + 16}
textAnchor="middle"
fontSize={9}
fill="#98a2b3"
>
{row.route.length > 12 ? `${row.route.slice(0, 11)}…` : row.route}
</text>
</g>
)
})}
<line
x1={0}
x2={FACET_WIDTH}
y1={PLOT_TOP + plotHeight}
y2={PLOT_TOP + plotHeight}
stroke="rgba(255,255,255,0.16)"
strokeWidth={1}
/>
</svg>
</div>
)
})}
</div>
</div>
)
}
51 changes: 51 additions & 0 deletions web/src/components/ProviderBenchmarkSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { motion } from 'framer-motion'
import { useProviderBenchmarks } from '../hooks/useProviderBenchmarks'
import ProviderBenchmarkChart from './ProviderBenchmarkChart'

export default function ProviderBenchmarkSection() {
const { entries, error } = useProviderBenchmarks()

return (
<section className="section-band pt-0">
<div className="section-shell">
<motion.p
className="section-kicker"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
>
Provider tests
</motion.p>
<motion.h2
className="section-title mt-3 max-w-3xl"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.08 }}
>
Model benchmarks
</motion.h2>
<motion.p
className="section-copy mt-4 max-w-2xl"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.16 }}
>
Direct single-route benchmark results for each upstream model, independent of any miner
submission.
</motion.p>
<div className="divider-line mt-8 mb-8" />

{error ? (
<div className="panel-soft rounded-xl border border-rose-400/20 px-5 py-4 text-sm text-rose-200">
{error}
</div>
) : (
<ProviderBenchmarkChart points={entries} />
)}
</div>
</section>
)
}
30 changes: 30 additions & 0 deletions web/src/hooks/useProviderBenchmarks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useEffect, useState } from 'react'
import { fetchProviderBenchmarks } from '../lib/api'
import type { ProviderBenchmarkEntry } from '../lib/api'

export function useProviderBenchmarks(limit = 200) {
const [entries, setEntries] = useState<ProviderBenchmarkEntry[]>([])
const [error, setError] = useState<string | null>(null)

useEffect(() => {
let active = true

fetchProviderBenchmarks(limit)
.then((nextEntries) => {
if (!active) return
setEntries(nextEntries)
setError(null)
})
.catch((err: unknown) => {
if (!active) return
setEntries([])
setError(err instanceof Error ? err.message : 'Failed to load provider benchmarks')
})

return () => {
active = false
}
}, [limit])

return { entries, error }
}
30 changes: 30 additions & 0 deletions web/src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,36 @@ export interface BackendSubmissionOut {
trains: BackendTrainOut[]
}

export interface ProviderBenchmarkEntry {
id: number
route: string
benchmark: string
score: number | null
repeat: number | null
cost_usd: number | null
duration_seconds: number | null
finished_at: string | null
}

interface BackendProviderBenchmarkResponse {
items: ProviderBenchmarkEntry[]
}

export async function fetchProviderBenchmarks(limit = 200): Promise<ProviderBenchmarkEntry[]> {
const response = await fetch(apiUrl(`/api/provider-benchmarks?limit=${limit}`), {
headers: {
Accept: 'application/json',
},
})

if (!response.ok) {
throw new Error(`Provider benchmark request failed with status ${response.status}`)
}

const payload = (await response.json()) as BackendProviderBenchmarkResponse
return payload.items
}

export async function fetchSubmission(submissionId: string): Promise<BackendSubmissionOut> {
const response = await fetch(apiUrl(`/api/submissions/${submissionId}`), {
headers: {
Expand Down
2 changes: 2 additions & 0 deletions web/src/pages/Leaderboard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import PageHeader from '../components/PageHeader'
import LeaderboardTable from '../components/LeaderboardTable'
import ProviderBenchmarkSection from '../components/ProviderBenchmarkSection'

export default function Leaderboard() {
return (
Expand All @@ -11,6 +12,7 @@ export default function Leaderboard() {
<section className="pb-24">
<LeaderboardTable />
</section>
<ProviderBenchmarkSection />
</>
)
}
Loading