Files
creator2/backend/ledger.py
2026-07-10 15:43:11 +02:00

62 lines
2.6 KiB
Python

"""Event-Ledger: jeder LLM-Call wird eine append-only-Zeile in `events`.
Kennzahlen sind Queries darauf; das Budget zählt live mit."""
import subprocess
import db
from config import PROJECT_ROOT
class BudgetErschoepft(Exception):
pass
def git_stand() -> tuple[str, bool]:
"""(hash, dirty) des Repos — fürs Lauf-Protokoll; Fehler → ('', False)."""
try:
h = subprocess.run(["git", "rev-parse", "HEAD"], cwd=PROJECT_ROOT,
capture_output=True, text=True, timeout=5).stdout.strip()
d = subprocess.run(["git", "status", "--porcelain"], cwd=PROJECT_ROOT,
capture_output=True, text=True, timeout=5).stdout.strip()
return h, bool(d)
except Exception:
return "", False
def log_call(run_id: int, *, ebene: str, stage: str, item: str = "", template: str = "",
template_hash: str = "", provider: str = "", model: str = "", role: str = "",
status: str, dur_ms: int = 0, wait_ms: int = 0, tokens: dict | None = None,
meta: dict | None = None) -> None:
t = tokens or {}
db.execute(
"INSERT INTO events(run_id, ebene, stage, item, template, template_hash, provider,"
" model, role, status, dur_ms, wait_ms, tok_in, tok_out, tok_cache_read, tok_cache_write, meta)"
" VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
(run_id, ebene, stage, item, template, template_hash, provider, model, role, status,
dur_ms, wait_ms, int(t.get("input", 0)), int(t.get("output", 0)),
int(t.get("cache_read", 0)), int(t.get("cache_write", 0)), db.j(meta or {})))
def verbraucht(run_id: int) -> int:
row = db.one("SELECT COALESCE(SUM(tok_in+tok_out),0) AS s FROM events WHERE run_id=?", (run_id,))
return int(row["s"]) if row else 0
def budget_pruefen(run_id: int) -> None:
"""Nach jedem Call. Hartes Limit: wirft statt weiterzubrennen."""
run = db.one("SELECT budget_tokens FROM runs WHERE id=?", (run_id,))
limit = int(run["budget_tokens"]) if run else 0
if limit and verbraucht(run_id) >= limit:
raise BudgetErschoepft(f"Token-Budget erreicht ({limit})")
def kennzahlen(run_id: int) -> list[dict]:
return db.query(
"SELECT ebene, stage, template, COUNT(*) AS calls,"
" SUM(tok_in) AS tok_in, SUM(tok_out) AS tok_out,"
" SUM(tok_cache_read) AS cache_read, SUM(dur_ms) AS dur_ms, SUM(wait_ms) AS wait_ms,"
" SUM(status='ok') AS ok, SUM(status='timeout') AS timeouts,"
" SUM(status IN ('error','infra')) AS fehler"
" FROM events WHERE run_id=? GROUP BY ebene, stage, template ORDER BY MIN(id)",
(run_id,))