76 lines
2.7 KiB
Python
76 lines
2.7 KiB
Python
"""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 = 4
|
|
PAUSE = 2.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")
|
|
|
|
|
|
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 * (2 ** 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
|