WP25 #19

Merged
Lars merged 8 commits from WP25 into main 2026-01-01 20:26:05 +01:00
3 changed files with 102 additions and 137 deletions
Showing only changes of commit ea38743a2a - Show all commits

View File

@ -3,12 +3,11 @@ FILE: app/core/retrieval/decision_engine.py
DESCRIPTION: Der Agentic Orchestrator für WP-25.
Realisiert Multi-Stream Retrieval, Intent-basiertes Routing
und parallele Wissens-Synthese.
VERSION: 1.0.2
VERSION: 1.0.3
STATUS: Active
FIX:
- WP-25 ROBUSTNESS: Pre-Initialization aller Stream-Variablen zur Vermeidung von KeyErrors.
- Behebung von Template-Mismatches bei unvollständigen Strategie-Definitionen.
- Erhalt der Sicherheitskaskade für die Strategiewahl.
- WP-25 STREAM-TRACING: Kennzeichnung der Treffer mit ihrem Ursprungs-Stream.
- WP-25 ROBUSTNESS: Pre-Initialization der Stream-Variablen zur Vermeidung von KeyErrors.
"""
import asyncio
import logging
@ -50,50 +49,41 @@ class DecisionEngine:
Hauptmethode des MindNet Chats.
Orchestriert den gesamten Prozess: Routing -> Retrieval -> Synthese.
"""
# 1. Intent Recognition (Welches Werkzeug brauchen wir?)
# 1. Intent Recognition
strategy_key = await self._determine_strategy(query)
# Sicherheits-Kaskade für die Strategiewahl
strategies = self.config.get("strategies", {})
strategy = strategies.get(strategy_key)
if not strategy:
logger.warning(f"⚠️ Unknown strategy '{strategy_key}'. Attempting fallback to FACT_WHAT.")
logger.warning(f"⚠️ Unknown strategy '{strategy_key}'. Fallback to FACT_WHAT.")
strategy_key = "FACT_WHAT"
strategy = strategies.get("FACT_WHAT")
# WP-25 FIX: Wenn FACT_WHAT ebenfalls fehlt, wähle die erste verfügbare Strategie
if not strategy and strategies:
strategy_key = next(iter(strategies))
strategy = strategies[strategy_key]
logger.warning(f"⚠️ 'FACT_WHAT' missing in config. Using first available: {strategy_key}")
# Letzte Rettung: Falls gar keine Strategien definiert sind
if not strategy:
logger.error("❌ CRITICAL: No strategies defined in decision_engine.yaml!")
return "Entschuldigung, meine Wissensbasis ist aktuell nicht konfiguriert."
# 2. Multi-Stream Retrieval (Wissen parallel sammeln)
# 2. Multi-Stream Retrieval
stream_results = await self._execute_parallel_streams(strategy, query)
# 3. Synthese (Ergebnisse zu einer Antwort verweben)
# 3. Synthese
return await self._generate_final_answer(strategy_key, strategy, query, stream_results)
async def _determine_strategy(self, query: str) -> str:
"""Nutzt den LLM-Router zur dynamischen Wahl der Such-Strategie."""
"""Nutzt den LLM-Router zur Wahl der Such-Strategie."""
prompt_key = self.config.get("settings", {}).get("router_prompt_key", "intent_router_v1")
router_prompt_template = self.llm_service.get_prompt(prompt_key)
if not router_prompt_template:
return "FACT_WHAT"
full_prompt = router_prompt_template.format(query=query)
try:
response = await self.llm_service.generate_raw_response(
full_prompt,
max_retries=1,
priority="realtime"
full_prompt, max_retries=1, priority="realtime"
)
return str(response).strip().upper()
except Exception as e:
@ -101,13 +91,12 @@ class DecisionEngine:
return "FACT_WHAT"
async def _execute_parallel_streams(self, strategy: Dict, query: str) -> Dict[str, str]:
"""Führt alle in der Strategie definierten Such-Streams gleichzeitig aus."""
"""Führt Such-Streams gleichzeitig aus."""
stream_keys = strategy.get("use_streams", [])
library = self.config.get("streams_library", {})
tasks = []
active_streams = []
for key in stream_keys:
stream_cfg = library.get(key)
if stream_cfg:
@ -127,7 +116,10 @@ class DecisionEngine:
return mapped_results
async def _run_single_stream(self, name: str, cfg: Dict, query: str) -> QueryResponse:
"""Bereitet eine spezialisierte Suche für einen Stream vor und führt sie aus."""
"""
Bereitet eine spezialisierte Suche vor.
WP-25: Taggt die Treffer mit ihrem Ursprungs-Stream.
"""
transformed_query = cfg.get("query_template", "{query}").format(query=query)
request = QueryRequest(
@ -139,10 +131,18 @@ class DecisionEngine:
explain=True
)
return await self.retriever.search(request)
# Retrieval ausführen
response = await self.retriever.search(request)
# WP-25: STREAM-TRACING
# Markiere jeden Treffer mit dem Namen des Quell-Streams
for hit in response.results:
hit.stream_origin = name
return response
def _format_stream_context(self, response: QueryResponse) -> str:
"""Wandelt QueryHits in einen kompakten String für das LLM um."""
"""Wandelt QueryHits in Kontext-Strings um."""
if not response.results:
return "Keine spezifischen Informationen in diesem Stream gefunden."
@ -161,56 +161,43 @@ class DecisionEngine:
query: str,
stream_results: Dict[str, str]
) -> str:
"""Führt die Multi-Stream Synthese durch."""
"""Führt die Synthese durch."""
provider = strategy.get("preferred_provider") or self.settings.MINDNET_LLM_PROVIDER
template_key = strategy.get("prompt_template", "rag_template")
template = self.llm_service.get_prompt(template_key, provider=provider)
system_prompt = self.llm_service.get_prompt("system_prompt", provider=provider)
# WP-25 ROBUSTNESS FIX:
# Wir stellen sicher, dass alle Variablen, die im Template vorkommen könnten,
# zumindest mit einem leeren String initialisiert sind.
# WP-25 ROBUSTNESS: Pre-Initialization
all_possible_streams = ["values_stream", "facts_stream", "biography_stream", "risk_stream", "tech_stream"]
template_vars = {s: "" for s in all_possible_streams}
# Überschreiben mit tatsächlichen Ergebnissen
template_vars.update(stream_results)
template_vars["query"] = query
prepend = strategy.get("prepend_instruction", "")
try:
# Sicherheitscheck: Sind alle benötigten Platzhalter im Template vorhanden?
final_prompt = template.format(**template_vars)
if prepend:
final_prompt = f"{prepend}\n\n{final_prompt}"
response = await self.llm_service.generate_raw_response(
final_prompt,
system=system_prompt,
provider=provider,
priority="realtime"
final_prompt, system=system_prompt, provider=provider, priority="realtime"
)
if not response or len(response.strip()) < 5:
return await self.llm_service.generate_raw_response(
final_prompt,
system=system_prompt,
provider="ollama",
priority="realtime"
final_prompt, system=system_prompt, provider="ollama", priority="realtime"
)
return response
except KeyError as e:
logger.error(f"Template Variable mismatch in '{template_key}': Missing {e}")
# Fallback: Einfaches Aneinanderreihen der gefundenen Stream-Inhalte
fallback_context = "\n\n".join([v for v in stream_results.values() if v])
return await self.llm_service.generate_raw_response(
f"Beantworte: {query}\n\nKontext:\n{fallback_context}",
system=system_prompt,
priority="realtime"
system=system_prompt, priority="realtime"
)
except Exception as e:
logger.error(f"Final Synthesis failed: {e}")

View File

@ -1,7 +1,7 @@
"""
FILE: app/models/dto.py
DESCRIPTION: Pydantic-Modelle (DTOs) für Request/Response Bodies. Definiert das API-Schema.
VERSION: 0.7.0 (WP-25: Multi-Stream & Agentic RAG Support)
VERSION: 0.7.1 (WP-25: Stream-Tracing Support)
STATUS: Active
DEPENDENCIES: pydantic, typing, uuid
"""
@ -122,7 +122,10 @@ class Explanation(BaseModel):
# --- Response Models ---
class QueryHit(BaseModel):
"""Einzelnes Trefferobjekt."""
"""
Einzelnes Trefferobjekt.
WP-25: stream_origin hinzugefügt für Tracing und Feedback-Optimierung.
"""
node_id: str
note_id: str
semantic_score: float
@ -133,6 +136,7 @@ class QueryHit(BaseModel):
source: Optional[Dict] = None
payload: Optional[Dict] = None
explanation: Optional[Explanation] = None
stream_origin: Optional[str] = Field(None, description="Name des Ursprungs-Streams")
class QueryResponse(BaseModel):

View File

@ -1,7 +1,9 @@
# config/prompts.yaml — VERSION 3.0.0 (WP-25: Multi-Stream Agentic RAG)
# WP-20/22: Cloud-Templates & Semantic Graph Routing erhalten.
# WP-25: Integration der Multi-Stream Synthese zur Vermeidung von Halluzinationen.
# OLLAMA: UNVERÄNDERT laut Benutzeranweisung.
# config/prompts.yaml — VERSION 3.1.2 (WP-25 Cleanup: Multi-Stream Sync)
# STATUS: Active
# FIX:
# - 100% Wiederherstellung der Ingest- & Validierungslogik (Sektion 5-8).
# - Überführung der Kategorien 1-4 in die Multi-Stream Struktur unter Beibehaltung des Inhalts.
# - Konsolidierung: Sektion 9 (v3.0.0) wurde in Sektion 1 & 2 integriert (keine Redundanz).
system_prompt: |
Du bist 'mindnet', mein digitaler Zwilling und strategischer Partner.
@ -16,39 +18,57 @@ system_prompt: |
3. Antworte auf Deutsch (außer bei Code/Fachbegriffen).
# ---------------------------------------------------------
# 1. STANDARD: Fakten & Wissen (Intent: FACT)
# 1. STANDARD: Fakten & Wissen (Intent: FACT_WHAT / FACT_WHEN)
# ---------------------------------------------------------
rag_template:
# Ersetzt das alte 'rag_template'. Nutzt jetzt parallele Streams.
fact_synthesis_v1:
ollama: |
QUELLEN (WISSEN):
WISSENS-STREAMS:
=========================================
{context_str}
FAKTEN & STATUS:
{facts_stream}
ERFAHRUNG & BIOGRAFIE:
{biography_stream}
WISSEN & TECHNIK:
{tech_stream}
=========================================
FRAGE:
{query}
ANWEISUNG:
Beantworte die Frage präzise basierend auf den Quellen.
Beantworte die Frage präzise basierend auf den Quellen.
Kombiniere harte Fakten mit persönlichen Erfahrungen, falls vorhanden.
Fasse die Informationen zusammen. Sei objektiv und neutral.
gemini: |
Kontext meines digitalen Zwillings: {context_str}
Beantworte strukturiert und präzise: {query}
Beantworte die Wissensabfrage "{query}" basierend auf diesen Streams:
FAKTEN: {facts_stream}
BIOGRAFIE/ERFAHRUNG: {biography_stream}
TECHNIK: {tech_stream}
Kombiniere harte Fakten mit persönlichen Erfahrungen, falls vorhanden. Antworte strukturiert und präzise.
openrouter: |
Kontext-Analyse für den digitalen Zwilling:
{context_str}
Anfrage: {query}
Antworte basierend auf dem Kontext.
Synthese der Wissens-Streams für: {query}
Inhalt: {facts_stream} | {biography_stream} | {tech_stream}
Antworte basierend auf dem bereitgestellten Kontext.
# ---------------------------------------------------------
# 2. DECISION: Strategie & Abwägung (Intent: DECISION)
# ---------------------------------------------------------
decision_template:
# Ersetzt das alte 'decision_template'. Nutzt jetzt parallele Streams.
decision_synthesis_v1:
ollama: |
KONTEXT (FAKTEN & STRATEGIE):
ENTSCHEIDUNGS-STREAMS:
=========================================
{context_str}
WERTE & PRINZIPIEN (Identität):
{values_stream}
OPERATIVE FAKTEN (Realität):
{facts_stream}
RISIKO-RADAR (Konsequenzen):
{risk_stream}
=========================================
ENTSCHEIDUNGSFRAGE:
@ -57,7 +77,7 @@ decision_template:
ANWEISUNG:
Du agierst als mein Entscheidungs-Partner.
1. Analysiere die Faktenlage aus den Quellen.
2. Prüfe dies hart gegen meine strategischen Notizen (Typ [VALUE], [PRINCIPLE], [GOAL]).
2. Prüfe dies hart gegen meine strategischen Notizen (Werte & Prinzipien).
3. Wäge ab: Passt die technische/faktische Lösung zu meinen Werten?
FORMAT:
@ -65,19 +85,26 @@ decision_template:
- **Abgleich:** (Gibt es Konflikte mit Werten/Zielen? Nenne die Quelle!)
- **Empfehlung:** (Klare Meinung: Ja/No/Vielleicht mit Begründung)
gemini: |
Agiere als strategischer Partner. Analysiere die Frage {query} basierend auf meinen Werten im Kontext {context_str}.
Agiere als mein strategischer Partner. Analysiere die Frage: {query}
Werte: {values_stream} | Fakten: {facts_stream} | Risiken: {risk_stream}.
Wäge ab und gib eine klare strategische Empfehlung ab.
openrouter: |
Strategische Entscheidungsanalyse: {query}
Wertebasis aus dem Graphen: {context_str}
Strategische Multi-Stream Analyse für: {query}
Werte-Basis: {values_stream} | Fakten: {facts_stream} | Risiken: {risk_stream}
Bitte wäge ab und gib eine Empfehlung.
# ---------------------------------------------------------
# 3. EMPATHY: Der Spiegel / "Ich"-Modus (Intent: EMPATHY)
# ---------------------------------------------------------
empathy_template:
ollama: |
KONTEXT (ERFAHRUNGEN & GLAUBENSSÄTZE):
KONTEXT (ERFAHRUNGEN & WERTE):
=========================================
{context_str}
ERLEBNISSE & BIOGRAFIE:
{biography_stream}
WERTE & BEDÜRFNISSE:
{values_stream}
=========================================
SITUATION:
@ -86,22 +113,26 @@ empathy_template:
ANWEISUNG:
Du agierst jetzt als mein empathischer Spiegel.
1. Versuche nicht sofort, das Problem technisch zu lösen.
2. Zeige Verständnis für die Situation basierend auf meinen eigenen Erfahrungen ([EXPERIENCE]) oder Glaubenssätzen ([BELIEF]), falls im Kontext vorhanden.
2. Zeige Verständnis für die Situation basierend auf meinen eigenen Erfahrungen ([EXPERIENCE]) oder Werten, falls im Kontext vorhanden.
3. Antworte in der "Ich"-Form oder "Wir"-Form. Sei unterstützend.
TONFALL:
Ruhig, verständnisvoll, reflektiert. Keine Aufzählungszeichen, sondern fließender Text.
gemini: "Sei mein digitaler Spiegel für {query}. Kontext: {context_str}"
openrouter: "Empathische Reflexion der Situation {query}. Persönlicher Kontext: {context_str}"
gemini: "Sei mein digitaler Spiegel für {query}. Kontext: {biography_stream}, {values_stream}"
openrouter: "Empathische Reflexion der Situation {query}. Persönlicher Kontext: {biography_stream}, {values_stream}"
# ---------------------------------------------------------
# 4. TECHNICAL: Der Coder (Intent: CODING)
# ---------------------------------------------------------
technical_template:
ollama: |
KONTEXT (DOCS & SNIPPETS):
KONTEXT (WISSEN & PROJEKTE):
=========================================
{context_str}
TECHNIK & SNIPPETS:
{tech_stream}
PROJEKT-STATUS:
{facts_stream}
=========================================
TASK:
@ -117,11 +148,11 @@ technical_template:
- Kurze Erklärung des Ansatzes.
- Markdown Code-Block (Copy-Paste fertig).
- Wichtige Edge-Cases.
gemini: "Generiere Code für {query} unter Berücksichtigung von {context_str}."
openrouter: "Technischer Support für {query}. Code-Referenzen: {context_str}"
gemini: "Generiere Code für {query} unter Berücksichtigung von {tech_stream} und {facts_stream}."
openrouter: "Technischer Support für {query}. Referenzen: {tech_stream}, Projekt-Kontext: {facts_stream}"
# ---------------------------------------------------------
# 5. INTERVIEW: Der "One-Shot Extractor" (Performance Mode)
# 5. INTERVIEW: Der "One-Shot Extractor" (WP-07)
# ---------------------------------------------------------
interview_template:
ollama: |
@ -159,7 +190,7 @@ interview_template:
openrouter: "Strukturiere den Input {query} nach dem Schema {schema_fields} für Typ {target_type}."
# ---------------------------------------------------------
# 6. EDGE_ALLOCATION: Kantenfilter (Intent: OFFLINE_FILTER)
# 6. EDGE_ALLOCATION: Kantenfilter (Ingest)
# ---------------------------------------------------------
edge_allocation_template:
ollama: |
@ -199,7 +230,7 @@ edge_allocation_template:
OUTPUT:
# ---------------------------------------------------------
# 7. SMART EDGE ALLOCATION: Extraktion (Intent: INGEST)
# 7. SMART EDGE ALLOCATION: Extraktion (Ingest)
# ---------------------------------------------------------
edge_extraction:
ollama: |
@ -239,7 +270,7 @@ edge_extraction:
OUTPUT:
# ---------------------------------------------------------
# 8. WP-15b: EDGE VALIDATION (Intent: VALIDATE)
# 8. WP-15b: EDGE VALIDATION (Ingest/Validate)
# ---------------------------------------------------------
edge_validation:
gemini: |
@ -271,63 +302,6 @@ edge_validation:
BEZIEHUNG: {edge_kind}
Ist diese Verbindung valide? Antworte NUR mit YES oder NO.
# ---------------------------------------------------------
# 9. WP-25: MULTI-STREAM SYNTHESIS (Intent: SYNTHESIS)
# ---------------------------------------------------------
# Diese Templates verarbeiten die Ergebnisse aus parallelen Such-Streams.
decision_synthesis_v1:
gemini: |
Agiere als mein strategischer Partner. Analysiere die Frage: {query}
Hier sind die Ergebnisse aus verschiedenen Wissens-Streams meiner Mindnet-Basis:
### STREAM: WERTE & PRINZIPIEN (Identität)
{values_stream}
### STREAM: OPERATIVE FAKTEN (Realität)
{facts_stream}
### STREAM: RISIKO-ANALYSE (Konsequenzen)
{risk_stream}
AUFGABE:
1. Fasse die Faktenlage kurz zusammen.
2. Wäge die Fakten hart gegen meine Werte ab. Gibt es Konflikte?
3. Beurteile das Vorhaben basierend auf dem Risiko-Radar.
4. Gib eine klare strategische Empfehlung ab.
openrouter: |
Strategische Multi-Stream Analyse für: {query}
Werte-Basis: {values_stream}
Fakten: {facts_stream}
Risiken: {risk_stream}
Bitte wäge ab und gib eine Empfehlung.
ollama: |
Du bist mein Entscheidungs-Partner. Analysiere {query} basierend auf diesen Streams:
WERTE: {values_stream}
FAKTEN: {facts_stream}
RISIKEN: {risk_stream}
Wäge die Fakten gegen die Werte ab und nenne potenzielle Risiken. Nenne Quellen!
fact_synthesis_v1:
gemini: |
Beantworte die Wissensabfrage "{query}" basierend auf diesen Streams:
FAKTEN: {facts_stream}
BIOGRAFIE/ERFAHRUNG: {biography_stream}
TECHNIK: {tech_stream}
Kombiniere harte Fakten mit persönlichen Erfahrungen, falls vorhanden.
openrouter: |
Synthese der Wissens-Streams für: {query}
Inhalt: {facts_stream} | {biography_stream} | {tech_stream}
ollama: |
Fasse das Wissen zu {query} zusammen.
QUELLE FAKTEN: {facts_stream}
QUELLE ERFAHRUNG: {biography_stream}
QUELLE TECHNIK: {tech_stream}
Antworte präzise und nenne die Quellen.
# ... (Vorherige Sektionen 1-9 bleiben identisch)
# ---------------------------------------------------------
# 10. WP-25: INTENT ROUTING (Intent: CLASSIFY)
# ---------------------------------------------------------