"""NeuroBridge FastAPI entrypoint. Exposes /health for liveness. Pipeline routes are mounted in Task 8. """ from __future__ import annotations from fastapi import FastAPI from src.api.routes import ( router as pipeline_router, predict_router, explain_router, experiments_router, ) from src.api.schemas import HealthResponse app = FastAPI( title="NeuroBridge Enterprise", description="Three-modality clinical-ML pipeline surface (BBB / EEG / MRI).", version="0.4.0", ) app.include_router(pipeline_router) app.include_router(predict_router) app.include_router(explain_router) app.include_router(experiments_router) @app.get("/health", response_model=HealthResponse) def health() -> HealthResponse: """Liveness probe — used by docker-compose health checks and Streamlit.""" return HealthResponse(status="ok", pipelines=["bbb", "eeg", "mri"]) @app.get("/diag/openrouter") def diag_openrouter() -> dict: """One-shot OpenRouter reachability probe — diagnostic only. Reports whether the explainer can reach OpenRouter from this container. Returns key presence (length + first 12 chars only — never the full secret), kill-switch state, the first model in the chain, and the HTTP/error status of an 8-token probe call against that model. Used to diagnose why /explain/* falls back to template in production. """ import os as _os from src.llm import explainer as _ex key = _os.environ.get("OPENROUTER_API_KEY") or "" chain = _ex._free_model_chain() first_model = chain[0] if chain else None out: dict = { "has_key": bool(key), "key_len": len(key), "key_prefix": key[:12] if key else None, "kill_switch_on": _os.environ.get("NEUROBRIDGE_DISABLE_LLM") == "1", "should_use_llm": _ex._should_use_llm(), "chain_len": len(chain), "first_model": first_model, "probe": None, } if not key or not first_model: out["probe"] = "skipped (no key or empty chain)" return out try: from openai import ( OpenAI, APIStatusError, APIConnectionError, APITimeoutError, RateLimitError, ) except ImportError as e: out["probe"] = f"openai SDK not importable: {e}" return out try: client = OpenAI( base_url="https://openrouter.ai/api/v1", api_key=key, timeout=8.0, ) c = client.chat.completions.create( model=first_model, messages=[{"role": "user", "content": "Reply with the single word OK."}], max_tokens=8, temperature=0, ) text = (c.choices[0].message.content or "").strip() out["probe"] = {"status": "OK", "preview": text[:60]} except RateLimitError: out["probe"] = {"status": "429", "note": "rate-limited"} except APIStatusError as e: out["probe"] = {"status": str(getattr(e, "status_code", "?")), "message": str(e)[:200]} except (APIConnectionError, APITimeoutError) as e: out["probe"] = {"status": "CONN", "exception": type(e).__name__} except Exception as e: out["probe"] = {"status": "ERR", "exception": type(e).__name__, "message": str(e)[:200]} return out