Spaces:
Running
Running
| """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") | |
| 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) | |