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,
)