Claude Claude Opus 4.8 commited on
Surface LLM provider/model/last-error on /health for diagnostics
Browse filesCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WN4QRr6dTE2W7hQ2SDnLmY
- backend/app/generate.py +17 -0
- backend/app/main.py +2 -2
backend/app/generate.py
CHANGED
|
@@ -53,11 +53,25 @@ _SYSTEM = (
|
|
| 53 |
)
|
| 54 |
|
| 55 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
def llm_available() -> bool:
|
| 57 |
"""True when an LLM API key is configured."""
|
| 58 |
return bool(os.getenv("LLM_API_KEY"))
|
| 59 |
|
| 60 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
def generate_answer(question: str, passages: list[str]) -> str | None:
|
| 62 |
"""Return a grounded answer from the passages, or None to fall back.
|
| 63 |
|
|
@@ -87,8 +101,11 @@ def generate_answer(question: str, passages: list[str]) -> str | None:
|
|
| 87 |
],
|
| 88 |
)
|
| 89 |
except Exception as exc: # never let generation break /ask
|
|
|
|
|
|
|
| 90 |
logger.warning("LLM generation failed, using extractive answer: %s", exc)
|
| 91 |
return None
|
| 92 |
|
|
|
|
| 93 |
text = (response.choices[0].message.content or "").strip()
|
| 94 |
return text or None
|
|
|
|
| 53 |
)
|
| 54 |
|
| 55 |
|
| 56 |
+
# Last generation error (for diagnostics via /health). Trimmed, no secrets.
|
| 57 |
+
last_error: str | None = None
|
| 58 |
+
|
| 59 |
+
|
| 60 |
def llm_available() -> bool:
|
| 61 |
"""True when an LLM API key is configured."""
|
| 62 |
return bool(os.getenv("LLM_API_KEY"))
|
| 63 |
|
| 64 |
|
| 65 |
+
def status() -> dict[str, object]:
|
| 66 |
+
"""Diagnostic snapshot surfaced on /health."""
|
| 67 |
+
return {
|
| 68 |
+
"llm_enabled": llm_available(),
|
| 69 |
+
"llm_provider": _provider,
|
| 70 |
+
"llm_model": _MODEL,
|
| 71 |
+
"llm_error": last_error,
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
|
| 75 |
def generate_answer(question: str, passages: list[str]) -> str | None:
|
| 76 |
"""Return a grounded answer from the passages, or None to fall back.
|
| 77 |
|
|
|
|
| 101 |
],
|
| 102 |
)
|
| 103 |
except Exception as exc: # never let generation break /ask
|
| 104 |
+
global last_error
|
| 105 |
+
last_error = f"{type(exc).__name__}: {exc}"[:400]
|
| 106 |
logger.warning("LLM generation failed, using extractive answer: %s", exc)
|
| 107 |
return None
|
| 108 |
|
| 109 |
+
last_error = None
|
| 110 |
text = (response.choices[0].message.content or "").strip()
|
| 111 |
return text or None
|
backend/app/main.py
CHANGED
|
@@ -15,7 +15,7 @@ from fastapi import FastAPI, File, Form, HTTPException, UploadFile
|
|
| 15 |
from fastapi.middleware.cors import CORSMiddleware
|
| 16 |
from pydantic import BaseModel
|
| 17 |
|
| 18 |
-
from . import __version__, db
|
| 19 |
from .generate import generate_answer, llm_available
|
| 20 |
from .parsing import DocumentError, chunk_text, extract_pdf_text
|
| 21 |
from .retrieval import DocumentIndex, best_sentence
|
|
@@ -59,7 +59,7 @@ app.add_middleware(
|
|
| 59 |
def health() -> dict[str, object]:
|
| 60 |
"""Liveness probe. Also reports the running version and whether LLM answer
|
| 61 |
generation is configured, which makes deploys easy to verify."""
|
| 62 |
-
return {"status": "ok", "version": __version__,
|
| 63 |
|
| 64 |
|
| 65 |
def _looks_like_pdf(file: UploadFile) -> bool:
|
|
|
|
| 15 |
from fastapi.middleware.cors import CORSMiddleware
|
| 16 |
from pydantic import BaseModel
|
| 17 |
|
| 18 |
+
from . import __version__, db, generate
|
| 19 |
from .generate import generate_answer, llm_available
|
| 20 |
from .parsing import DocumentError, chunk_text, extract_pdf_text
|
| 21 |
from .retrieval import DocumentIndex, best_sentence
|
|
|
|
| 59 |
def health() -> dict[str, object]:
|
| 60 |
"""Liveness probe. Also reports the running version and whether LLM answer
|
| 61 |
generation is configured, which makes deploys easy to verify."""
|
| 62 |
+
return {"status": "ok", "version": __version__, **generate.status()}
|
| 63 |
|
| 64 |
|
| 65 |
def _looks_like_pdf(file: UploadFile) -> bool:
|