max retries eingeführt
All checks were successful
Deploy mindnet to llm-node / deploy (push) Successful in 4s
All checks were successful
Deploy mindnet to llm-node / deploy (push) Successful in 4s
This commit is contained in:
parent
cbaf664123
commit
6ac1f318d0
|
|
@ -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-20 Fix: Bulletproof Prompt-Auflösung für format() Aufrufe.
|
||||||
WP-22/JSON: Optionales JSON-Schema + strict (für OpenRouter structured outputs).
|
WP-22/JSON: Optionales JSON-Schema + strict (für OpenRouter structured outputs).
|
||||||
FIX: Intelligente Rate-Limit Erkennung (429 Handling), v1-API Sync & Timeouts.
|
FIX: Intelligente Rate-Limit Erkennung (429 Handling), v1-API Sync & Timeouts.
|
||||||
VERSION: 3.3.6
|
VERSION: 3.3.7
|
||||||
STATUS: Active
|
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 httpx
|
||||||
import yaml
|
import yaml
|
||||||
|
|
@ -84,8 +87,6 @@ class LLMService:
|
||||||
Hole provider-spezifisches Template mit intelligenter Text-Kaskade.
|
Hole provider-spezifisches Template mit intelligenter Text-Kaskade.
|
||||||
HINWEIS: Dies ist nur ein Text-Lookup und verbraucht kein API-Kontingent.
|
HINWEIS: Dies ist nur ein Text-Lookup und verbraucht kein API-Kontingent.
|
||||||
Kaskade: Gewählter Provider -> Gemini (Cloud-Stil) -> Ollama (Basis-Stil).
|
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
|
active_provider = provider or self.settings.MINDNET_LLM_PROVIDER
|
||||||
data = self.prompts.get(key, "")
|
data = self.prompts.get(key, "")
|
||||||
|
|
@ -118,11 +119,6 @@ class LLMService:
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
Haupteinstiegspunkt für LLM-Anfragen mit Priorisierung.
|
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
|
target_provider = provider or self.settings.MINDNET_LLM_PROVIDER
|
||||||
|
|
||||||
|
|
@ -154,11 +150,12 @@ class LLMService:
|
||||||
strict_json_schema: bool
|
strict_json_schema: bool
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
Routet die Anfrage mit intelligenter Rate-Limit Erkennung (WP-20 + WP-76).
|
Routet die Anfrage mit intelligenter Rate-Limit Erkennung.
|
||||||
Schleife läuft über MINDNET_LLM_RATE_LIMIT_RETRIES.
|
Nutzt max_retries um die Rate-Limit Schleife zu begrenzen.
|
||||||
"""
|
"""
|
||||||
rate_limit_attempts = 0
|
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)
|
wait_time = getattr(self.settings, "LLM_RATE_LIMIT_WAIT", 60.0)
|
||||||
|
|
||||||
while rate_limit_attempts <= max_rate_retries:
|
while rate_limit_attempts <= max_rate_retries:
|
||||||
|
|
@ -182,18 +179,17 @@ class LLMService:
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
err_str = str(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"])
|
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:
|
if is_rate_limit and rate_limit_attempts < max_rate_retries:
|
||||||
rate_limit_attempts += 1
|
rate_limit_attempts += 1
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"⏳ [LLMService] Rate Limit (429) detected from {provider}. "
|
f"⏳ [LLMService] Rate Limit detected from {provider}. "
|
||||||
f"Attempt {rate_limit_attempts}/{max_rate_retries}. "
|
f"Attempt {rate_limit_attempts}/{max_rate_retries}. Waiting {wait_time}s..."
|
||||||
f"Waiting {wait_time}s before cloud retry..."
|
|
||||||
)
|
)
|
||||||
await asyncio.sleep(wait_time)
|
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)
|
# Wenn kein Rate-Limit oder Retries erschöpft -> Fallback zu Ollama (falls aktiviert)
|
||||||
if self.settings.LLM_FALLBACK_ENABLED and provider != "ollama":
|
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):
|
async def _execute_google(self, prompt, system, force_json, model_override):
|
||||||
"""Native Google SDK Integration (Gemini) mit v1 Fix."""
|
"""Native Google SDK Integration (Gemini) mit v1 Fix."""
|
||||||
model = model_override or self.settings.GEMINI_MODEL
|
model = model_override or self.settings.GEMINI_MODEL
|
||||||
# Fix: Bereinige Modellnamen (Entfernung von 'models/' Präfix)
|
|
||||||
clean_model = model.replace("models/", "")
|
clean_model = model.replace("models/", "")
|
||||||
|
|
||||||
config = types.GenerateContentConfig(
|
config = types.GenerateContentConfig(
|
||||||
system_instruction=system,
|
system_instruction=system,
|
||||||
response_mime_type="application/json" if force_json else "text/plain"
|
response_mime_type="application/json" if force_json else "text/plain"
|
||||||
)
|
)
|
||||||
# Thread-Offloading mit striktem Timeout gegen "Hangs"
|
|
||||||
response = await asyncio.wait_for(
|
response = await asyncio.wait_for(
|
||||||
asyncio.to_thread(
|
asyncio.to_thread(
|
||||||
self.google_client.models.generate_content,
|
self.google_client.models.generate_content,
|
||||||
|
|
@ -233,7 +227,7 @@ class LLMService:
|
||||||
json_schema_name: str = "mindnet_json",
|
json_schema_name: str = "mindnet_json",
|
||||||
strict_json_schema: bool = True
|
strict_json_schema: bool = True
|
||||||
) -> str:
|
) -> str:
|
||||||
"""OpenRouter API Integration (OpenAI-kompatibel) mit Schema-Support."""
|
"""OpenRouter API Integration (OpenAI-kompatibel)."""
|
||||||
model = model_override or self.settings.OPENROUTER_MODEL
|
model = model_override or self.settings.OPENROUTER_MODEL
|
||||||
messages = []
|
messages = []
|
||||||
if system:
|
if system:
|
||||||
|
|
@ -262,14 +256,14 @@ class LLMService:
|
||||||
return response.choices[0].message.content.strip()
|
return response.choices[0].message.content.strip()
|
||||||
|
|
||||||
async def _execute_ollama(self, prompt, system, force_json, max_retries, base_delay):
|
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 = {
|
payload = {
|
||||||
"model": self.settings.LLM_MODEL,
|
"model": self.settings.LLM_MODEL,
|
||||||
"prompt": prompt,
|
"prompt": prompt,
|
||||||
"stream": False,
|
"stream": False,
|
||||||
"options": {
|
"options": {
|
||||||
"temperature": 0.1 if force_json else 0.7,
|
"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:
|
if force_json:
|
||||||
|
|
@ -285,9 +279,11 @@ class LLMService:
|
||||||
return res.json().get("response", "").strip()
|
return res.json().get("response", "").strip()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
attempt += 1
|
attempt += 1
|
||||||
|
# WICHTIG: Wenn max_retries=0 (Chat), bricht dies nach dem 1. Versuch (attempt=1) sofort ab.
|
||||||
if attempt > max_retries:
|
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
|
raise e
|
||||||
|
|
||||||
wait_time = base_delay * (2 ** (attempt - 1))
|
wait_time = base_delay * (2 ** (attempt - 1))
|
||||||
logger.warning(f"⚠️ Ollama attempt {attempt} failed. Retrying in {wait_time}s...")
|
logger.warning(f"⚠️ Ollama attempt {attempt} failed. Retrying in {wait_time}s...")
|
||||||
await asyncio.sleep(wait_time)
|
await asyncio.sleep(wait_time)
|
||||||
|
|
@ -300,6 +296,7 @@ class LLMService:
|
||||||
|
|
||||||
final_prompt = rag_template.format(context_str=context_str, query=query)
|
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(
|
return await self.generate_raw_response(
|
||||||
final_prompt,
|
final_prompt,
|
||||||
system=system_prompt,
|
system=system_prompt,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user