max retries eingeführt
All checks were successful
Deploy mindnet to llm-node / deploy (push) Successful in 4s

This commit is contained in:
Lars 2025-12-26 10:33:40 +01:00
parent cbaf664123
commit 6ac1f318d0

View File

@ -6,9 +6,12 @@ DESCRIPTION: Hybrid-Client für Ollama, Google GenAI (Gemini) und OpenRouter.
WP-20 Fix: Bulletproof Prompt-Auflösung für format() Aufrufe.
WP-22/JSON: Optionales JSON-Schema + strict (für OpenRouter structured outputs).
FIX: Intelligente Rate-Limit Erkennung (429 Handling), v1-API Sync & Timeouts.
VERSION: 3.3.6
VERSION: 3.3.7
STATUS: Active
DEPENDENCIES: httpx, yaml, logging, asyncio, json, google-genai, openai, app.config
FIX:
- Implementiert striktes max_retries Handling für alle Provider (v.a. für Chat-Stabilität).
- Synchronisiert Rate-Limit Retries mit dem max_retries Parameter.
- Optimiert Logging für sofortige Fehlererkennung.
"""
import httpx
import yaml
@ -84,8 +87,6 @@ class LLMService:
Hole provider-spezifisches Template mit intelligenter Text-Kaskade.
HINWEIS: Dies ist nur ein Text-Lookup und verbraucht kein API-Kontingent.
Kaskade: Gewählter Provider -> Gemini (Cloud-Stil) -> Ollama (Basis-Stil).
WP-20 Fix: Garantiert die Rückgabe eines Strings, um AttributeError zu vermeiden.
"""
active_provider = provider or self.settings.MINDNET_LLM_PROVIDER
data = self.prompts.get(key, "")
@ -118,11 +119,6 @@ class LLMService:
) -> str:
"""
Haupteinstiegspunkt für LLM-Anfragen mit Priorisierung.
force_json:
- Ollama: nutzt payload["format"]="json"
- Gemini: nutzt response_mime_type="application/json"
- OpenRouter: nutzt response_format=json_object (Fallback) oder json_schema
"""
target_provider = provider or self.settings.MINDNET_LLM_PROVIDER
@ -154,11 +150,12 @@ class LLMService:
strict_json_schema: bool
) -> str:
"""
Routet die Anfrage mit intelligenter Rate-Limit Erkennung (WP-20 + WP-76).
Schleife läuft über MINDNET_LLM_RATE_LIMIT_RETRIES.
Routet die Anfrage mit intelligenter Rate-Limit Erkennung.
Nutzt max_retries um die Rate-Limit Schleife zu begrenzen.
"""
rate_limit_attempts = 0
max_rate_retries = getattr(self.settings, "LLM_RATE_LIMIT_RETRIES", 3)
# FIX: Wir nutzen max_retries als Limit für Rate-Limit Versuche, wenn explizit klein gewählt (z.B. Chat)
max_rate_retries = min(max_retries, getattr(self.settings, "LLM_RATE_LIMIT_RETRIES", 3))
wait_time = getattr(self.settings, "LLM_RATE_LIMIT_WAIT", 60.0)
while rate_limit_attempts <= max_rate_retries:
@ -182,18 +179,17 @@ class LLMService:
except Exception as e:
err_str = str(e)
# Intelligente 429 Erkennung für alle Cloud-Provider
# Intelligente 429 Erkennung
is_rate_limit = any(x in err_str for x in ["429", "RESOURCE_EXHAUSTED", "rate_limited", "Too Many Requests"])
if is_rate_limit and rate_limit_attempts < max_rate_retries:
rate_limit_attempts += 1
logger.warning(
f"⏳ [LLMService] Rate Limit (429) detected from {provider}. "
f"Attempt {rate_limit_attempts}/{max_rate_retries}. "
f"Waiting {wait_time}s before cloud retry..."
f"⏳ [LLMService] Rate Limit detected from {provider}. "
f"Attempt {rate_limit_attempts}/{max_rate_retries}. Waiting {wait_time}s..."
)
await asyncio.sleep(wait_time)
continue # Nächster Versuch in der Cloud-Schleife
continue
# Wenn kein Rate-Limit oder Retries erschöpft -> Fallback zu Ollama (falls aktiviert)
if self.settings.LLM_FALLBACK_ENABLED and provider != "ollama":
@ -206,14 +202,12 @@ class LLMService:
async def _execute_google(self, prompt, system, force_json, model_override):
"""Native Google SDK Integration (Gemini) mit v1 Fix."""
model = model_override or self.settings.GEMINI_MODEL
# Fix: Bereinige Modellnamen (Entfernung von 'models/' Präfix)
clean_model = model.replace("models/", "")
config = types.GenerateContentConfig(
system_instruction=system,
response_mime_type="application/json" if force_json else "text/plain"
)
# Thread-Offloading mit striktem Timeout gegen "Hangs"
response = await asyncio.wait_for(
asyncio.to_thread(
self.google_client.models.generate_content,
@ -233,7 +227,7 @@ class LLMService:
json_schema_name: str = "mindnet_json",
strict_json_schema: bool = True
) -> str:
"""OpenRouter API Integration (OpenAI-kompatibel) mit Schema-Support."""
"""OpenRouter API Integration (OpenAI-kompatibel)."""
model = model_override or self.settings.OPENROUTER_MODEL
messages = []
if system:
@ -262,14 +256,14 @@ class LLMService:
return response.choices[0].message.content.strip()
async def _execute_ollama(self, prompt, system, force_json, max_retries, base_delay):
"""Lokaler Ollama Call mit exponentiellem Backoff."""
"""Lokaler Ollama Call mit striktem Retry-Limit."""
payload = {
"model": self.settings.LLM_MODEL,
"prompt": prompt,
"stream": False,
"options": {
"temperature": 0.1 if force_json else 0.7,
"num_ctx": 8192
"num_ctx": 8192 # Begrenzung für Stabilität (WP-20)
}
}
if force_json:
@ -285,9 +279,11 @@ class LLMService:
return res.json().get("response", "").strip()
except Exception as e:
attempt += 1
# WICHTIG: Wenn max_retries=0 (Chat), bricht dies nach dem 1. Versuch (attempt=1) sofort ab.
if attempt > max_retries:
logger.error(f"❌ Ollama Error after {attempt} retries: {e}")
logger.error(f"❌ Ollama request failed after {attempt} attempt(s): {e}")
raise e
wait_time = base_delay * (2 ** (attempt - 1))
logger.warning(f"⚠️ Ollama attempt {attempt} failed. Retrying in {wait_time}s...")
await asyncio.sleep(wait_time)
@ -300,6 +296,7 @@ class LLMService:
final_prompt = rag_template.format(context_str=context_str, query=query)
# RAG Aufrufe im Chat nutzen nun standardmäßig max_retries=2 (überschreibbar)
return await self.generate_raw_response(
final_prompt,
system=system_prompt,