ai-memory-backend / api /dashboard.py
Baida07's picture
sync: 171 files from Baida98/AI [deploy-all]
24480a0 verified
Raw
History Blame Contribute Delete
7.11 kB
"""backend/api/dashboard.py β€” Unified observability snapshot (INT-2).
GET /api/dashboard/snapshot
Aggrega in una sola chiamata HTTP:
β€’ provider health + score (da heartbeat + benchmark)
β€’ telemetry timing p50/p90
β€’ _BENCH_LAST_RUN (ultimo quality benchmark)
β€’ incident count + ultimi 5
β€’ LLM router stats (routing table + provider ok/fail)
β€’ plugin registry (total/loaded/list)
Auth: nessuna (zero PII, solo aggregate). I valori sensibili rimangono mascherati.
"""
from __future__ import annotations
import time
import logging
from fastapi import APIRouter
from fastapi.responses import JSONResponse
router = APIRouter()
_logger = logging.getLogger("api.dashboard")
@router.get("/api/dashboard/snapshot")
async def dashboard_snapshot() -> JSONResponse:
"""Snapshot unificato β€” un endpoint, tutte le metriche operative."""
t0 = time.monotonic()
out: dict = {
"ok": True,
"generated_at": int(time.time()),
"version": "1.0",
}
# ── 1. Provider health + scores ──────────────────────────────────────────
try:
from api.state import _ai_health_cache, _PROVIDER_SCORES, _BENCH_LAST_RUN
cached_data = _ai_health_cache.get("data") or {}
raw_providers = cached_data.get("providers", [])
out["providers"] = {
r.get("name", r.get("provider", "?")): {
"ok": r.get("ok", False),
"status": r.get("status", "unknown"),
"latency_ms": r.get("latency_ms"),
"model": r.get("model"),
"error": r.get("error") if not r.get("ok") else None,
"score": _PROVIDER_SCORES.get(
r.get("name", r.get("provider", "")), None
),
}
for r in raw_providers
}
# Sintesi: quanti ok, best provider per score
scores_live = {k: v for k, v in _PROVIDER_SCORES.items() if v > 0}
best = max(scores_live, key=scores_live.get) if scores_live else None
out["provider_summary"] = {
"total": len(raw_providers),
"ok": sum(1 for r in raw_providers if r.get("ok")),
"best_provider": best,
"best_score": scores_live.get(best) if best else None,
"checked_at_ago_s": round(time.monotonic() - _ai_health_cache.get("at", 0)),
}
out["bench_last_run"] = {
"total_score": _BENCH_LAST_RUN.get("total_score"),
"categories_run": _BENCH_LAST_RUN.get("categories_run", 0),
"age_s": round(time.time() - _BENCH_LAST_RUN.get("timestamp", 0))
if _BENCH_LAST_RUN.get("timestamp") else None,
}
except Exception as exc:
out["providers"] = {"_error": str(exc)}
# ── 2. Telemetry (timing + repair) ──────────────────────────────────────
try:
from api.state import _TIMING_STORE, _REPAIR_STATS
from api.telemetry import _percentile
timing: dict = {}
for key, buf in _TIMING_STORE.items():
samples = list(buf)
if samples:
timing[key] = {
"p50": _percentile(samples, 50),
"p90": _percentile(samples, 90),
"n": len(samples),
}
# Repair summary: solo contatori non-zero
repair_nonzero = {k: v for k, v in _REPAIR_STATS.items() if v > 0}
out["telemetry"] = {
"timing": timing,
"repair_summary": repair_nonzero,
"repair_total": sum(_REPAIR_STATS.values()),
}
except Exception as exc:
out["telemetry"] = {"_error": str(exc)}
# ── 3. Incidents ─────────────────────────────────────────────────────────
try:
from api.incident_registry import _incidents # type: ignore[attr-defined]
incidents_list = list(_incidents.values()) if isinstance(_incidents, dict) else []
recent = sorted(
incidents_list,
key=lambda i: i.get("created_at", 0),
reverse=True,
)[:5]
out["incidents"] = {
"total": len(incidents_list),
"recent_5": [
{
"id": i.get("id", "?"),
"provider": i.get("provider"),
"severity": i.get("severity"),
"message": str(i.get("message", ""))[:120],
"at": i.get("created_at"),
}
for i in recent
],
}
except Exception as exc:
out["incidents"] = {"_error": str(exc), "total": 0}
# ── 4. LLM Router stats ──────────────────────────────────────────────────
try:
# LLMProviderRouter Γ¨ un singleton lazy β€” usa la classe direttamente
from api.llm_router import LLMProviderRouter
rtr = LLMProviderRouter()
out["llm_router"] = rtr.status()
except Exception as exc:
out["llm_router"] = {"_error": str(exc)}
# ── 5. Plugin registry ────────────────────────────────────────────────────
try:
from api.plugin_system import registry as _plugin_reg
# list_plugins() ritorna lista di dict con stato
plugins_raw = [
{
"id": pid,
"version": m.version,
"state": _plugin_reg._states.get(pid, "unknown"),
"caps": m.capabilities,
}
for pid, m in _plugin_reg._plugins.items()
]
out["plugins"] = {
"total": len(plugins_raw),
"loaded": sum(1 for p in plugins_raw if p["state"] == "loaded"),
"list": plugins_raw,
}
except Exception as exc:
out["plugins"] = {"_error": str(exc), "total": 0}
# ── 6. System health (da HealthManager) ─────────────────────────────────
try:
from api.health_manager import HealthManager
hm = HealthManager()
report = await hm.get_report()
out["system_health"] = {
"status": report.system_health.value if report else "unknown",
"active_alerts": report.active_alerts if report else [],
"recovery_actions": report.recovery_actions if report else [],
}
except Exception as exc:
out["system_health"] = {"_error": str(exc)}
out["elapsed_ms"] = round((time.monotonic() - t0) * 1000, 1)
return JSONResponse(out)