Spaces:
Sleeping
Sleeping
| """Admin endpoints β IP+password gated. NEVER exposed to ordinary users. | |
| The four endpoints: | |
| GET /api/admin/health β full LLM health snapshot (every model) | |
| POST /api/admin/probe β force a fresh probe of all models now | |
| GET /api/admin/chain β current brain/fast/judge chain order | |
| POST /api/admin/chain β reorder a chain (promote a model to primary) | |
| Access gates (BOTH must pass): | |
| 1. Client IP must match ADMIN_IP_ALLOWLIST env (comma-separated) | |
| 2. Request must include `X-Admin-Password` header matching ADMIN_PASSWORD env | |
| If either gate fails, returns 404 (not 401) β the endpoints DO NOT exist | |
| for unauthorized callers. Even the existence of /api/admin/* is hidden. | |
| Setup: | |
| Add to Space secrets (Settings β Variables and secrets): | |
| ADMIN_PASSWORD = your-strong-password-here | |
| ADMIN_IP_ALLOWLIST = your.public.ip.v4,your.other.ip | |
| Find your IP at https://api.ipify.org/ | |
| """ | |
| from __future__ import annotations | |
| import asyncio | |
| import json | |
| import os | |
| from pathlib import Path | |
| from typing import Optional | |
| from fastapi import APIRouter, HTTPException, Request, Header | |
| from pydantic import BaseModel | |
| from backend import llm_health | |
| router = APIRouter() | |
| def _ip_allowed(client_ip: str) -> bool: | |
| """Allowlist check. Empty/unset allowlist => deny everyone.""" | |
| allowed = os.environ.get("ADMIN_IP_ALLOWLIST", "").strip() | |
| if not allowed: | |
| return False | |
| return client_ip in {ip.strip() for ip in allowed.split(",") if ip.strip()} | |
| def _password_ok(supplied: Optional[str]) -> bool: | |
| expected = os.environ.get("ADMIN_PASSWORD", "").strip() | |
| if not expected: | |
| return False # no password configured => deny | |
| return supplied is not None and supplied == expected | |
| def _check_admin(request: Request, password: Optional[str]) -> None: | |
| """Both gates must pass. Returns 404 (not 401) on failure to hide existence.""" | |
| # Honor X-Forwarded-For for HF Space proxy headers (HF Spaces sets this) | |
| client_ip = request.headers.get("X-Forwarded-For", "").split(",")[0].strip() | |
| if not client_ip: | |
| client_ip = request.client.host if request.client else "" | |
| if not _ip_allowed(client_ip) or not _password_ok(password): | |
| raise HTTPException(status_code=404, detail="Not Found") | |
| # --- Endpoints -------------------------------------------------------------- | |
| async def admin_health( | |
| request: Request, | |
| x_admin_password: Optional[str] = Header(default=None, alias="X-Admin-Password"), | |
| ): | |
| _check_admin(request, x_admin_password) | |
| return llm_health.status_summary() | |
| async def admin_probe( | |
| request: Request, | |
| x_admin_password: Optional[str] = Header(default=None, alias="X-Admin-Password"), | |
| ): | |
| _check_admin(request, x_admin_password) | |
| state = await llm_health.probe_all() | |
| return {"probed": len(state), "summary": llm_health.status_summary()} | |
| async def admin_chain_get( | |
| request: Request, | |
| x_admin_password: Optional[str] = Header(default=None, alias="X-Admin-Password"), | |
| ): | |
| _check_admin(request, x_admin_password) | |
| from backend.providers.nvidia_nim_llm import ( | |
| BRAIN_CHAIN, FAST_BRAIN_CHAIN, JUDGE_CHAIN, | |
| ) | |
| return { | |
| "brain": BRAIN_CHAIN, | |
| "fast_brain": FAST_BRAIN_CHAIN, | |
| "judge": JUDGE_CHAIN, | |
| } | |
| class ChainReorderRequest(BaseModel): | |
| role: str # 'brain' | 'fast_brain' | 'judge' | |
| order: list[str] # new ordering of model ids | |
| async def admin_chain_set( | |
| body: ChainReorderRequest, | |
| request: Request, | |
| x_admin_password: Optional[str] = Header(default=None, alias="X-Admin-Password"), | |
| ): | |
| _check_admin(request, x_admin_password) | |
| if body.role not in ("brain", "fast_brain", "judge"): | |
| raise HTTPException(status_code=400, detail="role must be brain | fast_brain | judge") | |
| # Mutate the in-memory chain | |
| from backend.providers import nvidia_nim_llm as nim | |
| name = {"brain": "BRAIN_CHAIN", "fast_brain": "FAST_BRAIN_CHAIN", "judge": "JUDGE_CHAIN"}[body.role] | |
| setattr(nim, name, list(body.order)) | |
| # Persist for next process restart β write to data/admin_overrides.json | |
| override_path = Path(__file__).resolve().parent.parent / "data" / "admin_overrides.json" | |
| override_path.parent.mkdir(parents=True, exist_ok=True) | |
| state = {} | |
| if override_path.exists(): | |
| try: | |
| state = json.loads(override_path.read_text()) | |
| except Exception: | |
| state = {} | |
| state[body.role] = body.order | |
| override_path.write_text(json.dumps(state, indent=2) + "\n") | |
| return {"ok": True, "role": body.role, "new_chain": body.order} | |