""" Assistant Service for RAG Handles Q&A using LLM with retrieved context. """ import httpx from typing import List, Optional, Dict, Any import structlog from .search import SearchService logger = structlog.get_logger() SYSTEM_PROMPT = """Du bist ein Experte für Datenschutz- und Compliance-Recht. Beantworte Fragen basierend auf den bereitgestellten Rechtstexten. Zitiere immer die relevanten Artikel und Paragraphen. Antworte auf Deutsch. Wenn du dir nicht sicher bist, sage das klar. """ class AssistantService: """Service for legal Q&A using RAG.""" def __init__(self, settings): self.settings = settings self.search_service = SearchService(settings) async def ask( self, question: str, context: Optional[str] = None, regulation_codes: Optional[List[str]] = None, include_citations: bool = True ) -> Dict[str, Any]: """Answer a legal question using RAG.""" # Search for relevant context search_results = await self.search_service.search( query=question, regulation_codes=regulation_codes, limit=5, min_score=0.6 ) # Build context from search results retrieved_context = "\n\n".join([ f"[{r['regulation_code']} Art. {r['article']}]: {r['content']}" for r in search_results ]) # Add user-provided context if any if context: retrieved_context = f"{context}\n\n{retrieved_context}" # Build prompt prompt = f"""Kontext aus Rechtstexten: {retrieved_context} Frage: {question} Beantworte die Frage basierend auf dem Kontext. Zitiere relevante Artikel.""" # Generate answer answer = await self._generate_response(prompt) # Extract citations citations = [] if include_citations: for result in search_results: citations.append({ "regulation_code": result["regulation_code"], "article": result.get("article", ""), "text": result["content"][:200] + "...", "relevance": result["score"] }) return { "answer": answer, "citations": citations, "confidence": self._calculate_confidence(search_results) } async def _generate_response(self, prompt: str) -> str: """Generate response using Ollama.""" try: async with httpx.AsyncClient() as client: response = await client.post( f"{self.settings.ollama_url}/api/generate", json={ "model": self.settings.llm_model, "prompt": prompt, "system": SYSTEM_PROMPT, "stream": False, "options": { "temperature": 0.3, "top_p": 0.9 } }, timeout=120.0 ) response.raise_for_status() return response.json()["response"] except httpx.TimeoutException: logger.error("LLM request timed out") return "Die Anfrage hat zu lange gedauert. Bitte versuchen Sie es erneut." except Exception as e: logger.error("LLM generation failed", error=str(e)) # Return fallback response return self._generate_fallback_response(prompt) def _generate_fallback_response(self, prompt: str) -> str: """Generate a fallback response without LLM.""" return """Basierend auf den verfügbaren Rechtstexten: Die relevanten Regelungen finden sich in den zitierten Artikeln. Für eine detaillierte rechtliche Bewertung empfehle ich die Konsultation der vollständigen Gesetzestexte oder eines Rechtsbeistands. Hinweis: Dies ist eine automatisch generierte Antwort. Der LLM-Dienst war nicht verfügbar.""" def _calculate_confidence(self, search_results: List[Dict]) -> float: """Calculate confidence score based on search results.""" if not search_results: return 0.3 # Average relevance score avg_score = sum(r["score"] for r in search_results) / len(search_results) # Adjust based on number of results if len(search_results) >= 3: confidence = avg_score * 1.1 else: confidence = avg_score * 0.9 return min(confidence, 1.0)