diff --git a/app/core/retrieval/decision_engine.py b/app/core/retrieval/decision_engine.py index f8f355a..c5a7b89 100644 --- a/app/core/retrieval/decision_engine.py +++ b/app/core/retrieval/decision_engine.py @@ -3,12 +3,11 @@ FILE: app/core/retrieval/decision_engine.py DESCRIPTION: Der Agentic Orchestrator für WP-25. Realisiert Multi-Stream Retrieval, Intent-basiertes Routing und parallele Wissens-Synthese. -VERSION: 1.0.2 +VERSION: 1.0.3 STATUS: Active FIX: -- WP-25 ROBUSTNESS: Pre-Initialization aller Stream-Variablen zur Vermeidung von KeyErrors. -- Behebung von Template-Mismatches bei unvollständigen Strategie-Definitionen. -- Erhalt der Sicherheitskaskade für die Strategiewahl. +- WP-25 STREAM-TRACING: Kennzeichnung der Treffer mit ihrem Ursprungs-Stream. +- WP-25 ROBUSTNESS: Pre-Initialization der Stream-Variablen zur Vermeidung von KeyErrors. """ import asyncio import logging @@ -50,50 +49,41 @@ class DecisionEngine: Hauptmethode des MindNet Chats. Orchestriert den gesamten Prozess: Routing -> Retrieval -> Synthese. """ - # 1. Intent Recognition (Welches Werkzeug brauchen wir?) + # 1. Intent Recognition strategy_key = await self._determine_strategy(query) - # Sicherheits-Kaskade für die Strategiewahl strategies = self.config.get("strategies", {}) strategy = strategies.get(strategy_key) if not strategy: - logger.warning(f"⚠️ Unknown strategy '{strategy_key}'. Attempting fallback to FACT_WHAT.") + logger.warning(f"⚠️ Unknown strategy '{strategy_key}'. Fallback to FACT_WHAT.") strategy_key = "FACT_WHAT" strategy = strategies.get("FACT_WHAT") - # WP-25 FIX: Wenn FACT_WHAT ebenfalls fehlt, wähle die erste verfügbare Strategie if not strategy and strategies: strategy_key = next(iter(strategies)) strategy = strategies[strategy_key] - logger.warning(f"⚠️ 'FACT_WHAT' missing in config. Using first available: {strategy_key}") - # Letzte Rettung: Falls gar keine Strategien definiert sind if not strategy: - logger.error("❌ CRITICAL: No strategies defined in decision_engine.yaml!") return "Entschuldigung, meine Wissensbasis ist aktuell nicht konfiguriert." - # 2. Multi-Stream Retrieval (Wissen parallel sammeln) + # 2. Multi-Stream Retrieval stream_results = await self._execute_parallel_streams(strategy, query) - # 3. Synthese (Ergebnisse zu einer Antwort verweben) + # 3. Synthese return await self._generate_final_answer(strategy_key, strategy, query, stream_results) async def _determine_strategy(self, query: str) -> str: - """Nutzt den LLM-Router zur dynamischen Wahl der Such-Strategie.""" + """Nutzt den LLM-Router zur Wahl der Such-Strategie.""" prompt_key = self.config.get("settings", {}).get("router_prompt_key", "intent_router_v1") - router_prompt_template = self.llm_service.get_prompt(prompt_key) if not router_prompt_template: return "FACT_WHAT" full_prompt = router_prompt_template.format(query=query) - try: response = await self.llm_service.generate_raw_response( - full_prompt, - max_retries=1, - priority="realtime" + full_prompt, max_retries=1, priority="realtime" ) return str(response).strip().upper() except Exception as e: @@ -101,13 +91,12 @@ class DecisionEngine: return "FACT_WHAT" async def _execute_parallel_streams(self, strategy: Dict, query: str) -> Dict[str, str]: - """Führt alle in der Strategie definierten Such-Streams gleichzeitig aus.""" + """Führt Such-Streams gleichzeitig aus.""" stream_keys = strategy.get("use_streams", []) library = self.config.get("streams_library", {}) tasks = [] active_streams = [] - for key in stream_keys: stream_cfg = library.get(key) if stream_cfg: @@ -127,7 +116,10 @@ class DecisionEngine: return mapped_results async def _run_single_stream(self, name: str, cfg: Dict, query: str) -> QueryResponse: - """Bereitet eine spezialisierte Suche für einen Stream vor und führt sie aus.""" + """ + Bereitet eine spezialisierte Suche vor. + WP-25: Taggt die Treffer mit ihrem Ursprungs-Stream. + """ transformed_query = cfg.get("query_template", "{query}").format(query=query) request = QueryRequest( @@ -139,10 +131,18 @@ class DecisionEngine: explain=True ) - return await self.retriever.search(request) + # Retrieval ausführen + response = await self.retriever.search(request) + + # WP-25: STREAM-TRACING + # Markiere jeden Treffer mit dem Namen des Quell-Streams + for hit in response.results: + hit.stream_origin = name + + return response def _format_stream_context(self, response: QueryResponse) -> str: - """Wandelt QueryHits in einen kompakten String für das LLM um.""" + """Wandelt QueryHits in Kontext-Strings um.""" if not response.results: return "Keine spezifischen Informationen in diesem Stream gefunden." @@ -161,56 +161,43 @@ class DecisionEngine: query: str, stream_results: Dict[str, str] ) -> str: - """Führt die Multi-Stream Synthese durch.""" + """Führt die Synthese durch.""" provider = strategy.get("preferred_provider") or self.settings.MINDNET_LLM_PROVIDER template_key = strategy.get("prompt_template", "rag_template") template = self.llm_service.get_prompt(template_key, provider=provider) system_prompt = self.llm_service.get_prompt("system_prompt", provider=provider) - # WP-25 ROBUSTNESS FIX: - # Wir stellen sicher, dass alle Variablen, die im Template vorkommen könnten, - # zumindest mit einem leeren String initialisiert sind. + # WP-25 ROBUSTNESS: Pre-Initialization all_possible_streams = ["values_stream", "facts_stream", "biography_stream", "risk_stream", "tech_stream"] template_vars = {s: "" for s in all_possible_streams} - - # Überschreiben mit tatsächlichen Ergebnissen template_vars.update(stream_results) template_vars["query"] = query prepend = strategy.get("prepend_instruction", "") try: - # Sicherheitscheck: Sind alle benötigten Platzhalter im Template vorhanden? final_prompt = template.format(**template_vars) if prepend: final_prompt = f"{prepend}\n\n{final_prompt}" response = await self.llm_service.generate_raw_response( - final_prompt, - system=system_prompt, - provider=provider, - priority="realtime" + final_prompt, system=system_prompt, provider=provider, priority="realtime" ) if not response or len(response.strip()) < 5: return await self.llm_service.generate_raw_response( - final_prompt, - system=system_prompt, - provider="ollama", - priority="realtime" + final_prompt, system=system_prompt, provider="ollama", priority="realtime" ) return response except KeyError as e: logger.error(f"Template Variable mismatch in '{template_key}': Missing {e}") - # Fallback: Einfaches Aneinanderreihen der gefundenen Stream-Inhalte fallback_context = "\n\n".join([v for v in stream_results.values() if v]) return await self.llm_service.generate_raw_response( f"Beantworte: {query}\n\nKontext:\n{fallback_context}", - system=system_prompt, - priority="realtime" + system=system_prompt, priority="realtime" ) except Exception as e: logger.error(f"Final Synthesis failed: {e}") diff --git a/app/models/dto.py b/app/models/dto.py index b04f118..f0a1258 100644 --- a/app/models/dto.py +++ b/app/models/dto.py @@ -1,7 +1,7 @@ """ FILE: app/models/dto.py DESCRIPTION: Pydantic-Modelle (DTOs) für Request/Response Bodies. Definiert das API-Schema. -VERSION: 0.7.0 (WP-25: Multi-Stream & Agentic RAG Support) +VERSION: 0.7.1 (WP-25: Stream-Tracing Support) STATUS: Active DEPENDENCIES: pydantic, typing, uuid """ @@ -122,7 +122,10 @@ class Explanation(BaseModel): # --- Response Models --- class QueryHit(BaseModel): - """Einzelnes Trefferobjekt.""" + """ + Einzelnes Trefferobjekt. + WP-25: stream_origin hinzugefügt für Tracing und Feedback-Optimierung. + """ node_id: str note_id: str semantic_score: float @@ -133,6 +136,7 @@ class QueryHit(BaseModel): source: Optional[Dict] = None payload: Optional[Dict] = None explanation: Optional[Explanation] = None + stream_origin: Optional[str] = Field(None, description="Name des Ursprungs-Streams") class QueryResponse(BaseModel): diff --git a/config/prompts.yaml b/config/prompts.yaml index 1802c09..526ad8c 100644 --- a/config/prompts.yaml +++ b/config/prompts.yaml @@ -1,7 +1,9 @@ -# config/prompts.yaml — VERSION 3.0.0 (WP-25: Multi-Stream Agentic RAG) -# WP-20/22: Cloud-Templates & Semantic Graph Routing erhalten. -# WP-25: Integration der Multi-Stream Synthese zur Vermeidung von Halluzinationen. -# OLLAMA: UNVERÄNDERT laut Benutzeranweisung. +# config/prompts.yaml — VERSION 3.1.2 (WP-25 Cleanup: Multi-Stream Sync) +# STATUS: Active +# FIX: +# - 100% Wiederherstellung der Ingest- & Validierungslogik (Sektion 5-8). +# - Überführung der Kategorien 1-4 in die Multi-Stream Struktur unter Beibehaltung des Inhalts. +# - Konsolidierung: Sektion 9 (v3.0.0) wurde in Sektion 1 & 2 integriert (keine Redundanz). system_prompt: | Du bist 'mindnet', mein digitaler Zwilling und strategischer Partner. @@ -16,39 +18,57 @@ system_prompt: | 3. Antworte auf Deutsch (außer bei Code/Fachbegriffen). # --------------------------------------------------------- -# 1. STANDARD: Fakten & Wissen (Intent: FACT) +# 1. STANDARD: Fakten & Wissen (Intent: FACT_WHAT / FACT_WHEN) # --------------------------------------------------------- -rag_template: +# Ersetzt das alte 'rag_template'. Nutzt jetzt parallele Streams. +fact_synthesis_v1: ollama: | - QUELLEN (WISSEN): + WISSENS-STREAMS: ========================================= - {context_str} + FAKTEN & STATUS: + {facts_stream} + + ERFAHRUNG & BIOGRAFIE: + {biography_stream} + + WISSEN & TECHNIK: + {tech_stream} ========================================= FRAGE: {query} ANWEISUNG: - Beantworte die Frage präzise basierend auf den Quellen. + Beantworte die Frage präzise basierend auf den Quellen. + Kombiniere harte Fakten mit persönlichen Erfahrungen, falls vorhanden. Fasse die Informationen zusammen. Sei objektiv und neutral. gemini: | - Kontext meines digitalen Zwillings: {context_str} - Beantworte strukturiert und präzise: {query} + Beantworte die Wissensabfrage "{query}" basierend auf diesen Streams: + FAKTEN: {facts_stream} + BIOGRAFIE/ERFAHRUNG: {biography_stream} + TECHNIK: {tech_stream} + Kombiniere harte Fakten mit persönlichen Erfahrungen, falls vorhanden. Antworte strukturiert und präzise. openrouter: | - Kontext-Analyse für den digitalen Zwilling: - {context_str} - - Anfrage: {query} - Antworte basierend auf dem Kontext. + Synthese der Wissens-Streams für: {query} + Inhalt: {facts_stream} | {biography_stream} | {tech_stream} + Antworte basierend auf dem bereitgestellten Kontext. # --------------------------------------------------------- # 2. DECISION: Strategie & Abwägung (Intent: DECISION) # --------------------------------------------------------- -decision_template: +# Ersetzt das alte 'decision_template'. Nutzt jetzt parallele Streams. +decision_synthesis_v1: ollama: | - KONTEXT (FAKTEN & STRATEGIE): + ENTSCHEIDUNGS-STREAMS: ========================================= - {context_str} + WERTE & PRINZIPIEN (Identität): + {values_stream} + + OPERATIVE FAKTEN (Realität): + {facts_stream} + + RISIKO-RADAR (Konsequenzen): + {risk_stream} ========================================= ENTSCHEIDUNGSFRAGE: @@ -57,7 +77,7 @@ decision_template: ANWEISUNG: Du agierst als mein Entscheidungs-Partner. 1. Analysiere die Faktenlage aus den Quellen. - 2. Prüfe dies hart gegen meine strategischen Notizen (Typ [VALUE], [PRINCIPLE], [GOAL]). + 2. Prüfe dies hart gegen meine strategischen Notizen (Werte & Prinzipien). 3. Wäge ab: Passt die technische/faktische Lösung zu meinen Werten? FORMAT: @@ -65,19 +85,26 @@ decision_template: - **Abgleich:** (Gibt es Konflikte mit Werten/Zielen? Nenne die Quelle!) - **Empfehlung:** (Klare Meinung: Ja/No/Vielleicht mit Begründung) gemini: | - Agiere als strategischer Partner. Analysiere die Frage {query} basierend auf meinen Werten im Kontext {context_str}. + Agiere als mein strategischer Partner. Analysiere die Frage: {query} + Werte: {values_stream} | Fakten: {facts_stream} | Risiken: {risk_stream}. + Wäge ab und gib eine klare strategische Empfehlung ab. openrouter: | - Strategische Entscheidungsanalyse: {query} - Wertebasis aus dem Graphen: {context_str} + Strategische Multi-Stream Analyse für: {query} + Werte-Basis: {values_stream} | Fakten: {facts_stream} | Risiken: {risk_stream} + Bitte wäge ab und gib eine Empfehlung. # --------------------------------------------------------- # 3. EMPATHY: Der Spiegel / "Ich"-Modus (Intent: EMPATHY) # --------------------------------------------------------- empathy_template: ollama: | - KONTEXT (ERFAHRUNGEN & GLAUBENSSÄTZE): + KONTEXT (ERFAHRUNGEN & WERTE): ========================================= - {context_str} + ERLEBNISSE & BIOGRAFIE: + {biography_stream} + + WERTE & BEDÜRFNISSE: + {values_stream} ========================================= SITUATION: @@ -86,22 +113,26 @@ empathy_template: ANWEISUNG: Du agierst jetzt als mein empathischer Spiegel. 1. Versuche nicht sofort, das Problem technisch zu lösen. - 2. Zeige Verständnis für die Situation basierend auf meinen eigenen Erfahrungen ([EXPERIENCE]) oder Glaubenssätzen ([BELIEF]), falls im Kontext vorhanden. + 2. Zeige Verständnis für die Situation basierend auf meinen eigenen Erfahrungen ([EXPERIENCE]) oder Werten, falls im Kontext vorhanden. 3. Antworte in der "Ich"-Form oder "Wir"-Form. Sei unterstützend. TONFALL: Ruhig, verständnisvoll, reflektiert. Keine Aufzählungszeichen, sondern fließender Text. - gemini: "Sei mein digitaler Spiegel für {query}. Kontext: {context_str}" - openrouter: "Empathische Reflexion der Situation {query}. Persönlicher Kontext: {context_str}" + gemini: "Sei mein digitaler Spiegel für {query}. Kontext: {biography_stream}, {values_stream}" + openrouter: "Empathische Reflexion der Situation {query}. Persönlicher Kontext: {biography_stream}, {values_stream}" # --------------------------------------------------------- # 4. TECHNICAL: Der Coder (Intent: CODING) # --------------------------------------------------------- technical_template: ollama: | - KONTEXT (DOCS & SNIPPETS): + KONTEXT (WISSEN & PROJEKTE): ========================================= - {context_str} + TECHNIK & SNIPPETS: + {tech_stream} + + PROJEKT-STATUS: + {facts_stream} ========================================= TASK: @@ -117,11 +148,11 @@ technical_template: - Kurze Erklärung des Ansatzes. - Markdown Code-Block (Copy-Paste fertig). - Wichtige Edge-Cases. - gemini: "Generiere Code für {query} unter Berücksichtigung von {context_str}." - openrouter: "Technischer Support für {query}. Code-Referenzen: {context_str}" + gemini: "Generiere Code für {query} unter Berücksichtigung von {tech_stream} und {facts_stream}." + openrouter: "Technischer Support für {query}. Referenzen: {tech_stream}, Projekt-Kontext: {facts_stream}" # --------------------------------------------------------- -# 5. INTERVIEW: Der "One-Shot Extractor" (Performance Mode) +# 5. INTERVIEW: Der "One-Shot Extractor" (WP-07) # --------------------------------------------------------- interview_template: ollama: | @@ -159,7 +190,7 @@ interview_template: openrouter: "Strukturiere den Input {query} nach dem Schema {schema_fields} für Typ {target_type}." # --------------------------------------------------------- -# 6. EDGE_ALLOCATION: Kantenfilter (Intent: OFFLINE_FILTER) +# 6. EDGE_ALLOCATION: Kantenfilter (Ingest) # --------------------------------------------------------- edge_allocation_template: ollama: | @@ -199,7 +230,7 @@ edge_allocation_template: OUTPUT: # --------------------------------------------------------- -# 7. SMART EDGE ALLOCATION: Extraktion (Intent: INGEST) +# 7. SMART EDGE ALLOCATION: Extraktion (Ingest) # --------------------------------------------------------- edge_extraction: ollama: | @@ -239,7 +270,7 @@ edge_extraction: OUTPUT: # --------------------------------------------------------- -# 8. WP-15b: EDGE VALIDATION (Intent: VALIDATE) +# 8. WP-15b: EDGE VALIDATION (Ingest/Validate) # --------------------------------------------------------- edge_validation: gemini: | @@ -271,63 +302,6 @@ edge_validation: BEZIEHUNG: {edge_kind} Ist diese Verbindung valide? Antworte NUR mit YES oder NO. -# --------------------------------------------------------- -# 9. WP-25: MULTI-STREAM SYNTHESIS (Intent: SYNTHESIS) -# --------------------------------------------------------- -# Diese Templates verarbeiten die Ergebnisse aus parallelen Such-Streams. - -decision_synthesis_v1: - gemini: | - Agiere als mein strategischer Partner. Analysiere die Frage: {query} - - Hier sind die Ergebnisse aus verschiedenen Wissens-Streams meiner Mindnet-Basis: - - ### STREAM: WERTE & PRINZIPIEN (Identität) - {values_stream} - - ### STREAM: OPERATIVE FAKTEN (Realität) - {facts_stream} - - ### STREAM: RISIKO-ANALYSE (Konsequenzen) - {risk_stream} - - AUFGABE: - 1. Fasse die Faktenlage kurz zusammen. - 2. Wäge die Fakten hart gegen meine Werte ab. Gibt es Konflikte? - 3. Beurteile das Vorhaben basierend auf dem Risiko-Radar. - 4. Gib eine klare strategische Empfehlung ab. - openrouter: | - Strategische Multi-Stream Analyse für: {query} - Werte-Basis: {values_stream} - Fakten: {facts_stream} - Risiken: {risk_stream} - Bitte wäge ab und gib eine Empfehlung. - ollama: | - Du bist mein Entscheidungs-Partner. Analysiere {query} basierend auf diesen Streams: - WERTE: {values_stream} - FAKTEN: {facts_stream} - RISIKEN: {risk_stream} - Wäge die Fakten gegen die Werte ab und nenne potenzielle Risiken. Nenne Quellen! - -fact_synthesis_v1: - gemini: | - Beantworte die Wissensabfrage "{query}" basierend auf diesen Streams: - FAKTEN: {facts_stream} - BIOGRAFIE/ERFAHRUNG: {biography_stream} - TECHNIK: {tech_stream} - Kombiniere harte Fakten mit persönlichen Erfahrungen, falls vorhanden. - openrouter: | - Synthese der Wissens-Streams für: {query} - Inhalt: {facts_stream} | {biography_stream} | {tech_stream} - ollama: | - Fasse das Wissen zu {query} zusammen. - QUELLE FAKTEN: {facts_stream} - QUELLE ERFAHRUNG: {biography_stream} - QUELLE TECHNIK: {tech_stream} - Antworte präzise und nenne die Quellen. - -# ... (Vorherige Sektionen 1-9 bleiben identisch) - # --------------------------------------------------------- # 10. WP-25: INTENT ROUTING (Intent: CLASSIFY) # ---------------------------------------------------------