From 6359a2b2a5e856dd126eeb034b0a40d0eee7d052 Mon Sep 17 00:00:00 2001 From: VAI Date: Sat, 27 Jun 2026 09:51:18 +0000 Subject: [PATCH] style(core): restore high-density professional tables and dense metrics for v1.5.1 --- litellm_dashboard.py | 101 +++++++++++++++++++++++-------------------- 1 file changed, 53 insertions(+), 48 deletions(-) diff --git a/litellm_dashboard.py b/litellm_dashboard.py index c841748..9162a9f 100644 --- a/litellm_dashboard.py +++ b/litellm_dashboard.py @@ -2,10 +2,10 @@ title: LiteLLM Dashboard author: Kagamine Rin & VAI author_url: https://rin.captcouch.xyz -description: Consolidated Hardcore LiteLLM observability — status, usage, and history. +description: Professional LiteLLM observability — High-density health, cost breakdown, and request history. required_open_webui_version: 0.4.0 requirements: requests -version: 1.5.0 +version: 1.5.1 licence: MIT """ @@ -26,7 +26,7 @@ class Tools: """LiteLLM proxy connection settings.""" litellm_url: str = Field( default="http://localhost:4000", - description="LiteLLM proxy base URL", + description="LiteLLM proxy base URL (e.g. http://localhost:4000)", ) litellm_master_key: str = Field( default="", @@ -55,7 +55,6 @@ class Tools: return self.valves.litellm_url.rstrip("/") def _fetch_live_prices(self): - """Sync internal price cache with LiteLLM's live model registry.""" if not self.valves.enable_dynamic_pricing or self._price_cache: return try: @@ -95,7 +94,6 @@ class Tools: return date_str def _audit_cost(self, model: str, prompt: int, cached: int, completion: int, reported_cost: float, reasoning: int = 0) -> str: - """Audit reported cost with dynamic pricing.""" self._fetch_live_prices() pricing = self._price_cache.get(model) if not pricing: return "" @@ -114,40 +112,38 @@ class Tools: Comprehensive system check: Proxy readiness, DB connectivity, and Model health. Use this to verify the infrastructure is up and which models are available. """ - lines = [f"LiteLLM Infrastructure Status — {self._base_url()}", "=" * 50] + lines = [f"LiteLLM Infrastructure Status — {self._base_url()}"] + lines.append("=" * 60) - # 1. Readiness try: r = requests.get(f"{self._base_url()}/health/readiness", timeout=5) if r.status_code == 200: d = r.json() - lines.append(f"Proxy: ✅ {d.get('status')} | DB: {d.get('db')} | v{d.get('litellm_version')}") - else: lines.append(f"Proxy Readiness: ⚠️ Status {r.status_code}") - except Exception: lines.append("Proxy Readiness: ❌ Connection Error") + lines.append(f"✅ Proxy: {d.get('status')} | DB: {d.get('db')} | v{d.get('litellm_version')}") + else: lines.append(f"⚠️ Proxy Readiness: Status {r.status_code}") + except Exception: lines.append("❌ Proxy Readiness: Connection Error") - # 2. Model Health try: h = requests.get(f"{self._base_url()}/health", headers=self._headers(), timeout=10) if h.status_code == 200: hd = h.json() healthy = hd.get("healthy_endpoints", []) unhealthy = hd.get("unhealthy_endpoints", []) - lines.append(f"Models: {len(healthy)} healthy, {len(unhealthy)} unhealthy") - for ep in healthy[:15]: # Cap display to save tokens + lines.append(f"\nHealthy Models: {len(healthy)} | Unhealthy: {len(unhealthy)}") + lines.append("-" * 60) + for ep in healthy: m = ep.get("model", "unknown") - lines.append(f" ✅ {m:25s} | rpm: {ep.get('rpm','?'):>3}") - if len(healthy) > 15: lines.append(f" ... and {len(healthy)-15} more healthy models.") + display = m.replace("gemini/", "").replace("openai/", "") + lines.append(f" ✅ {display:30s} — rpm: {ep.get('rpm','?'):>3}, timeout: {ep.get('timeout','?')}s") for ep in unhealthy: lines.append(f" ❌ {ep.get('model', 'unknown')}") - except Exception: lines.append("Model Health: ❌ Failed to fetch") + except Exception as e: lines.append(f"\n❌ Health Check Failed: {str(e)}") return "\n".join(lines) def litellm_usage(self, start_date: str = "today", end_date: str = "today", detail_level: Literal["summary", "full"] = "summary") -> str: """ Get fiscal usage and spend data. - :param start_date: YYYY-MM-DD or shortcut (today, yesterday, 7 days ago) - :param end_date: YYYY-MM-DD or shortcut :param detail_level: 'summary' for quick spend snapshot, 'full' for daily breakdown and sparklines. """ if not self.valves.litellm_master_key: return "❌ Master key required." @@ -160,13 +156,15 @@ class Tools: if s.status_code == 200: models = sorted(s.json(), key=lambda x: x.get("total_spend", 0), reverse=True) total = sum(m.get("total_spend", 0) for m in models) - lines = [f"LiteLLM Spend Summary (Lifetime)", "=" * 40, f"Total Lifetime Spend: ${total:.2f}"] - for m in models[:5]: - lines.append(f" - {m['model']}: ${m['total_spend']:.2f}") + lines = [f"LiteLLM Spend Snapshot (Lifetime)", "=" * 50, f"Total Lifetime Spend: ${total:.2f}\n"] + lines.append(f"{'Model':30s} | {'Spend':>10s}") + lines.append("-" * 45) + for m in models[:8]: + display = m['model'].split("/")[-1] + lines.append(f" {display:28s} | ${m['total_spend']:9.2f}") return "\n".join(lines) except Exception: pass - # Full Report try: url = f"{self._base_url()}/user/daily/activity" resp = requests.get(url, headers=self._headers(), params={"start_date": r_start, "end_date": r_end}, timeout=30) @@ -186,43 +184,47 @@ class Tools: return " [" + "".join(chars[int((v - low) / rng * 7)] for v in vals) + "]" meta = data.get("metadata", {}) - lines = [f"LiteLLM Usage Report: {r_start} to {r_end}", "=" * 60] - lines.append(f"Spend: ${meta.get('total_spend', 0):.2f}{get_spark(spends)}") - lines.append(f"Requests: {meta.get('total_successful_requests', 0)} ok, {meta.get('total_failed_requests', 0)} fail") + lines = [f"LiteLLM Usage Report: {r_start} to {r_end}"] + lines.append("=" * 75) + lines.append(f"Total Spend: ${meta.get('total_spend', 0):.2f}{get_spark(spends)}") + lines.append(f"Total Tokens: {meta.get('total_tokens', 0):,} ({meta.get('total_prompt_tokens',0):,} prompt + {meta.get('total_completion_tokens',0):,} completion)") + lines.append(f"Requests: {meta.get('total_successful_requests', 0)} successful, {meta.get('total_failed_requests', 0)} failed") - # Aggregated Model Breakdown + # Model Breakdown model_totals = {} for day in results: models = day.get("breakdown", {}).get("models", {}) for m_name, m_data in models.items(): metrics = m_data.get("metrics", {}) if m_name not in model_totals: - model_totals[m_name] = {"spend": 0, "reqs": 0} + model_totals[m_name] = {"spend": 0, "reqs": 0, "tokens": 0} model_totals[m_name]["spend"] += metrics.get("spend", 0) model_totals[m_name]["reqs"] += metrics.get("successful_requests", 0) + model_totals[m_name]["tokens"] += metrics.get("total_tokens", 0) - if model_totals: - lines.append("\nBy Model (Spend > $0.001):") - sorted_models = sorted(model_totals.items(), key=lambda x: x[1]["spend"], reverse=True) - for m_name, m_stats in sorted_models: - if m_stats["spend"] >= 0.001 or not self.valves.default_hide_zero_spend: - # Strip common prefixes for readability - display = m_name.split("/")[-1] - lines.append(f" {display:25s} | ${m_stats['spend']:7.2f} | {m_stats['reqs']:4d} ok") + lines.append(f"\n{'Model':30s} | {'Spend':>10s} | {'Requests':>12s} | {'Tokens':>12s}") + lines.append("-" * 75) + sorted_models = sorted(model_totals.items(), key=lambda x: x[1]["spend"], reverse=True) + hidden = 0 + for m_name, m_stats in sorted_models: + if m_stats["spend"] < 0.001 and self.valves.default_hide_zero_spend: + hidden += 1 + continue + display = m_name.split("/")[-1][:30] + lines.append(f" {display:28s} | ${m_stats['spend']:9.2f} | {m_stats['reqs']:10d} ok | {m_stats['tokens']:10,d}") + if hidden: lines.append(f" ... ({hidden} models with sub-$0.001 spend hidden)") - lines.append("\nBy Endpoint:") + lines.append(f"\n{'Endpoint':30s} | {'Spend':>10s} | {'Requests':>12s}") + lines.append("-" * 60) ends = results[0].get("breakdown", {}).get("endpoints", {}) if results else {} - for path, edata in ends.items(): + for path, edata in sorted(ends.items(), key=lambda x: x[1].get("metrics",{}).get("spend",0), reverse=True): m = edata.get("metrics", {}) - lines.append(f" {path:20s} | ${m.get('spend',0):.4f} | {m.get('successful_requests',0)} reqs") + lines.append(f" {path:28s} | ${m.get('spend',0):9.4f} | {m.get('successful_requests',0):10d} ok") + return "\n".join(lines) def litellm_history(self, limit: int = 20, filter_model: Optional[str] = None) -> str: - """ - Get recent request logs with audit data and thought detection. - :param limit: Number of requests (1-50) - :param filter_model: Optional string to filter by model name (e.g. 'gpt-4') - """ + """Get recent request logs with audit data and thought detection.""" if not self.valves.litellm_master_key: return "❌ Master key required." try: resp = requests.get(f"{self._base_url()}/spend/logs", headers=self._headers(), timeout=30) @@ -234,11 +236,11 @@ class Tools: logs = [l for l in logs if filter_model.lower() in l.get("model", "").lower()] recent = logs[:max(1, min(50, limit))] - lines = [f"LiteLLM History (Most Recent {len(recent)})", "=" * 95] + lines = [f"LiteLLM History — {self._base_url()}", "=" * 105] for i, entry in enumerate(recent, 1): ts = entry.get("startTime", "?")[5:16] - model = entry.get("model", "unknown")[:25] + model = entry.get("model", "unknown").split("/")[-1][:25] spend = entry.get("spend", 0) prompt = entry.get("prompt_tokens", 0) or 0 completion = entry.get("completion_tokens", 0) or 0 @@ -249,9 +251,12 @@ class Tools: cached = entry.get("cache_read_input_tokens", 0) or 0 cache_str = f" 💾{int(cached/prompt*100)}%" if prompt and cached else "" - audit_display = self._audit_cost(entry.get("model"), prompt, cached, completion, spend, reasoning=thought) - status = "❌" if entry.get("metadata", {}).get("exception") else "✅" + audit = self._audit_cost(entry.get("model"), prompt, cached, completion, spend, reasoning=thought) + status = "✅" if not entry.get("metadata", {}).get("exception") else "❌" - lines.append(f"{i:2d}. {ts} {status} | {model:25s} | ${spend:.4f}{audit_display} | {prompt}→{completion}{thought_str}{cache_str}") + p_s = f"{prompt/1000:.1f}k" if prompt >= 1000 else str(prompt) + c_s = f"{completion/1000:.1f}k" if completion >= 1000 else str(completion) + + lines.append(f"{i:2d}. {ts} {status} | {model:25s} | ${spend:.4f}{audit:18s} | {p_s:>6s}→{c_s:<6s}{thought_str}{cache_str}") return "\n".join(lines)