diff --git a/app/core/ingestion/ingestion_utils.py b/app/core/ingestion/ingestion_utils.py
index f8af8ff..74cb1e6 100644
--- a/app/core/ingestion/ingestion_utils.py
+++ b/app/core/ingestion/ingestion_utils.py
@@ -1,37 +1,49 @@
"""
FILE: app/core/ingestion/ingestion_utils.py
DESCRIPTION: Hilfswerkzeuge für JSON-Recovery, Typ-Registry und Konfigurations-Lookups.
- AUDIT v2.13.7: Dynamisierung von Cleanup-Patterns und Default-Typen (WP-14).
+ AUDIT v2.13.8: Zentralisierung der Text-Bereinigung für LLM-Antworten.
"""
import os
import json
import re
import yaml
-from typing import Any, Optional, Dict
+from typing import Any, Optional, Dict, List
-def extract_json_from_response(text: str, registry: Optional[dict] = None) -> Any:
+def clean_llm_text(text: str, registry: Optional[dict] = None) -> str:
"""
- Extrahiert JSON-Daten und bereinigt LLM-Steuerzeichen (v2.11.14 Logic).
- WP-14: Nutzt nun dynamische cleanup_patterns aus der Registry.
+ Entfernt LLM-Steuerzeichen und Artefakte aus einem Text.
+ Nutzt die cleanup_patterns aus der Registry oder Standardwerte.
"""
- if not text or not isinstance(text, str):
- return []
-
- # Fallback-Patterns für die Bereinigung
- patterns = ["", "", "[OUT]", "[/OUT]"]
+ if not text or not isinstance(text, str):
+ return ""
+
+ # Fallback-Patterns, falls die Registry nicht greift
+ default_patterns = ["", "", "[OUT]", "[/OUT]"]
# Falls keine Registry übergeben wurde, versuchen wir sie zu laden
reg = registry or load_type_registry()
- if reg:
- # Lade Patterns aus llm_settings (WP-14 Erweiterung)
- patterns = reg.get("llm_settings", {}).get("cleanup_patterns", patterns)
+
+ # Lade Patterns aus llm_settings (WP-14 Erweiterung)
+ patterns: List[str] = reg.get("llm_settings", {}).get("cleanup_patterns", default_patterns)
clean = text
for p in patterns:
clean = clean.replace(p, "")
- clean = clean.strip()
+ return clean.strip()
+
+def extract_json_from_response(text: str, registry: Optional[dict] = None) -> Any:
+ """
+ Extrahiert JSON-Daten und bereinigt LLM-Steuerzeichen.
+ WP-14: Nutzt nun die zentrale clean_llm_text Funktion.
+ """
+ if not text:
+ return []
+ # 1. Text zentral bereinigen
+ clean = clean_llm_text(text, registry)
+
+ # 2. Markdown-Code-Blöcke extrahieren
match = re.search(r"```(?:json)?\s*(.*?)\s*```", clean, re.DOTALL)
payload = match.group(1) if match else clean
diff --git a/app/services/llm_service.py b/app/services/llm_service.py
index 17ecea6..b5ce923 100644
--- a/app/services/llm_service.py
+++ b/app/services/llm_service.py
@@ -6,12 +6,11 @@ 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.7
+VERSION: 3.3.8
STATUS: Active
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.
+- Integriert clean_llm_text zur Entfernung von Steuerzeichen (, [OUT] etc.) in Antworten.
+- Stellt sicher, dass Chat-Antworten sauber formatiert ausgegeben werden.
"""
import httpx
import yaml
@@ -25,6 +24,9 @@ from pathlib import Path
from typing import Optional, Dict, Any, Literal
from app.config import get_settings
+# Import der zentralen Bereinigungs-Logik (WP-14 Fix)
+from app.core.ingestion.ingestion_utils import clean_llm_text
+
logger = logging.getLogger(__name__)
@@ -119,22 +121,26 @@ class LLMService:
) -> str:
"""
Haupteinstiegspunkt für LLM-Anfragen mit Priorisierung.
+ Wendet die Bereinigung auf Text-Antworten an.
"""
target_provider = provider or self.settings.MINDNET_LLM_PROVIDER
if priority == "background":
async with LLMService._background_semaphore:
- return await self._dispatch(
+ res = await self._dispatch(
target_provider, prompt, system, force_json,
max_retries, base_delay, model_override,
json_schema, json_schema_name, strict_json_schema
)
+ # WP-14 Fix: Bereinige Text-Antworten vor Rückgabe
+ return clean_llm_text(res) if not force_json else res
- return await self._dispatch(
+ res = await self._dispatch(
target_provider, prompt, system, force_json,
max_retries, base_delay, model_override,
json_schema, json_schema_name, strict_json_schema
)
+ return clean_llm_text(res) if not force_json else res
async def _dispatch(
self,
@@ -206,6 +212,7 @@ class LLMService:
config = types.GenerateContentConfig(
system_instruction=system,
+ # WICHTIG: Gemini 1.5+ unterstützt response_mime_type nativ
response_mime_type="application/json" if force_json else "text/plain"
)
response = await asyncio.wait_for(
@@ -297,6 +304,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)
+ # Durch den Aufruf von generate_raw_response wird die Bereinigung automatisch angewendet.
return await self.generate_raw_response(
final_prompt,
system=system_prompt,