Spaces:
Running
Running
File size: 7,107 Bytes
24480a0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 | """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)
|