""" Qualitätsbewertung der KI-Rassen-Anreicherung via Claude (LLM-as-Judge). Bewertet eine Zufallsstichprobe angereicherter Rassen nach: - Vollständigkeit (alle Felder befüllt?) - Korrektheit (plausible Fakten?) - Sprachqualität (natürliches Deutsch?) - Konsistenz (Felder stimmen untereinander überein?) """ import json import logging import os import asyncio logger = logging.getLogger(__name__) _EVAL_PROMPT = '''\ Du bist ein Qualitätsprüfer für Hunderassen-Daten. Bewerte den folgenden \ Datensatz für die Rasse "{name}" auf einer Skala von 1-5. Datensatz: {data} Antworte NUR als JSON: {{ "vollstaendigkeit": <1-5>, "korrektheit": <1-5>, "sprachqualitaet": <1-5>, "konsistenz": <1-5>, "gesamt": <1-5>, "hinweis": "Kurze Begründung oder auffällige Mängel (max 1 Satz)" }} Bewertungskriterien: - Vollständigkeit: Sind beschreibung, vorkommen_de, groesse, gewicht, \ lebensdauer, aktivitaet, erfahrung, kinder_geeignet, wohnung_geeignet, \ temperament alle befüllt? - Korrektheit: Stimmen die Angaben mit bekannten Fakten überein? - Sprachqualität: Ist der deutsche Text natürlich, fehlerfrei und informativ? - Konsistenz: Passen die Felder zueinander (z.B. Gewicht zur Größe, \ Aktivität zur Erfahrung)? ''' async def evaluate_enrichment(sample_size: int = 20, user_id: int | None = None) -> dict: """ Bewertet `sample_size` zufällig gewählte angereicherte Rassen als LLM-as-Judge. Läuft über die zentrale KI-Abstraktion (ki.complete). Admins/Moderatoren werden dort Cloud-priorisiert (Claude); ist die Cloud nicht erreichbar, fällt die Bewertung sauber auf das lokale Modell zurück, statt hart abzubrechen. Returns dict mit aggregierten Scores und Einzelergebnissen. """ import sys, os sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from database import db import ki if ki.KI_MODE == "off": raise RuntimeError("KI ist deaktiviert (KI_MODE=off) — Evaluierung nicht möglich.") with db() as conn: rassen = conn.execute( """SELECT name, beschreibung, vorkommen_de, groesse, gewicht_min_kg, gewicht_max_kg, lebensdauer, aktivitaet, erfahrung, kinder_geeignet, wohnung_geeignet, temperament, ki_model FROM wiki_rassen WHERE ki_enriched = 1 AND (ki_model IS NULL OR ki_model NOT LIKE 'claude%') ORDER BY RANDOM() LIMIT ?""", (sample_size,), ).fetchall() if not rassen: return {"error": "Keine angereicherten Rassen gefunden."} _EVAL_SYSTEM = "Du bist ein präziser Qualitätsprüfer. Antworte ausschließlich als JSON." results = [] sources = set() totals = {"vollstaendigkeit": 0, "korrektheit": 0, "sprachqualitaet": 0, "konsistenz": 0, "gesamt": 0} for rasse in rassen: name = rasse["name"] data = { "beschreibung": rasse["beschreibung"], "vorkommen_de": rasse["vorkommen_de"], "groesse": rasse["groesse"], "gewicht_min_kg": rasse["gewicht_min_kg"], "gewicht_max_kg": rasse["gewicht_max_kg"], "lebensdauer": rasse["lebensdauer"], "aktivitaet": rasse["aktivitaet"], "erfahrung": rasse["erfahrung"], "kinder_geeignet": rasse["kinder_geeignet"], "wohnung_geeignet": rasse["wohnung_geeignet"], "temperament": rasse["temperament"], } prompt = _EVAL_PROMPT.format( name=name, data=json.dumps(data, ensure_ascii=False, indent=2), ) try: raw, source = await ki.complete( prompt, system=_EVAL_SYSTEM, max_tokens=256, json_mode=True, user_id=user_id, return_source=True, ) sources.add(source) # JSON extrahieren (lokale Modelle wrappen gern in ```json … ```) import re match = re.search(r"\{[\s\S]+\}", raw) scores = json.loads(match.group(0)) if match else {} entry = {"name": name, **scores} results.append(entry) for key in totals: totals[key] += scores.get(key, 0) except Exception as e: logger.error("Evaluierung fehlgeschlagen für %s: %s", name, e) results.append({"name": name, "error": str(e)}) await asyncio.sleep(0.5) count = len([r for r in results if "error" not in r]) averages = {k: round(v / count, 2) for k, v in totals.items()} if count else {} judge_source = "/".join(sorted(sources)) if sources else "unbekannt" return { "sample_size": len(rassen), "evaluated": count, "averages": averages, "judge_source": judge_source, # "cloud" (Claude) oder "local" (LM Studio) "results": results, }