import re import httpx from .config import MINIMAX_KEY, MINIMAX_MODEL, MINIMAX_URL THINK_BLOCK = re.compile(r".*?", re.DOTALL) SYSTEM = ( "Du schreibst kurze deutsche Teaser für Horrorfilme. " "Antworte immer auf Deutsch, auch wenn die Vorlage englisch ist. " "Übersetze die Vorlage nicht, sondern formuliere neu. " "Genau zwei Sätze, zusammen höchstens 40 Wörter. " "Atmosphärisch und reißerisch, aber ohne Spoiler und ohne das Ende zu verraten. " "Kein Filmtitel im Text, keine Anführungszeichen, keine Einleitung. " "Gib nur den Teaser aus." ) def build_prompt(movie): year = movie.get("year") or "unbekannt" overview = (movie.get("overview") or "").strip() if overview: return ( f"Film: {movie['title']} ({year})\n" f"Inhaltsangabe: {overview}\n\n" "Schreibe den Teaser." ) return ( f"Film: {movie['title']} ({year})\n" "Es liegt keine Inhaltsangabe vor. Schreibe einen passenden, " "allgemein gehaltenen Horror-Teaser zu diesem Titel." ) def clean(text): text = THINK_BLOCK.sub("", text) return " ".join(text.split()).strip('"„“ ') def fallback(movie): """MiniMax nicht erreichbar: gekürzte TMDB-Synopsis.""" overview = (movie.get("overview") or "").strip() if not overview: return "" sentences = re.split(r"(?<=[.!?])\s+", overview) return " ".join(sentences[:2]) def build_payload(movie): return { "model": MINIMAX_MODEL, "messages": [ {"role": "system", "content": SYSTEM}, {"role": "user", "content": build_prompt(movie)}, ], # M3 denkt sonst über das Token-Limit hinaus und liefert nur den -Block "reasoning_effort": "low", "max_tokens": 1500, "temperature": 0.8, } async def ask(http, movie, max_tokens): payload = {**build_payload(movie), "max_tokens": max_tokens} resp = await http.post(MINIMAX_URL, json=payload) resp.raise_for_status() return clean(resp.json()["choices"][0]["message"]["content"]) def complete(text): """Am Token-Limit bricht der Teaser mitten im Wort ab.""" return bool(text) and text.rstrip().endswith((".", "!", "?", "…", '"')) async def generate(http, movie, semaphore): async with semaphore: try: text = await ask(http, movie, 1500) if not complete(text): # Denkt das Modell zu lang, bleibt vom Teaser nichts oder # nur ein angefangener Satz übrig retry = await ask(http, movie, 4000) if complete(retry) or not text: text = retry return text or fallback(movie) except (httpx.HTTPError, KeyError, IndexError): return fallback(movie) def client(): return httpx.AsyncClient( headers={ "Authorization": f"Bearer {MINIMAX_KEY}", "Content-Type": "application/json", }, timeout=120.0, )