"""Anbindung an MiniMax über das Anthropic-Messages-Format. Kein Fallback: fehlt die Konfiguration oder antwortet der Provider nicht, bricht der Aufruf mit sichtbarer Meldung ab.""" import asyncio import httpx import config ZEITLIMIT = 180.0 MAX_TOKENS = 8000 API_VERSION = "2023-06-01" # Der Provider drosselt bei vielen gleichzeitigen Anfragen WIEDERHOLBAR = (429, 500, 502, 503, 504) WIEDERHOLUNGEN = 6 PAUSE = 2.0 # Länger als das zu warten hilft nicht mehr; dann ist die Drossel zu weit offen HOECHSTPAUSE = 60.0 class NichtKonfiguriert(ValueError): """Zugangsdaten fehlen. Eigener Typ, damit die Route das nicht mit „Prüfung läuft bereits" verwechselt — beides wäre sonst ein ValueError.""" def pruefe_konfiguration(): fehlend = [ name for name, wert in ( ("MINIMAX_API_KEY", config.MINIMAX_API_KEY), ("MINIMAX_MODELL", config.MINIMAX_MODELL), ("MINIMAX_URL", config.MINIMAX_URL), ) if not wert ] if fehlend: raise NichtKonfiguriert(f"Nicht konfiguriert: {', '.join(fehlend)} in .env fehlt") def _pause(res, versuch: int) -> float: """Wie lange bis zum nächsten Versuch — der Provider darf es selbst sagen.""" gesagt = res.headers.get("retry-after") try: return min(float(gesagt), HOECHSTPAUSE) except (TypeError, ValueError): return min(PAUSE * (2 ** versuch), HOECHSTPAUSE) async def frage(system: str, inhalt: str, temperatur: float = 0.0) -> str: """Eine Anfrage, eine Antwort — der Text der Modellantwort. Kalt geroutet: Temperatur 0, kein Thinking; die Prüfung soll nicht raten. Für Mehrheitsentscheide wird sie bewusst angehoben, sonst wären die Stimmen gleich.""" pruefe_konfiguration() daten = { "model": config.MINIMAX_MODELL, "max_tokens": MAX_TOKENS, "system": system, "messages": [{"role": "user", "content": inhalt}], "temperature": temperatur, "thinking": {"type": "disabled"}, } async with httpx.AsyncClient(timeout=httpx.Timeout(ZEITLIMIT, connect=30)) as client: for versuch in range(WIEDERHOLUNGEN): res = await client.post( config.MINIMAX_URL, headers={"x-api-key": config.MINIMAX_API_KEY, "anthropic-version": API_VERSION}, json=daten, ) if res.status_code not in WIEDERHOLBAR or versuch == WIEDERHOLUNGEN - 1: break # Zu viele Anfragen oder Serverfehler: warten und noch einmal. # Ohne das fielen bei großen Läufen ganze Stimmen aus — und ein # fehlendes Urteil sah aus wie „nichts gefunden". await asyncio.sleep(_pause(res, versuch)) if res.status_code != 200: raise RuntimeError(f"Provider antwortet mit HTTP {res.status_code}") antwort = res.json() text = "".join( b.get("text", "") for b in antwort.get("content", []) if b.get("type") == "text" ) if not text.strip(): raise RuntimeError(f"Leere Antwort (stop_reason={antwort.get('stop_reason')})") return text