Concurrently probes Claude / GPT / Gemini every PROBE_INTERVAL seconds
(asyncio.gather), scores their health, and serves a snapshot at GET /health
whose JSON matches frontend/src/lib/types.ts exactly.
At startup each provider's list-models endpoint is queried and two models are picked by rule — no hard-coded versions that 404 when a model is retired:
- flagship — Anthropic: latest
opus· OpenAI: highest-versiongpt-*(excludes mini/nano/audio/realtime/pro —gpt-*-prois responses-only) · Gemini: latestgemini-*pro - mid — Anthropic: latest
sonnet· OpenAI: that family's-mini· Gemini: latestflashexcludinglite
"Latest" compares version/date suffixes in the model name. Discovery failure (or
missing key) falls back per-tier to the *_MODEL / *_MODEL_MID env vars.
Each provider is then probed at both tiers; the dashboard shows a card per tier.
- Latency / availability — one minimal QA request, round-trip timed, HTTP status recorded.
- QA probe — asks
"What is 2+2? Answer with just the number."; passes if the reply contains"4". - Token rate — output tokens ÷ latency (rough tokens/sec).
- Health score — coarse weighting: start 100, −35 on QA fail, latency penalty above a 1s budget (capped 40).
≥85 operational · ≥50 degraded · else down.
A provider with no API key is reported as unknown (never crashes the loop).
cd backend
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
.venv/bin/playwright install chromium # Gemini official status (headless browser)
cp .env.example .env # fill in the keys you have
.venv/bin/uvicorn app.main:app --reload --port 8000
curl localhost:8000/healthSet BACKEND_URL=http://localhost:8000 for the Next.js app — its /api/health
route proxies this engine (and falls back to mock data if unreachable).
app/config.py— env config (keys, models, interval, CORS)app/probes.py— per-provider adapters, scoring, rolling history,probe_allapp/models.py— Pydantic models mirroring the frontend typesapp/main.py— FastAPI app, background probe loop,GET /healthapp/gemini_status_browser.py— optional isolated adapter: headless Chromium intercepts AI StudioListIncidentsHistoryfor Gemini official status. Disable withGEMINI_STATUS_BROWSER=0; delete the file + one import to remove.
PYTHONPATH=. .venv/bin/python tests/test_discovery.py — 8 offline tests
(selection rules, version comparison, live-then-fallback, list parsing).
- Discovery + probing verified live against all three providers: it selected
claude-opus-4-8/claude-sonnet-4-6,gpt-5/gpt-5-mini,gemini-*-pro/gemini-*-flash— real current models, probedoperational. - Provider quirks handled (found via live probes): OpenAI needs
max_completion_tokens(notmax_tokens) andgpt-*-prois responses-only; Gemini 3.x reasoning needs a larger output budget or returns empty content. - Missing key →
unknown; non-200 / overloaded (e.g. Gemini503) →down.
Alerts are minimal rule-based stubs — the full "Agent" chain (history-based
recovery ETA, LLM insight, Sentry event) and Supabase persistence come next.
OpenAI/Gemini list-models pagination is single-page (pageSize=1000).