Update Decision Engine and DTOs for WP-25: Bump version to 1.0.3 and 0.7.1 respectively. Introduce stream tracing support by adding 'stream_origin' to QueryHit model. Enhance robustness with pre-initialization of stream variables and improve logging for unknown strategies. Refactor prompts for clarity and consistency in multi-stream synthesis.

This commit is contained in:
Lars 2026-01-01 12:38:32 +01:00
parent 5ab01c5150
commit ea38743a2a
3 changed files with 102 additions and 137 deletions

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,13 +18,21 @@ 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:
@ -30,25 +40,35 @@ rag_template:
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: |
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)
# ---------------------------------------------------------