verschlanken aus Sicht WP11
This commit is contained in:
parent
d965d96cde
commit
c5e613d2b1
|
|
@ -1,6 +1,6 @@
|
||||||
"""
|
"""
|
||||||
app/services/llm_service.py — LLM Client (Ollama)
|
app/services/llm_service.py — LLM Client (Ollama)
|
||||||
Version: 0.5.1 (Full: Retry Strategy + Chat Support + JSON Mode)
|
Version: 0.5.2 (Fix: Removed strict limits, increased Context)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
|
@ -15,7 +15,7 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class Settings:
|
class Settings:
|
||||||
OLLAMA_URL = os.getenv("MINDNET_OLLAMA_URL", "http://127.0.0.1:11434")
|
OLLAMA_URL = os.getenv("MINDNET_OLLAMA_URL", "http://127.0.0.1:11434")
|
||||||
# Timeout für den einzelnen Request (nicht für den gesamten Retry-Zyklus)
|
# Timeout für die Generierung (lang)
|
||||||
LLM_TIMEOUT = float(os.getenv("MINDNET_LLM_TIMEOUT", 300.0))
|
LLM_TIMEOUT = float(os.getenv("MINDNET_LLM_TIMEOUT", 300.0))
|
||||||
LLM_MODEL = os.getenv("MINDNET_LLM_MODEL", "phi3:mini")
|
LLM_MODEL = os.getenv("MINDNET_LLM_MODEL", "phi3:mini")
|
||||||
PROMPTS_PATH = os.getenv("MINDNET_PROMPTS_PATH", "./config/prompts.yaml")
|
PROMPTS_PATH = os.getenv("MINDNET_PROMPTS_PATH", "./config/prompts.yaml")
|
||||||
|
|
@ -28,13 +28,17 @@ class LLMService:
|
||||||
self.settings = get_settings()
|
self.settings = get_settings()
|
||||||
self.prompts = self._load_prompts()
|
self.prompts = self._load_prompts()
|
||||||
|
|
||||||
# Connection Limits erhöhen für Parallelität im Import
|
# FIX 1: Keine künstlichen Limits mehr. httpx defaults (100) sind besser.
|
||||||
limits = httpx.Limits(max_keepalive_connections=5, max_connections=10)
|
# Wir wollen nicht, dass der Chat wartet, nur weil im Hintergrund Embeddings laufen.
|
||||||
|
|
||||||
|
# Timeout-Konfiguration:
|
||||||
|
# connect=10.0: Wenn Ollama nicht da ist, failen wir schnell.
|
||||||
|
# read=LLM_TIMEOUT: Wenn Ollama denkt, geben wir ihm Zeit.
|
||||||
|
self.timeout = httpx.Timeout(self.settings.LLM_TIMEOUT, connect=10.0)
|
||||||
|
|
||||||
self.client = httpx.AsyncClient(
|
self.client = httpx.AsyncClient(
|
||||||
base_url=self.settings.OLLAMA_URL,
|
base_url=self.settings.OLLAMA_URL,
|
||||||
timeout=self.settings.LLM_TIMEOUT,
|
timeout=self.timeout
|
||||||
limits=limits
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def _load_prompts(self) -> dict:
|
def _load_prompts(self) -> dict:
|
||||||
|
|
@ -53,24 +57,21 @@ class LLMService:
|
||||||
prompt: str,
|
prompt: str,
|
||||||
system: str = None,
|
system: str = None,
|
||||||
force_json: bool = False,
|
force_json: bool = False,
|
||||||
max_retries: int = 0, # Standard: 0 (Chat failt sofort, Import nutzt >0)
|
max_retries: int = 0,
|
||||||
base_delay: float = 5.0 # Start-Wartezeit für Backoff
|
base_delay: float = 2.0
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
Führt einen LLM Call aus.
|
Führt einen LLM Call aus.
|
||||||
Features:
|
|
||||||
- JSON Mode (für Semantic Analyzer)
|
|
||||||
- System Prompt (für Persona)
|
|
||||||
- Aggressive Retry (für robusten Import bei Überlast)
|
|
||||||
"""
|
"""
|
||||||
payload: Dict[str, Any] = {
|
payload: Dict[str, Any] = {
|
||||||
"model": self.settings.LLM_MODEL,
|
"model": self.settings.LLM_MODEL,
|
||||||
"prompt": prompt,
|
"prompt": prompt,
|
||||||
"stream": False,
|
"stream": False,
|
||||||
"options": {
|
"options": {
|
||||||
# JSON braucht niedrige Temperature für valide Syntax
|
|
||||||
"temperature": 0.1 if force_json else 0.7,
|
"temperature": 0.1 if force_json else 0.7,
|
||||||
"num_ctx": 4096
|
# FIX 2: Kontext auf 8192 erhöht.
|
||||||
|
# Wichtig für komplexe Schemas und JSON-Stabilität.
|
||||||
|
"num_ctx": 8192
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,7 +83,6 @@ class LLMService:
|
||||||
|
|
||||||
attempt = 0
|
attempt = 0
|
||||||
|
|
||||||
# RETRY LOOP
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
response = await self.client.post("/api/generate", json=payload)
|
response = await self.client.post("/api/generate", json=payload)
|
||||||
|
|
@ -91,43 +91,29 @@ class LLMService:
|
||||||
data = response.json()
|
data = response.json()
|
||||||
return data.get("response", "").strip()
|
return data.get("response", "").strip()
|
||||||
else:
|
else:
|
||||||
# HTTP Fehler simulieren, um in den except-Block zu springen
|
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# CATCH-ALL: Wir fangen Timeouts, Connection Errors UND Protokollfehler
|
|
||||||
attempt += 1
|
attempt += 1
|
||||||
|
|
||||||
# Check: Haben wir noch Versuche?
|
|
||||||
if attempt > max_retries:
|
if attempt > max_retries:
|
||||||
# Finaler Fehler (wird im Chat oder Log angezeigt)
|
|
||||||
logger.error(f"LLM Final Error (Versuch {attempt}): {e}")
|
logger.error(f"LLM Final Error (Versuch {attempt}): {e}")
|
||||||
return "Interner LLM Fehler."
|
# Wir werfen den Fehler weiter, damit der Router nicht "Interner Fehler" als Typ interpretiert
|
||||||
|
raise e
|
||||||
|
|
||||||
# Backoff berechnen (5s, 10s, 20s, 40s...)
|
|
||||||
wait_time = base_delay * (2 ** (attempt - 1))
|
wait_time = base_delay * (2 ** (attempt - 1))
|
||||||
error_msg = str(e) if str(e) else repr(e)
|
logger.warning(f"⚠️ LLM Retry ({attempt}/{max_retries}) in {wait_time}s: {e}")
|
||||||
|
|
||||||
logger.warning(
|
|
||||||
f"⚠️ LLM Fehler ({attempt}/{max_retries}). "
|
|
||||||
f"Warte {wait_time}s zur Abkühlung... Grund: {error_msg}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Warten und Loop wiederholen
|
|
||||||
await asyncio.sleep(wait_time)
|
await asyncio.sleep(wait_time)
|
||||||
|
|
||||||
async def generate_rag_response(self, query: str, context_str: str) -> str:
|
async def generate_rag_response(self, query: str, context_str: str) -> str:
|
||||||
"""
|
"""
|
||||||
WICHTIG FÜR CHAT:
|
WICHTIG FÜR CHAT:
|
||||||
Generiert eine Antwort basierend auf RAG-Kontext.
|
Kein JSON, keine Retries (User-Latency).
|
||||||
Nutzt KEINE Retries (User will nicht warten), KEIN JSON.
|
|
||||||
"""
|
"""
|
||||||
system_prompt = self.prompts.get("system_prompt", "")
|
system_prompt = self.prompts.get("system_prompt", "")
|
||||||
rag_template = self.prompts.get("rag_template", "{context_str}\n\n{query}")
|
rag_template = self.prompts.get("rag_template", "{context_str}\n\n{query}")
|
||||||
|
|
||||||
final_prompt = rag_template.format(context_str=context_str, query=query)
|
final_prompt = rag_template.format(context_str=context_str, query=query)
|
||||||
|
|
||||||
# Chat-Call: force_json=False, max_retries=0
|
|
||||||
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