feat(core): restore original high-density architecture and specialized power-functions for v1.6.0

This commit is contained in:
vai committed 2026-06-27 09:53:32 +00:00
1 parent 6359a2b2a5
commit 49b95b1f62
1 file changed
+144 -87
+144 -87
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: Professional LiteLLM observability — High-density health, cost breakdown, and request history. description: High-performance LiteLLM observability — Comprehensive health, cost breakdowns, and request history.
required_open_webui_version: 0.4.0 required_open_webui_version: 0.4.0
requirements: requests requirements: requests
version: 1.5.1 version: 1.6.0
licence: MIT licence: MIT
""" """
@@ -14,7 +14,7 @@ import json
from datetime import datetime, timedelta from datetime import datetime, timedelta
from zoneinfo import ZoneInfo from zoneinfo import ZoneInfo
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from typing import Optional, List, Dict, Any, Literal from typing import Optional, List, Dict, Any
class Tools: class Tools:
@@ -35,11 +35,11 @@ class Tools:
) )
default_hide_zero_spend: bool = Field( default_hide_zero_spend: bool = Field(
default=True, default=True,
description="Default behavior for zero-spend model filtering.", description="Default behavior for zero-spend model filtering. When True, models with sub-$0.001 spend are hidden by default.",
) )
timezone: str = Field( timezone: str = Field(
default="UTC", default="UTC",
description="IANA Timezone name (e.g. America/New_York).", description="IANA Timezone name (e.g. America/New_York) for date resolution.",
) )
enable_dynamic_pricing: bool = Field( enable_dynamic_pricing: bool = Field(
default=True, default=True,
@@ -55,6 +55,7 @@ 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:
@@ -87,6 +88,14 @@ class Tools:
n = int(date_str.split()[0]) n = int(date_str.split()[0])
return (today - timedelta(days=n)).isoformat() return (today - timedelta(days=n)).isoformat()
except Exception: pass except Exception: pass
# Day names
days = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"]
if date_str in days:
target_day = days.index(date_str)
days_back = (today.weekday() - target_day) % 7
return (today - timedelta(days=days_back)).isoformat()
try: try:
datetime.strptime(date_str, "%Y-%m-%d") datetime.strptime(date_str, "%Y-%m-%d")
return date_str return date_str
@@ -94,88 +103,107 @@ 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 against 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 ""
uncached = max(0, prompt - cached) uncached = max(0, prompt - cached)
actual_output = max(0, completion - reasoning) actual_output = max(0, completion - reasoning)
calc = (cached * (pricing.get("input_cached") or 0)) + \ calc = (cached * (pricing.get("input_cached") or 0)) + \
(uncached * (pricing.get("input") or 0)) + \ (uncached * (pricing.get("input") or 0)) + \
(actual_output * (pricing.get("output") or 0)) + \ (actual_output * (pricing.get("output") or 0)) + \
(reasoning * (pricing.get("reasoning") or pricing.get("output") or 0)) (reasoning * (pricing.get("reasoning") or pricing.get("output") or 0))
delta = reported_cost - calc delta = reported_cost - calc
symbol = "⚠️" if abs(delta) > 0.005 else "✓" symbol = "⚠️" if abs(delta) > 0.005 else "✓"
return f" [calc: ${calc:.4f} {symbol}]" return f" [calc: ${calc:.4f} {symbol}]"
def litellm_status(self) -> str: def litellm_health(self) -> str:
""" """
Comprehensive system check: Proxy readiness, DB connectivity, and Model health. Check LiteLLM proxy health and authentication status.
Use this to verify the infrastructure is up and which models are available. Returns detailed model health, rate limits, and infrastructure readiness.
""" """
lines = [f"LiteLLM Infrastructure Status — {self._base_url()}"] lines = [f"LiteLLM Health Check — {self._base_url()}"]
lines.append("=" * 60) lines.append("=" * 60)
# Readiness Header
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}") except Exception: pass
except Exception: lines.append("❌ Proxy Readiness: Connection Error")
if not self.valves.litellm_master_key:
lines.append("⚠️ No master key configured — showing public health only")
else:
lines.append("✅ Authenticated with master key")
try: try:
h = requests.get(f"{self._base_url()}/health", headers=self._headers(), timeout=10) url = f"{self._base_url()}/health"
if h.status_code == 200: resp = requests.get(url, headers=self._headers(), timeout=15)
hd = h.json() if resp.status_code != 200:
healthy = hd.get("healthy_endpoints", []) return f"❌ Unexpected status {resp.status_code}: {resp.text[:500]}"
unhealthy = hd.get("unhealthy_endpoints", [])
lines.append(f"\nHealthy Models: {len(healthy)} | Unhealthy: {len(unhealthy)}") data = resp.json()
lines.append("-" * 60) healthy = data.get("healthy_endpoints", [])
unhealthy = data.get("unhealthy_endpoints", [])
lines.append(f"\nHealthy: {len(healthy)} | Unhealthy: {len(unhealthy)}")
if healthy:
lines.append(f"\n--- Healthy Models ({len(healthy)}) ---")
for ep in healthy: for ep in healthy:
m = ep.get("model", "unknown") model = ep.get("model", "unknown")
display = m.replace("gemini/", "").replace("openai/", "") display = model.replace("gemini/", "").replace("openai/", "")
lines.append(f" ✅ {display:30s} — rpm: {ep.get('rpm','?'):>3}, timeout: {ep.get('timeout','?')}s") rpm = ep.get("rpm", "?")
timeout = ep.get("timeout", "?")
lines.append(f" ✅ {display:30s} — rpm: {rpm:>3}, timeout: {timeout}s")
if unhealthy:
lines.append(f"\n--- Unhealthy Models ({len(unhealthy)}) ---")
for ep in unhealthy: for ep in unhealthy:
lines.append(f" ❌ {ep.get('model', 'unknown')}") lines.append(f" ❌ {ep.get('model', 'unknown')}")
except Exception as e: lines.append(f"\n❌ Health Check Failed: {str(e)}")
except Exception as e:
return f"❌ Connection 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_cost(self, start_date: str, end_date: str, hide_zero_spend: bool = False) -> str:
""" """
Get fiscal usage and spend data. Get LiteLLM cost breakdown for a date range.
:param detail_level: 'summary' for quick spend snapshot, 'full' for daily breakdown and sparklines. Returns per-model and per-day spend, token counts, cache hit rate, and trend sparklines.
""" """
if not self.valves.litellm_master_key: return "❌ Master key required." if not self.valves.litellm_master_key:
return "❌ Master key required for cost data."
effective_hide = hide_zero_spend or self.valves.default_hide_zero_spend
r_start = self._resolve_date(start_date) r_start = self._resolve_date(start_date)
r_end = self._resolve_date(end_date) r_end = self._resolve_date(end_date)
if detail_level == "summary": url = f"{self._base_url()}/user/daily/activity"
try:
s = requests.get(f"{self._base_url()}/global/spend/models", headers=self._headers(), timeout=10)
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 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
try: 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) resp = requests.get(url, headers=self._headers(), params={"start_date": r_start, "end_date": r_end}, timeout=30)
if resp.status_code != 200: return f"❌ Error {resp.status_code}" if resp.status_code != 200: return f"❌ Unexpected status {resp.status_code}"
data = resp.json() data = resp.json()
except Exception as e: return f"❌ Failed: {str(e)}" except Exception as e: return f"❌ Connection failed: {str(e)}"
results = data.get("results", []) results = data.get("results", [])
if not results: return f"No usage found for {r_start} to {r_end}." metadata = data.get("metadata", {})
if not results: return f"No cost data found for {r_start} to {r_end}."
# Totals
total_spend = metadata.get("total_spend", 0)
total_prompt = metadata.get("total_prompt_tokens", 0)
total_completion = metadata.get("total_completion_tokens", 0)
total_tokens = metadata.get("total_tokens", 0)
cache_read = metadata.get("total_cache_read_input_tokens", 0)
spends = [d.get("metrics", {}).get("spend", 0) for d in results] # Sparkline
spends = [day.get("metrics", {}).get("spend", 0) for day in results]
def get_spark(vals): def get_spark(vals):
if len(vals) < 2: return "" if len(vals) < 2: return ""
chars = " ▂▃▄▅▆▇█" chars = " ▂▃▄▅▆▇█"
@@ -183,80 +211,109 @@ class Tools:
rng = (high - low) or 1 rng = (high - low) or 1
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", {}) lines = [f"LiteLLM Cost Report: {r_start} to {r_end}"]
lines = [f"LiteLLM Usage Report: {r_start} to {r_end}"]
lines.append("=" * 75) lines.append("=" * 75)
lines.append(f"Total Spend: ${meta.get('total_spend', 0):.2f}{get_spark(spends)}") lines.append(f"Total Spend: ${total_spend:.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"Total Tokens: {total_tokens:,} ({total_prompt:,} prompt + {total_completion:,} completion)")
lines.append(f"Requests: {meta.get('total_successful_requests', 0)} successful, {meta.get('total_failed_requests', 0)} failed")
# Model Breakdown if total_prompt > 0 and cache_read > 0:
hit_rate = (cache_read / total_prompt) * 100
lines.append(f"Cache Hit Rate: {hit_rate:.1f}% ({cache_read:,} / {total_prompt:,} input tokens)")
lines.append(f"Requests: {metadata.get('total_successful_requests', 0)} ok, {metadata.get('total_failed_requests', 0)} fail")
# Model Aggregation
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", {}) m_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, "tokens": 0} model_totals[m_name] = {"spend": 0, "reqs": 0, "tokens": 0, "cache": 0}
model_totals[m_name]["spend"] += metrics.get("spend", 0) model_totals[m_name]["spend"] += m_metrics.get("spend", 0)
model_totals[m_name]["reqs"] += metrics.get("successful_requests", 0) model_totals[m_name]["reqs"] += m_metrics.get("successful_requests", 0)
model_totals[m_name]["tokens"] += metrics.get("total_tokens", 0) model_totals[m_name]["tokens"] += m_metrics.get("total_tokens", 0)
model_totals[m_name]["cache"] += m_metrics.get("cache_read_input_tokens", 0)
lines.append(f"\n{'Model':30s} | {'Spend':>10s} | {'Requests':>12s} | {'Tokens':>12s}") lines.append(f"\n{'Model':30s} | {'Spend':>10s} | {'Requests':>14s} | {'Tokens':>12s}")
lines.append("-" * 75) lines.append("-" * 75)
hidden_count = 0
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 name, mt in sorted_models:
for m_name, m_stats in sorted_models: if effective_hide and mt["spend"] < 0.001:
if m_stats["spend"] < 0.001 and self.valves.default_hide_zero_spend: hidden_count += 1
hidden += 1
continue continue
display = m_name.split("/")[-1][:30] display = name.replace("gemini/", "").replace("openai/", "")[:30]
lines.append(f" {display:28s} | ${m_stats['spend']:9.2f} | {m_stats['reqs']:10d} ok | {m_stats['tokens']:10,d}") cache_info = ""
if hidden: lines.append(f" ... ({hidden} models with sub-$0.001 spend hidden)") if mt["cache"] > 0:
hit = (mt["cache"] / (mt["tokens"] or 1) * 100)
cache_info = f" 💾{hit:.0f}%"
lines.append(f" {display:28s} | ${mt['spend']:9.2f} | {mt['reqs']:6d} ok | {mt['tokens']:10,d}{cache_info}")
if hidden_count: lines.append(f" ... ({hidden_count} model(s) with sub-$0.001 spend hidden)")
lines.append(f"\n{'Endpoint':30s} | {'Spend':>10s} | {'Requests':>12s}") # Date breakdown
lines.append("-" * 60) lines.append("\nBy Date:")
ends = results[0].get("breakdown", {}).get("endpoints", {}) if results else {} for day in results:
for path, edata in sorted(ends.items(), key=lambda x: x[1].get("metrics",{}).get("spend",0), reverse=True): m = day.get("metrics", {})
m = edata.get("metrics", {}) lines.append(f" {day.get('date')}: ${m.get('spend',0):.2f} | {m.get('successful_requests',0)} ok | {m.get('total_tokens',0):,} tokens")
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, show_failed: bool = True) -> str:
"""Get recent request logs with audit data and thought detection.""" """
if not self.valves.litellm_master_key: return "❌ Master key required." Get recent LiteLLM request history with audit data and thought detection.
Returns a high-density table of recent requests.
"""
if not self.valves.litellm_master_key:
return "❌ Master key required for request history."
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)
if resp.status_code != 200: return f"❌ Error {resp.status_code}" if resp.status_code != 200: return f"❌ Unexpected status {resp.status_code}"
logs = sorted(resp.json(), key=lambda x: x.get("startTime", ""), reverse=True) logs = sorted(resp.json(), key=lambda x: x.get("startTime", ""), reverse=True)
except Exception as e: return f"❌ Failed: {str(e)}" except Exception as e: return f"❌ Connection failed: {str(e)}"
if filter_model: if not show_failed:
logs = [l for l in logs if filter_model.lower() in l.get("model", "").lower()] logs = [l for l in logs if not l.get("metadata", {}).get("exception")]
recent = logs[:max(1, min(50, limit))] recent = logs[:max(1, min(50, limit))]
lines = [f"LiteLLM History — {self._base_url()}", "=" * 105] lines = [f"LiteLLM Request History — {self._base_url()}"]
lines.append(f"Showing {len(recent)} of {len(logs)} total requests")
lines.append("=" * 105)
for i, entry in enumerate(recent, 1): for i, entry in enumerate(recent, 1):
ts = entry.get("startTime", "?")[5:16] start_time = entry.get("startTime", "?")[5:16] # MM-DD HH:MM
model = entry.get("model", "unknown").split("/")[-1][:25] model = entry.get("model", "unknown").split("/")[-1][:28]
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
# Metadata & Status
meta = entry.get("metadata", {}) or {}
is_failed = bool(meta.get("exception")) or meta.get("status") == "failure"
status = "❌ FAIL" if is_failed else "✅ OK"
# Advanced Tokens
usage = entry.get("usage", {}) or {} usage = entry.get("usage", {}) or {}
thought = usage.get("completion_tokens_details", {}).get("reasoning_tokens", 0) thought = usage.get("completion_tokens_details", {}).get("reasoning_tokens", 0)
thought_str = f" [💭{thought}]" if thought else "" thought_str = f" [💭{thought}]" if thought else ""
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 = self._audit_cost(entry.get("model"), prompt, cached, completion, spend, reasoning=thought) # Format numbers
status = "✅" if not entry.get("metadata", {}).get("exception") else "❌"
p_s = f"{prompt/1000:.1f}k" if prompt >= 1000 else str(prompt) 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) 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}") audit = self._audit_cost(entry.get("model"), prompt, cached, completion, spend, reasoning=thought)
line = f" {i:2d}. {start_time} | {model:28s} | {status} | ${spend:.4f}{audit:18s} | {p_s:>6s}→{c_s:<6s}{thought_str}{cache_str}"
lines.append(line)
if is_failed:
err = meta.get("exception", "unknown error")
lines.append(f" └─ {err[:80]}")
return "\n".join(lines) return "\n".join(lines)