Admin: KI-Anfragen nach Quelle aufschlüsseln (cloud/local/luna)

- ki_daily_calls: PK auf (user_id, date, source) erweitert + Index; Migration
  baut Tabelle mit neuer Struktur neu auf, behält Altdaten als 'cloud'
- ki.py: return_source=True-Parameter gibt (text, 'cloud'|'local') zurück
- training.py: ki_source aus ki.complete() auslesen, in DB speichern
- social.py: _ki_complete_tracked() zählt Luna-Anfragen mit source='luna';
  alle Content-Endpoints (generate, evaluate, training-tip, breed-of-day,
  pflege-tipp) nutzen tracking-Variante
- admin.py: Stats aufgeteilt in ki_cloud/ki_local/ki_luna je heute+Monat
- admin.js: KI-Karte zeigt 9 Zeilen mit ☁️ Claude / 🖥️ LM Studio / 🌙 Luna
- SW by-v359, APP_VER 344
This commit is contained in:
rene 2026-04-25 08:20:29 +02:00
parent 74b6c03bb3
commit 8d5c7a19b1
6 changed files with 136 additions and 33 deletions

View file

@ -25,7 +25,7 @@ logger = logging.getLogger(__name__)
KI_MODE = os.getenv("KI_MODE", "local") # off | local | cloud
LOCAL_BASE_URL = os.getenv("KI_LOCAL_URL", "http://10.47.11.70:11435/v1")
LOCAL_MODEL = os.getenv("KI_LOCAL_MODEL", "gemma-4-31b-it")
CLOUD_MODEL = os.getenv("KI_CLOUD_MODEL", "claude-opus-4-6")
CLOUD_MODEL = os.getenv("KI_CLOUD_MODEL", "claude-sonnet-4-6")
ANTHROPIC_KEY = os.getenv("ANTHROPIC_API_KEY", "")
# Lazy Imports — nur laden wenn wirklich benötigt
@ -70,6 +70,7 @@ async def complete(
user_is_premium: bool = False,
json_mode: bool = False,
return_model: bool = False,
return_source: bool = False,
) -> str:
"""
KI-Completion. Wählt automatisch den richtigen Backend.
@ -81,9 +82,10 @@ async def complete(
requires_premium: True = nur für Premium-User (nutzt Cloud)
user_is_premium: Ob der anfragende User Premium hat
json_mode: Antwort als JSON anfordern
return_source: Falls True: gibt (text, source) zurück, source = 'cloud'|'local'
Returns:
KI-Antwort als String
KI-Antwort als String, oder (str, str) wenn return_source=True
Raises:
KIPremiumRequired: Cloud-Feature ohne Premium
@ -101,20 +103,26 @@ async def complete(
# Cloud-Aufruf: nur wenn Premium UND cloud-Modus
if requires_premium and user_is_premium and KI_MODE == "cloud":
text = await _cloud_complete(prompt, system, max_tokens, json_mode)
return (text, CLOUD_MODEL) if return_model else text
if return_model:
return (text, CLOUD_MODEL)
return (text, "cloud") if return_source else text
# Lokaler Aufruf: Entwicklung + Free-User
if KI_MODE in ("local", "cloud"):
try:
text = await _local_complete(prompt, system, max_tokens, json_mode)
return (text, LOCAL_MODEL) if return_model else text
if return_model:
return (text, LOCAL_MODEL)
return (text, "local") if return_source else text
except Exception as e:
logger.warning(f"Lokales KI-Modell nicht erreichbar: {e}")
# Cloud-Fallback: im cloud-Modus immer, sonst nur für Premium-User
if ANTHROPIC_KEY and (KI_MODE == "cloud" or (requires_premium and user_is_premium)):
logger.info("Fallback auf Cloud-KI.")
text = await _cloud_complete(prompt, system, max_tokens, json_mode)
return (text, CLOUD_MODEL) if return_model else text
if return_model:
return (text, CLOUD_MODEL)
return (text, "cloud") if return_source else text
raise KIUnavailableError(
"KI-Modell momentan nicht erreichbar. Bitte später erneut versuchen."
) from e