feat(costs): Pro-Nutzer-Kostenerfassung Stufe 1 (OpenRouter-Ist-Kosten)

CostMeter via ContextVars (app/core/costs.py, multitool-ready). Alle
OpenRouter-Calls (LLM, Sonar, Koreferenz, Fallback) senden usage:{include:true}
und buchen die Ist-Kosten je Kategorie (llm/web_search) — inkl. finalem
Streaming-usage-Chunk. cost_usage-Tabelle (user/day/category) + Store-Methoden;
ws.py/chat.py start_meter pro Turn + persist_costs. Statistik: Spalten
"Kosten (Monat)" + "Ø/Anfrage" + Gesamt. Live: 1 Such-Turn ~ $0,0052
(web_search dominiert, LLM ~ $0,00007). Tests: 318.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-06-30 11:08:45 +02:00
commit d87f0ce424
9 changed files with 171 additions and 10 deletions

View file

@ -1827,6 +1827,8 @@ async function loadMetrics() {
const totalReq = data.reduce((s, u) => s + (u.total_requests || 0), 0);
const totalWS = data.reduce((s, u) => s + (u.total_web_searches || 0), 0);
const totalCost = data.reduce((s, u) => s + (u.month_cost_usd || 0), 0);
const fmtUsd = (n) => "$" + (Number(n) || 0).toFixed(4);
const maxReq = Math.max(...data.map((u) => u.total_requests || 0), 1);
const bars = data.map((u) => {
@ -1849,6 +1851,8 @@ async function loadMetrics() {
<th class="text-left px-4 py-2.5">Nutzer</th>
<th class="text-right px-4 py-2.5">Anfragen gesamt</th>
<th class="text-right px-4 py-2.5">mit Websuche</th>
<th class="text-right px-4 py-2.5">Kosten (Monat)</th>
<th class="text-right px-4 py-2.5">Ø/Anfrage</th>
<th class="text-right px-4 py-2.5">Einheiten</th>
<th class="text-right px-4 py-2.5">Letzte Aktivität</th>
</tr>
@ -1859,6 +1863,8 @@ async function loadMetrics() {
<td class="px-4 py-2.5 font-medium">${escHtml(u.display_name)}</td>
<td class="px-4 py-2.5 text-right font-mono text-xs">${u.total_requests ?? 0}</td>
<td class="px-4 py-2.5 text-right font-mono text-xs">${u.total_web_searches ?? 0}</td>
<td class="px-4 py-2.5 text-right font-mono text-xs">${fmtUsd(u.month_cost_usd)}</td>
<td class="px-4 py-2.5 text-right font-mono text-xs">${u.month_requests ? fmtUsd((u.month_cost_usd || 0) / u.month_requests) : "—"}</td>
<td class="px-4 py-2.5 text-right font-mono text-xs">${u.total_units ?? 0}</td>
<td class="px-4 py-2.5 text-right text-xs text-slate-400">${u.last_active || "—"}</td>
</tr>`).join("")}
@ -1868,6 +1874,8 @@ async function loadMetrics() {
<td class="px-4 py-2">Gesamt</td>
<td class="px-4 py-2 text-right font-mono">${totalReq}</td>
<td class="px-4 py-2 text-right font-mono">${totalWS}</td>
<td class="px-4 py-2 text-right font-mono">${fmtUsd(totalCost)}</td>
<td class="px-4 py-2"></td>
<td class="px-4 py-2"></td>
<td class="px-4 py-2"></td>
</tr>