"""Event-Ledger: eine Zeile je LLM-Versuch (im finally — jeder Ausgang zählt, auch Hedge-Verlierer). Budget = harte Grenze, geprüft VOR und NACH jedem Call.""" from . import config, db class BudgetErschoepft(Exception): pass def log_call(run_id: int, *, stufe: str = "", knoten: str = "", item: str = "", skills_liste: list | None = None, skill_hash: str = "", model: str = "", role: str = "", status: str = "", dur_ms: int = 0, wait_ms: int = 0, tokens: dict | None = None, meta: dict | None = None, prompt: str = "", antwort: str = "") -> int | None: t = tokens or {} event_id = db.insert( "events", run_id=run_id, ts=db.now(), stufe=stufe, knoten=knoten, item=item, skills=db.j(skills_liste or []), skill_hash=skill_hash, model=model, role=role, status=status, dur_ms=dur_ms, wait_ms=wait_ms, tok_in=t.get("input", 0), tok_out=t.get("output", 0), tok_cache_read=t.get("cache_read", 0), tok_cache_write=t.get("cache_write", 0), meta=db.j(meta or {})) if config.LLM_LOG and event_id and (prompt or antwort): db.insert("call_texte", event_id=event_id, prompt=prompt, antwort=antwort) return event_id def verbraucht(run_id: int) -> int: r = db.one("SELECT COALESCE(SUM(tok_in + tok_out), 0) s FROM events " "WHERE run_id=?", run_id) return r["s"] def budget_pruefen(run_id: int) -> None: run = db.one("SELECT budget_tokens FROM runs WHERE id=?", run_id) limit = (run or {}).get("budget_tokens") or 0 if limit > 0 and verbraucht(run_id) >= limit: raise BudgetErschoepft(f"Budget {limit} Tokens erreicht") def kennzahlen(run_id: int) -> dict: zeilen = db.query( "SELECT stufe, knoten, COUNT(*) calls, SUM(tok_in) tok_in, " "SUM(tok_out) tok_out, SUM(tok_cache_read) cache_read, " "SUM(dur_ms) dur_ms, SUM(wait_ms) wait_ms, " "SUM(status='ok') ok, SUM(status IN ('error','infra','parse','cap')) fehler " "FROM events WHERE run_id=? GROUP BY stufe, knoten ORDER BY MIN(id)", run_id) gesamt = verbraucht(run_id) kosten = sum(z["tok_in"] or 0 for z in zeilen) / 1e6 * config.PREIS_IN + \ sum(z["tok_out"] or 0 for z in zeilen) / 1e6 * config.PREIS_OUT return {"zeilen": zeilen, "verbraucht": gesamt, "kosten_usd": round(kosten, 2)}