feat: add detailed request logging to /api/score and global 422 handler
- Log incoming request (client, content-type, metrics, has_gt) on each /api/score call - Log scoring result (latency, skipped metrics, scores) on success - Register global RequestValidationError handler: logs url/content-type/errors so 422 causes are visible in server log without checking HTTP response body - Fix jsonable_encoder for exc.errors() to handle non-serializable ctx objects Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -2,10 +2,13 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import time
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Header, HTTPException
|
||||
from fastapi import APIRouter, Header, HTTPException, Request
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from rag_eval.metrics.weights import compute_weighted_score
|
||||
from rag_eval.settings import EvaluationSettings
|
||||
@@ -13,6 +16,7 @@ from webapp.models import ScoreRequest, ScoreResponse
|
||||
from webapp.services.inline_scorer import inline_scorer
|
||||
|
||||
router = APIRouter(prefix="/api/score", tags=["score"])
|
||||
logger = logging.getLogger("webapp.api.score")
|
||||
|
||||
|
||||
def _get_settings() -> EvaluationSettings:
|
||||
@@ -58,6 +62,7 @@ def _check_auth(authorization: str | None, token: str) -> None:
|
||||
},
|
||||
)
|
||||
def score_sample(
|
||||
raw_request: Request,
|
||||
request: ScoreRequest,
|
||||
authorization: Annotated[str | None, Header()] = None,
|
||||
) -> ScoreResponse:
|
||||
@@ -88,6 +93,15 @@ def score_sample(
|
||||
**鉴权**:若 `.env` 中配置了 `SCORE_API_TOKEN`,需在请求头携带
|
||||
`Authorization: Bearer <token>`;留空则无需鉴权(适合内网部署)。
|
||||
"""
|
||||
client = f"{raw_request.client.host}:{raw_request.client.port}" if raw_request.client else "unknown"
|
||||
logger.info(
|
||||
"[score] incoming client=%s method=%s content_type=%s metrics=%s has_gt=%s",
|
||||
client,
|
||||
raw_request.method,
|
||||
raw_request.headers.get("content-type", ""),
|
||||
request.metrics,
|
||||
request.ground_truth is not None,
|
||||
)
|
||||
settings = _get_settings()
|
||||
|
||||
# Require Bearer auth only when the deployment configured a shared token.
|
||||
@@ -141,6 +155,12 @@ def score_sample(
|
||||
{},
|
||||
)
|
||||
|
||||
logger.info(
|
||||
"[score] done latency=%dms skipped=%s scores=%s",
|
||||
latency_ms,
|
||||
skipped,
|
||||
{k: (round(v, 4) if v is not None else None) for k, v in all_scores.items()},
|
||||
)
|
||||
return ScoreResponse(
|
||||
scores=all_scores,
|
||||
weighted_score=round(weighted, 4) if weighted is not None else None,
|
||||
|
||||
@@ -7,15 +7,19 @@ the server starts even when the evaluation dependencies are not yet installed.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.responses import FileResponse
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.responses import FileResponse, JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from webapp.api import evaluations, llm_profiles, pipeline, runs, scenarios, score
|
||||
|
||||
STATIC_DIR = Path(__file__).resolve().parent / "static"
|
||||
logger = logging.getLogger("webapp.server")
|
||||
|
||||
# OpenAPI tag metadata — controls the grouping and descriptions in /docs.
|
||||
OPENAPI_TAGS = [
|
||||
@@ -103,6 +107,21 @@ def create_app() -> FastAPI:
|
||||
app.include_router(pipeline.router)
|
||||
app.include_router(score.router)
|
||||
|
||||
@app.exception_handler(RequestValidationError)
|
||||
async def validation_exception_handler(request: Request, exc: RequestValidationError) -> JSONResponse:
|
||||
"""Log full validation error detail to help diagnose 422 responses."""
|
||||
errors = jsonable_encoder(exc.errors())
|
||||
logger.warning(
|
||||
"[422] validation error url=%s content_type=%s errors=%s",
|
||||
request.url.path,
|
||||
request.headers.get("content-type", ""),
|
||||
errors,
|
||||
)
|
||||
return JSONResponse(
|
||||
status_code=422,
|
||||
content={"detail": errors},
|
||||
)
|
||||
|
||||
@app.get("/api/health", tags=["meta"])
|
||||
def health() -> dict[str, str]:
|
||||
"""Report basic liveness so the UI can confirm the server is reachable."""
|
||||
|
||||
Reference in New Issue
Block a user