feat(costs): Stufe 2 — geschätzte Quellen (lokaler Strom, Cartesia, Notruf)

Lokales LLM: Call-Dauer gemessen -> Stromkosten je Job ((max-idle)*load*n/
3.6e6*Preis(EUR/kWh)*usd_per_eur), Kategorie llm_local. Cartesia-TTS: Zeichen x
cartesia_usd_per_char (tts). Notruf: pro Webhook-Alarm 0,20€*usd_per_eur
(alert), nur Doku, nie Sperre. Helfer in costs.py; neue Settings (GPU-Watt/Last/
Strompreis/usd_per_eur/cartesia/alert). Statistik: "exakt vs. geschätzt"
(Tooltip + Summenzeile, Kategorien tts/alert/llm_local). Tests: 318.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-06-30 13:27:50 +02:00
commit d45e2c9ae3
8 changed files with 85 additions and 5 deletions

View file

@ -1,8 +1,10 @@
import time
from collections.abc import AsyncIterator
import httpx
from app.core import clock
from app.core.costs import add_cost, electricity_usd
from app.providers.llm.base import (
LLMProvider,
lang_instruction,
@ -88,6 +90,7 @@ class LocalOpenAICompatibleLLM(LLMProvider):
session_id: str | None = None,
language: str | None = None,
) -> str:
t0 = time.monotonic()
async with httpx.AsyncClient(timeout=120) as client:
response = await client.post(
f"{self.base_url}/chat/completions",
@ -96,7 +99,8 @@ class LocalOpenAICompatibleLLM(LLMProvider):
)
response.raise_for_status()
data = response.json()
return data["choices"][0]["message"]["content"]
add_cost(electricity_usd(time.monotonic() - t0), "llm_local")
return data["choices"][0]["message"]["content"]
async def stream(
self,
@ -106,6 +110,7 @@ class LocalOpenAICompatibleLLM(LLMProvider):
language: str | None = None,
**kwargs, # z. B. on_tool_start — hier ignoriert (kein Tool-Calling)
) -> AsyncIterator[str]:
t0 = time.monotonic()
async with httpx.AsyncClient(timeout=120) as client:
async with client.stream(
"POST",
@ -123,3 +128,4 @@ class LocalOpenAICompatibleLLM(LLMProvider):
delta = sse_delta(line)
if delta:
yield delta
add_cost(electricity_usd(time.monotonic() - t0), "llm_local")