style(core): restore high-density professional tables and dense metrics for v1.5.1

This commit is contained in:
vai committed 2026-06-27 09:51:18 +00:00
1 parent 6add0680ac
commit 6359a2b2a5
1 file changed
+51 -46
+51 -46
View File
@@ -2,10 +2,10 @@
title: LiteLLM Dashboard title: LiteLLM Dashboard
author: Kagamine Rin & VAI author: Kagamine Rin & VAI
author_url: https://rin.captcouch.xyz 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 required_open_webui_version: 0.4.0
requirements: requests requirements: requests
version: 1.5.0 version: 1.5.1
licence: MIT licence: MIT
""" """
@@ -26,7 +26,7 @@ class Tools:
"""LiteLLM proxy connection settings.""" """LiteLLM proxy connection settings."""
litellm_url: str = Field( litellm_url: str = Field(
default="http://localhost:4000", default="http://localhost:4000",
description="LiteLLM proxy base URL", description="LiteLLM proxy base URL (e.g. http://localhost:4000)",
) )
litellm_master_key: str = Field( litellm_master_key: str = Field(
default="", default="",
@@ -55,7 +55,6 @@ class Tools:
return self.valves.litellm_url.rstrip("/") return self.valves.litellm_url.rstrip("/")
def _fetch_live_prices(self): 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: if not self.valves.enable_dynamic_pricing or self._price_cache:
return return
try: try:
@@ -95,7 +94,6 @@ class Tools:
return date_str return date_str
def _audit_cost(self, model: str, prompt: int, cached: int, completion: int, reported_cost: float, reasoning: int = 0) -> 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() self._fetch_live_prices()
pricing = self._price_cache.get(model) pricing = self._price_cache.get(model)
if not pricing: return "" if not pricing: return ""
@@ -114,40 +112,38 @@ class Tools:
Comprehensive system check: Proxy readiness, DB connectivity, and Model health. Comprehensive system check: Proxy readiness, DB connectivity, and Model health.
Use this to verify the infrastructure is up and which models are available. 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: try:
r = requests.get(f"{self._base_url()}/health/readiness", timeout=5) r = requests.get(f"{self._base_url()}/health/readiness", timeout=5)
if r.status_code == 200: if r.status_code == 200:
d = r.json() d = r.json()
lines.append(f"Proxy: ✅ {d.get('status')} | DB: {d.get('db')} | v{d.get('litellm_version')}") 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}") else: lines.append(f"⚠️ Proxy Readiness: Status {r.status_code}")
except Exception: lines.append("Proxy Readiness: ❌ Connection Error") except Exception: lines.append("❌ Proxy Readiness: Connection Error")
# 2. Model Health
try: try:
h = requests.get(f"{self._base_url()}/health", headers=self._headers(), timeout=10) h = requests.get(f"{self._base_url()}/health", headers=self._headers(), timeout=10)
if h.status_code == 200: if h.status_code == 200:
hd = h.json() hd = h.json()
healthy = hd.get("healthy_endpoints", []) healthy = hd.get("healthy_endpoints", [])
unhealthy = hd.get("unhealthy_endpoints", []) unhealthy = hd.get("unhealthy_endpoints", [])
lines.append(f"Models: {len(healthy)} healthy, {len(unhealthy)} unhealthy") lines.append(f"\nHealthy Models: {len(healthy)} | Unhealthy: {len(unhealthy)}")
for ep in healthy[:15]: # Cap display to save tokens lines.append("-" * 60)
for ep in healthy:
m = ep.get("model", "unknown") m = ep.get("model", "unknown")
lines.append(f" ✅ {m:25s} | rpm: {ep.get('rpm','?'):>3}") display = m.replace("gemini/", "").replace("openai/", "")
if len(healthy) > 15: lines.append(f" ... and {len(healthy)-15} more healthy models.") lines.append(f" ✅ {display:30s} — rpm: {ep.get('rpm','?'):>3}, timeout: {ep.get('timeout','?')}s")
for ep in unhealthy: for ep in unhealthy:
lines.append(f" ❌ {ep.get('model', 'unknown')}") 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) return "\n".join(lines)
def litellm_usage(self, start_date: str = "today", end_date: str = "today", detail_level: Literal["summary", "full"] = "summary") -> str: 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. 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. :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." if not self.valves.litellm_master_key: return "❌ Master key required."
@@ -160,13 +156,15 @@ class Tools:
if s.status_code == 200: if s.status_code == 200:
models = sorted(s.json(), key=lambda x: x.get("total_spend", 0), reverse=True) 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) total = sum(m.get("total_spend", 0) for m in models)
lines = [f"LiteLLM Spend Summary (Lifetime)", "=" * 40, f"Total Lifetime Spend: ${total:.2f}"] lines = [f"LiteLLM Spend Snapshot (Lifetime)", "=" * 50, f"Total Lifetime Spend: ${total:.2f}\n"]
for m in models[:5]: lines.append(f"{'Model':30s} | {'Spend':>10s}")
lines.append(f" - {m['model']}: ${m['total_spend']:.2f}") 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) return "\n".join(lines)
except Exception: pass except Exception: pass
# Full Report
try: try:
url = f"{self._base_url()}/user/daily/activity" 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) 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) + "]" return " [" + "".join(chars[int((v - low) / rng * 7)] for v in vals) + "]"
meta = data.get("metadata", {}) meta = data.get("metadata", {})
lines = [f"LiteLLM Usage Report: {r_start} to {r_end}", "=" * 60] lines = [f"LiteLLM Usage Report: {r_start} to {r_end}"]
lines.append(f"Spend: ${meta.get('total_spend', 0):.2f}{get_spark(spends)}") lines.append("=" * 75)
lines.append(f"Requests: {meta.get('total_successful_requests', 0)} ok, {meta.get('total_failed_requests', 0)} fail") 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 = {} model_totals = {}
for day in results: for day in results:
models = day.get("breakdown", {}).get("models", {}) models = day.get("breakdown", {}).get("models", {})
for m_name, m_data in models.items(): for m_name, m_data in models.items():
metrics = m_data.get("metrics", {}) metrics = m_data.get("metrics", {})
if m_name not in model_totals: 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]["spend"] += metrics.get("spend", 0)
model_totals[m_name]["reqs"] += metrics.get("successful_requests", 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(f"\n{'Model':30s} | {'Spend':>10s} | {'Requests':>12s} | {'Tokens':>12s}")
lines.append("\nBy Model (Spend > $0.001):") lines.append("-" * 75)
sorted_models = sorted(model_totals.items(), key=lambda x: x[1]["spend"], reverse=True) sorted_models = sorted(model_totals.items(), key=lambda x: x[1]["spend"], reverse=True)
hidden = 0
for m_name, m_stats in sorted_models: for m_name, m_stats in sorted_models:
if m_stats["spend"] >= 0.001 or not self.valves.default_hide_zero_spend: if m_stats["spend"] < 0.001 and self.valves.default_hide_zero_spend:
# Strip common prefixes for readability hidden += 1
display = m_name.split("/")[-1] continue
lines.append(f" {display:25s} | ${m_stats['spend']:7.2f} | {m_stats['reqs']:4d} ok") 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 {} 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", {}) 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) return "\n".join(lines)
def litellm_history(self, limit: int = 20, filter_model: Optional[str] = None) -> str: def litellm_history(self, limit: int = 20, filter_model: Optional[str] = None) -> str:
""" """Get recent request logs with audit data and thought detection."""
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')
"""
if not self.valves.litellm_master_key: return "❌ Master key required." if not self.valves.litellm_master_key: return "❌ Master key required."
try: try:
resp = requests.get(f"{self._base_url()}/spend/logs", headers=self._headers(), timeout=30) 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()] logs = [l for l in logs if filter_model.lower() in l.get("model", "").lower()]
recent = logs[:max(1, min(50, limit))] 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): for i, entry in enumerate(recent, 1):
ts = entry.get("startTime", "?")[5:16] 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) spend = entry.get("spend", 0)
prompt = entry.get("prompt_tokens", 0) or 0 prompt = entry.get("prompt_tokens", 0) or 0
completion = entry.get("completion_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 cached = entry.get("cache_read_input_tokens", 0) or 0
cache_str = f" 💾{int(cached/prompt*100)}%" if prompt and cached else "" 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) audit = self._audit_cost(entry.get("model"), prompt, cached, completion, spend, reasoning=thought)
status = "❌" if entry.get("metadata", {}).get("exception") else "✅" 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) return "\n".join(lines)