KI-Vision-Model, Breed-Scraper, Karte/Routen + Release v1292

Parallele Arbeit (auf Staging mitgetestet): KI-Vision-Model (VISION_MODEL in
ki.py/routes, im KI-Status sichtbar), Breed-Scraper-Anpassungen
(breed_enricher/breed_evaluator, evaluate_enrichment mit user_id),
Karten-/Routen-Änderungen (map.js, routes.js), kleinere UI-Anpassungen
(admin.js, components.css), docker-compose, MARKETING, nav-loop-Test.

Version-Bump auf 1292 (VERSION, sw.js, app.js, index.html, landing.html).
This commit is contained in:
rene 2026-06-14 20:23:21 +02:00
parent 51aad6cf1b
commit f7370028da
17 changed files with 322 additions and 100 deletions

View file

@ -360,30 +360,47 @@ async def _fetch_wikimedia_photo(name: str) -> str | None:
return None
async def _haiku_complete(prompt: str) -> str:
"""Claude Haiku direkt aufrufen (immer Cloud, für maximale Genauigkeit)."""
import anthropic
async def _haiku_complete(prompt: str) -> tuple[str, str]:
"""
Fakten-Extraktion. Bevorzugt Claude Haiku (günstig + genau); ist kein
Cloud-Key gesetzt oder die Cloud nicht erreichbar, fällt es sauber auf das
lokale Modell (LM Studio) zurück, statt hart abzubrechen.
Returns (text, model) model fließt in wiki_rassen.ki_model, damit der
Evaluator lokal-angereicherte Rassen weiterhin zur QC erkennt.
"""
key = os.getenv("ANTHROPIC_API_KEY", "")
if not key:
raise RuntimeError("ANTHROPIC_API_KEY nicht gesetzt")
def _call():
client = anthropic.Anthropic(api_key=key)
return client.messages.create(
model=_HAIKU_MODEL,
max_tokens=700,
system=[{
"type": "text",
"text": _SYSTEM,
"cache_control": {"type": "ephemeral"},
}],
messages=[{"role": "user", "content": prompt}],
)
# 1. Bevorzugt: Claude Haiku direkt (günstigstes Cloud-Modell)
if key:
try:
import anthropic
loop = asyncio.get_event_loop()
resp = await loop.run_in_executor(None, _call)
return resp.content[0].text.strip()
def _call():
client = anthropic.Anthropic(api_key=key)
return client.messages.create(
model=_HAIKU_MODEL,
max_tokens=700,
system=[{
"type": "text",
"text": _SYSTEM,
"cache_control": {"type": "ephemeral"},
}],
messages=[{"role": "user", "content": prompt}],
)
loop = asyncio.get_event_loop()
resp = await loop.run_in_executor(None, _call)
return resp.content[0].text.strip(), _HAIKU_MODEL
except Exception as e:
logger.warning("Haiku (Cloud) nicht erreichbar, Fallback lokal: %s", e)
# 2. Fallback: lokales Modell über die zentrale KI-Abstraktion
import ki
if ki.KI_MODE == "off":
raise RuntimeError("Kein Cloud-Key und KI_MODE=off — Anreicherung nicht möglich.")
text = await ki._local_complete(prompt, _SYSTEM, max_tokens=700, json_mode=False)
return text, ki.LOCAL_MODEL
async def _enrich_one(rasse, dry_run: bool = False) -> bool:
@ -411,12 +428,12 @@ async def _enrich_one(rasse, dry_run: bool = False) -> bool:
logger.info("[DRY-RUN] Gefunden: %s (WP-%s, %d Zeichen)", name, wiki_lang.upper(), len(wiki_text))
return True
# 2. Haiku extrahiert Fakten aus dem Quelltext
# 2. KI extrahiert Fakten aus dem Quelltext (Haiku, sonst lokaler Fallback)
prompt = _PROMPT.format(name=name, lang=wiki_lang.upper(), wiki_text=wiki_text)
try:
raw = await _haiku_complete(prompt)
raw, used_model = await _haiku_complete(prompt)
except Exception as e:
logger.error("Haiku-Anfrage fehlgeschlagen für %s: %s", name, e)
logger.error("KI-Anfrage fehlgeschlagen für %s: %s", name, e)
await asyncio.sleep(3)
return False
@ -435,7 +452,7 @@ async def _enrich_one(rasse, dry_run: bool = False) -> bool:
if "temperament" in updates:
updates["temperament"] = translate_temperament(updates["temperament"])
updates["ki_enriched"] = 1
updates["ki_model"] = _HAIKU_MODEL
updates["ki_model"] = used_model
updates["ki_source"] = f"wikipedia_{wiki_lang}"
cols = ", ".join(f"{k}=?" for k in updates)