feat: add detailed logging to all API routes and global access log middleware
Each API module now logs: - evaluations: trigger (scenario path, task_id), status polls, list - runs: list (count), detail (run_id, metrics, sample counts) - scenarios: list (total, valid, error counts) - pipeline: submit (docs_path, models, max_docs), status polls, list - llm_profiles: CRUD ops (name, model, id), probe/test (model, ok, latency), apply (patched fields) - score: already had per-request logging Global middleware (webapp.access logger): - Every API request: METHOD path -> status (latency_ms) at INFO - Static file requests demoted to DEBUG to reduce noise Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import time
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
@@ -19,6 +20,7 @@ from webapp.services.profile_manager import profile_manager
|
||||
from webapp.services.yaml_patcher import apply_profiles_to_scenario
|
||||
|
||||
router = APIRouter(prefix="/api/llm-profiles", tags=["llm-profiles"])
|
||||
logger = logging.getLogger("webapp.api.llm_profiles")
|
||||
|
||||
|
||||
def _do_connectivity_test(
|
||||
@@ -50,35 +52,44 @@ def _do_connectivity_test(
|
||||
@router.post("/probe", response_model=ProfileTestResponse, tags=["llm-profiles"])
|
||||
def probe_connectivity(request: ProfileProbeRequest) -> ProfileTestResponse:
|
||||
"""Test LLM connectivity with inline credentials (no saved profile required)."""
|
||||
return _do_connectivity_test(
|
||||
logger.info("[probe] model=%s base_url=%s", request.model, request.base_url)
|
||||
result = _do_connectivity_test(
|
||||
model=request.model,
|
||||
base_url=request.base_url,
|
||||
api_key=request.api_key,
|
||||
timeout_seconds=request.timeout_seconds,
|
||||
)
|
||||
logger.info("[probe] ok=%s latency=%sms msg=%s", result.ok, result.latency_ms, result.message)
|
||||
return result
|
||||
|
||||
|
||||
@router.get("", response_model=dict)
|
||||
def list_profiles() -> dict:
|
||||
"""Return all saved LLM profiles."""
|
||||
return {"profiles": [p.model_dump() for p in profile_manager.list_all()]}
|
||||
profiles = profile_manager.list_all()
|
||||
logger.info("[list_profiles] count=%d", len(profiles))
|
||||
return {"profiles": [p.model_dump() for p in profiles]}
|
||||
|
||||
|
||||
@router.post("", status_code=201, response_model=LLMProfile)
|
||||
def create_profile(request: CreateProfileRequest) -> LLMProfile:
|
||||
"""Create a new LLM profile."""
|
||||
return profile_manager.create(
|
||||
logger.info("[create_profile] name=%r model=%s base_url=%s", request.name, request.model, request.base_url)
|
||||
profile = profile_manager.create(
|
||||
name=request.name,
|
||||
model=request.model,
|
||||
base_url=request.base_url,
|
||||
api_key=request.api_key,
|
||||
timeout_seconds=request.timeout_seconds,
|
||||
)
|
||||
logger.info("[create_profile] created id=%s", profile.profile_id)
|
||||
return profile
|
||||
|
||||
|
||||
@router.put("/{profile_id}", response_model=LLMProfile)
|
||||
def update_profile(profile_id: str, request: CreateProfileRequest) -> LLMProfile:
|
||||
"""Update an existing LLM profile by id."""
|
||||
logger.info("[update_profile] id=%s name=%r model=%s", profile_id, request.name, request.model)
|
||||
updated = profile_manager.update(
|
||||
profile_id=profile_id,
|
||||
name=request.name,
|
||||
@@ -88,16 +99,21 @@ def update_profile(profile_id: str, request: CreateProfileRequest) -> LLMProfile
|
||||
timeout_seconds=request.timeout_seconds,
|
||||
)
|
||||
if updated is None:
|
||||
logger.warning("[update_profile] not found id=%s", profile_id)
|
||||
raise HTTPException(status_code=404, detail=f"Profile not found: {profile_id}")
|
||||
logger.info("[update_profile] updated id=%s", profile_id)
|
||||
return updated
|
||||
|
||||
|
||||
@router.delete("/{profile_id}", response_model=dict)
|
||||
def delete_profile(profile_id: str) -> dict:
|
||||
"""Delete an LLM profile by id."""
|
||||
logger.info("[delete_profile] id=%s", profile_id)
|
||||
deleted = profile_manager.delete(profile_id)
|
||||
if not deleted:
|
||||
logger.warning("[delete_profile] not found id=%s", profile_id)
|
||||
raise HTTPException(status_code=404, detail=f"Profile not found: {profile_id}")
|
||||
logger.info("[delete_profile] deleted id=%s", profile_id)
|
||||
return {"deleted": True}
|
||||
|
||||
|
||||
@@ -106,18 +122,31 @@ def test_profile(profile_id: str) -> ProfileTestResponse:
|
||||
"""Test LLM connectivity for a saved profile."""
|
||||
profile = profile_manager.get(profile_id)
|
||||
if profile is None:
|
||||
logger.warning("[test_profile] not found id=%s", profile_id)
|
||||
raise HTTPException(status_code=404, detail=f"Profile not found: {profile_id}")
|
||||
return _do_connectivity_test(
|
||||
logger.info("[test_profile] id=%s model=%s base_url=%s", profile_id, profile.model, profile.base_url)
|
||||
result = _do_connectivity_test(
|
||||
model=profile.model,
|
||||
base_url=profile.base_url,
|
||||
api_key=profile.api_key,
|
||||
timeout_seconds=profile.timeout_seconds,
|
||||
)
|
||||
logger.info("[test_profile] ok=%s latency=%sms", result.ok, result.latency_ms)
|
||||
return result
|
||||
|
||||
|
||||
@router.post("/apply", response_model=ProfileApplyResponse)
|
||||
def apply_profiles(request: ProfileApplyRequest) -> ProfileApplyResponse:
|
||||
"""Patch selected LLM profiles into the target scenario YAML file."""
|
||||
logger.info(
|
||||
"[apply_profiles] scenario=%s judge=%s answer=%s dataset=%s metric_weights=%s doc_weights=%s",
|
||||
request.scenario_path,
|
||||
request.judge_profile_id,
|
||||
request.answer_profile_id,
|
||||
request.dataset_profile_id,
|
||||
bool(request.metric_weights),
|
||||
bool(request.doc_weights),
|
||||
)
|
||||
role_profiles: dict[str, LLMProfile | None] = {
|
||||
"judge": profile_manager.get(request.judge_profile_id) if request.judge_profile_id else None,
|
||||
"answer": profile_manager.get(request.answer_profile_id) if request.answer_profile_id else None,
|
||||
@@ -135,6 +164,7 @@ def apply_profiles(request: ProfileApplyRequest) -> ProfileApplyResponse:
|
||||
]
|
||||
|
||||
if missing:
|
||||
logger.warning("[apply_profiles] missing profiles for roles: %s", missing)
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Profile(s) not found for roles: {', '.join(missing)}",
|
||||
@@ -148,6 +178,7 @@ def apply_profiles(request: ProfileApplyRequest) -> ProfileApplyResponse:
|
||||
metric_weights=request.metric_weights,
|
||||
doc_weights=request.doc_weights,
|
||||
)
|
||||
logger.info("[apply_profiles] patched fields: %s", patched)
|
||||
return ProfileApplyResponse(
|
||||
scenario_path=request.scenario_path,
|
||||
patched_fields=patched,
|
||||
|
||||
Reference in New Issue
Block a user