mekosotto Claude Opus 4.7 (1M context) commited on
Commit
dc31dba
·
1 Parent(s): e033c11

chore(diag): add /diag/openrouter endpoint + sidebar button

Browse files

One-shot in-container probe to diagnose why /explain/* falls back
to template on HF Space. Shows key presence (length + 12-char prefix
only — never full secret), kill-switch state, and an 8-token probe
against the chain's first model. Surfaces 401/429/CONN errors as
JSON so deployment issues can be diagnosed without container shell
or HF logs API access.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Files changed (2) hide show
  1. src/api/main.py +70 -0
  2. src/frontend/app.py +7 -0
src/api/main.py CHANGED
@@ -30,3 +30,73 @@ app.include_router(experiments_router)
30
  def health() -> HealthResponse:
31
  """Liveness probe — used by docker-compose health checks and Streamlit."""
32
  return HealthResponse(status="ok", pipelines=["bbb", "eeg", "mri"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  def health() -> HealthResponse:
31
  """Liveness probe — used by docker-compose health checks and Streamlit."""
32
  return HealthResponse(status="ok", pipelines=["bbb", "eeg", "mri"])
33
+
34
+
35
+ @app.get("/diag/openrouter")
36
+ def diag_openrouter() -> dict:
37
+ """One-shot OpenRouter reachability probe — diagnostic only.
38
+
39
+ Reports whether the explainer can reach OpenRouter from this container.
40
+ Returns key presence (length + first 12 chars only — never the full
41
+ secret), kill-switch state, the first model in the chain, and the
42
+ HTTP/error status of an 8-token probe call against that model. Used
43
+ to diagnose why /explain/* falls back to template in production.
44
+ """
45
+ import os as _os
46
+ from src.llm import explainer as _ex
47
+
48
+ key = _os.environ.get("OPENROUTER_API_KEY") or ""
49
+ chain = _ex._free_model_chain()
50
+ first_model = chain[0] if chain else None
51
+
52
+ out: dict = {
53
+ "has_key": bool(key),
54
+ "key_len": len(key),
55
+ "key_prefix": key[:12] if key else None,
56
+ "kill_switch_on": _os.environ.get("NEUROBRIDGE_DISABLE_LLM") == "1",
57
+ "should_use_llm": _ex._should_use_llm(),
58
+ "chain_len": len(chain),
59
+ "first_model": first_model,
60
+ "probe": None,
61
+ }
62
+
63
+ if not key or not first_model:
64
+ out["probe"] = "skipped (no key or empty chain)"
65
+ return out
66
+
67
+ try:
68
+ from openai import (
69
+ OpenAI,
70
+ APIStatusError,
71
+ APIConnectionError,
72
+ APITimeoutError,
73
+ RateLimitError,
74
+ )
75
+ except ImportError as e:
76
+ out["probe"] = f"openai SDK not importable: {e}"
77
+ return out
78
+
79
+ try:
80
+ client = OpenAI(
81
+ base_url="https://openrouter.ai/api/v1",
82
+ api_key=key,
83
+ timeout=8.0,
84
+ )
85
+ c = client.chat.completions.create(
86
+ model=first_model,
87
+ messages=[{"role": "user", "content": "Reply with the single word OK."}],
88
+ max_tokens=8,
89
+ temperature=0,
90
+ )
91
+ text = (c.choices[0].message.content or "").strip()
92
+ out["probe"] = {"status": "OK", "preview": text[:60]}
93
+ except RateLimitError:
94
+ out["probe"] = {"status": "429", "note": "rate-limited"}
95
+ except APIStatusError as e:
96
+ out["probe"] = {"status": str(getattr(e, "status_code", "?")), "message": str(e)[:200]}
97
+ except (APIConnectionError, APITimeoutError) as e:
98
+ out["probe"] = {"status": "CONN", "exception": type(e).__name__}
99
+ except Exception as e:
100
+ out["probe"] = {"status": "ERR", "exception": type(e).__name__, "message": str(e)[:200]}
101
+
102
+ return out
src/frontend/app.py CHANGED
@@ -1074,6 +1074,13 @@ def _render_sidebar(api_ok: bool, api_status: str) -> None:
1074
  unsafe_allow_html=True,
1075
  )
1076
 
 
 
 
 
 
 
 
1077
  st.markdown("### About")
1078
  st.markdown(
1079
  "<p style='font-size:0.86rem;color:var(--ng-text-secondary);"
 
1074
  unsafe_allow_html=True,
1075
  )
1076
 
1077
+ if st.button("🔧 Diagnose LLM", key="diag_llm_btn", help="Probe OpenRouter from this container"):
1078
+ try:
1079
+ diag = httpx.get(f"{_API_URL}/diag/openrouter", timeout=15.0).json()
1080
+ st.json(diag)
1081
+ except Exception as e:
1082
+ st.error(f"diag failed: {e!r}")
1083
+
1084
  st.markdown("### About")
1085
  st.markdown(
1086
  "<p style='font-size:0.86rem;color:var(--ng-text-secondary);"