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