Feat: Add Token Stats and Cost Savings dashboard card and backend tracking

This commit is contained in:
Hitonabi
2026-06-26 13:48:32 +02:00
parent 311f4d7b68
commit 2e4cddc840
9 changed files with 546 additions and 391 deletions
+33 -10
View File
@@ -1,18 +1,11 @@
"""
Eingebauter Routing-Gateway (OpenAI-kompatibel) — EIN Endpunkt für Hermes + IDEs.
`model: auto` → Komplexitäts-Routing fast↔heavy; jeder andere Name geht als
llama-swap-Alias durch (das lädt das Modell bei Bedarf). Streaming wird
durchgereicht. Ersetzt LiteLLM (das auf Python 3.14 nicht baut) — gleicher
Vertrag, später austauschbar.
"""
import json
import httpx
from fastapi import APIRouter, Request
from fastapi.responses import JSONResponse, StreamingResponse
from config import LLAMA_SWAP_URL
from services.router_logic import FAST, FAST_NO_THINK, choose_model
from services.token_stats import increment_tokens
router = APIRouter(prefix="/v1")
@@ -44,12 +37,42 @@ async def _proxy(path: str, request: Request):
async with httpx.AsyncClient(timeout=None) as c:
async with c.stream("POST", url, json=body) as r:
async for chunk in r.aiter_raw():
try:
chunk_str = chunk.decode("utf-8", errors="ignore")
if '"usage":' in chunk_str:
for line in chunk_str.splitlines():
if line.startswith("data:"):
data_str = line[5:].strip()
if data_str == "[DONE]":
continue
try:
data_json = json.loads(data_str)
usage = data_json.get("usage")
if usage:
prompt = usage.get("prompt_tokens", 0)
completion = usage.get("completion_tokens", 0)
if prompt or completion:
increment_tokens(prompt, completion)
except Exception:
pass
except Exception:
pass
yield chunk
return StreamingResponse(gen(), media_type="text/event-stream", headers=routed)
async with httpx.AsyncClient(timeout=600) as c:
r = await c.post(url, json=body)
return JSONResponse(r.json(), status_code=r.status_code, headers=routed)
resp_json = r.json()
try:
usage = resp_json.get("usage")
if usage:
prompt = usage.get("prompt_tokens", 0)
completion = usage.get("completion_tokens", 0)
if prompt or completion:
increment_tokens(prompt, completion)
except Exception:
pass
return JSONResponse(resp_json, status_code=r.status_code, headers=routed)
@router.post("/chat/completions")
+22
View File
@@ -88,3 +88,25 @@ def self_update() -> dict:
reset = _run(["git", "reset", "--hard", "origin/main"], cwd=SOURCE_DIR)
restart_res = _run(["systemctl", "--user", "restart", "mission-control-2"])
return {"pull": pull, "reset": reset, "restart": restart_res}
from services.token_stats import get_stats
@router.get("/system/token-stats")
def token_stats() -> dict:
stats = get_stats()
p = stats.get("prompt_tokens", 0)
c = stats.get("completion_tokens", 0)
total = p + c
# Blended savings based on a premium cloud model rate (e.g. GPT-4o / Claude 3.5 Sonnet: $3.00/1M input, $15.00/1M output)
saved_usd = (p * 3.0 + c * 15.0) / 1_000_000.0
saved_eur = saved_usd * 0.92 # 1 USD = 0.92 EUR
return {
"prompt_tokens": p,
"completion_tokens": c,
"total_tokens": total,
"saved_usd": round(saved_usd, 2),
"saved_eur": round(saved_eur, 2)
}