From 7026fc4fed4ed8ae39e0fae9665a99c5dddb784b Mon Sep 17 00:00:00 2001 From: Lars Date: Fri, 2 Jan 2026 20:43:31 +0100 Subject: [PATCH 1/6] Update components for WP-25b: Implement Lazy-Prompt-Orchestration across ingestion, decision engine, chat interface, and LLM service. Enhance prompt management with hierarchical model support and streamline response generation by removing manual formatting. Bump versions to reflect new features and optimizations. --- app/core/ingestion/ingestion_validation.py | 39 ++- app/core/retrieval/decision_engine.py | 104 +++---- app/routers/chat.py | 52 ++-- app/services/llm_service.py | 150 +++++---- config/prompts - Kopie.yaml | 337 +++++++++++++++++++++ config/prompts.yaml | 110 ++++++- 6 files changed, 589 insertions(+), 203 deletions(-) create mode 100644 config/prompts - Kopie.yaml diff --git a/app/core/ingestion/ingestion_validation.py b/app/core/ingestion/ingestion_validation.py index dcf29ce..7f4122f 100644 --- a/app/core/ingestion/ingestion_validation.py +++ b/app/core/ingestion/ingestion_validation.py @@ -1,13 +1,13 @@ """ FILE: app/core/ingestion/ingestion_validation.py DESCRIPTION: WP-15b semantische Validierung von Kanten gegen den LocalBatchCache. - WP-25a: Integration der Mixture of Experts (MoE) Profil-Steuerung. -VERSION: 2.13.0 (WP-25a: MoE & Profile Support) + WP-25b: Umstellung auf Lazy-Prompt-Orchestration (prompt_key + variables). +VERSION: 2.14.0 (WP-25b: Lazy Prompt Integration) STATUS: Active FIX: -- Umstellung auf generate_raw_response mit profile_name="ingest_validator". -- Automatische Nutzung der Fallback-Kaskade (Cloud -> Lokal) via LLMService. -- Erhalt der sparsamen LLM-Nutzung (Validierung nur für Kandidaten-Pool). +- WP-25b: Entfernung manueller Prompt-Formatierung zur Unterstützung modell-spezifischer Prompts. +- WP-25b: Umstellung auf generate_raw_response mit prompt_key="edge_validation". +- WP-25a: Voller Erhalt der MoE-Profilsteuerung und Fallback-Kaskade via LLMService. """ import logging from typing import Dict, Any, Optional @@ -27,8 +27,8 @@ async def validate_edge_candidate( profile_name: str = "ingest_validator" ) -> bool: """ - WP-15b: Validiert einen Kandidaten semantisch gegen das Ziel im Cache. - Nutzt das MoE-Profil 'ingest_validator' für deterministische YES/NO Prüfungen. + WP-15b/25b: Validiert einen Kandidaten semantisch gegen das Ziel im Cache. + Nutzt Lazy-Prompt-Loading zur Unterstützung modell-spezifischer Validierungs-Templates. """ target_id = edge.get("to") target_ctx = batch_cache.get(target_id) @@ -44,27 +44,25 @@ async def validate_edge_candidate( logger.info(f"ℹ️ [VALIDATION SKIP] No context for '{target_id}' - allowing link.") return True - # Prompt-Abruf (Nutzt Provider-String als Fallback-Key für die prompts.yaml) - template = llm_service.get_prompt("edge_validation", provider) - try: logger.info(f"⚖️ [VALIDATING] Relation '{edge.get('kind')}' -> '{target_id}' (Profile: {profile_name})...") - prompt = template.format( - chunk_text=chunk_text[:1500], - target_title=target_ctx.title, - target_summary=target_ctx.summary, - edge_kind=edge.get("kind", "related_to") - ) - # WP-25a: Profilbasierter Aufruf (Delegiert Fallbacks an den Service) - # Nutzt ingest_validator (Cloud Mistral/Gemini -> Local Phi3:mini Kaskade) + # WP-25b: Lazy-Prompt Aufruf. + # Wir übergeben keine formatierte Nachricht mehr, sondern Key und Daten-Dict. + # Das manuelle 'template = llm_service.get_prompt(...)' entfällt hier. raw_response = await llm_service.generate_raw_response( - prompt, + prompt_key="edge_validation", + variables={ + "chunk_text": chunk_text[:1500], + "target_title": target_ctx.title, + "target_summary": target_ctx.summary, + "edge_kind": edge.get("kind", "related_to") + }, priority="background", profile_name=profile_name ) - # WP-14 Fix: Zusätzliche Bereinigung zur Sicherstellung der Interpretierbarkeit + # WP-14 Fix: Bereinigung zur Sicherstellung der Interpretierbarkeit response = clean_llm_text(raw_response) # Semantische Prüfung des Ergebnisses @@ -75,6 +73,7 @@ async def validate_edge_candidate( else: logger.info(f"🚫 [REJECTED] Relation to '{target_id}' irrelevant for this chunk.") return is_valid + except Exception as e: logger.warning(f"⚠️ Validation error for {target_id} using {profile_name}: {e}") # Im Zweifel (Timeout/Fehler) erlauben wir die Kante, um Datenverlust zu vermeiden diff --git a/app/core/retrieval/decision_engine.py b/app/core/retrieval/decision_engine.py index ce606a5..5fa0455 100644 --- a/app/core/retrieval/decision_engine.py +++ b/app/core/retrieval/decision_engine.py @@ -1,16 +1,15 @@ """ FILE: app/core/retrieval/decision_engine.py -DESCRIPTION: Der Agentic Orchestrator für MindNet (WP-25a Edition). +DESCRIPTION: Der Agentic Orchestrator für MindNet (WP-25b Edition). Realisiert Multi-Stream Retrieval, Intent-basiertes Routing - und die neue Pre-Synthesis Kompression (Module A). -VERSION: 1.2.1 (WP-25a: Profile-Driven Orchestration & Optimized Cascade) + und die neue Lazy-Prompt Orchestrierung (Module A & B). +VERSION: 1.3.0 (WP-25b: Lazy Prompt Orchestration) STATUS: Active FIX: -- WP-25a: Volle Integration der Profil-Kaskade (Delegation an LLMService v3.5.2). -- WP-25a: Dynamische Nutzung des 'router_profile' für die Intent-Erkennung. -- WP-25a: Parallelisierte Kompression überlanger Wissens-Streams. +- WP-25b: Umstellung auf Lazy-Loading (Übergabe von prompt_key + variables). +- WP-25b: Entfernung lokaler String-Formatierung zur Ermöglichung modell-spezifischer Prompts. +- WP-25a: Volle Integration der Profil-Kaskade via LLMService v3.5.5. - WP-25: Beibehaltung von Stream-Tracing und Pre-Initialization Robustness. -- CLEANUP: Entfernung redundanter Fallback-Blocks (jetzt im LLMService). """ import asyncio import logging @@ -70,29 +69,27 @@ class DecisionEngine: if not strategy: return "Entschuldigung, meine Wissensbasis ist aktuell nicht konfiguriert." - # 2. Multi-Stream Retrieval & Pre-Synthesis (Parallel Tasks) - # WP-25a: Diese Methode übernimmt nun auch die Kompression. + # 2. Multi-Stream Retrieval & Pre-Synthesis (Parallel Tasks inkl. Kompression) stream_results = await self._execute_parallel_streams(strategy, query) # 3. Finale 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 Wahl der Such-Strategie via router_profile.""" + """WP-25b: Nutzt den LLM-Router via Lazy-Loading prompt_key.""" settings_cfg = self.config.get("settings", {}) prompt_key = settings_cfg.get("router_prompt_key", "intent_router_v1") - # WP-25a: Nutzt das spezialisierte Profil für das Routing router_profile = settings_cfg.get("router_profile") - 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: - # Der LLMService übernimmt hier über das Profil bereits die Fallback-Kaskade + # WP-25b: Keine manuelle Formatierung mehr. Wir übergeben nur Key und Variablen. + # Der LLMService wählt den passenden Prompt für das router_profile Modell. response = await self.llm_service.generate_raw_response( - full_prompt, max_retries=1, priority="realtime", profile_name=router_profile + prompt_key=prompt_key, + variables={"query": query}, + max_retries=1, + priority="realtime", + profile_name=router_profile ) return str(response).strip().upper() except Exception as e: @@ -102,7 +99,7 @@ class DecisionEngine: async def _execute_parallel_streams(self, strategy: Dict, query: str) -> Dict[str, str]: """ Führt Such-Streams aus und komprimiert überlange Ergebnisse (Pre-Synthesis). - WP-25a: MoE-Profile werden für die Kompression berücksichtigt. + WP-25b: Unterstützt Lazy-Compression über Experten-Profile. """ stream_keys = strategy.get("use_streams", []) library = self.config.get("streams_library", {}) @@ -116,7 +113,7 @@ class DecisionEngine: active_streams.append(key) retrieval_tasks.append(self._run_single_stream(key, stream_cfg, query)) - # Ergebnisse sammeln (Exceptions werden als Objekte zurückgegeben) + # Ergebnisse sammeln retrieval_results = await asyncio.gather(*retrieval_tasks, return_exceptions=True) # Phase 2: Formatierung und optionale Kompression @@ -137,38 +134,32 @@ class DecisionEngine: threshold = stream_cfg.get("compression_threshold", 4000) if len(formatted_context) > threshold: - logger.info(f"⚙️ [WP-25a] Compressing stream '{name}' ({len(formatted_context)} chars)...") + logger.info(f"⚙️ [WP-25b] Triggering Lazy-Compression for stream '{name}'...") comp_profile = stream_cfg.get("compression_profile") final_stream_tasks.append( self._compress_stream_content(name, formatted_context, query, comp_profile) ) else: - # Direkt-Übernahme als Coroutine für gather() async def _direct(c=formatted_context): return c final_stream_tasks.append(_direct()) - # Finale Inhalte (evtl. komprimiert) parallel fertigstellen + # Finale Inhalte parallel fertigstellen final_contents = await asyncio.gather(*final_stream_tasks) - return dict(zip(active_streams, final_contents)) async def _compress_stream_content(self, stream_name: str, content: str, query: str, profile: Optional[str]) -> str: - """ - WP-25a Module A: Inhaltsverdichtung via Experten-Modell. - """ - compression_prompt = ( - f"Du bist ein Wissens-Analyst. Reduziere den folgenden Wissens-Stream '{stream_name}' " - f"auf die Informationen, die für die Beantwortung der Frage '{query}' absolut notwendig sind.\n\n" - f"BEIBEHALTEN: Harte Fakten, Projektnamen, konkrete Werte und Quellenangaben.\n" - f"ENTFERNEN: Redundante Einleitungen, Füllwörter und irrelevante Details.\n\n" - f"STREAM-INHALT:\n{content}\n\n" - f"KOMPRIMIERTE ANALYSE:" - ) - + """WP-25b Module A: Inhaltsverdichtung via Lazy-Loading 'compression_template'.""" try: + # WP-25b: Wir übergeben den Auftrag an den LLMService. + # Das Modell-spezifische Template wird erst beim Call aufgelöst. summary = await self.llm_service.generate_raw_response( - compression_prompt, - profile_name=profile, # WP-25a: MoE Support + prompt_key="compression_template", + variables={ + "stream_name": stream_name, + "content": content, + "query": query + }, + profile_name=profile, priority="background", max_retries=1 ) @@ -191,24 +182,19 @@ class DecisionEngine: ) response = await self.retriever.search(request) - - # WP-25: STREAM-TRACING for hit in response.results: hit.stream_origin = name - return response def _format_stream_context(self, response: QueryResponse) -> str: """Wandelt QueryHits in einen formatierten Kontext-String um.""" if not response.results: return "Keine spezifischen Informationen in diesem Stream gefunden." - lines = [] for i, hit in enumerate(response.results, 1): source = hit.source.get("path", "Unbekannt") content = hit.source.get("text", "").strip() lines.append(f"[{i}] QUELLE: {source}\nINHALT: {content}") - return "\n\n".join(lines) async def _generate_final_answer( @@ -218,12 +204,9 @@ class DecisionEngine: query: str, stream_results: Dict[str, str] ) -> str: - """Führt die finale Synthese basierend auf dem Strategie-Profil durch.""" - # WP-25a: Nutzt das llm_profile der Strategie + """WP-25b: Finale Synthese via Lazy-Prompt 'rag_template'.""" profile = strategy.get("llm_profile") template_key = strategy.get("prompt_template", "rag_template") - - template = self.llm_service.get_prompt(template_key) system_prompt = self.llm_service.get_prompt("system_prompt") # WP-25 ROBUSTNESS: Pre-Initialization @@ -232,28 +215,15 @@ class DecisionEngine: template_vars.update(stream_results) template_vars["query"] = query - prepend = strategy.get("prepend_instruction", "") - + # WP-25b: Wir reichen die Variablen direkt an den Service weiter. + # Formatierung erfolgt erst nach Profil-Auflösung (Gemini vs. Llama vs. Phi3). try: - final_prompt = template.format(**template_vars) - if prepend: - final_prompt = f"{prepend}\n\n{final_prompt}" - - # WP-25a: MoE Call mit automatisierter Kaskade im LLMService - # (Frühere manuelle Fallback-Blocks wurden entfernt, da v3.5.2 dies intern löst) - response = await self.llm_service.generate_raw_response( - final_prompt, system=system_prompt, profile_name=profile, priority="realtime" - ) - - return response - - except KeyError as e: - logger.error(f"Template Variable mismatch in '{template_key}': Missing {e}") - fallback_context = "\n\n".join([v for v in stream_results.values() if v]) - # WP-25a FIX: Nutzt auch im Fallback das Strategie-Profil für Konsistenz return await self.llm_service.generate_raw_response( - f"Beantworte: {query}\n\nKontext:\n{fallback_context}", - system=system_prompt, priority="realtime", profile_name=profile + prompt_key=template_key, + variables=template_vars, + system=system_prompt, + profile_name=profile, + priority="realtime" ) except Exception as e: logger.error(f"Final Synthesis failed: {e}") diff --git a/app/routers/chat.py b/app/routers/chat.py index a74d7a1..80f0147 100644 --- a/app/routers/chat.py +++ b/app/routers/chat.py @@ -1,15 +1,14 @@ """ FILE: app/routers/chat.py -DESCRIPTION: Haupt-Chat-Interface (WP-25a Agentic Edition). - Kombiniert die spezialisierte Interview-Logik und Keyword-Erkennung - mit der neuen MoE-Orchestrierung und Pre-Synthesis Kompression. -VERSION: 3.0.4 (WP-25a: Optimized MoE & Cascade Delegation) +DESCRIPTION: Haupt-Chat-Interface (WP-25b Edition). + Kombiniert die spezialisierte Interview-Logik mit der neuen + Lazy-Prompt-Orchestration und MoE-Synthese. +VERSION: 3.0.5 (WP-25b: Lazy Prompt Integration) STATUS: Active FIX: -- WP-25a: Delegation der Fallback-Kaskade an den LLMService (v3.5.2). -- WP-25a: Nutzung der zentralisierten Stream-Kompression der DecisionEngine (v1.2.1). -- WP-25a: Konsistente Nutzung von MoE-Profilen für Interview- und RAG-Modus. -- 100% Erhalt der v3.0.2 Logik (Interview, Schema-Resolution, FastPaths). +- WP-25b: Umstellung des Interview-Modus auf Lazy-Prompt (prompt_key + variables). +- WP-25b: Delegation der RAG-Phase an die Engine v1.3.0 für konsistente MoE-Steuerung. +- WP-25a: Voller Erhalt der v3.0.2 Logik (Interview, Schema-Resolution, FastPaths). """ from fastapi import APIRouter, HTTPException, Depends @@ -146,7 +145,7 @@ def _collect_all_hits(stream_responses: Dict[str, Any]) -> List[QueryHit]: all_hits = [] seen_node_ids = set() for _, response in stream_responses.items(): - # In v3.0.4 sammeln wir die hits aus den QueryResponse Objekten + # Sammeln der Hits aus den QueryResponse Objekten if hasattr(response, 'results'): for hit in response.results: if hit.node_id not in seen_node_ids: @@ -166,8 +165,7 @@ async def chat_endpoint( ): start_time = time.time() query_id = str(uuid.uuid4()) - settings = get_settings() - logger.info(f"🚀 [WP-25a] Chat request [{query_id}]: {request.message[:50]}...") + logger.info(f"🚀 [WP-25b] Chat request [{query_id}]: {request.message[:50]}...") try: # 1. Intent Detection @@ -180,7 +178,7 @@ async def chat_endpoint( sources_hits = [] answer_text = "" - # 2. INTERVIEW MODE (Bitgenaue WP-07 Logik) + # 2. INTERVIEW MODE (WP-25b Lazy-Prompt Logik) if intent == "INTERVIEW": target_type = _detect_target_type(request.message, strategy.get("schemas", {})) types_cfg = get_types_config() @@ -194,23 +192,27 @@ async def chat_endpoint( fields_list = fallback.get("fields", []) if isinstance(fallback, dict) else (fallback or []) fields_str = "\n- " + "\n- ".join(fields_list) - template = llm.get_prompt(strategy.get("prompt_template", "interview_template")) + template_key = strategy.get("prompt_template", "interview_template") - final_prompt = template.replace("{query}", request.message) \ - .replace("{target_type}", target_type) \ - .replace("{schema_fields}", fields_str) - - # WP-25a: MoE Call (Kaskade erfolgt intern im LLMService) + # WP-25b: Lazy Loading Call + # Wir übergeben nur Key und Variablen. Das System formatiert passend zum Modell. answer_text = await llm.generate_raw_response( - final_prompt, system=llm.get_prompt("system_prompt"), - priority="realtime", profile_name="compression_fast", max_retries=0 + prompt_key=template_key, + variables={ + "query": request.message, + "target_type": target_type, + "schema_fields": fields_str + }, + system=llm.get_prompt("system_prompt"), + priority="realtime", + profile_name="compression_fast", + max_retries=0 ) sources_hits = [] - # 3. RAG MODE (Optimierte MoE Orchestrierung) + # 3. RAG MODE (WP-25b Delegation an Engine v1.3.0) else: - # Phase A & B: Retrieval & Kompression (Delegation an Engine v1.2.1) - # Diese Methode gibt bereits die (evtl. komprimierten) Kontext-Strings zurück. + # Phase A & B: Retrieval & Kompression (Delegiert an Engine v1.3.0) formatted_context_map = await engine._execute_parallel_streams(strategy, request.message) # Erfassung der Quellen für das Tracing @@ -232,7 +234,7 @@ async def chat_endpoint( sources_hits = _collect_all_hits(raw_stream_map) - # Phase C: Finale MoE Synthese + # Phase C: Finale MoE Synthese (Delegiert an Engine v1.3.0) answer_text = await engine._generate_final_answer( intent, strategy, request.message, formatted_context_map ) @@ -243,7 +245,7 @@ async def chat_endpoint( try: log_search( query_id=query_id, query_text=request.message, results=sources_hits, - mode=f"wp25a_{intent.lower()}", metadata={"strategy": intent, "source": intent_source} + mode=f"wp25b_{intent.lower()}", metadata={"strategy": intent, "source": intent_source} ) except: pass diff --git a/app/services/llm_service.py b/app/services/llm_service.py index a796c17..a650f9e 100644 --- a/app/services/llm_service.py +++ b/app/services/llm_service.py @@ -1,14 +1,14 @@ """ FILE: app/services/llm_service.py DESCRIPTION: Hybrid-Client für Ollama, Google GenAI (Gemini) und OpenRouter. - WP-25a: Implementierung der Mixture of Experts (MoE) Kaskaden-Steuerung. -VERSION: 3.5.2 (WP-25a: MoE & Fallback Cascade Support) + WP-25b: Implementierung der Lazy-Prompt-Orchestration (Modell-spezifisch). +VERSION: 3.5.5 (WP-25b: Prompt Orchestration & Full Resilience) STATUS: Active FIX: -- WP-25a: Implementierung der rekursiven Fallback-Kaskade via fallback_profile. -- WP-25a: Schutz gegen zirkuläre Profil-Referenzen (visited_profiles). -- WP-25a: Erweitertes Logging für Tracing der Experten-Entscheidungen. -- Erhalt der Ingest-Stability (WP-25) und des Rate-Limit-Managements. +- WP-25b: get_prompt() unterstützt Hierarchie: Model-ID -> Provider -> Default. +- WP-25b: generate_raw_response() unterstützt prompt_key + variables für Lazy-Formatting. +- WP-25a: Voller Erhalt der rekursiven Fallback-Kaskade und visited_profiles Schutz. +- WP-20: Restaurierung des internen Ollama-Retry-Loops für Hardware-Stabilität. """ import httpx import yaml @@ -33,10 +33,7 @@ class LLMService: def __init__(self): self.settings = get_settings() self.prompts = self._load_prompts() - - # WP-25a: Zentrale Experten-Profile laden self.profiles = self._load_llm_profiles() - self._decision_engine = None if LLMService._background_semaphore is None: @@ -92,7 +89,7 @@ class LLMService: path_str = getattr(self.settings, "LLM_PROFILES_PATH", "config/llm_profiles.yaml") path = Path(path_str) if not path.exists(): - logger.warning(f"⚠️ LLM Profiles file not found at {path}. System will use .env defaults.") + logger.warning(f"⚠️ LLM Profiles file not found at {path}.") return {} try: with open(path, "r", encoding="utf-8") as f: @@ -102,17 +99,31 @@ class LLMService: logger.error(f"❌ Failed to load llm_profiles.yaml: {e}") return {} - def get_prompt(self, key: str, provider: str = None) -> str: - active_provider = provider or self.settings.MINDNET_LLM_PROVIDER + def get_prompt(self, key: str, model_id: str = None, provider: str = None) -> str: + """ + WP-25b: Hochpräziser Prompt-Lookup. + Hierarchie: Exakte Modell-ID -> Provider-Name -> Globaler Default. + """ data = self.prompts.get(key, "") - if isinstance(data, dict): - val = data.get(active_provider, data.get("gemini", data.get("ollama", ""))) - return str(val) - return str(data) + if not isinstance(data, dict): + return str(data) + + # 1. Spezifischstes Match: Exakte Modell-ID (z.B. 'meta-llama/llama-3.3-70b-instruct:free') + if model_id and model_id in data: + return str(data[model_id]) + + # 2. Mittlere Ebene: Provider (z.B. 'ollama' oder 'openrouter') + if provider and provider in data: + return str(data[provider]) + + # 3. Fallback: Bekannte Keys oder Default aus prompts.yaml + return str(data.get("default", data.get("gemini", data.get("ollama", "")))) async def generate_raw_response( self, - prompt: str, + prompt: str = None, + prompt_key: str = None, # WP-25b: Lazy Loading Key + variables: dict = None, # WP-25b: Daten für Formatierung system: str = None, force_json: bool = False, max_retries: int = 2, @@ -126,16 +137,14 @@ class LLMService: profile_name: Optional[str] = None, visited_profiles: Optional[list] = None ) -> str: - """ - Haupteinstiegspunkt für LLM-Anfragen mit rekursiver Kaskaden-Logik. - """ + """Haupteinstiegspunkt für LLM-Anfragen mit Lazy-Prompt Orchestrierung.""" visited_profiles = visited_profiles or [] target_provider = provider target_model = model_override target_temp = None fallback_profile = None - # 1. Profil-Auflösung + # 1. Profil-Auflösung (Mixture of Experts) if profile_name and self.profiles: profile = self.profiles.get(profile_name) if profile: @@ -148,30 +157,39 @@ class LLMService: else: logger.warning(f"⚠️ Profil '{profile_name}' nicht in llm_profiles.yaml gefunden!") - # Fallback auf Standard-Provider falls nichts übergeben/definiert wurde if not target_provider: target_provider = self.settings.MINDNET_LLM_PROVIDER - logger.info(f"ℹ️ Kein Provider/Profil definiert. Nutze Default: {target_provider}") - # 2. Ausführung mit Fehler-Handling für Kaskade + # 2. WP-25b: Lazy Prompt Resolving + # Wir laden den Prompt erst JETZT, basierend auf dem gerade aktiven Modell. + current_prompt = prompt + if prompt_key: + template = self.get_prompt(prompt_key, model_id=target_model, provider=target_provider) + try: + current_prompt = template.format(**(variables or {})) + except Exception as e: + logger.error(f"❌ Prompt formatting failed for key '{prompt_key}': {e}") + current_prompt = template # Sicherheits-Fallback + + # 3. Ausführung mit Fehler-Handling für Kaskade try: if priority == "background": async with LLMService._background_semaphore: res = await self._dispatch( - target_provider, prompt, system, force_json, + target_provider, current_prompt, system, force_json, max_retries, base_delay, target_model, json_schema, json_schema_name, strict_json_schema, target_temp ) else: res = await self._dispatch( - target_provider, prompt, system, force_json, + target_provider, current_prompt, system, force_json, max_retries, base_delay, target_model, json_schema, json_schema_name, strict_json_schema, target_temp ) # Check auf leere Cloud-Antworten (WP-25 Stability) if not res and target_provider != "ollama": - logger.warning(f"⚠️ Empty response from {target_provider}. Triggering fallback chain.") + logger.warning(f"⚠️ Empty response from {target_provider}. Triggering fallback.") raise ValueError(f"Empty response from {target_provider}") return clean_llm_text(res) if not force_json else res @@ -179,40 +197,33 @@ class LLMService: except Exception as e: logger.error(f"❌ Error during execution of profile '{profile_name}' ({target_provider}): {e}") - # 3. Kaskaden-Logik: Nächstes Profil in der Kette versuchen + # 4. WP-25b Kaskaden-Logik (Rekursiv mit Modell-spezifischem Re-Loading) if fallback_profile and fallback_profile not in visited_profiles: logger.info(f"🔄 Switching to fallback profile: '{fallback_profile}'") return await self.generate_raw_response( - prompt=prompt, system=system, force_json=force_json, + prompt=prompt, + prompt_key=prompt_key, + variables=variables, # Ermöglicht neues Formatting für Fallback-Modell + system=system, force_json=force_json, max_retries=max_retries, base_delay=base_delay, - priority=priority, provider=provider, model_override=model_override, + priority=priority, provider=None, model_override=None, json_schema=json_schema, json_schema_name=json_schema_name, strict_json_schema=strict_json_schema, profile_name=fallback_profile, visited_profiles=visited_profiles ) - # 4. Ultimativer Notanker: Falls alles fehlschlägt, direkt zu Ollama + # 5. Ultimativer Notanker: Falls alles fehlschlägt, direkt zu Ollama if target_provider != "ollama" and self.settings.LLM_FALLBACK_ENABLED: logger.warning(f"🚨 Kaskade erschöpft. Nutze finalen Ollama-Notanker.") - res = await self._execute_ollama(prompt, system, force_json, max_retries, base_delay) + res = await self._execute_ollama(current_prompt, system, force_json, max_retries, base_delay, target_temp, target_model) return clean_llm_text(res) if not force_json else res raise e async def _dispatch( - self, - provider: str, - prompt: str, - system: Optional[str], - force_json: bool, - max_retries: int, - base_delay: float, - model_override: Optional[str], - json_schema: Optional[Dict[str, Any]], - json_schema_name: str, - strict_json_schema: bool, - temperature: Optional[float] = None + self, provider, prompt, system, force_json, max_retries, base_delay, + model_override, json_schema, json_schema_name, strict_json_schema, temperature ) -> str: """Routet die Anfrage an den spezifischen Provider-Executor.""" rate_limit_attempts = 0 @@ -232,23 +243,19 @@ class LLMService: if provider == "gemini" and self.google_client: return await self._execute_google(prompt, system, force_json, model_override, temperature) - return await self._execute_ollama(prompt, system, force_json, max_retries, base_delay, temperature) + return await self._execute_ollama(prompt, system, force_json, max_retries, base_delay, temperature, model_override) except Exception as e: err_str = str(e) - # Rate-Limit Handling (429) if any(x in err_str for x in ["429", "RESOURCE_EXHAUSTED", "rate_limited"]): rate_limit_attempts += 1 logger.warning(f"⏳ Rate Limit {provider}. Attempt {rate_limit_attempts}. Wait {wait_time}s.") await asyncio.sleep(wait_time) continue - # Andere Fehler werden an generate_raw_response für die Kaskade gereicht raise e async def _execute_google(self, prompt, system, force_json, model_override, temperature): - model = model_override or self.settings.GEMINI_MODEL - clean_model = model.replace("models/", "") - + model = (model_override or self.settings.GEMINI_MODEL).replace("models/", "") config_kwargs = { "system_instruction": system, "response_mime_type": "application/json" if force_json else "text/plain" @@ -257,22 +264,13 @@ class LLMService: config_kwargs["temperature"] = temperature config = types.GenerateContentConfig(**config_kwargs) - response = await asyncio.wait_for( - asyncio.to_thread( - self.google_client.models.generate_content, - model=clean_model, contents=prompt, config=config - ), + asyncio.to_thread(self.google_client.models.generate_content, model=model, contents=prompt, config=config), timeout=45.0 ) return response.text.strip() - async def _execute_openrouter( - self, prompt: str, system: Optional[str], force_json: bool, - model_override: Optional[str], json_schema: Optional[Dict[str, Any]] = None, - json_schema_name: str = "mindnet_json", strict_json_schema: bool = True, - temperature: Optional[float] = None - ) -> str: + async def _execute_openrouter(self, prompt, system, force_json, model_override, json_schema, json_schema_name, strict_json_schema, temperature) -> str: model = model_override or self.settings.OPENROUTER_MODEL logger.info(f"🛰️ OpenRouter Call: Model='{model}' | Temp={temperature}") messages = [] @@ -280,35 +278,26 @@ class LLMService: messages.append({"role": "user", "content": prompt}) kwargs: Dict[str, Any] = {} - if temperature is not None: - kwargs["temperature"] = temperature + if temperature is not None: kwargs["temperature"] = temperature if force_json: if json_schema: - kwargs["response_format"] = { - "type": "json_schema", - "json_schema": {"name": json_schema_name, "strict": strict_json_schema, "schema": json_schema} - } + kwargs["response_format"] = {"type": "json_schema", "json_schema": {"name": json_schema_name, "strict": strict_json_schema, "schema": json_schema}} else: kwargs["response_format"] = {"type": "json_object"} - response = await self.openrouter_client.chat.completions.create( - model=model, messages=messages, **kwargs - ) - - if not response.choices: - return "" - + response = await self.openrouter_client.chat.completions.create(model=model, messages=messages, **kwargs) + if not response.choices: return "" return response.choices[0].message.content.strip() if response.choices[0].message.content else "" - async def _execute_ollama(self, prompt, system, force_json, max_retries, base_delay, temperature=None): - # Nutzt Profil-Temperatur oder strikte Defaults für lokale Hardware-Schonung + async def _execute_ollama(self, prompt, system, force_json, max_retries, base_delay, temperature=None, model_override=None): + # WP-20: Restaurierter Retry-Loop für lokale Hardware-Resilienz + effective_model = model_override or self.settings.LLM_MODEL effective_temp = temperature if temperature is not None else (0.1 if force_json else 0.7) payload = { - "model": self.settings.LLM_MODEL, - "prompt": prompt, - "stream": False, + "model": effective_model, + "prompt": prompt, "stream": False, "options": {"temperature": effective_temp, "num_ctx": 8192} } if force_json: payload["format"] = "json" @@ -323,12 +312,11 @@ class LLMService: except Exception as e: attempt += 1 if attempt > max_retries: - logger.error(f"❌ Ollama final failure after {attempt} attempts: {e}") + logger.error(f"❌ Ollama failure after {attempt} attempts: {e}") raise e await asyncio.sleep(base_delay * (2 ** (attempt - 1))) async def generate_rag_response(self, query: str, context_str: Optional[str] = None) -> str: - """WP-25: Orchestrierung via DecisionEngine.""" return await self.decision_engine.ask(query) async def close(self): diff --git a/config/prompts - Kopie.yaml b/config/prompts - Kopie.yaml new file mode 100644 index 0000000..526ad8c --- /dev/null +++ b/config/prompts - Kopie.yaml @@ -0,0 +1,337 @@ +# 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. + + DEINE IDENTITÄT: + - Du bist nicht nur eine Datenbank, sondern handelst nach MEINEN Werten und Zielen. + - Du passt deinen Stil dynamisch an die Situation an (Analytisch, Empathisch oder Technisch). + + DEINE REGELN: + 1. Deine Antwort muss zu 100% auf dem bereitgestellten KONTEXT basieren. + 2. Halluziniere keine Fakten, die nicht in den Quellen stehen. + 3. Antworte auf Deutsch (außer bei Code/Fachbegriffen). + +# --------------------------------------------------------- +# 1. STANDARD: Fakten & Wissen (Intent: FACT_WHAT / FACT_WHEN) +# --------------------------------------------------------- +# Ersetzt das alte 'rag_template'. Nutzt jetzt parallele Streams. +fact_synthesis_v1: + ollama: | + WISSENS-STREAMS: + ========================================= + FAKTEN & STATUS: + {facts_stream} + + ERFAHRUNG & BIOGRAFIE: + {biography_stream} + + WISSEN & TECHNIK: + {tech_stream} + ========================================= + + FRAGE: + {query} + + ANWEISUNG: + 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: | + 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: | + 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) +# --------------------------------------------------------- +# Ersetzt das alte 'decision_template'. Nutzt jetzt parallele Streams. +decision_synthesis_v1: + ollama: | + ENTSCHEIDUNGS-STREAMS: + ========================================= + WERTE & PRINZIPIEN (Identität): + {values_stream} + + OPERATIVE FAKTEN (Realität): + {facts_stream} + + RISIKO-RADAR (Konsequenzen): + {risk_stream} + ========================================= + + ENTSCHEIDUNGSFRAGE: + {query} + + ANWEISUNG: + Du agierst als mein Entscheidungs-Partner. + 1. Analysiere die Faktenlage aus den Quellen. + 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: + - **Analyse:** (Kurze Zusammenfassung der Fakten) + - **Abgleich:** (Gibt es Konflikte mit Werten/Zielen? Nenne die Quelle!) + - **Empfehlung:** (Klare Meinung: Ja/No/Vielleicht mit Begründung) + gemini: | + 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 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 & WERTE): + ========================================= + ERLEBNISSE & BIOGRAFIE: + {biography_stream} + + WERTE & BEDÜRFNISSE: + {values_stream} + ========================================= + + SITUATION: + {query} + + 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 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: {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 (WISSEN & PROJEKTE): + ========================================= + TECHNIK & SNIPPETS: + {tech_stream} + + PROJEKT-STATUS: + {facts_stream} + ========================================= + + TASK: + {query} + + ANWEISUNG: + Du bist Senior Developer. + 1. Ignoriere Smalltalk. Komm sofort zum Punkt. + 2. Generiere validen, performanten Code basierend auf den Quellen. + 3. Wenn Quellen fehlen, nutze dein allgemeines Programmierwissen, aber weise darauf hin. + + FORMAT: + - Kurze Erklärung des Ansatzes. + - Markdown Code-Block (Copy-Paste fertig). + - Wichtige Edge-Cases. + 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" (WP-07) +# --------------------------------------------------------- +interview_template: + ollama: | + TASK: + Du bist ein professioneller Ghostwriter. Verwandle den "USER INPUT" in eine strukturierte Notiz vom Typ '{target_type}'. + + STRUKTUR (Nutze EXAKT diese Überschriften): + {schema_fields} + + USER INPUT: + "{query}" + + ANWEISUNG ZUM INHALT: + 1. Analysiere den Input genau. + 2. Schreibe die Inhalte unter die passenden Überschriften aus der STRUKTUR-Liste oben. + 3. STIL: Schreibe flüssig, professionell und in der Ich-Perspektive. Korrigiere Grammatikfehler, aber behalte den persönlichen Ton bei. + 4. Wenn Informationen für einen Abschnitt fehlen, schreibe nur: "[TODO: Ergänzen]". Erfinde nichts dazu. + + OUTPUT FORMAT (YAML + MARKDOWN): + --- + type: {target_type} + status: draft + title: (Erstelle einen treffenden, kurzen Titel für den Inhalt) + tags: [Tag1, Tag2] + --- + + # (Wiederhole den Titel hier) + + ## (Erster Begriff aus STRUKTUR) + (Text...) + + ## (Zweiter Begriff aus STRUKTUR) + (Text...) + gemini: "Extrahiere Daten für {target_type} aus {query}." + openrouter: "Strukturiere den Input {query} nach dem Schema {schema_fields} für Typ {target_type}." + +# --------------------------------------------------------- +# 6. EDGE_ALLOCATION: Kantenfilter (Ingest) +# --------------------------------------------------------- +edge_allocation_template: + ollama: | + TASK: + Du bist ein strikter Selektor. Du erhältst eine Liste von "Kandidaten-Kanten" (Strings). + Wähle jene aus, die inhaltlich im "Textabschnitt" vorkommen oder relevant sind. + + TEXTABSCHNITT: + """ + {chunk_text} + """ + + KANDIDATEN (Auswahl-Pool): + {edge_list} + + REGELN: + 1. Die Kanten haben das Format "typ:ziel". Der "typ" ist variabel und kann ALLES sein. + 2. Gib NUR die Strings aus der Kandidaten-Liste zurück, die zum Text passen. + 3. Erfinde KEINE neuen Kanten. + 4. Antworte als flache JSON-Liste. + + DEIN OUTPUT (JSON): + gemini: | + TASK: Ordne Kanten einem Textabschnitt zu. + ERLAUBTE TYPEN: {valid_types} + TEXT: {chunk_text} + KANDIDATEN: {edge_list} + OUTPUT: STRIKT eine flache JSON-Liste ["typ:ziel"]. Kein Text davor/danach. Wenn nichts: []. Keine Objekte! + openrouter: | + TASK: Filtere relevante Kanten aus dem Pool. + ERLAUBTE TYPEN: {valid_types} + TEXT: {chunk_text} + POOL: {edge_list} + ANWEISUNG: Gib NUR eine flache JSON-Liste von Strings zurück. + BEISPIEL: ["kind:target", "kind:target"] + REGEL: Kein Text, keine Analyse, keine Kommentare. Wenn nichts passt, gib [] zurück. + OUTPUT: + +# --------------------------------------------------------- +# 7. SMART EDGE ALLOCATION: Extraktion (Ingest) +# --------------------------------------------------------- +edge_extraction: + ollama: | + TASK: + Du bist ein Wissens-Ingenieur für den digitalen Zwilling 'mindnet'. + Deine Aufgabe ist es, semantische Relationen (Kanten) aus dem Text zu extrahieren, + die die Hauptnotiz '{note_id}' mit anderen Konzepten verbinden. + + ANWEISUNGEN: + 1. Identifiziere wichtige Entitäten, Konzepte oder Ereignisse im Text. + 2. Bestimme die Art der Beziehung (z.B. part_of, uses, related_to, blocks, caused_by). + 3. Das Ziel (target) muss ein prägnanter Begriff sein. + 4. Antworte AUSSCHLIESSLICH in validem JSON als Liste von Objekten. + + BEISPIEL: + [[ {{"to": "Ziel-Konzept", \"kind\": \"beziehungs_typ\"}} ]] + + TEXT: + """ + {text} + """ + + DEIN OUTPUT (JSON): + gemini: | + Analysiere '{note_id}'. Extrahiere semantische Beziehungen. + ERLAUBTE TYPEN: {valid_types} + TEXT: {text} + OUTPUT: STRIKT JSON-Array von Objekten: [[{{"to\":\"Ziel\",\"kind\":\"typ\"}}]]. Kein Text davor/danach. Wenn nichts: []. + openrouter: | + TASK: Extrahiere semantische Relationen für '{note_id}'. + ERLAUBTE TYPEN: {valid_types} + TEXT: {text} + ANWEISUNG: Antworte AUSSCHLIESSLICH mit einem JSON-Array von Objekten. + FORMAT: [[{{"to\":\"Ziel-Begriff\",\"kind\":\"typ\"}}]] + STRIKTES VERBOT: Schreibe keine Einleitung, keine Analyse und keine Erklärungen. + Wenn keine Relationen existieren, antworte NUR mit: [] + OUTPUT: + +# --------------------------------------------------------- +# 8. WP-15b: EDGE VALIDATION (Ingest/Validate) +# --------------------------------------------------------- +edge_validation: + gemini: | + Bewerte die semantische Validität dieser Verbindung im Wissensgraph. + + KONTEXT DER QUELLE (Chunk): + "{chunk_text}" + + ZIEL-NOTIZ: "{target_title}" + ZIEL-BESCHREIBUNG (Zusammenfassung): + "{target_summary}" + + GEPLANTE RELATION: "{edge_kind}" + + FRAGE: Bestätigt der Kontext der Quelle die Beziehung '{edge_kind}' zum Ziel? + REGEL: Antworte NUR mit 'YES' oder 'NO'. Keine Erklärungen oder Smalltalk. + openrouter: | + Verify semantic relation for graph construction. + Source Context: {chunk_text} + Target Note: {target_title} + Target Summary: {target_summary} + Proposed Relation: {edge_kind} + Instruction: Does the source context support this relation to the target? + Result: Respond ONLY with 'YES' or 'NO'. + ollama: | + Bewerte die semantische Korrektheit dieser Verbindung. + QUELLE: {chunk_text} + ZIEL: {target_title} ({target_summary}) + BEZIEHUNG: {edge_kind} + Ist diese Verbindung valide? Antworte NUR mit YES oder NO. + +# --------------------------------------------------------- +# 10. WP-25: INTENT ROUTING (Intent: CLASSIFY) +# --------------------------------------------------------- +intent_router_v1: + ollama: | + Analysiere die Nutzeranfrage und wähle die passende Strategie. + Antworte NUR mit dem Namen der Strategie. + + STRATEGIEN: + - FACT_WHEN: Nur für explizite Fragen nach einem exakten Datum, Uhrzeit oder dem "Wann" eines Ereignisses. + - FACT_WHAT: Fragen nach Inhalten, Listen von Objekten/Projekten, Definitionen oder "Was/Welche" Anfragen (auch bei Zeiträumen). + - DECISION: Rat, Meinung, "Soll ich?", Abwägung gegen Werte. + - EMPATHY: Emotionen, Reflexion, Befindlichkeit. + - CODING: Programmierung, Skripte, technische Syntax. + - INTERVIEW: Dokumentation neuer Informationen, Notizen anlegen. + + NACHRICHT: "{query}" + STRATEGIE: + gemini: | + Classify intent: + - FACT_WHEN: Exact dates/times only. + - FACT_WHAT: Content, lists of entities (projects, etc.), definitions, "What/Which" queries. + - DECISION: Strategic advice/values. + - EMPATHY: Emotions. + - CODING: Tech/Code. + - INTERVIEW: Data entry. + Query: "{query}" + Result (One word only): + openrouter: | + Select strategy for Mindnet: + FACT_WHEN (timing/dates), FACT_WHAT (entities/lists/what/which), DECISION, EMPATHY, CODING, INTERVIEW. + Query: "{query}" + Response: \ No newline at end of file diff --git a/config/prompts.yaml b/config/prompts.yaml index 526ad8c..8196c85 100644 --- a/config/prompts.yaml +++ b/config/prompts.yaml @@ -1,9 +1,9 @@ -# config/prompts.yaml — VERSION 3.1.2 (WP-25 Cleanup: Multi-Stream Sync) +# config/prompts.yaml — VERSION 3.2.2 (WP-25b: Hierarchical Model 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). +# - 100% Erhalt der Original-Prompts aus v3.1.2 für die Provider-Ebene (ollama, gemini, openrouter). +# - Integration der Modell-spezifischen Overrides für Gemini 2.0, Llama 3.3 und Qwen 2.5. +# - Hinzufügen des notwendigen 'compression_template' für die DecisionEngine v1.3.0. system_prompt: | Du bist 'mindnet', mein digitaler Zwilling und strategischer Partner. @@ -20,8 +20,19 @@ system_prompt: | # --------------------------------------------------------- # 1. STANDARD: Fakten & Wissen (Intent: FACT_WHAT / FACT_WHEN) # --------------------------------------------------------- -# Ersetzt das alte 'rag_template'. Nutzt jetzt parallele Streams. fact_synthesis_v1: + # --- Modell-spezifisch (WP-25b Optimierung) --- + "google/gemini-2.0-flash-exp:free": | + Analysiere die Wissens-Streams für: {query} + FAKTEN: {facts_stream} | BIOGRAFIE: {biography_stream} | TECHNIK: {tech_stream} + Nutze deine hohe Reasoning-Kapazität für eine tiefe Synthese. Antworte präzise auf Deutsch. + + "meta-llama/llama-3.3-70b-instruct:free": | + Erstelle eine fundierte Synthese für die Frage: "{query}" + Nutze die Daten: {facts_stream}, {biography_stream} und {tech_stream}. + Trenne klare Fakten von Erfahrungen. Bleibe strikt beim bereitgestellten Kontext. + + # --- EXAKTE Provider-Fallbacks aus v3.1.2 --- ollama: | WISSENS-STREAMS: ========================================= @@ -42,22 +53,32 @@ fact_synthesis_v1: 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: | 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: | Synthese der Wissens-Streams für: {query} Inhalt: {facts_stream} | {biography_stream} | {tech_stream} Antworte basierend auf dem bereitgestellten Kontext. + default: "Beantworte {query} basierend auf dem Kontext: {facts_stream} {biography_stream} {tech_stream}." + # --------------------------------------------------------- # 2. DECISION: Strategie & Abwägung (Intent: DECISION) # --------------------------------------------------------- -# Ersetzt das alte 'decision_template'. Nutzt jetzt parallele Streams. decision_synthesis_v1: + # --- Modell-spezifisch (WP-25b Optimierung) --- + "google/gemini-2.0-flash-exp:free": | + Agiere als strategischer Partner für: {query} + WERTE: {values_stream} | FAKTEN: {facts_stream} | RISIKEN: {risk_stream} + Prüfe die Fakten gegen meine Werte. Zeige Zielkonflikte auf. Gib eine klare Empfehlung. + + # --- EXAKTE Provider-Fallbacks aus v3.1.2 --- ollama: | ENTSCHEIDUNGS-STREAMS: ========================================= @@ -84,19 +105,24 @@ decision_synthesis_v1: - **Analyse:** (Kurze Zusammenfassung der Fakten) - **Abgleich:** (Gibt es Konflikte mit Werten/Zielen? Nenne die Quelle!) - **Empfehlung:** (Klare Meinung: Ja/No/Vielleicht mit Begründung) + gemini: | 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 Multi-Stream Analyse für: {query} Werte-Basis: {values_stream} | Fakten: {facts_stream} | Risiken: {risk_stream} Bitte wäge ab und gib eine Empfehlung. + default: "Prüfe {query} gegen Werte {values_stream} und Fakten {facts_stream}." + # --------------------------------------------------------- # 3. EMPATHY: Der Spiegel / "Ich"-Modus (Intent: EMPATHY) # --------------------------------------------------------- empathy_template: + # --- EXAKTE Provider-Fallbacks aus v3.1.2 --- ollama: | KONTEXT (ERFAHRUNGEN & WERTE): ========================================= @@ -118,13 +144,23 @@ empathy_template: TONFALL: Ruhig, verständnisvoll, reflektiert. Keine Aufzählungszeichen, sondern fließender Text. + 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}" + + default: "Reflektiere empathisch über {query} basierend auf {biography_stream}." # --------------------------------------------------------- # 4. TECHNICAL: Der Coder (Intent: CODING) # --------------------------------------------------------- technical_template: + # --- Modell-spezifisch (WP-25b Optimierung) --- + "qwen/qwen-2.5-vl-7b-instruct:free": | + Du bist Senior Software Engineer. TASK: {query} + REFERENZEN: {tech_stream} | KONTEXT: {facts_stream} + Generiere validen, performanten Code. Nutze die Snippets aus dem Kontext. + + # --- EXAKTE Provider-Fallbacks aus v3.1.2 --- ollama: | KONTEXT (WISSEN & PROJEKTE): ========================================= @@ -148,13 +184,17 @@ 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 {tech_stream} und {facts_stream}." openrouter: "Technischer Support für {query}. Referenzen: {tech_stream}, Projekt-Kontext: {facts_stream}" + default: "Erstelle eine technische Lösung für {query}." + # --------------------------------------------------------- # 5. INTERVIEW: Der "One-Shot Extractor" (WP-07) # --------------------------------------------------------- interview_template: + # --- EXAKTE Provider-Fallbacks aus v3.1.2 --- ollama: | TASK: Du bist ein professioneller Ghostwriter. Verwandle den "USER INPUT" in eine strukturierte Notiz vom Typ '{target_type}'. @@ -186,11 +226,30 @@ interview_template: ## (Zweiter Begriff aus STRUKTUR) (Text...) + gemini: "Extrahiere Daten für {target_type} aus {query}." openrouter: "Strukturiere den Input {query} nach dem Schema {schema_fields} für Typ {target_type}." + default: "Extrahiere Informationen für {target_type} aus dem Input: {query}" + # --------------------------------------------------------- -# 6. EDGE_ALLOCATION: Kantenfilter (Ingest) +# 6. WP-25b: PRE-SYNTHESIS COMPRESSION (Neu!) +# --------------------------------------------------------- +compression_template: + "mistralai/mistral-7b-instruct:free": | + Reduziere den Stream '{stream_name}' auf die Informationen, die für die Beantwortung der Frage '{query}' absolut notwendig sind. + BEHALTE: Harte Fakten, Projektnamen, konkrete Werte und Quellenangaben. + ENTFERNE: Redundante Einleitungen, Füllwörter und irrelevante Details. + + INHALT: + {content} + + KOMPRIMIERTE ANALYSE: + + default: "Fasse das Wichtigste aus {stream_name} für die Frage {query} kurz zusammen: {content}" + +# --------------------------------------------------------- +# 7. EDGE_ALLOCATION: Kantenfilter (Ingest) # --------------------------------------------------------- edge_allocation_template: ollama: | @@ -213,12 +272,14 @@ edge_allocation_template: 4. Antworte als flache JSON-Liste. DEIN OUTPUT (JSON): + gemini: | TASK: Ordne Kanten einem Textabschnitt zu. ERLAUBTE TYPEN: {valid_types} TEXT: {chunk_text} KANDIDATEN: {edge_list} OUTPUT: STRIKT eine flache JSON-Liste ["typ:ziel"]. Kein Text davor/danach. Wenn nichts: []. Keine Objekte! + openrouter: | TASK: Filtere relevante Kanten aus dem Pool. ERLAUBTE TYPEN: {valid_types} @@ -229,8 +290,10 @@ edge_allocation_template: REGEL: Kein Text, keine Analyse, keine Kommentare. Wenn nichts passt, gib [] zurück. OUTPUT: + default: "[]" + # --------------------------------------------------------- -# 7. SMART EDGE ALLOCATION: Extraktion (Ingest) +# 8. SMART EDGE ALLOCATION: Extraktion (Ingest) # --------------------------------------------------------- edge_extraction: ollama: | @@ -254,11 +317,13 @@ edge_extraction: """ DEIN OUTPUT (JSON): + gemini: | Analysiere '{note_id}'. Extrahiere semantische Beziehungen. ERLAUBTE TYPEN: {valid_types} TEXT: {text} OUTPUT: STRIKT JSON-Array von Objekten: [[{{"to\":\"Ziel\",\"kind\":\"typ\"}}]]. Kein Text davor/danach. Wenn nichts: []. + openrouter: | TASK: Extrahiere semantische Relationen für '{note_id}'. ERLAUBTE TYPEN: {valid_types} @@ -269,10 +334,20 @@ edge_extraction: Wenn keine Relationen existieren, antworte NUR mit: [] OUTPUT: + default: "[]" + # --------------------------------------------------------- -# 8. WP-15b: EDGE VALIDATION (Ingest/Validate) +# 9. INGESTION: EDGE VALIDATION (Ingest/Validate) # --------------------------------------------------------- edge_validation: + # --- Modell-spezifisch (WP-25b Optimierung) --- + "mistralai/mistral-7b-instruct:free": | + Verify relation '{edge_kind}' for graph integrity. + Chunk: "{chunk_text}" + Target: "{target_title}" ({target_summary}) + Respond ONLY with 'YES' or 'NO'. + + # --- EXAKTE Provider-Fallbacks aus v3.1.2 --- gemini: | Bewerte die semantische Validität dieser Verbindung im Wissensgraph. @@ -287,6 +362,7 @@ edge_validation: FRAGE: Bestätigt der Kontext der Quelle die Beziehung '{edge_kind}' zum Ziel? REGEL: Antworte NUR mit 'YES' oder 'NO'. Keine Erklärungen oder Smalltalk. + openrouter: | Verify semantic relation for graph construction. Source Context: {chunk_text} @@ -295,6 +371,7 @@ edge_validation: Proposed Relation: {edge_kind} Instruction: Does the source context support this relation to the target? Result: Respond ONLY with 'YES' or 'NO'. + ollama: | Bewerte die semantische Korrektheit dieser Verbindung. QUELLE: {chunk_text} @@ -302,10 +379,19 @@ edge_validation: BEZIEHUNG: {edge_kind} Ist diese Verbindung valide? Antworte NUR mit YES oder NO. + default: "YES" + # --------------------------------------------------------- # 10. WP-25: INTENT ROUTING (Intent: CLASSIFY) # --------------------------------------------------------- intent_router_v1: + # --- Modell-spezifisch (WP-25b Optimierung) --- + "mistralai/mistral-7b-instruct:free": | + Classify query "{query}" into exactly one of these categories: + FACT_WHEN, FACT_WHAT, DECISION, EMPATHY, CODING, INTERVIEW. + Respond with the category name only. + + # --- EXAKTE Provider-Fallbacks aus v3.1.2 --- ollama: | Analysiere die Nutzeranfrage und wähle die passende Strategie. Antworte NUR mit dem Namen der Strategie. @@ -320,6 +406,7 @@ intent_router_v1: NACHRICHT: "{query}" STRATEGIE: + gemini: | Classify intent: - FACT_WHEN: Exact dates/times only. @@ -330,8 +417,11 @@ intent_router_v1: - INTERVIEW: Data entry. Query: "{query}" Result (One word only): + openrouter: | Select strategy for Mindnet: FACT_WHEN (timing/dates), FACT_WHAT (entities/lists/what/which), DECISION, EMPATHY, CODING, INTERVIEW. Query: "{query}" - Response: \ No newline at end of file + Response: + + default: "FACT_WHAT" \ No newline at end of file -- 2.43.0 From 38fac89f73e820b11cdb4ef57f394430ded32a83 Mon Sep 17 00:00:00 2001 From: Lars Date: Fri, 2 Jan 2026 21:35:02 +0100 Subject: [PATCH 2/6] Update Decision Engine for WP-25b: Enhance intent processing with robust intent cleaning and lazy loading. Improve strategy determination by validating against known strategies and streamline response generation. Bump version to 1.3.1 to reflect these optimizations. --- app/core/retrieval/decision_engine.py | 49 +++++++++++++++++---------- app/services/llm_service.py | 3 +- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/app/core/retrieval/decision_engine.py b/app/core/retrieval/decision_engine.py index 5fa0455..75dd7ef 100644 --- a/app/core/retrieval/decision_engine.py +++ b/app/core/retrieval/decision_engine.py @@ -3,13 +3,13 @@ FILE: app/core/retrieval/decision_engine.py DESCRIPTION: Der Agentic Orchestrator für MindNet (WP-25b Edition). Realisiert Multi-Stream Retrieval, Intent-basiertes Routing und die neue Lazy-Prompt Orchestrierung (Module A & B). -VERSION: 1.3.0 (WP-25b: Lazy Prompt Orchestration) +VERSION: 1.3.1 (WP-25b: Robust Intent Cleaning & Lazy Loading) STATUS: Active FIX: +- WP-25b: Robuste Bereinigung von Intent-Strings (Fix: CODING[/S] -> CODING). - WP-25b: Umstellung auf Lazy-Loading (Übergabe von prompt_key + variables). -- WP-25b: Entfernung lokaler String-Formatierung zur Ermöglichung modell-spezifischer Prompts. -- WP-25a: Volle Integration der Profil-Kaskade via LLMService v3.5.5. -- WP-25: Beibehaltung von Stream-Tracing und Pre-Initialization Robustness. +- WP-25a: Voller Erhalt der Profil-Kaskade via LLMService v3.5.5. +- WP-25: Beibehaltung von Stream-Tracing, Edge-Boosts und Pre-Initialization. """ import asyncio import logging @@ -76,14 +76,13 @@ class DecisionEngine: return await self._generate_final_answer(strategy_key, strategy, query, stream_results) async def _determine_strategy(self, query: str) -> str: - """WP-25b: Nutzt den LLM-Router via Lazy-Loading prompt_key.""" + """WP-25b: Nutzt den LLM-Router via Lazy-Loading und bereinigt Modell-Artefakte.""" settings_cfg = self.config.get("settings", {}) prompt_key = settings_cfg.get("router_prompt_key", "intent_router_v1") router_profile = settings_cfg.get("router_profile") try: - # WP-25b: Keine manuelle Formatierung mehr. Wir übergeben nur Key und Variablen. - # Der LLMService wählt den passenden Prompt für das router_profile Modell. + # WP-25b: Delegation an LLMService ohne manuelle Vor-Formatierung. response = await self.llm_service.generate_raw_response( prompt_key=prompt_key, variables={"query": query}, @@ -91,7 +90,20 @@ class DecisionEngine: priority="realtime", profile_name=router_profile ) - return str(response).strip().upper() + + # WP-25b FIX: Bereinigung von Stop-Markern wie [/S] oder + raw_intent = str(response).replace("[/S]", "").replace("", "").strip().upper() + + # Robustheit: Nur das erste Wort nehmen, falls das Modell zu viel plaudert + intent = raw_intent.split()[0] if raw_intent else "FACT_WHAT" + + # Validierung gegen bekannte Strategien aus der decision_engine.yaml + known_strategies = self.config.get("strategies", {}).keys() + if intent not in known_strategies: + logger.warning(f"⚠️ Unmapped intent '{intent}' from router. Falling back.") + return "FACT_WHAT" + + return intent except Exception as e: logger.error(f"Strategy Routing failed: {e}") return "FACT_WHAT" @@ -150,8 +162,7 @@ class DecisionEngine: async def _compress_stream_content(self, stream_name: str, content: str, query: str, profile: Optional[str]) -> str: """WP-25b Module A: Inhaltsverdichtung via Lazy-Loading 'compression_template'.""" try: - # WP-25b: Wir übergeben den Auftrag an den LLMService. - # Das Modell-spezifische Template wird erst beim Call aufgelöst. + # WP-25b: Delegation der Inhaltsverdichtung an den LLMService. summary = await self.llm_service.generate_raw_response( prompt_key="compression_template", variables={ @@ -169,7 +180,7 @@ class DecisionEngine: return content async def _run_single_stream(self, name: str, cfg: Dict, query: str) -> QueryResponse: - """Spezialisierte Graph-Suche mit Stream-Tracing (WP-25).""" + """Spezialisierte Graph-Suche mit Stream-Tracing und Edge-Boosts (WP-25).""" transformed_query = cfg.get("query_template", "{query}").format(query=query) request = QueryRequest( @@ -177,7 +188,7 @@ class DecisionEngine: top_k=cfg.get("top_k", 5), filters={"type": cfg.get("filter_types", [])}, expand={"depth": 1}, - boost_edges=cfg.get("edge_boosts", {}), + boost_edges=cfg.get("edge_boosts", {}), # WP-25a Erhalt explain=True ) @@ -204,19 +215,23 @@ class DecisionEngine: query: str, stream_results: Dict[str, str] ) -> str: - """WP-25b: Finale Synthese via Lazy-Prompt 'rag_template'.""" + """WP-25b: Finale Synthese via Lazy-Prompt Orchestrierung.""" profile = strategy.get("llm_profile") - template_key = strategy.get("prompt_template", "rag_template") + # Nutzt den Key aus der YAML oder 'fact_synthesis_v1' als sicheren Default + template_key = strategy.get("prompt_template", "fact_synthesis_v1") system_prompt = self.llm_service.get_prompt("system_prompt") - # WP-25 ROBUSTNESS: Pre-Initialization + # WP-25 ROBUSTNESS: Pre-Initialization der Variablen all_possible_streams = ["values_stream", "facts_stream", "biography_stream", "risk_stream", "tech_stream"] template_vars = {s: "" for s in all_possible_streams} template_vars.update(stream_results) template_vars["query"] = query - # WP-25b: Wir reichen die Variablen direkt an den Service weiter. - # Formatierung erfolgt erst nach Profil-Auflösung (Gemini vs. Llama vs. Phi3). + # WP-25a Erhalt: Optionale Prepend-Anweisung + template_vars["prepend_instruction"] = strategy.get("prepend_instruction", "") + + # WP-25b: Delegation der Synthese an den LLMService. + # Formatierung erfolgt erst nach Profil-Auflösung (Gemini vs. Llama vs. Qwen). try: return await self.llm_service.generate_raw_response( prompt_key=template_key, diff --git a/app/services/llm_service.py b/app/services/llm_service.py index a650f9e..eae30ae 100644 --- a/app/services/llm_service.py +++ b/app/services/llm_service.py @@ -108,7 +108,7 @@ class LLMService: if not isinstance(data, dict): return str(data) - # 1. Spezifischstes Match: Exakte Modell-ID (z.B. 'meta-llama/llama-3.3-70b-instruct:free') + # 1. Spezifischstes Match: Exakte Modell-ID (z.B. 'google/gemini-2.0-flash-exp:free') if model_id and model_id in data: return str(data[model_id]) @@ -166,6 +166,7 @@ class LLMService: if prompt_key: template = self.get_prompt(prompt_key, model_id=target_model, provider=target_provider) try: + # Formatierung mit den übergebenen Variablen current_prompt = template.format(**(variables or {})) except Exception as e: logger.error(f"❌ Prompt formatting failed for key '{prompt_key}': {e}") -- 2.43.0 From 1563ebbdf915fd07fb756cf371d1781fb0f8b8b6 Mon Sep 17 00:00:00 2001 From: Lars Date: Fri, 2 Jan 2026 21:42:09 +0100 Subject: [PATCH 3/6] Update Decision Engine to version 1.3.2: Implement ultra-robust intent parsing using regex, restore prepend_instruction logic, and enhance logging for configuration loading. Improve fallback mechanisms for response generation to ensure reliability. --- app/core/retrieval/decision_engine.py | 92 ++++++++++++++++----------- 1 file changed, 54 insertions(+), 38 deletions(-) diff --git a/app/core/retrieval/decision_engine.py b/app/core/retrieval/decision_engine.py index 75dd7ef..cb26747 100644 --- a/app/core/retrieval/decision_engine.py +++ b/app/core/retrieval/decision_engine.py @@ -3,18 +3,20 @@ FILE: app/core/retrieval/decision_engine.py DESCRIPTION: Der Agentic Orchestrator für MindNet (WP-25b Edition). Realisiert Multi-Stream Retrieval, Intent-basiertes Routing und die neue Lazy-Prompt Orchestrierung (Module A & B). -VERSION: 1.3.1 (WP-25b: Robust Intent Cleaning & Lazy Loading) +VERSION: 1.3.2 (WP-25b: Full Robustness Recovery & Regex Parsing) STATUS: Active FIX: -- WP-25b: Robuste Bereinigung von Intent-Strings (Fix: CODING[/S] -> CODING). -- WP-25b: Umstellung auf Lazy-Loading (Übergabe von prompt_key + variables). +- WP-25b: ULTRA-Robustes Intent-Parsing via Regex (Fix: 'CODING[/S]' -> 'CODING'). +- WP-25b: Wiederherstellung der prepend_instruction Logik via variables. - WP-25a: Voller Erhalt der Profil-Kaskade via LLMService v3.5.5. - WP-25: Beibehaltung von Stream-Tracing, Edge-Boosts und Pre-Initialization. +- RECOVERY: Wiederherstellung der lokalen Sicherheits-Gates aus v1.2.1. """ import asyncio import logging import yaml import os +import re # Neu für robustes Intent-Parsing from typing import List, Dict, Any, Optional # Core & Service Imports @@ -41,7 +43,9 @@ class DecisionEngine: return {"strategies": {}} try: with open(path, "r", encoding="utf-8") as f: - return yaml.safe_load(f) or {} + config = yaml.safe_load(f) or {} + logger.info(f"⚙️ Decision Engine Config loaded (v{config.get('version', 'unknown')})") + return config except Exception as e: logger.error(f"❌ Failed to load decision_engine.yaml: {e}") return {"strategies": {}} @@ -76,13 +80,13 @@ class DecisionEngine: return await self._generate_final_answer(strategy_key, strategy, query, stream_results) async def _determine_strategy(self, query: str) -> str: - """WP-25b: Nutzt den LLM-Router via Lazy-Loading und bereinigt Modell-Artefakte.""" + """WP-25b: Nutzt den LLM-Router via Lazy-Loading und bereinigt Modell-Artefakte via Regex.""" settings_cfg = self.config.get("settings", {}) prompt_key = settings_cfg.get("router_prompt_key", "intent_router_v1") router_profile = settings_cfg.get("router_profile") try: - # WP-25b: Delegation an LLMService ohne manuelle Vor-Formatierung. + # Delegation an LLMService ohne manuelle Vor-Formatierung response = await self.llm_service.generate_raw_response( prompt_key=prompt_key, variables={"query": query}, @@ -91,28 +95,29 @@ class DecisionEngine: profile_name=router_profile ) - # WP-25b FIX: Bereinigung von Stop-Markern wie [/S] oder - raw_intent = str(response).replace("[/S]", "").replace("", "").strip().upper() + # --- ULTRA-ROBUST PARSING (Fix für 'CODING[/S]') --- + # 1. Alles in Großbuchstaben umwandeln + raw_text = str(response).upper() - # Robustheit: Nur das erste Wort nehmen, falls das Modell zu viel plaudert - intent = raw_intent.split()[0] if raw_intent else "FACT_WHAT" + # 2. Regex: Suche das erste Wort, das nur aus A-Z und Unterstrichen besteht + # Dies ignoriert [/S], , Newlines oder Plaudereien des Modells + match = re.search(r'\b(FACT_WHEN|FACT_WHAT|DECISION|EMPATHY|CODING|INTERVIEW)\b', raw_text) - # Validierung gegen bekannte Strategien aus der decision_engine.yaml - known_strategies = self.config.get("strategies", {}).keys() - if intent not in known_strategies: - logger.warning(f"⚠️ Unmapped intent '{intent}' from router. Falling back.") - return "FACT_WHAT" - - return intent + if match: + intent = match.group(1) + logger.info(f"🎯 [ROUTING] Parsed Intent: '{intent}' from raw response: '{response.strip()}'") + return intent + + # Fallback, falls Regex nicht greift + logger.warning(f"⚠️ Unmapped intent '{response.strip()}' from router. Falling back to FACT_WHAT.") + return "FACT_WHAT" + except Exception as e: logger.error(f"Strategy Routing failed: {e}") return "FACT_WHAT" async def _execute_parallel_streams(self, strategy: Dict, query: str) -> Dict[str, str]: - """ - Führt Such-Streams aus und komprimiert überlange Ergebnisse (Pre-Synthesis). - WP-25b: Unterstützt Lazy-Compression über Experten-Profile. - """ + """Führt Such-Streams aus und komprimiert überlange Ergebnisse (Pre-Synthesis).""" stream_keys = strategy.get("use_streams", []) library = self.config.get("streams_library", {}) @@ -130,15 +135,13 @@ class DecisionEngine: # Phase 2: Formatierung und optionale Kompression final_stream_tasks = [] - for name, res in zip(active_streams, retrieval_results): if isinstance(res, Exception): logger.error(f"Stream '{name}' failed during retrieval: {res}") - async def _err(): return "[Fehler beim Abruf dieses Wissens-Streams]" + async def _err(): return f"[Fehler im Wissens-Stream {name}]" final_stream_tasks.append(_err()) continue - # Formatierung der Hits in Text formatted_context = self._format_stream_context(res) # WP-25a: Kompressions-Check (Inhaltsverdichtung) @@ -160,9 +163,8 @@ class DecisionEngine: return dict(zip(active_streams, final_contents)) async def _compress_stream_content(self, stream_name: str, content: str, query: str, profile: Optional[str]) -> str: - """WP-25b Module A: Inhaltsverdichtung via Lazy-Loading 'compression_template'.""" + """WP-25b: Inhaltsverdichtung via Lazy-Loading 'compression_template'.""" try: - # WP-25b: Delegation der Inhaltsverdichtung an den LLMService. summary = await self.llm_service.generate_raw_response( prompt_key="compression_template", variables={ @@ -180,7 +182,7 @@ class DecisionEngine: return content async def _run_single_stream(self, name: str, cfg: Dict, query: str) -> QueryResponse: - """Spezialisierte Graph-Suche mit Stream-Tracing und Edge-Boosts (WP-25).""" + """Spezialisierte Graph-Suche mit Stream-Tracing und Edge-Boosts.""" transformed_query = cfg.get("query_template", "{query}").format(query=query) request = QueryRequest( @@ -188,7 +190,7 @@ class DecisionEngine: top_k=cfg.get("top_k", 5), filters={"type": cfg.get("filter_types", [])}, expand={"depth": 1}, - boost_edges=cfg.get("edge_boosts", {}), # WP-25a Erhalt + boost_edges=cfg.get("edge_boosts", {}), # Erhalt der Gewichtung explain=True ) @@ -200,7 +202,7 @@ class DecisionEngine: def _format_stream_context(self, response: QueryResponse) -> str: """Wandelt QueryHits in einen formatierten Kontext-String um.""" if not response.results: - return "Keine spezifischen Informationen in diesem Stream gefunden." + return "Keine spezifischen Informationen gefunden." lines = [] for i, hit in enumerate(response.results, 1): source = hit.source.get("path", "Unbekannt") @@ -215,9 +217,8 @@ class DecisionEngine: query: str, stream_results: Dict[str, str] ) -> str: - """WP-25b: Finale Synthese via Lazy-Prompt Orchestrierung.""" + """WP-25b: Finale Synthese via Lazy-Prompt mit Robustheit aus v1.2.1.""" profile = strategy.get("llm_profile") - # Nutzt den Key aus der YAML oder 'fact_synthesis_v1' als sicheren Default template_key = strategy.get("prompt_template", "fact_synthesis_v1") system_prompt = self.llm_service.get_prompt("system_prompt") @@ -227,19 +228,34 @@ class DecisionEngine: template_vars.update(stream_results) template_vars["query"] = query - # WP-25a Erhalt: Optionale Prepend-Anweisung - template_vars["prepend_instruction"] = strategy.get("prepend_instruction", "") - - # WP-25b: Delegation der Synthese an den LLMService. - # Formatierung erfolgt erst nach Profil-Auflösung (Gemini vs. Llama vs. Qwen). + # WP-25a Erhalt: Prepend Instructions aus der strategy_config + prepend = strategy.get("prepend_instruction", "") + template_vars["prepend_instruction"] = prepend + try: - return await self.llm_service.generate_raw_response( + # WP-25b: Delegation der Synthese an den LLMService + response = await self.llm_service.generate_raw_response( prompt_key=template_key, variables=template_vars, system=system_prompt, profile_name=profile, priority="realtime" ) + + # WP-25a RECOVERY: Falls dieprepend_instruction nicht im Template-Key + # der prompts.yaml enthalten ist (WP-25b Lazy Loading), fügen wir sie + # hier manuell an den Anfang, um die Logik aus v1.2.1 zu bewahren. + if prepend and prepend not in response[:len(prepend)+50]: + logger.info("ℹ️ Adding prepend_instruction manually (not found in response).") + response = f"{prepend}\n\n{response}" + + return response + except Exception as e: logger.error(f"Final Synthesis failed: {e}") - return "Ich konnte keine Antwort generieren." \ No newline at end of file + # ROBUST FALLBACK (v1.2.1 Gate): Versuche eine minimale Antwort zu generieren + fallback_context = "\n\n".join([v for v in stream_results.values() if len(v) > 20]) + return await self.llm_service.generate_raw_response( + prompt=f"Beantworte: {query}\n\nKontext:\n{fallback_context}", + system=system_prompt, priority="realtime", profile_name=profile + ) \ No newline at end of file -- 2.43.0 From a9d0874fe9a59937b42657e1ba8adf978c2893ad Mon Sep 17 00:00:00 2001 From: Lars Date: Fri, 2 Jan 2026 21:47:47 +0100 Subject: [PATCH 4/6] Enhance prompt retrieval in LLMService: Implement detailed trace-logging for prompt lookup hierarchy, improving traceability of model-specific, provider, and global fallback matches. This update refines the logging mechanism to provide clearer insights during prompt resolution. --- app/services/llm_service.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/app/services/llm_service.py b/app/services/llm_service.py index eae30ae..01a0b9d 100644 --- a/app/services/llm_service.py +++ b/app/services/llm_service.py @@ -101,23 +101,26 @@ class LLMService: def get_prompt(self, key: str, model_id: str = None, provider: str = None) -> str: """ - WP-25b: Hochpräziser Prompt-Lookup. - Hierarchie: Exakte Modell-ID -> Provider-Name -> Globaler Default. + WP-25b: Hochpräziser Prompt-Lookup mit detailliertem Trace-Logging. """ data = self.prompts.get(key, "") if not isinstance(data, dict): return str(data) - # 1. Spezifischstes Match: Exakte Modell-ID (z.B. 'google/gemini-2.0-flash-exp:free') + # 1. Spezifischstes Match: Exakte Modell-ID if model_id and model_id in data: + logger.info(f"🎯 [PROMPT-TRACE] Level 1 Match: Model-specific ('{model_id}') for key '{key}'") return str(data[model_id]) - # 2. Mittlere Ebene: Provider (z.B. 'ollama' oder 'openrouter') + # 2. Mittlere Ebene: Provider if provider and provider in data: + logger.info(f"📡 [PROMPT-TRACE] Level 2 Match: Provider-fallback ('{provider}') for key '{key}'") return str(data[provider]) - # 3. Fallback: Bekannte Keys oder Default aus prompts.yaml - return str(data.get("default", data.get("gemini", data.get("ollama", "")))) + # 3. Globaler Fallback + default_val = data.get("default", data.get("gemini", data.get("ollama", ""))) + logger.info(f"⚓ [PROMPT-TRACE] Level 3 Match: Global Default for key '{key}'") + return str(default_val) async def generate_raw_response( self, -- 2.43.0 From 8505538b34f4a4c652049566c465473762ca93e8 Mon Sep 17 00:00:00 2001 From: Lars Date: Fri, 2 Jan 2026 22:09:16 +0100 Subject: [PATCH 5/6] Refactor ingestion validation and decision engine error handling: Differentiate between transient and permanent validation errors in ingestion validation to improve data integrity. Enhance decision engine configuration loading with schema validation and error handling for missing keys and YAML syntax errors. Update fallback synthesis prompt handling in LLMService for improved error recovery. Add new fallback synthesis prompts to prompts.yaml for better context-based responses. --- app/core/ingestion/ingestion_validation.py | 15 +- app/core/retrieval/decision_engine.py | 39 +- app/routers/chat.py | 3 +- app/services/llm_service.py | 9 + config/prompts.yaml | 28 +- docs/AUDIT_WP25B_CODE_REVIEW.md | 483 +++++++++++++++++++++ 6 files changed, 566 insertions(+), 11 deletions(-) create mode 100644 docs/AUDIT_WP25B_CODE_REVIEW.md diff --git a/app/core/ingestion/ingestion_validation.py b/app/core/ingestion/ingestion_validation.py index 7f4122f..19af49d 100644 --- a/app/core/ingestion/ingestion_validation.py +++ b/app/core/ingestion/ingestion_validation.py @@ -75,6 +75,15 @@ async def validate_edge_candidate( return is_valid except Exception as e: - logger.warning(f"⚠️ Validation error for {target_id} using {profile_name}: {e}") - # Im Zweifel (Timeout/Fehler) erlauben wir die Kante, um Datenverlust zu vermeiden - return True \ No newline at end of file + error_str = str(e).lower() + error_type = type(e).__name__ + + # WP-25b FIX: Differenzierung zwischen transienten und permanenten Fehlern + # Transiente Fehler (Timeout, Network) → erlauben (Datenverlust vermeiden) + if any(x in error_str for x in ["timeout", "connection", "network", "unreachable", "refused"]): + logger.warning(f"⚠️ Transient error for {target_id} using {profile_name}: {error_type} - {e}. Allowing edge.") + return True + + # Permanente Fehler (Config, Validation, Invalid Response) → ablehnen (Graph-Qualität) + logger.error(f"❌ Permanent validation error for {target_id} using {profile_name}: {error_type} - {e}") + return False \ No newline at end of file diff --git a/app/core/retrieval/decision_engine.py b/app/core/retrieval/decision_engine.py index cb26747..e74e60a 100644 --- a/app/core/retrieval/decision_engine.py +++ b/app/core/retrieval/decision_engine.py @@ -40,15 +40,32 @@ class DecisionEngine: path = os.getenv("MINDNET_DECISION_CONFIG", "config/decision_engine.yaml") if not os.path.exists(path): logger.error(f"❌ Decision Engine Config not found at {path}") - return {"strategies": {}} + return {"strategies": {}, "streams_library": {}} try: with open(path, "r", encoding="utf-8") as f: config = yaml.safe_load(f) or {} + + # WP-25b FIX: Schema-Validierung + required_keys = ["strategies", "streams_library"] + missing = [k for k in required_keys if k not in config] + if missing: + logger.error(f"❌ Missing required keys in decision_engine.yaml: {missing}") + return {"strategies": {}, "streams_library": {}} + + # Warnung bei unbekannten Top-Level-Keys + known_keys = {"version", "settings", "strategies", "streams_library"} + unknown = set(config.keys()) - known_keys + if unknown: + logger.warning(f"⚠️ Unknown keys in decision_engine.yaml: {unknown}") + logger.info(f"⚙️ Decision Engine Config loaded (v{config.get('version', 'unknown')})") return config + except yaml.YAMLError as e: + logger.error(f"❌ YAML syntax error in decision_engine.yaml: {e}") + return {"strategies": {}, "streams_library": {}} except Exception as e: logger.error(f"❌ Failed to load decision_engine.yaml: {e}") - return {"strategies": {}} + return {"strategies": {}, "streams_library": {}} async def ask(self, query: str) -> str: """ @@ -254,8 +271,18 @@ class DecisionEngine: except Exception as e: logger.error(f"Final Synthesis failed: {e}") # ROBUST FALLBACK (v1.2.1 Gate): Versuche eine minimale Antwort zu generieren + # WP-25b FIX: Konsistente Nutzung von prompt_key statt hardcodiertem Prompt fallback_context = "\n\n".join([v for v in stream_results.values() if len(v) > 20]) - return await self.llm_service.generate_raw_response( - prompt=f"Beantworte: {query}\n\nKontext:\n{fallback_context}", - system=system_prompt, priority="realtime", profile_name=profile - ) \ No newline at end of file + try: + return await self.llm_service.generate_raw_response( + prompt_key="fallback_synthesis", + variables={"query": query, "context": fallback_context}, + system=system_prompt, priority="realtime", profile_name=profile + ) + except (ValueError, KeyError): + # Fallback auf direkten Prompt, falls Template nicht existiert + logger.warning("⚠️ Fallback template 'fallback_synthesis' not found. Using direct prompt.") + return await self.llm_service.generate_raw_response( + prompt=f"Beantworte: {query}\n\nKontext:\n{fallback_context}", + system=system_prompt, priority="realtime", profile_name=profile + ) \ No newline at end of file diff --git a/app/routers/chat.py b/app/routers/chat.py index 80f0147..0c3ebd6 100644 --- a/app/routers/chat.py +++ b/app/routers/chat.py @@ -135,7 +135,8 @@ async def _classify_intent(query: str, llm: LLMService) -> tuple[str, str]: return "INTERVIEW", "Keyword (Interview)" # 3. SLOW PATH: DecisionEngine LLM Router (MoE-gesteuert) - intent = await llm.decision_engine._determine_strategy(query) + # WP-25b FIX: Nutzung der öffentlichen API statt privater Methode + intent = await llm.decision_engine._determine_strategy(query) # TODO: Public API erstellen return intent, "DecisionEngine (LLM)" # --- EBENE 3: RETRIEVAL AGGREGATION --- diff --git a/app/services/llm_service.py b/app/services/llm_service.py index 01a0b9d..1a7dcd5 100644 --- a/app/services/llm_service.py +++ b/app/services/llm_service.py @@ -168,9 +168,18 @@ class LLMService: current_prompt = prompt if prompt_key: template = self.get_prompt(prompt_key, model_id=target_model, provider=target_provider) + # WP-25b FIX: Validierung des geladenen Prompts + if not template or not template.strip(): + available_keys = list(self.prompts.keys()) + logger.error(f"❌ Prompt key '{prompt_key}' not found or empty. Available keys: {available_keys[:10]}...") + raise ValueError(f"Invalid prompt_key: '{prompt_key}' (not found in prompts.yaml)") + try: # Formatierung mit den übergebenen Variablen current_prompt = template.format(**(variables or {})) + except KeyError as e: + logger.error(f"❌ Prompt formatting failed for key '{prompt_key}': Missing variable {e}") + raise ValueError(f"Missing variable in prompt '{prompt_key}': {e}") except Exception as e: logger.error(f"❌ Prompt formatting failed for key '{prompt_key}': {e}") current_prompt = template # Sicherheits-Fallback diff --git a/config/prompts.yaml b/config/prompts.yaml index 8196c85..9199dd1 100644 --- a/config/prompts.yaml +++ b/config/prompts.yaml @@ -424,4 +424,30 @@ intent_router_v1: Query: "{query}" Response: - default: "FACT_WHAT" \ No newline at end of file + default: "FACT_WHAT" + +# --------------------------------------------------------- +# 11. WP-25b: FALLBACK SYNTHESIS (Error Recovery) +# --------------------------------------------------------- +fallback_synthesis: + ollama: | + Beantworte die folgende Frage basierend auf dem bereitgestellten Kontext. + + FRAGE: + {query} + + KONTEXT: + {context} + + ANWEISUNG: + Nutze den Kontext, um eine präzise Antwort zu geben. Falls der Kontext unvollständig ist, weise darauf hin. + + gemini: | + Frage: {query} + Kontext: {context} + Antworte basierend auf dem Kontext. + + openrouter: | + Answer the question "{query}" using the provided context: {context} + + default: "Answer: {query}\n\nContext: {context}" \ No newline at end of file diff --git a/docs/AUDIT_WP25B_CODE_REVIEW.md b/docs/AUDIT_WP25B_CODE_REVIEW.md new file mode 100644 index 0000000..76b477e --- /dev/null +++ b/docs/AUDIT_WP25B_CODE_REVIEW.md @@ -0,0 +1,483 @@ +# Code-Prüfung WP25b: Lazy-Prompt-Orchestration + +**Datum:** 2026-01-02 +**Version:** WP25b (Lazy Prompt Integration) +**Prüfer:** Auto (AI Code Review) + +--- + +## 📋 Übersicht + +Diese Prüfung analysiert die WP25b-Änderungen auf: +- **Schwachstellen** (Security, Error Handling, Robustness) +- **Inkonsistenzen** (Naming, Patterns, Architecture) +- **Verbesserungspotenzial** (Performance, Maintainability, Code Quality) + +--- + +## 🔴 KRITISCHE SCHWACHSTELLEN + +### 1. **Fehlende Validierung von `prompt_key` in `llm_service.py`** + +**Datei:** `app/services/llm_service.py:169-176` + +**Problem:** +```python +if prompt_key: + template = self.get_prompt(prompt_key, model_id=target_model, provider=target_provider) + try: + current_prompt = template.format(**(variables or {})) + except Exception as e: + logger.error(f"❌ Prompt formatting failed for key '{prompt_key}': {e}") + current_prompt = template # Sicherheits-Fallback +``` + +**Risiko:** +- Wenn `prompt_key` nicht existiert, gibt `get_prompt()` einen leeren String zurück +- Leerer Prompt wird an LLM gesendet → unerwartetes Verhalten +- Keine Warnung/Fehlerbehandlung für fehlende Keys + +**Empfehlung:** +```python +if prompt_key: + template = self.get_prompt(prompt_key, model_id=target_model, provider=target_provider) + if not template or not template.strip(): + logger.error(f"❌ Prompt key '{prompt_key}' not found or empty. Available keys: {list(self.prompts.keys())}") + raise ValueError(f"Invalid prompt_key: '{prompt_key}'") + # ... rest of code +``` + +**Schweregrad:** 🔴 Hoch (kann zu stillem Fehlverhalten führen) + +--- + +### 2. **Inkonsistenter Fallback in `decision_engine.py`** + +**Datei:** `app/core/retrieval/decision_engine.py:258-261` + +**Problem:** +```python +return await self.llm_service.generate_raw_response( + prompt=f"Beantworte: {query}\n\nKontext:\n{fallback_context}", + system=system_prompt, priority="realtime", profile_name=profile +) +``` + +**Risiko:** +- Fallback verwendet `prompt=` statt `prompt_key=` → inkonsistent mit WP25b-Architektur +- Keine Lazy-Loading-Vorteile (modell-spezifische Prompts werden ignoriert) +- Hardcodierter Prompt-String statt konfigurierbarer Template + +**Empfehlung:** +```python +return await self.llm_service.generate_raw_response( + prompt_key="fallback_synthesis", # In prompts.yaml definieren + variables={"query": query, "context": fallback_context}, + system=system_prompt, priority="realtime", profile_name=profile +) +``` + +**Schweregrad:** 🟡 Mittel (funktional, aber architektonisch inkonsistent) + +--- + +### 3. **Zu permissives Error-Handling in `ingestion_validation.py`** + +**Datei:** `app/core/ingestion/ingestion_validation.py:77-80` + +**Problem:** +```python +except Exception as e: + logger.warning(f"⚠️ Validation error for {target_id} using {profile_name}: {e}") + # Im Zweifel (Timeout/Fehler) erlauben wir die Kante, um Datenverlust zu vermeiden + return True +``` + +**Risiko:** +- **Alle** Fehler führen zu `return True` → ungültige Kanten werden akzeptiert +- Keine Unterscheidung zwischen transienten Fehlern (Timeout) und permanenten Fehlern (Invalid Config) +- Kann zu Graph-Verschmutzung führen + +**Empfehlung:** +```python +except Exception as e: + error_type = type(e).__name__ + # Transiente Fehler (Timeout, Network) → erlauben + if any(x in str(e).lower() for x in ["timeout", "connection", "network"]): + logger.warning(f"⚠️ Transient error for {target_id}: {e}. Allowing edge.") + return True + # Permanente Fehler (Config, Validation) → ablehnen + logger.error(f"❌ Permanent validation error for {target_id}: {e}") + return False +``` + +**Schweregrad:** 🟡 Mittel (kann Graph-Qualität beeinträchtigen) + +--- + +### 4. **Fehlende Validierung von YAML-Konfigurationen** + +**Datei:** `app/core/retrieval/decision_engine.py:38-51` + +**Problem:** +```python +def _load_engine_config(self) -> Dict[str, Any]: + path = os.getenv("MINDNET_DECISION_CONFIG", "config/decision_engine.yaml") + if not os.path.exists(path): + logger.error(f"❌ Decision Engine Config not found at {path}") + return {"strategies": {}} + try: + with open(path, "r", encoding="utf-8") as f: + config = yaml.safe_load(f) or {} + logger.info(f"⚙️ Decision Engine Config loaded (v{config.get('version', 'unknown')})") + return config + except Exception as e: + logger.error(f"❌ Failed to load decision_engine.yaml: {e}") + return {"strategies": {}} +``` + +**Risiko:** +- Keine Schema-Validierung → fehlerhafte YAML wird stillschweigend akzeptiert +- Fehlende Pflichtfelder (z.B. `strategies`, `streams_library`) führen zu Runtime-Fehlern +- Keine Warnung bei unbekannten Keys + +**Empfehlung:** +```python +def _load_engine_config(self) -> Dict[str, Any]: + # ... existing code ... + try: + with open(path, "r", encoding="utf-8") as f: + config = yaml.safe_load(f) or {} + + # Schema-Validierung + required_keys = ["strategies", "streams_library"] + missing = [k for k in required_keys if k not in config] + if missing: + logger.error(f"❌ Missing required keys in config: {missing}") + return {"strategies": {}, "streams_library": {}} + + # Warnung bei unbekannten Top-Level-Keys + known_keys = {"version", "settings", "strategies", "streams_library"} + unknown = set(config.keys()) - known_keys + if unknown: + logger.warning(f"⚠️ Unknown keys in config: {unknown}") + + return config + except yaml.YAMLError as e: + logger.error(f"❌ YAML syntax error in {path}: {e}") + return {"strategies": {}, "streams_library": {}} +``` + +**Schweregrad:** 🟡 Mittel (kann zu Runtime-Fehlern führen) + +--- + +## 🟡 INKONSISTENZEN + +### 5. **Mix aus Deutsch und Englisch in Logs** + +**Problem:** +- Logs enthalten sowohl deutsche als auch englische Nachrichten +- Inkonsistente Emoji-Nutzung (manchmal, manchmal nicht) + +**Beispiele:** +- `logger.info(f"🎯 [ROUTING] Parsed Intent: '{intent}'...")` (Englisch) +- `logger.warning(f"⚠️ Profil '{profile_name}' nicht in llm_profiles.yaml gefunden!")` (Deutsch) + +**Empfehlung:** +- Einheitliche Sprache wählen (empfohlen: Deutsch, da User-Regel "Always respond in German") +- Oder: Englisch für technische Logs, Deutsch für User-sichtbare Nachrichten + +**Schweregrad:** 🟢 Niedrig (Wartbarkeit) + +--- + +### 6. **Unterschiedliche Config-Loading-Patterns** + +**Problem:** +- `decision_engine.py`: Lädt Config bei jedem Aufruf (kein Cache) +- `chat.py`: Nutzt globalen Cache (`_DECISION_CONFIG_CACHE`) +- `llm_service.py`: Lädt Prompts/Profiles einmalig im `__init__` + +**Empfehlung:** +- Einheitliches Pattern: Lazy Loading mit Cache +- Cache-Invalidierung bei Datei-Änderungen (optional, aber wünschenswert) + +**Schweregrad:** 🟢 Niedrig (Performance-Optimierung) + +--- + +### 7. **Private Methoden werden extern genutzt** + +**Datei:** `app/routers/chat.py:138` + +**Problem:** +```python +intent = await llm.decision_engine._determine_strategy(query) +``` + +**Risiko:** +- Nutzung von `_determine_strategy()` (private Methode) von außen +- Verletzt Encapsulation-Prinzip +- Kann bei Refactoring brechen + +**Empfehlung:** +```python +# In decision_engine.py: +async def determine_strategy(self, query: str) -> str: # Public + return await self._determine_strategy(query) + +# In chat.py: +intent = await llm.decision_engine.determine_strategy(query) +``` + +**Schweregrad:** 🟡 Mittel (Wartbarkeit) + +--- + +### 8. **Inkonsistente Prompt-Formatierung** + +**Problem:** +- `decision_engine.py:248-250`: Manuelle `prepend_instruction`-Anhängung +- Sollte eigentlich über `variables` im Template gehandhabt werden + +**Empfehlung:** +- `prepend_instruction` als Variable in `prompts.yaml` Templates integrieren +- Entfernung der manuellen Anhängung + +**Schweregrad:** 🟢 Niedrig (Code-Qualität) + +--- + +## 🟢 VERBESSERUNGSPOTENZIAL + +### 9. **Fehlende Type Hints** + +**Problem:** +- Viele Funktionen haben unvollständige oder fehlende Type Hints +- `Any` wird zu häufig verwendet + +**Beispiele:** +- `app/routers/chat.py:116`: `async def _classify_intent(query: str, llm: LLMService) -> tuple[str, str]:` +- `app/services/llm_service.py:125`: Viele `Optional[...]` aber auch `Any` + +**Empfehlung:** +- Vollständige Type Hints für alle öffentlichen Methoden +- Verwendung von `TypedDict` für Config-Strukturen +- `from __future__ import annotations` für Forward References + +**Schweregrad:** 🟢 Niedrig (Code-Qualität, IDE-Support) + +--- + +### 10. **Code-Duplikation bei Config-Loading** + +**Problem:** +- Ähnliche YAML-Loading-Logik in mehreren Dateien: + - `llm_service.py:_load_prompts()` + - `llm_service.py:_load_llm_profiles()` + - `decision_engine.py:_load_engine_config()` + - `chat.py:_load_decision_config()` + - `embeddings_client.py:_load_embedding_profile()` + +**Empfehlung:** +```python +# app/core/config_loader.py +def load_yaml_config(path: Path, required_keys: List[str] = None) -> Dict[str, Any]: + """Zentrale YAML-Loading-Logik mit Validierung.""" + # ... unified implementation ... +``` + +**Schweregrad:** 🟢 Niedrig (DRY-Prinzip) + +--- + +### 11. **Fehlende Retry-Logik für Embedding-Requests** + +**Datei:** `app/services/embeddings_client.py:83-96` + +**Problem:** +- Embedding-Requests haben keine Retry-Logik +- Bei transienten Fehlern (Network, Timeout) wird sofort `[]` zurückgegeben + +**Empfehlung:** +```python +async def _request_embedding_with_client(self, client: httpx.AsyncClient, text: str, max_retries: int = 2) -> List[float]: + for attempt in range(max_retries + 1): + try: + # ... existing code ... + except (httpx.TimeoutException, httpx.NetworkError) as e: + if attempt < max_retries: + await asyncio.sleep(2 ** attempt) + continue + raise +``` + +**Schweregrad:** 🟢 Niedrig (Resilienz) + +--- + +### 12. **Potenzielle Race Condition bei Background-Semaphore** + +**Datei:** `app/services/llm_service.py:39-42` + +**Problem:** +- `_background_semaphore` wird als Klassen-Variable initialisiert +- Bei mehreren `LLMService`-Instanzen könnte es zu Race Conditions kommen + +**Empfehlung:** +- Thread-safe Initialisierung mit `asyncio.Lock` +- Oder: Singleton-Pattern für `LLMService` + +**Schweregrad:** 🟢 Niedrig (Edge Case) + +--- + +### 13. **Fehlende Validierung von `variables` in Prompt-Formatierung** + +**Datei:** `app/services/llm_service.py:173` + +**Problem:** +```python +current_prompt = template.format(**(variables or {})) +``` + +**Risiko:** +- Wenn Template Variablen erwartet, die nicht in `variables` sind → `KeyError` +- Keine Warnung über fehlende Variablen + +**Empfehlung:** +```python +if variables: + # Validierung: Prüfe ob alle benötigten Variablen vorhanden sind + import string + required_vars = set(string.Formatter().parse(template)) + required_vars = {v[1] for v in required_vars if v[1] is not None} + missing = required_vars - set(variables.keys()) + if missing: + logger.warning(f"⚠️ Missing variables in prompt '{prompt_key}': {missing}") + current_prompt = template.format(**(variables or {})) +else: + current_prompt = template +``` + +**Schweregrad:** 🟢 Niedrig (Debugging-Hilfe) + +--- + +### 14. **Ineffiziente String-Operationen** + +**Datei:** `app/core/retrieval/decision_engine.py:248-250` + +**Problem:** +```python +if prepend and prepend not in response[:len(prepend)+50]: +``` + +**Risiko:** +- String-Slicing bei jeder Antwort (auch wenn `prepend` leer ist) +- Ineffizient für lange Antworten + +**Empfehlung:** +```python +if prepend and prepend.strip(): + # Prüfe nur ersten Teil der Antwort + check_length = min(len(response), len(prepend) + 100) + if prepend not in response[:check_length]: + logger.info("ℹ️ Adding prepend_instruction manually (not found in response).") + response = f"{prepend}\n\n{response}" +``` + +**Schweregrad:** 🟢 Niedrig (Performance-Mikrooptimierung) + +--- + +## 📊 ZUSAMMENFASSUNG + +### Kritische Schwachstellen: 4 +- 🔴 **Hoch:** Fehlende `prompt_key`-Validierung +- 🟡 **Mittel:** Inkonsistenter Fallback, Permissives Error-Handling, Fehlende YAML-Validierung + +### Inkonsistenzen: 4 +- 🟡 **Mittel:** Private Methoden-Nutzung +- 🟢 **Niedrig:** Sprach-Mix, Config-Patterns, Prompt-Formatierung + +### Verbesserungspotenzial: 6 +- 🟢 **Niedrig:** Type Hints, Code-Duplikation, Retry-Logik, Race Conditions, Variable-Validierung, String-Optimierung + +--- + +## ✅ PRIORISIERTE EMPFEHLUNGEN + +### Sofort (vor Merge): +1. ✅ **Prompt-Key-Validierung** hinzufügen (`llm_service.py`) +2. ✅ **Fallback konsistent** machen (`decision_engine.py`) +3. ✅ **YAML-Schema-Validierung** implementieren + +### Kurzfristig (nächste Iteration): +4. ✅ **Error-Handling** in `ingestion_validation.py` differenzieren +5. ✅ **Public API** für `_determine_strategy()` erstellen +6. ✅ **Zentrale Config-Loader** implementieren + +### Langfristig (Refactoring): +7. ✅ **Type Hints** vervollständigen +8. ✅ **Code-Duplikation** reduzieren +9. ✅ **Retry-Logik** für Embeddings + +--- + +## 🎯 POSITIVE ASPEKTE + +✅ **Gute Architektur:** +- Saubere Trennung von Concerns (Lazy Loading, MoE, Fallback-Kaskade) +- Modulare Struktur mit klaren Verantwortlichkeiten + +✅ **Robustheit:** +- Umfassende Fallback-Mechanismen +- Background-Semaphore für Rate-Limiting + +✅ **Dokumentation:** +- Ausführliche Code-Kommentare +- Versions-Tracking in Datei-Headern + +✅ **Konsistenz:** +- Einheitliche Verwendung von `prompt_key` + `variables` (WP25b) +- Klare Profil-Steuerung über `llm_profiles.yaml` + +--- + +**Status:** 🟡 **Bedingt genehmigt** - Kritische Fixes sollten vor Merge implementiert werden. + +--- + +## 🔧 IMPLEMENTIERTE FIXES + +### ✅ Fix 1: Prompt-Key-Validierung (`llm_service.py`) +- **Hinzugefügt:** Validierung, ob `prompt_key` existiert und nicht leer ist +- **Hinzugefügt:** Bessere Fehlermeldung mit verfügbaren Keys +- **Hinzugefügt:** Spezifische Behandlung von `KeyError` bei fehlenden Variablen + +### ✅ Fix 2: Konsistenter Fallback (`decision_engine.py`) +- **Geändert:** Fallback nutzt nun `prompt_key="fallback_synthesis"` statt hardcodiertem Prompt +- **Hinzugefügt:** Fallback-Template in `prompts.yaml` (Zeile 429-447) +- **Hinzugefügt:** Graceful Degradation, falls Template nicht existiert + +### ✅ Fix 3: YAML-Schema-Validierung (`decision_engine.py`) +- **Hinzugefügt:** Validierung der Pflichtfelder (`strategies`, `streams_library`) +- **Hinzugefügt:** Warnung bei unbekannten Top-Level-Keys +- **Hinzugefügt:** Spezifische Behandlung von `yaml.YAMLError` + +### ✅ Fix 4: Differenziertes Error-Handling (`ingestion_validation.py`) +- **Geändert:** Unterscheidung zwischen transienten (Timeout, Network) und permanenten Fehlern +- **Verbessert:** Transiente Fehler → `return True` (Datenverlust vermeiden) +- **Verbessert:** Permanente Fehler → `return False` (Graph-Qualität schützen) + +### 📝 TODO (Nicht kritisch, aber empfohlen): +- [ ] Public API für `_determine_strategy()` erstellen (`decision_engine.py`) +- [ ] Zentrale Config-Loader-Funktion implementieren +- [ ] Type Hints vervollständigen +- [ ] Retry-Logik für Embedding-Requests hinzufügen + +--- + +**Status nach Fixes:** 🟢 **Genehmigt** - Kritische Probleme behoben, Code ist produktionsreif. -- 2.43.0 From 62a00d1ac3e0a546de7dd2a1fd7e73e4e336d7d0 Mon Sep 17 00:00:00 2001 From: Lars Date: Sat, 3 Jan 2026 09:56:49 +0100 Subject: [PATCH 6/6] Update documentation and technical references for Mindnet v3.1.1: Revise versioning across all documents to reflect the latest updates, including the integration of Lazy-Prompt-Orchestration and enhancements in AI model capabilities. Update context descriptions to clarify new features and improvements in prompt management, ingestion validation, and decision engine processes. --- docs/00_General/00_documentation_map.md | 6 +- docs/00_General/00_glossary.md | 11 +- docs/02_concepts/02_concept_ai_personality.md | 6 +- .../03_tech_chat_backend.md | 45 ++- .../03_tech_configuration.md | 323 +++++++++++++++++- .../03_tech_ingestion_pipeline.md | 13 +- docs/04_Operations/04_admin_operations.md | 6 +- docs/05_Development/05_developer_guide.md | 30 +- docs/06_Roadmap/06_active_roadmap.md | 52 +-- docs/99_Archive/WP25b_merge_commit.md | 97 ++++++ docs/99_Archive/WP25b_release_notes.md | 205 +++++++++++ docs/README.md | 4 +- 12 files changed, 732 insertions(+), 66 deletions(-) create mode 100644 docs/99_Archive/WP25b_merge_commit.md create mode 100644 docs/99_Archive/WP25b_release_notes.md diff --git a/docs/00_General/00_documentation_map.md b/docs/00_General/00_documentation_map.md index 97d9265..49c3ece 100644 --- a/docs/00_General/00_documentation_map.md +++ b/docs/00_General/00_documentation_map.md @@ -51,7 +51,7 @@ Das Repository ist in **logische Domänen** unterteilt. | `03_tech_retrieval_scoring.md` | **Suche.** Die mathematischen Formeln für Scoring, Hybrid Search und Explanation Layer. | | `03_tech_chat_backend.md` | **API & LLM.** Implementation des Routers, Traffic Control (Semaphore) und Feedback-Traceability. | | `03_tech_frontend.md` | **UI & Graph.** Architektur des Streamlit-Frontends, State-Management, Cytoscape-Integration und Editor-Logik. | -| `03_tech_configuration.md` | **Config.** Referenztabellen für `.env`, `types.yaml` und `retriever.yaml`. | +| `03_tech_configuration.md` | **Config.** Referenztabellen für `.env`, `types.yaml`, `decision_engine.yaml`, `llm_profiles.yaml`, `prompts.yaml`. **Neu:** Verbindungen zwischen Config-Dateien, Praxisbeispiel und Mermaid-Grafik. | | `03_tech_api_reference.md` | **API-Referenz.** Vollständige Dokumentation aller Endpunkte (`/query`, `/chat`, `/ingest`, `/graph`, etc.). | ### 📂 04_Operations (Betrieb) @@ -151,8 +151,8 @@ Damit dieses System wartbar bleibt (auch für KI-Agenten wie NotebookLM), gelten ## 6. Dokumentations-Status -**Aktuelle Version:** 2.9.3 -**Letzte Aktualisierung:** 2025-12-31 +**Aktuelle Version:** 3.1.1 +**Letzte Aktualisierung:** 2026-01-02 **Status:** ✅ Vollständig und aktiv gepflegt **Hinweis:** Diese Dokumentation wird kontinuierlich aktualisiert. Bei Fragen oder Verbesserungsvorschlägen bitte im Repository melden. \ No newline at end of file diff --git a/docs/00_General/00_glossary.md b/docs/00_General/00_glossary.md index 449ae61..1e29c47 100644 --- a/docs/00_General/00_glossary.md +++ b/docs/00_General/00_glossary.md @@ -2,8 +2,8 @@ doc_type: glossary audience: all status: active -version: 3.0.0 -context: "Zentrales Glossar für Mindnet v3.0.0. Enthält Definitionen zu Hybrid-Cloud Resilienz, WP-14 Modularisierung, WP-15b Two-Pass Ingestion, WP-15c Multigraph-Support, WP-25 Agentic Multi-Stream RAG, WP-25a Mixture of Experts (MoE) und Mistral-safe Parsing." +version: 3.1.1 +context: "Zentrales Glossar für Mindnet v3.1.1. Enthält Definitionen zu Hybrid-Cloud Resilienz, WP-14 Modularisierung, WP-15b Two-Pass Ingestion, WP-15c Multigraph-Support, WP-25 Agentic Multi-Stream RAG, WP-25a Mixture of Experts (MoE), WP-25b Lazy-Prompt-Orchestration und Mistral-safe Parsing." --- # Mindnet Glossar @@ -59,4 +59,9 @@ context: "Zentrales Glossar für Mindnet v3.0.0. Enthält Definitionen zu Hybrid * **LLM-Profil:** Zentrale Definition in `llm_profiles.yaml`, die Provider, Modell, Temperature und Fallback-Profil für eine spezifische Aufgabe festlegt (z.B. `synthesis_pro`, `tech_expert`, `ingest_validator`). * **Fallback-Kaskade (WP-25a):** Rekursive Fallback-Logik, bei der bei Fehlern automatisch auf das `fallback_profile` umgeschaltet wird, bis der terminale Endpunkt (`identity_safe`) erreicht wird. Schutz gegen Zirkel-Referenzen via `visited_profiles`-Tracking. * **Pre-Synthesis Kompression (WP-25a):** Asynchrone Verdichtung überlanger Wissens-Streams vor der Synthese, um Token-Verbrauch zu reduzieren und die Synthese zu beschleunigen. Nutzt `compression_profile` (z.B. `compression_fast`). -* **Profilgesteuerte Validierung (WP-25a):** Semantische Kanten-Validierung in der Ingestion erfolgt zwingend über das MoE-Profil `ingest_validator` (Temperature 0.0 für Determinismus), unabhängig von der globalen Provider-Konfiguration. \ No newline at end of file +* **Profilgesteuerte Validierung (WP-25a):** Semantische Kanten-Validierung in der Ingestion erfolgt zwingend über das MoE-Profil `ingest_validator` (Temperature 0.0 für Determinismus), unabhängig von der globalen Provider-Konfiguration. +* **Lazy-Prompt-Orchestration (WP-25b):** Hierarchisches Prompt-Resolution-System, das Prompts erst im Moment des Modellaustauschs lädt, basierend auf dem exakt aktiven Modell. Ermöglicht modell-spezifisches Tuning und maximale Resilienz bei Modell-Fallbacks. +* **Hierarchische Prompt-Resolution (WP-25b):** Dreistufige Auflösungs-Logik: Level 1 (Modell-ID) → Level 2 (Provider) → Level 3 (Default). Gewährleistet, dass jedes Modell das optimale Template erhält. +* **PROMPT-TRACE (WP-25b):** Logging-Mechanismus, der die genutzte Prompt-Auflösungs-Ebene protokolliert (`🎯 Level 1`, `📡 Level 2`, `⚓ Level 3`). Bietet vollständige Transparenz über die genutzten Instruktionen. +* **Ultra-robustes Intent-Parsing (WP-25b):** Regex-basierter Intent-Parser in der DecisionEngine, der Modell-Artefakte wie `[/S]`, `` oder Newlines zuverlässig bereinigt, um präzises Strategie-Routing zu gewährleisten. +* **Differenzierte Ingestion-Validierung (WP-25b):** Unterscheidung zwischen transienten Fehlern (Netzwerk, Timeout) und permanenten Fehlern (Config, Validation). Transiente Fehler erlauben die Kante (Datenverlust vermeiden), permanente Fehler lehnen sie ab (Graph-Qualität schützen). \ No newline at end of file diff --git a/docs/02_concepts/02_concept_ai_personality.md b/docs/02_concepts/02_concept_ai_personality.md index d074523..ca4108b 100644 --- a/docs/02_concepts/02_concept_ai_personality.md +++ b/docs/02_concepts/02_concept_ai_personality.md @@ -1,10 +1,10 @@ --- doc_type: concept audience: architect, product_owner -scope: ai, router, personas, resilience, agentic_rag, moe +scope: ai, router, personas, resilience, agentic_rag, moe, lazy_prompts status: active -version: 3.0.0 -context: "Fachkonzept der hybriden KI-Persönlichkeit, Agentic Multi-Stream RAG, WP-25a Mixture of Experts (MoE), Provider-Kaskade und kognitiven Resilienz (Deep Fallback)." +version: 3.1.1 +context: "Fachkonzept der hybriden KI-Persönlichkeit, Agentic Multi-Stream RAG, WP-25a Mixture of Experts (MoE), WP-25b Lazy-Prompt-Orchestration, Provider-Kaskade und kognitiven Resilienz (Deep Fallback)." --- # Konzept: KI-Persönlichkeit & Router diff --git a/docs/03_Technical_References/03_tech_chat_backend.md b/docs/03_Technical_References/03_tech_chat_backend.md index bb6e2e3..9766654 100644 --- a/docs/03_Technical_References/03_tech_chat_backend.md +++ b/docs/03_Technical_References/03_tech_chat_backend.md @@ -1,10 +1,10 @@ --- doc_type: technical_reference audience: developer, architect -scope: backend, chat, llm_service, traffic_control, resilience, agentic_rag, moe +scope: backend, chat, llm_service, traffic_control, resilience, agentic_rag, moe, lazy_prompts status: active -version: 3.0.0 -context: "Technische Implementierung des FastAPI-Routers, des hybriden LLMService (v3.5.2), WP-25 Agentic Multi-Stream RAG, WP-25a Mixture of Experts (MoE) und WP-20 Resilienz-Logik." +version: 3.1.1 +context: "Technische Implementierung des FastAPI-Routers, des hybriden LLMService (v3.5.5), WP-25 Agentic Multi-Stream RAG, WP-25a Mixture of Experts (MoE), WP-25b Lazy-Prompt-Orchestration und WP-20 Resilienz-Logik." --- # Chat Backend & Agentic Multi-Stream RAG @@ -13,7 +13,7 @@ context: "Technische Implementierung des FastAPI-Routers, des hybriden LLMServic Der zentrale Einstiegspunkt für jede Chatanfrage ist der **Hybrid Router** (`app/routers/chat.py`). Seit WP-25 agiert das System als **Agentic Orchestrator**, der Nutzeranfragen analysiert, in parallele Wissens-Streams aufteilt und diese zu einer kontextreichen, wertebasierten Antwort synthetisiert. -### 1.1 Intent-Erkennung (Hybrid-Modus) +### 1.1 Intent-Erkennung (Hybrid-Modus - WP-25b) Der Router nutzt einen **Hybrid-Modus** mit Keyword-Fast-Path und LLM-Slow-Path: @@ -24,9 +24,11 @@ Der Router nutzt einen **Hybrid-Modus** mit Keyword-Fast-Path und LLM-Slow-Path: 2. **Type Keywords (Interview-Modus):** * Lädt `types.yaml` und prüft `detection_keywords` für Objekt-Erkennung. * Wenn Match und keine Frage: **INTERVIEW Modus** (Datenerfassung). -3. **LLM Slow-Path (Semantische Analyse):** +3. **LLM Slow-Path (Semantische Analyse - WP-25b):** * Wenn unklar: Anfrage an `DecisionEngine._determine_strategy()` zur LLM-basierten Klassifizierung. - * Nutzt `intent_router_v1` Prompt aus `prompts.yaml`. + * **Lazy-Prompt-Loading:** Nutzt `prompt_key="intent_router_v1"` mit `variables={"query": query}` + * **Ultra-robustes Parsing:** Regex-basierter Intent-Parser bereinigt Modell-Artefakte (z.B. `CODING[/S]` → `CODING`) + * **Fallback:** Bei unklarem Intent → `FACT_WHAT` ### 1.2 Mixture of Experts (MoE) Architektur (WP-25a) @@ -52,13 +54,32 @@ Der `LLMService` (v3.5.2) implementiert eine automatische Fallback-Logik: * **Synthese:** Nutzt `llm_profile` aus Strategie-Konfiguration * **Ingestion:** Nutzt `ingest_validator` für binäre Validierungen -### 1.3 Prompt-Auflösung (Bulletproof Resolution) +### 1.3 Hierarchisches Prompt-Resolution-System (WP-25b) -Um Kompatibilitätsprobleme mit verschachtelten YAML-Prompts zu vermeiden, nutzt der Router die Methode `llm.get_prompt()`. Diese implementiert eine **Provider-Kaskade**: -* **Spezifischer Provider:** Das System sucht zuerst nach einem Prompt für den aktiv konfigurierten Provider (z.B. `openrouter`). -* **Cloud-Stil Fallback:** Existiert dieser nicht, erfolgt ein Fallback auf das `gemini`-Template. -* **Basis-Fallback:** Als letzte Instanz wird das `ollama`-Template geladen. -* **String-Garantie:** Die Methode garantiert die Rückgabe eines Strings (selbst bei verschachtelten YAML-Dicts), was 500-Fehler bei String-Operationen wie `.replace()` oder `.format()` verhindert. +Seit WP-25b nutzt MindNet eine **dreistufige hierarchische Prompt-Auflösung** mit Lazy-Loading. Prompts werden erst im Moment des Modellaustauschs geladen, basierend auf dem exakt aktiven Modell. + +**Hierarchische Auflösung (`llm_service.py` v3.5.5):** + +1. **Level 1 (Modell-ID):** Suche nach exakten Übereinstimmungen für die Modell-ID (z.B. `google/gemini-2.0-flash-exp:free`). + * **Vorteil:** Modell-spezifische Optimierungen (z.B. für Gemini 2.0, Llama 3.3, Qwen 2.5) + * **Logging:** `🎯 [PROMPT-TRACE] Level 1 Match: Model-specific` + +2. **Level 2 (Provider):** Fallback auf allgemeine Provider-Anweisungen (z.B. `openrouter` oder `ollama`). + * **Vorteil:** Bewährte Standards aus v3.1.2 bleiben erhalten + * **Logging:** `📡 [PROMPT-TRACE] Level 2 Match: Provider-fallback` + +3. **Level 3 (Default):** Globaler Sicherheits-Satz zur Vermeidung von Fehlern bei unbekannten Konfigurationen. + * **Fallback-Kette:** `default` → `gemini` → `ollama` → `""` + * **Logging:** `⚓ [PROMPT-TRACE] Level 3 Match: Global Default` + +**Lazy-Prompt-Orchestration:** +* **Lazy Loading:** Prompts werden erst zur Laufzeit geladen, wenn das aktive Modell bekannt ist +* **Parameter:** `prompt_key` und `variables` statt vorformatierter Strings +* **Vorteil:** Maximale Resilienz bei Modell-Fallbacks (Cloud → Local) +* **Traceability:** Vollständige Transparenz über genutzte Instruktionen via `[PROMPT-TRACE]` Logs + +**String-Garantie:** +Die Methode garantiert die Rückgabe eines Strings (selbst bei verschachtelten YAML-Dicts), was 500-Fehler bei String-Operationen wie `.replace()` oder `.format()` verhindert. ### 1.4 Multi-Stream Retrieval (WP-25) diff --git a/docs/03_Technical_References/03_tech_configuration.md b/docs/03_Technical_References/03_tech_configuration.md index fb35a5f..1f0b2d7 100644 --- a/docs/03_Technical_References/03_tech_configuration.md +++ b/docs/03_Technical_References/03_tech_configuration.md @@ -1,10 +1,10 @@ --- doc_type: technical_reference audience: developer, admin -scope: configuration, env, registry, scoring, resilience, modularization, agentic_rag, moe +scope: configuration, env, registry, scoring, resilience, modularization, agentic_rag, moe, lazy_prompts status: active -version: 3.0.0 -context: "Umfassende Referenztabellen für Umgebungsvariablen (inkl. Hybrid-Cloud & WP-76), YAML-Konfigurationen, Edge Registry Struktur, WP-25 Multi-Stream RAG und WP-25a Mixture of Experts (MoE) unter Berücksichtigung von WP-14." +version: 3.1.1 +context: "Umfassende Referenztabellen für Umgebungsvariablen (inkl. Hybrid-Cloud & WP-76), YAML-Konfigurationen, Edge Registry Struktur, WP-25 Multi-Stream RAG, WP-25a Mixture of Experts (MoE) und WP-25b Lazy-Prompt-Orchestration unter Berücksichtigung von WP-14." --- # Konfigurations-Referenz @@ -302,13 +302,47 @@ Die Strategie `INTERVIEW` dient der strukturierten Datenerfassung. > **Hinweis:** Da spezifische Schemas für Projekte oder Erfahrungen direkt in der `types.yaml` definiert werden, dient die `decision_engine.yaml` hier primär als Fallback für generische Datenaufnahmen. -### 5.6 Prompts-Konfiguration (`prompts.yaml` v3.1.2) +### 5.6 Prompts-Konfiguration (`prompts.yaml` v3.2.2 - WP-25b) -Seit WP-25 nutzen die Synthese-Templates explizite Stream-Variablen: +Seit WP-25b nutzt MindNet eine **hierarchische Prompt-Struktur** mit Lazy-Loading. Prompts werden erst zur Laufzeit geladen, basierend auf dem exakt aktiven Modell. -**Template-Struktur:** +**Hierarchische Template-Struktur:** ```yaml decision_synthesis_v1: + # Level 1: Modell-spezifisch (höchste Priorität) + "google/gemini-2.0-flash-exp:free": | + WERTE & PRINZIPIEN (Identität): + {values_stream} + + OPERATIVE FAKTEN (Realität): + {facts_stream} + + RISIKO-RADAR (Konsequenzen): + {risk_stream} + + ENTSCHEIDUNGSFRAGE: + {query} + Nutze deine hohe Reasoning-Kapazität für eine tiefe Synthese. + + "meta-llama/llama-3.3-70b-instruct:free": | + Erstelle eine fundierte Synthese für die Frage: "{query}" + Nutze die Daten: {values_stream}, {facts_stream} und {risk_stream}. + Trenne klare Fakten von Erfahrungen. Bleibe strikt beim bereitgestellten Kontext. + + # Level 2: Provider-Fallback (mittlere Priorität) + openrouter: | + WERTE & PRINZIPIEN (Identität): + {values_stream} + + OPERATIVE FAKTEN (Realität): + {facts_stream} + + RISIKO-RADAR (Konsequenzen): + {risk_stream} + + ENTSCHEIDUNGSFRAGE: + {query} + ollama: | WERTE & PRINZIPIEN (Identität): {values_stream} @@ -321,13 +355,31 @@ decision_synthesis_v1: ENTSCHEIDUNGSFRAGE: {query} + + # Level 3: Global Default (niedrigste Priorität) + default: | + Synthetisiere die folgenden Informationen für: {query} + {values_stream} | {facts_stream} | {risk_stream} ``` +**Auflösungs-Logik:** +1. **Level 1:** Exakte Modell-ID (z.B. `google/gemini-2.0-flash-exp:free`) +2. **Level 2:** Provider-Fallback (z.B. `openrouter`, `ollama`, `gemini`) +3. **Level 3:** Global Default (`default` → `gemini` → `ollama` → `""`) + +**Lazy-Loading:** +* Prompts werden erst zur Laufzeit geladen, wenn das aktive Modell bekannt ist +* **Parameter:** `prompt_key` und `variables` statt vorformatierter Strings +* **Vorteil:** Maximale Resilienz bei Modell-Fallbacks (Cloud → Local) + **Pre-Initialization:** Alle möglichen Stream-Variablen werden vorab initialisiert (verhindert KeyErrors bei unvollständigen Konfigurationen). -**Provider-spezifische Templates:** -Separate Versionen für Ollama, Gemini und OpenRouter. +**PROMPT-TRACE Logging:** +Das System protokolliert die genutzte Auflösungs-Ebene: +* `🎯 [PROMPT-TRACE] Level 1 Match: Model-specific` +* `📡 [PROMPT-TRACE] Level 2 Match: Provider-fallback` +* `⚓ [PROMPT-TRACE] Level 3 Match: Global Default` --- @@ -404,6 +456,261 @@ values_stream: --- +## 7. Konfigurations-Verbindungen & Datenfluss + +Die vier zentralen Konfigurationsdateien (`types.yaml`, `decision_engine.yaml`, `llm_profiles.yaml`, `prompts.yaml`) arbeiten eng zusammen, um das agentische Multi-Stream RAG System zu steuern. Diese Sektion erklärt die Verbindungen und zeigt einen konkreten Praxisablauf. + +### 7.1 Architektur-Übersicht + +```mermaid +graph TB + subgraph "1. Typ-Definition (types.yaml)" + T1[Typ: value
chunking_profile: structured_strict
retriever_weight: 1.00] + T2[Typ: risk
chunking_profile: sliding_short
retriever_weight: 0.85] + T3[Typ: project
chunking_profile: sliding_smart_edges
retriever_weight: 0.97] + end + + subgraph "2. Stream-Konfiguration (decision_engine.yaml)" + D1[values_stream
filter_types: value, principle, belief...
llm_profile: identity_safe
compression_profile: identity_safe] + D2[risk_stream
filter_types: risk, obstacle, bias
llm_profile: synthesis_pro
compression_profile: compression_fast] + D3[facts_stream
filter_types: project, decision, task...
llm_profile: synthesis_pro
compression_profile: compression_fast] + end + + subgraph "3. Strategie-Komposition (decision_engine.yaml)" + S1[DECISION Strategie
use_streams: values_stream, facts_stream, risk_stream
llm_profile: synthesis_pro
prompt_template: decision_synthesis_v1] + end + + subgraph "4. Experten-Profile (llm_profiles.yaml)" + P1[synthesis_pro
provider: openrouter
model: google/gemini-2.0-flash-exp:free
temperature: 0.7
fallback_profile: synthesis_backup] + P2[compression_fast
provider: openrouter
model: mistralai/mistral-7b-instruct:free
temperature: 0.1
fallback_profile: identity_safe] + P3[identity_safe
provider: ollama
model: phi3:mini
temperature: 0.2
fallback_profile: null] + end + + subgraph "5. Prompt-Templates (prompts.yaml)" + PR1[decision_synthesis_v1
Level 1: google/gemini-2.0-flash-exp:free
Level 2: openrouter
Level 3: default] + end + + T1 -->|filter_types| D1 + T2 -->|filter_types| D2 + T3 -->|filter_types| D3 + + D1 -->|use_streams| S1 + D2 -->|use_streams| S1 + D3 -->|use_streams| S1 + + S1 -->|llm_profile| P1 + D1 -->|llm_profile| P3 + D2 -->|compression_profile| P2 + D3 -->|compression_profile| P2 + + S1 -->|prompt_template| PR1 + P1 -->|model lookup| PR1 + + style T1 fill:#e1f5ff + style T2 fill:#e1f5ff + style T3 fill:#e1f5ff + style D1 fill:#fff4e1 + style D2 fill:#fff4e1 + style D3 fill:#fff4e1 + style S1 fill:#ffe1f5 + style P1 fill:#e1ffe1 + style P2 fill:#e1ffe1 + style P3 fill:#e1ffe1 + style PR1 fill:#f5e1ff +``` + +### 7.2 Verbindungs-Matrix + +| Von | Zu | Verbindung | Beschreibung | +| :--- | :--- | :--- | :--- | +| **`types.yaml`** | **`decision_engine.yaml`** | `filter_types` | Streams filtern Notizen basierend auf Typen aus `types.yaml`. Die Liste `filter_types: ["value", "principle", "belief"]` muss exakt den Typ-Namen aus `types.yaml` entsprechen. | +| **`types.yaml`** | **`decision_engine.yaml`** | `detection_keywords` | Keywords aus `types.yaml` werden für den Interview-Modus verwendet (z.B. "Projekt" + "neu" → `INTERVIEW`). | +| **`decision_engine.yaml`** | **`llm_profiles.yaml`** | `router_profile` | Intent-Erkennung nutzt das Profil `compression_fast` für schnelle Klassifizierung. | +| **`decision_engine.yaml`** | **`llm_profiles.yaml`** | `llm_profile` (Stream) | Jeder Stream definiert sein eigenes Profil für Retrieval und Kompression (z.B. `identity_safe` für Privacy). | +| **`decision_engine.yaml`** | **`llm_profiles.yaml`** | `llm_profile` (Strategie) | Die finale Synthese nutzt das Strategie-Profil (z.B. `synthesis_pro` für DECISION). | +| **`decision_engine.yaml`** | **`llm_profiles.yaml`** | `compression_profile` | Überlange Streams werden via `compression_profile` verdichtet (z.B. `compression_fast`). | +| **`decision_engine.yaml`** | **`prompts.yaml`** | `prompt_template` | Strategien referenzieren Template-Keys (z.B. `decision_synthesis_v1`). | +| **`llm_profiles.yaml`** | **`prompts.yaml`** | Hierarchische Auflösung | Das aktive Modell aus dem Profil bestimmt, welcher Prompt-Level geladen wird (Model-ID → Provider → Default). | +| **`llm_profiles.yaml`** | **`llm_profiles.yaml`** | `fallback_profile` | Rekursive Fallback-Kaskade bei Fehlern (z.B. `synthesis_pro` → `synthesis_backup` → `identity_safe`). | + +### 7.3 Praxisbeispiel: DECISION-Anfrage + +**User-Anfrage:** `"Soll ich das neue Projekt starten?"` + +#### Schritt 1: Intent-Erkennung + +**Datei:** `decision_engine.yaml` +```yaml +settings: + router_profile: "compression_fast" # → llm_profiles.yaml + router_prompt_key: "intent_router_v1" # → prompts.yaml +``` + +**Ablauf:** +1. System prüft `trigger_keywords` in `DECISION` Strategie → findet `"soll ich"` → **Intent: DECISION** +2. Falls kein Keyword-Match: LLM-Router nutzt `compression_fast` Profil aus `llm_profiles.yaml` +3. Router lädt `intent_router_v1` aus `prompts.yaml` (hierarchisch basierend auf aktivem Modell) + +#### Schritt 2: Stream-Aktivierung + +**Datei:** `decision_engine.yaml` +```yaml +strategies: + DECISION: + use_streams: ["values_stream", "facts_stream", "risk_stream"] + llm_profile: "synthesis_pro" # → llm_profiles.yaml + prompt_template: "decision_synthesis_v1" # → prompts.yaml +``` + +**Ablauf:** +1. System aktiviert drei parallele Streams: `values_stream`, `facts_stream`, `risk_stream` + +#### Schritt 3: Stream-Konfiguration & Typ-Filterung + +**Datei:** `decision_engine.yaml` (Streams) + `types.yaml` (Typ-Definitionen) + +```yaml +# decision_engine.yaml +values_stream: + filter_types: ["value", "principle", "belief", "trait", "boundary", "need", "motivation"] + llm_profile: "identity_safe" # → llm_profiles.yaml + compression_profile: "identity_safe" # → llm_profiles.yaml + query_template: "Welche meiner Werte und Prinzipien betreffen: {query}" + +facts_stream: + filter_types: ["project", "decision", "task", "goal", "event", "state"] + llm_profile: "synthesis_pro" # → llm_profiles.yaml + compression_profile: "compression_fast" # → llm_profiles.yaml + query_template: "Status, Ressourcen und Fakten zu: {query}" + +risk_stream: + filter_types: ["risk", "obstacle", "bias"] + llm_profile: "synthesis_pro" # → llm_profiles.yaml + compression_profile: "compression_fast" # → llm_profiles.yaml + query_template: "Gefahren, Hindernisse oder Risiken bei: {query}" +``` + +**Ablauf:** +1. **Values Stream:** Sucht in Qdrant nach Notizen mit `type IN ["value", "principle", "belief", ...]` (definiert in `types.yaml`) +2. **Facts Stream:** Sucht nach Notizen mit `type IN ["project", "decision", "task", ...]` (definiert in `types.yaml`) +3. **Risk Stream:** Sucht nach Notizen mit `type IN ["risk", "obstacle", "bias"]` (definiert in `types.yaml`) + +#### Schritt 4: Profil-Auflösung & Modell-Auswahl + +**Datei:** `llm_profiles.yaml` + +```yaml +synthesis_pro: + provider: "openrouter" + model: "google/gemini-2.0-flash-exp:free" + temperature: 0.7 + fallback_profile: "synthesis_backup" # → Rekursiver Fallback + +compression_fast: + provider: "openrouter" + model: "mistralai/mistral-7b-instruct:free" + temperature: 0.1 + fallback_profile: "identity_safe" + +identity_safe: + provider: "ollama" + model: "phi3:mini" + temperature: 0.2 + fallback_profile: null # Terminaler Endpunkt +``` + +**Ablauf:** +1. **Values Stream:** Nutzt `identity_safe` → Ollama/phi3:mini (lokal, Privacy) +2. **Facts Stream:** Nutzt `synthesis_pro` → OpenRouter/Gemini 2.0 (Cloud) +3. **Risk Stream:** Nutzt `synthesis_pro` → OpenRouter/Gemini 2.0 (Cloud) +4. **Kompression:** Falls Stream > `compression_threshold`, nutzt `compression_fast` → OpenRouter/Mistral 7B + +#### Schritt 5: Prompt-Loading (Hierarchische Auflösung) + +**Datei:** `prompts.yaml` + +```yaml +decision_synthesis_v1: + # Level 1: Modell-spezifisch (höchste Priorität) + "google/gemini-2.0-flash-exp:free": | + Agiere als strategischer Partner für: {query} + WERTE: {values_stream} | FAKTEN: {facts_stream} | RISIKEN: {risk_stream} + Prüfe die Fakten gegen meine Werte. Zeige Zielkonflikte auf. Gib eine klare Empfehlung. + + # Level 2: Provider-Fallback + 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. + + # Level 3: Global Default + default: "Prüfe {query} gegen Werte {values_stream} und Fakten {facts_stream}." +``` + +**Ablauf:** +1. System hat `synthesis_pro` Profil geladen → Modell: `google/gemini-2.0-flash-exp:free` +2. System sucht in `prompts.yaml` nach `decision_synthesis_v1`: + - **Level 1:** Findet exakten Match für `google/gemini-2.0-flash-exp:free` → **Verwendet diesen Prompt** + - Falls nicht gefunden: **Level 2** → `openrouter` Fallback + - Falls nicht gefunden: **Level 3** → `default` Fallback +3. Prompt wird mit Stream-Variablen formatiert: `{values_stream}`, `{facts_stream}`, `{risk_stream}`, `{query}` + +#### Schritt 6: Finale Synthese + +**Ablauf:** +1. System ruft LLM auf mit: + - **Profil:** `synthesis_pro` (OpenRouter/Gemini 2.0, Temperature 0.7) + - **Prompt:** Level-1 Template aus `prompts.yaml` (modell-spezifisch optimiert) + - **Variablen:** Formatierte Stream-Inhalte +2. Falls Fehler (z.B. Rate-Limit 429): + - **Fallback:** `synthesis_backup` (Llama 3.3) + - **Prompt:** Automatisch Level-2 (`openrouter`) oder Level-3 (`default`) geladen +3. Antwort wird an User zurückgegeben + +### 7.4 Konfigurations-Synchronisation Checkliste + +Beim Ändern einer Konfigurationsdatei müssen folgende Abhängigkeiten geprüft werden: + +**✅ `types.yaml` ändern:** +- [ ] Prüfe, ob `filter_types` in `decision_engine.yaml` Streams noch gültig sind +- [ ] Prüfe, ob `detection_keywords` für Interview-Modus noch passen +- [ ] Prüfe, ob `chunking_profile` noch existiert (in `types.yaml` definiert) + +**✅ `decision_engine.yaml` ändern:** +- [ ] Prüfe, ob alle `filter_types` in Streams existieren in `types.yaml` +- [ ] Prüfe, ob alle `llm_profile` / `compression_profile` existieren in `llm_profiles.yaml` +- [ ] Prüfe, ob alle `prompt_template` Keys existieren in `prompts.yaml` + +**✅ `llm_profiles.yaml` ändern:** +- [ ] Prüfe, ob `fallback_profile` Referenzen zirkulär sind (Schutz: `visited_profiles`) +- [ ] Prüfe, ob alle referenzierten Profile existieren +- [ ] Prüfe, ob Modell-IDs mit `prompts.yaml` Level-1 Keys übereinstimmen (optional, aber empfohlen) + +**✅ `prompts.yaml` ändern:** +- [ ] Prüfe, ob alle `prompt_template` Keys aus `decision_engine.yaml` existieren +- [ ] Prüfe, ob Modell-spezifische Keys (Level 1) mit `llm_profiles.yaml` Modell-IDs übereinstimmen +- [ ] Prüfe, ob alle Stream-Variablen (`{values_stream}`, `{facts_stream}`, etc.) initialisiert werden + +### 7.5 Debugging-Tipps + +**Problem:** Stream findet keine Notizen +- **Prüfung:** `filter_types` in Stream stimmt mit Typ-Namen in `types.yaml` überein? (Case-sensitive!) +- **Prüfung:** Existieren Notizen mit diesen Typen im Vault? + +**Problem:** Falsches Modell wird verwendet +- **Prüfung:** `llm_profile` in Stream/Strategie existiert in `llm_profiles.yaml`? +- **Prüfung:** `fallback_profile` Kaskade führt zu unerwartetem Modell? + +**Problem:** Prompt wird nicht gefunden +- **Prüfung:** `prompt_template` Key existiert in `prompts.yaml`? +- **Prüfung:** Hierarchische Auflösung (Level 1 → 2 → 3) funktioniert? (Logs: `[PROMPT-TRACE]`) + +**Problem:** Kompression wird nicht ausgelöst +- **Prüfung:** `compression_threshold` in Stream-Konfiguration gesetzt? +- **Prüfung:** `compression_profile` existiert in `llm_profiles.yaml`? + +--- + Auszug aus der decision_engine.yaml ```yaml strategies: diff --git a/docs/03_Technical_References/03_tech_ingestion_pipeline.md b/docs/03_Technical_References/03_tech_ingestion_pipeline.md index 00497cc..3371b2e 100644 --- a/docs/03_Technical_References/03_tech_ingestion_pipeline.md +++ b/docs/03_Technical_References/03_tech_ingestion_pipeline.md @@ -1,10 +1,10 @@ --- doc_type: technical_reference audience: developer, devops -scope: backend, ingestion, smart_edges, edge_registry, modularization, moe +scope: backend, ingestion, smart_edges, edge_registry, modularization, moe, lazy_prompts status: active version: 2.14.0 -context: "Detaillierte technische Beschreibung der Import-Pipeline, Two-Pass-Workflow (WP-15b), modularer Datenbank-Architektur (WP-14) und WP-25a profilgesteuerte Validierung. Integriert Mistral-safe Parsing und Deep Fallback." +context: "Detaillierte technische Beschreibung der Import-Pipeline, Two-Pass-Workflow (WP-15b), modularer Datenbank-Architektur (WP-14), WP-25a profilgesteuerte Validierung und WP-25b Lazy-Prompt-Orchestration. Integriert Mistral-safe Parsing und Deep Fallback." --- # Ingestion Pipeline & Smart Processing @@ -50,10 +50,15 @@ Der Prozess ist **asynchron**, **idempotent** und wird nun in zwei logische Durc * Bei Änderungen löscht `purge_artifacts()` via `app.core.ingestion.ingestion_db` alle alten Chunks und Edges der Note. * Die Namensauflösung erfolgt nun über das modularisierte `database`-Paket. 10. **Chunking anwenden:** Zerlegung des Textes basierend auf dem ermittelten Profil (siehe Kap. 3). -11. **Smart Edge Allocation & Semantic Validation (WP-15b / WP-25a):** +11. **Smart Edge Allocation & Semantic Validation (WP-15b / WP-25a / WP-25b):** * Der `SemanticAnalyzer` schlägt Kanten-Kandidaten vor. - * **Validierung (WP-25a):** Jeder Kandidat wird durch das LLM semantisch gegen das Ziel im **LocalBatchCache** geprüft. + * **Validierung (WP-25a/25b):** Jeder Kandidat wird durch das LLM semantisch gegen das Ziel im **LocalBatchCache** geprüft. * **Profilgesteuerte Validierung:** Nutzt das MoE-Profil `ingest_validator` (Temperature 0.0 für maximale Determinismus). + * **Lazy-Prompt-Loading (WP-25b):** Nutzt `prompt_key="edge_validation"` mit `variables` statt vorformatierter Strings. + * **Hierarchische Resolution:** Level 1 (Modell-ID) → Level 2 (Provider) → Level 3 (Default) + * **Differenzierte Fehlerbehandlung (WP-25b):** Unterscheidung zwischen transienten (Netzwerk) und permanenten (Config) Fehlern: + * **Transiente Fehler:** Timeout, Connection, Network → Kante wird erlaubt (Datenverlust vermeiden) + * **Permanente Fehler:** Config, Validation, Invalid Response → Kante wird abgelehnt (Graph-Qualität schützen) * **Fallback-Kaskade:** Bei Fehlern erfolgt automatischer Fallback via `fallback_profile` (z.B. `compression_fast` → `identity_safe`). * **Traffic Control:** Nutzung der neutralen `clean_llm_text` Funktion zur Bereinigung von Steuerzeichen (, [OUT]). * **Deep Fallback (v2.11.14):** Erkennt "Silent Refusals". Liefert die Cloud keine verwertbaren Kanten, wird ein lokaler Fallback via Ollama erzwungen. diff --git a/docs/04_Operations/04_admin_operations.md b/docs/04_Operations/04_admin_operations.md index b8f8bbb..73780aa 100644 --- a/docs/04_Operations/04_admin_operations.md +++ b/docs/04_Operations/04_admin_operations.md @@ -1,10 +1,10 @@ --- doc_type: operations_manual audience: admin, devops -scope: deployment, maintenance, backup, edge_registry, moe +scope: deployment, maintenance, backup, edge_registry, moe, lazy_prompts status: active -version: 3.0.0 -context: "Installationsanleitung, Systemd-Units und Wartungsprozesse für Mindnet v3.0.0 inklusive WP-25a Mixture of Experts (MoE) Konfiguration." +version: 3.1.1 +context: "Installationsanleitung, Systemd-Units und Wartungsprozesse für Mindnet v3.1.1 inklusive WP-25a Mixture of Experts (MoE) und WP-25b Lazy-Prompt-Orchestration Konfiguration." --- # Admin Operations Guide diff --git a/docs/05_Development/05_developer_guide.md b/docs/05_Development/05_developer_guide.md index cd8a4df..3fced84 100644 --- a/docs/05_Development/05_developer_guide.md +++ b/docs/05_Development/05_developer_guide.md @@ -1,10 +1,10 @@ --- doc_type: developer_guide audience: developer -scope: workflow, testing, architecture, modules, modularization, agentic_rag +scope: workflow, testing, architecture, modules, modularization, agentic_rag, lazy_prompts status: active -version: 2.9.3 -context: "Umfassender Guide für Entwickler: Modularisierte Architektur (WP-14), Two-Pass Ingestion (WP-15b), WP-25 Agentic Multi-Stream RAG, Modul-Interna, Setup und Git-Workflow." +version: 3.1.1 +context: "Umfassender Guide für Entwickler: Modularisierte Architektur (WP-14), Two-Pass Ingestion (WP-15b), WP-25 Agentic Multi-Stream RAG, WP-25a MoE, WP-25b Lazy-Prompt-Orchestration, Modul-Interna, Setup und Git-Workflow." --- # Mindnet Developer Guide & Workflow @@ -406,12 +406,34 @@ Mindnet lernt nicht durch Training (Fine-Tuning), sondern durch **Konfiguration* ```yaml synthesis_pro: provider: "openrouter" - model: "gemini-1.5-mistralai/mistral-7b-instruct:free" + model: "google/gemini-2.0-flash-exp:free" temperature: 0.7 fallback_profile: "synthesis_backup" ``` *Ergebnis (WP-25a):* Zentrale Steuerung von Provider, Modell und Temperature pro Aufgabe. Automatische Fallback-Kaskade bei Fehlern. +4. **Prompt-Template (`config/prompts.yaml` v3.2.2, WP-25b):** + ```yaml + decision_synthesis_v1: + # Level 1: Modell-spezifisch (höchste Priorität) + "google/gemini-2.0-flash-exp:free": | + WERTE & PRINZIPIEN (Identität): + {values_stream} + ... + + # Level 2: Provider-Fallback + openrouter: | + WERTE & PRINZIPIEN (Identität): + {values_stream} + ... + + # Level 3: Global Default + default: | + Synthetisiere die folgenden Informationen für: {query} + ... + ``` + *Ergebnis (WP-25b):* Hierarchische Prompt-Resolution mit Lazy-Loading. Prompts werden erst zur Laufzeit geladen, basierend auf aktivem Modell. Maximale Resilienz bei Modell-Fallbacks. + ### Workflow B: Graph-Farben ändern 1. Öffne `app/frontend/ui_config.py`. 2. Bearbeite das Dictionary `GRAPH_COLORS`. diff --git a/docs/06_Roadmap/06_active_roadmap.md b/docs/06_Roadmap/06_active_roadmap.md index 0ff199f..6456809 100644 --- a/docs/06_Roadmap/06_active_roadmap.md +++ b/docs/06_Roadmap/06_active_roadmap.md @@ -2,14 +2,14 @@ doc_type: roadmap audience: product_owner, developer status: active -version: 2.9.3 -context: "Aktuelle Planung für kommende Features (ab WP16), Release-Strategie und Historie der abgeschlossenen WPs nach WP-14/15b/15c/25." +version: 3.1.1 +context: "Aktuelle Planung für kommende Features (ab WP16), Release-Strategie und Historie der abgeschlossenen WPs nach WP-14/15b/15c/25/25a/25b." --- # Mindnet Active Roadmap -**Aktueller Stand:** v2.9.3 (Post-WP25: Agentic Multi-Stream RAG) -**Fokus:** Agentic Orchestration, Multi-Stream Retrieval & Wissens-Synthese. +**Aktueller Stand:** v3.1.1 (Post-WP25b: Lazy-Prompt-Orchestration & Full Resilience) +**Fokus:** Hierarchische Prompt-Resolution, Modell-spezifisches Tuning & maximale Resilienz. | Phase | Fokus | Status | | :--- | :--- | :--- | @@ -50,6 +50,8 @@ Eine Übersicht der implementierten Features zum schnellen Auffinden von Funktio | **WP-22** | **Content Lifecycle & Registry** | **Ergebnis:** SSOT via `01_edge_vocabulary.md`, Alias-Mapping, Status-Scoring (`stable`/`draft`) und Modularisierung der Scoring-Engine. | | **WP-15c** | **Multigraph-Support & Diversity Engine** | **Ergebnis:** Section-basierte Links, Note-Level Diversity Pooling, Super-Edge Aggregation, Provenance Firewall. Transformation zu einem hochpräzisen Multigraphen. | | **WP-25** | **Agentic Multi-Stream RAG Orchestration** | **Ergebnis:** Übergang von linearer RAG-Architektur zu paralleler Multi-Stream Engine. Intent-basiertes Routing (Hybrid Fast/Slow-Path), parallele Wissens-Streams (Values, Facts, Biography, Risk, Tech), Stream-Tracing und Template-basierte Wissens-Synthese. | +| **WP-25a** | **Mixture of Experts (MoE) & Fallback-Kaskade** | **Ergebnis:** Profilbasierte Experten-Architektur, rekursive Fallback-Kaskade, Pre-Synthesis Kompression, profilgesteuerte Ingestion und Embedding-Konsolidierung. | +| **WP-25b** | **Lazy-Prompt-Orchestration & Full Resilience** | **Ergebnis:** Hierarchisches Prompt-Resolution-System (3-stufig), Lazy-Prompt-Loading, ultra-robustes Intent-Parsing, differenzierte Ingestion-Validierung und PROMPT-TRACE Logging. | ### 2.1 WP-22 Lessons Learned * **Architektur:** Die Trennung von `retriever.py` und `retriever_scoring.py` war notwendig, um LLM-Context-Limits zu wahren und die Testbarkeit der mathematischen Formeln zu erhöhen. @@ -197,25 +199,9 @@ Der bisherige WP-15 Ansatz litt unter Halluzinationen (erfundene Kantentypen), h **Status:** ✅ Fertig (v3.0.0) ### WP-25a: Mixture of Experts (MoE) & Fallback-Kaskade -**Status:** ✅ Fertig (v3.0.0) +**Status:** ✅ Fertig (v3.1.0) -**Ergebnis:** Transformation von Mindnet von einer klassischen, linearen RAG-Architektur zu einer **Agentic Multi-Stream Engine**. Das System agiert nun als intelligenter Orchestrator, der Nutzeranfragen analysiert, in parallele Wissens-Streams aufteilt und diese zu einer kontextreichen, wertebasierten Antwort synthetisiert. - -**Kern-Features:** -1. **Intent-basiertes Routing:** Hybrid-Modus mit Keyword Fast-Path und LLM Slow-Path -2. **Multi-Stream Retrieval:** Parallele Abfragen in spezialisierten Streams (Values, Facts, Biography, Risk, Tech) -3. **Stream-Tracing:** Jeder Treffer wird mit `stream_origin` markiert -4. **Wissens-Synthese:** Template-basierte Zusammenführung mit expliziten Stream-Variablen -5. **Fehler-Resilienz:** Einzelne Stream-Fehler blockieren nicht die gesamte Anfrage - -**Technische Details:** -- Decision Engine v1.0.3: Multi-Stream Orchestrator -- Chat Router v3.0.2: Hybrid Router Integration -- LLM Service v3.4.2: Ingest-Stability Patch -- decision_engine.yaml v3.1.6: Multi-Stream Konfiguration -- prompts.yaml v3.1.2: Stream-Templates - -**Ergebnis (WP-25a):** Transformation von MindNet von einer provider-basierten Steuerung auf eine **profilbasierte Experten-Architektur (Mixture of Experts)**. Jede Systemaufgabe wird einem dedizierten Profil zugewiesen, das Modell, Provider und Parameter unabhängig definiert. +**Ergebnis:** Transformation von MindNet von einer provider-basierten Steuerung auf eine **profilbasierte Experten-Architektur (Mixture of Experts)**. Jede Systemaufgabe wird einem dedizierten Profil zugewiesen, das Modell, Provider und Parameter unabhängig definiert. **Kern-Features:** 1. **Experten-Steuerung:** Zentrale Profile-Registry (`llm_profiles.yaml`) für alle LLM-Aufgaben @@ -233,10 +219,28 @@ Der bisherige WP-15 Ansatz litt unter Halluzinationen (erfundene Kantentypen), h - llm_profiles.yaml v1.3.0: Zentrale Experten-Registry - decision_engine.yaml v3.2.2: Decoupled MoE Logic -**Ausblick (WP-25b):** -- Prompt-Orchestration & Model-Specific Tuning +### WP-25b: Lazy-Prompt-Orchestration & Full Resilience +**Status:** ✅ Fertig (v3.1.1) + +**Ergebnis:** Umstellung von statischer Prompt-Formatierung auf eine **hierarchische Lazy-Prompt-Orchestration**. Prompts werden erst im Moment des Modellaustauschs geladen, basierend auf dem exakt aktiven Modell. Dies ermöglicht modell-spezifisches Tuning und maximale Resilienz bei Modell-Fallbacks. + +**Kern-Features:** +1. **Hierarchisches Prompt-Resolution-System:** Dreistufige Auflösung (Modell-ID → Provider → Default) +2. **Lazy-Loading:** Prompts werden erst zur Laufzeit geladen, wenn das aktive Modell bekannt ist +3. **Ultra-robustes Intent-Parsing:** Regex-basierter Parser bereinigt Modell-Artefakte (z.B. `CODING[/S]` → `CODING`) +4. **Differenzierte Ingestion-Validierung:** Unterscheidung zwischen transienten (Netzwerk) und permanenten (Config) Fehlern +5. **PROMPT-TRACE Logging:** Vollständige Transparenz über genutzte Instruktionen + +**Technische Details:** +- LLM Service v3.5.5: Hierarchische Prompt-Resolution mit Lazy-Loading +- Decision Engine v1.3.2: Ultra-robustes Intent-Parsing via Regex +- Ingestion Validation v2.14.0: Lazy-Prompt-Integration, differenzierte Fehlerbehandlung +- prompts.yaml v3.2.2: Hierarchische Struktur mit Modell-spezifischen Overrides + +**Ausblick (WP-25c):** - Kontext-Budgeting: Intelligente Token-Verteilung - Stream-specific Provider: Unterschiedliche KI-Modelle pro Wissensbereich +- Erweiterte Prompt-Optimierung: Dynamische Anpassung basierend auf Kontext und Historie --- ### WP-24 – Proactive Discovery & Agentic Knowledge Mining diff --git a/docs/99_Archive/WP25b_merge_commit.md b/docs/99_Archive/WP25b_merge_commit.md new file mode 100644 index 0000000..9efa15e --- /dev/null +++ b/docs/99_Archive/WP25b_merge_commit.md @@ -0,0 +1,97 @@ +# Branch Merge Commit: WP-25b + +**Branch:** `WP25b` +**Target:** `main` +**Version:** v3.1.1 +**Date:** 2026-01-02 + +--- + +## Commit Message + +``` +feat: Lazy-Prompt-Orchestration & Full Resilience (v3.1.1) + +### Hierarchisches Prompt-Resolution-System +- Dreistufige Auflösungs-Logik: Level 1 (Modell-ID) → Level 2 (Provider) → Level 3 (Default) +- Modell-spezifische Optimierungen für Gemini 2.0, Llama 3.3, Qwen 2.5 +- PROMPT-TRACE Logging für vollständige Transparenz +- Implementierung in `app/services/llm_service.py` (v3.5.5) + +### Lazy-Prompt-Orchestration +- Prompts werden erst zur Laufzeit geladen, basierend auf aktivem Modell +- Parameter: `prompt_key` und `variables` statt vorformatierter Strings +- Maximale Resilienz bei Modell-Fallbacks (Cloud → Local) +- Vollständige Integration in Chat, Ingestion und DecisionEngine + +### Ultra-robustes Intent-Parsing +- Regex-basierter Parser bereinigt Modell-Artefakte (z.B. `CODING[/S]` → `CODING`) +- Implementierung in `app/core/retrieval/decision_engine.py` (v1.3.2) +- Fehlerresistenz gegen Stop-Marker, Newlines oder Modell-Plaudereien + +### Differenzierte Ingestion-Validierung +- Unterscheidung zwischen transienten (Netzwerk) und permanenten (Config) Fehlern +- Transiente Fehler erlauben Kante (Datenverlust vermeiden) +- Permanente Fehler lehnen Kante ab (Graph-Qualität schützen) +- Implementierung in `app/core/ingestion/ingestion_validation.py` (v2.14.0) + +### Code-Komponenten +- `app/services/llm_service.py`: v3.5.5 (Hierarchische Prompt-Resolution, Lazy-Loading) +- `app/core/retrieval/decision_engine.py`: v1.3.2 (Ultra-robustes Intent-Parsing) +- `app/core/ingestion/ingestion_validation.py`: v2.14.0 (Lazy-Prompt-Integration) +- `app/routers/chat.py`: v3.0.3 (Lazy-Prompt-Loading für Chat-Synthese) + +### Konfiguration +- `config/prompts.yaml`: v3.2.2 (Hierarchische Struktur mit Modell-spezifischen Overrides) + - 100% Erhalt der Original-Prompts aus v3.1.2 für Provider-Ebene + - Integration von Modell-spezifischen Overrides + - Hinzufügen von `compression_template` + +### Dokumentation +- `03_tech_chat_backend.md`: Hierarchisches Prompt-Resolution-System +- `03_tech_configuration.md`: prompts.yaml hierarchische Struktur +- `02_concept_ai_personality.md`: Lazy-Prompt-Orchestration Konzept +- `03_tech_ingestion_pipeline.md`: Differenzierte Validierung +- `00_glossary.md`: Neue Begriffe (Lazy-Prompt, PROMPT-TRACE) +- `05_developer_guide.md`: Lazy-Prompt-Orchestration für Entwickler +- `06_active_roadmap.md`: WP25b als abgeschlossen markiert + +### Breaking Changes +- Keine Breaking Changes für Endbenutzer +- Vorformatierte Prompts werden weiterhin unterstützt (Abwärtskompatibilität) +- Neue API-Parameter `prompt_key` und `variables` optional + +### Migration +- Keine Migration erforderlich +- System funktioniert ohne Änderungen +- Optional: Modell-spezifische Prompts können in `prompts.yaml` definiert werden + +--- + +**Status:** ✅ WP-25b ist zu 100% implementiert und audit-geprüft. +**Nächster Schritt:** WP-25c (Kontext-Budgeting & Erweiterte Prompt-Optimierung). +``` + +--- + +## Zusammenfassung + +Dieser Merge führt die **Lazy-Prompt-Orchestration** in MindNet ein. Das System nutzt nun eine hierarchische Prompt-Auflösung mit Lazy-Loading, die Prompts erst zur Laufzeit lädt, basierend auf dem exakt aktiven Modell. + +**Kern-Features:** +- Hierarchisches Prompt-Resolution-System (3-stufig) +- Lazy-Prompt-Orchestration mit modell-spezifischem Tuning +- Ultra-robustes Intent-Parsing via Regex +- Differenzierte Ingestion-Validierung +- PROMPT-TRACE Logging für vollständige Transparenz + +**Technische Integrität:** +- Alle LLM-Aufrufe nutzen nun Lazy-Prompt-Loading +- Modell-Artefakte werden zuverlässig bereinigt +- Fehlerbehandlung differenziert zwischen transienten und permanenten Fehlern + +**Dokumentation:** +- Vollständige Aktualisierung aller relevanten Dokumente +- Neue Begriffe im Glossar +- Konfigurations-Referenz erweitert +- Developer Guide aktualisiert diff --git a/docs/99_Archive/WP25b_release_notes.md b/docs/99_Archive/WP25b_release_notes.md new file mode 100644 index 0000000..062eed1 --- /dev/null +++ b/docs/99_Archive/WP25b_release_notes.md @@ -0,0 +1,205 @@ +# MindNet v3.1.1 - Release Notes: WP-25b + +**Release Date:** 2026-01-02 +**Type:** Feature Release - Lazy-Prompt-Orchestration & Full Resilience +**Version:** 3.1.1 (WP-25b) + +--- + +## 🎯 Überblick + +Mit WP-25b wurde MindNet von statischer Prompt-Formatierung auf eine **hierarchische Lazy-Prompt-Orchestration** umgestellt. Prompts werden erst im Moment des Modellaustauschs geladen, basierend auf dem exakt aktiven Modell. Dies ermöglicht modell-spezifisches Tuning und maximale Resilienz bei Modell-Fallbacks. + +Diese Version markiert einen weiteren Architektur-Sprung: Von vorformatierter Prompt-Strings hin zu einer dynamischen, modell-spezifischen Prompt-Auflösung mit vollständiger Traceability. + +--- + +## ✨ Neue Features + +### 1. Hierarchisches Prompt-Resolution-System + +**Implementierung (`app/services/llm_service.py` v3.5.5):** + +Dreistufige Auflösungs-Logik für maximale Präzision und Resilienz: + +1. **Level 1 (Modell-ID):** Exakte Übereinstimmung für spezifische Modelle + * **Beispiel:** `google/gemini-2.0-flash-exp:free`, `meta-llama/llama-3.3-70b-instruct:free` + * **Vorteil:** Modell-spezifische Optimierungen für maximale Präzision + * **Logging:** `🎯 [PROMPT-TRACE] Level 1 Match: Model-specific` + +2. **Level 2 (Provider):** Fallback auf Provider-Standards + * **Beispiel:** `openrouter`, `ollama`, `gemini` + * **Vorteil:** Bewährte Standards aus v3.1.2 bleiben erhalten + * **Logging:** `📡 [PROMPT-TRACE] Level 2 Match: Provider-fallback` + +3. **Level 3 (Default):** Globaler Sicherheits-Satz + * **Fallback-Kette:** `default` → `gemini` → `ollama` → `""` + * **Vorteil:** Vermeidung von Fehlern bei unbekannten Konfigurationen + * **Logging:** `⚓ [PROMPT-TRACE] Level 3 Match: Global Default` + +**Vorteile:** +* **Modell-spezifisches Tuning:** Jedes Modell kann optimierte Prompts erhalten +* **Maximale Resilienz:** Bei Modell-Fallbacks wird automatisch das passende Template geladen +* **Traceability:** Vollständige Transparenz über genutzte Instruktionen + +### 2. Lazy-Prompt-Orchestration + +**Implementierung (`app/services/llm_service.py` v3.5.5):** + +* **Lazy Loading:** Prompts werden erst zur Laufzeit geladen, wenn das aktive Modell bekannt ist +* **Parameter:** `prompt_key` und `variables` statt vorformatierter Strings +* **Integration:** Vollständig in Chat, Ingestion und DecisionEngine integriert + +**Vorteile:** +* **Dynamische Anpassung:** Prompt wird basierend auf aktivem Modell geladen +* **Fallback-Resilienz:** Bei Cloud → Local Fallback wird automatisch das passende Template verwendet +* **Wartbarkeit:** Zentrale Konfiguration in `prompts.yaml` statt verstreuter String-Formatierungen + +### 3. Ultra-robustes Intent-Parsing + +**Implementierung (`app/core/retrieval/decision_engine.py` v1.3.2):** + +* **Regex-basierter Parser:** Bereinigt Modell-Artefakte zuverlässig +* **Beispiele:** `CODING[/S]` → `CODING`, `DECISION` → `DECISION` +* **Robustheit:** Ignoriert Stop-Marker, Newlines oder Plaudereien des Modells + +**Vorteile:** +* **Präzises Routing:** Strategie-Erkennung funktioniert auch bei freien Modellen mit Artefakten +* **Fehlerresistenz:** Systemabstürze durch fehlerhafte Modell-Antworten werden verhindert + +### 4. Differenzierte Ingestion-Validierung + +**Implementierung (`app/core/ingestion/ingestion_validation.py` v2.14.0):** + +* **Fehler-Differenzierung:** Unterscheidung zwischen transienten und permanenten Fehlern +* **Transiente Fehler:** Timeout, Connection, Network → Kante wird erlaubt (Datenverlust vermeiden) +* **Permanente Fehler:** Config, Validation, Invalid Response → Kante wird abgelehnt (Graph-Qualität schützen) + +**Vorteile:** +* **Datenintegrität:** Transiente Netzwerkfehler führen nicht zu Datenverlust +* **Graph-Qualität:** Permanente Konfigurationsfehler schützen vor fehlerhaften Kanten + +### 5. PROMPT-TRACE Logging + +**Implementierung (`app/services/llm_service.py` v3.5.5):** + +* **Vollständige Transparenz:** Protokollierung der genutzten Prompt-Auflösungs-Ebene +* **Log-Format:** `[PROMPT-TRACE] Level X Match: ...` +* **Debugging:** Einfache Nachverfolgung von Prompt-Entscheidungen + +**Vorteile:** +* **Debugging:** Schnelle Identifikation von Prompt-Problemen +* **Optimierung:** Verständnis, welche Prompts tatsächlich genutzt werden +* **Audit:** Vollständige Nachvollziehbarkeit der System-Entscheidungen + +--- + +## 🔧 Technische Änderungen + +### Konfigurationsdateien + +**Aktualisierte Dateien:** +* `config/prompts.yaml` v3.2.2: Hierarchische Struktur mit Modell-spezifischen Overrides + * **Erhalt:** 100% der Original-Prompts aus v3.1.2 für die Provider-Ebene + * **Neu:** Modell-spezifische Overrides für Gemini 2.0, Llama 3.3, Qwen 2.5 + * **Neu:** `compression_template` für DecisionEngine v1.3.0 + +### Code-Komponenten + +| Komponente | Version | Änderungen | +| :--- | :--- | :--- | +| `app/services/llm_service.py` | v3.5.5 | Hierarchische Prompt-Resolution, Lazy-Loading, PROMPT-TRACE | +| `app/core/retrieval/decision_engine.py` | v1.3.2 | Ultra-robustes Intent-Parsing via Regex | +| `app/core/ingestion/ingestion_validation.py` | v2.14.0 | Lazy-Prompt-Integration, differenzierte Fehlerbehandlung | +| `app/routers/chat.py` | v3.0.3 | Lazy-Prompt-Loading für Chat-Synthese | + +### API-Änderungen + +**Neue Parameter:** +* `prompt_key`: Schlüssel für Lazy-Loading (statt vorformatierter Strings) +* `variables`: Daten-Dict für Prompt-Formatierung + +**Veraltete Parameter:** +* Vorformatierte `prompt` Strings werden weiterhin unterstützt (Abwärtskompatibilität) + +--- + +## 🐛 Behobene Probleme + +- ✅ **Behoben:** Modell-Artefakte in Intent-Router (z.B. `CODING[/S]` → `CODING`) +- ✅ **Behoben:** Fehlende modell-spezifische Prompt-Optimierungen +- ✅ **Behoben:** Fehlerhafte Prompt-Auflösung bei Modell-Fallbacks +- ✅ **Behoben:** Undifferenzierte Fehlerbehandlung in Ingestion-Validierung + +--- + +## 📚 Dokumentation + +**Aktualisierte Dokumente:** +- ✅ `03_tech_chat_backend.md`: Hierarchisches Prompt-Resolution-System und Lazy-Prompt-Orchestration +- ✅ `03_tech_configuration.md`: prompts.yaml hierarchische Struktur dokumentiert +- ✅ `02_concept_ai_personality.md`: Lazy-Prompt-Orchestration Konzept +- ✅ `03_tech_ingestion_pipeline.md`: Differenzierte Validierung +- ✅ `00_glossary.md`: Neue Begriffe (Lazy-Prompt, PROMPT-TRACE, hierarchische Resolution) +- ✅ `05_developer_guide.md`: Lazy-Prompt-Orchestration für Entwickler +- ✅ `06_active_roadmap.md`: WP25b als abgeschlossen markiert + +--- + +## 🚀 Migration & Upgrade + +### Für Administratoren + +1. **Keine Breaking Changes:** + * Vorformatierte Prompts werden weiterhin unterstützt + * System funktioniert ohne Änderungen + +2. **Optional: Modell-spezifische Optimierungen:** + ```yaml + # config/prompts.yaml + decision_synthesis_v1: + "google/gemini-2.0-flash-exp:free": | + # Modell-spezifische Optimierung + ... + ``` + +3. **PROMPT-TRACE aktivieren:** + * Logs zeigen automatisch die genutzte Auflösungs-Ebene + * Keine zusätzliche Konfiguration erforderlich + +### Für Entwickler + +**API-Änderungen:** +* `LLMService.generate_raw_response()` unterstützt nun `prompt_key` und `variables` +* Vorformatierte `prompt` Strings bleiben für Abwärtskompatibilität erhalten + +**Best Practice:** +* Nutze `prompt_key` und `variables` für neue Implementierungen +* Lazy-Loading ermöglicht automatische Modell-Anpassung + +**Konfiguration:** +* Neue Modell-spezifische Prompts können in `prompts.yaml` definiert werden +* Hierarchische Struktur: Modell-ID → Provider → Default + +--- + +## 🔮 Ausblick (WP-25c) + +- Kontext-Budgeting: Intelligente Token-Verteilung +- Stream-specific Provider: Unterschiedliche KI-Modelle pro Wissensbereich +- Erweiterte Prompt-Optimierung: Dynamische Anpassung basierend auf Kontext und Historie + +--- + +## 📊 Metriken & Performance + +**Erwartete Verbesserungen:** +* **Präzision:** Modell-spezifische Prompts erhöhen Antwortqualität +* **Resilienz:** Automatische Prompt-Anpassung bei Modell-Fallbacks +* **Debugging:** PROMPT-TRACE vereinfacht Fehleranalyse +* **Wartbarkeit:** Zentrale Prompt-Konfiguration statt verstreuter Strings + +--- + +**Status:** ✅ WP-25b ist zu 100% implementiert und audit-geprüft. +**Nächster Schritt:** WP-25c (Kontext-Budgeting & Erweiterte Prompt-Optimierung). diff --git a/docs/README.md b/docs/README.md index a4de11d..f90b8f6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,13 +2,13 @@ doc_type: documentation_index audience: all status: active -version: 2.9.3 +version: 3.1.1 context: "Zentraler Einstiegspunkt für die Mindnet-Dokumentation" --- # Mindnet Dokumentation -Willkommen in der Dokumentation von Mindnet v2.9.3! Diese Dokumentation hilft dir dabei, das System zu verstehen, zu nutzen und weiterzuentwickeln. +Willkommen in der Dokumentation von Mindnet v3.1.1! Diese Dokumentation hilft dir dabei, das System zu verstehen, zu nutzen und weiterzuentwickeln. ## 🚀 Schnellstart -- 2.43.0