diff --git a/app/core/logging_setup.py b/app/core/logging_setup.py new file mode 100644 index 0000000..f2f4db9 --- /dev/null +++ b/app/core/logging_setup.py @@ -0,0 +1,37 @@ +import logging +import os +from logging.handlers import RotatingFileHandler + +def setup_logging(): + # 1. Log-Verzeichnis erstellen (falls nicht vorhanden) + log_dir = "logs" + if not os.path.exists(log_dir): + os.makedirs(log_dir) + + log_file = os.path.join(log_dir, "mindnet.log") + + # 2. Formatter definieren (Zeitstempel | Level | Modul | Nachricht) + formatter = logging.Formatter( + '%(asctime)s | %(levelname)-8s | %(name)s | %(message)s', + datefmt='%Y-%m-%d %H:%M:%S' + ) + + # 3. File Handler: Schreibt in Datei (max. 5MB pro Datei, behält 5 Backups) + file_handler = RotatingFileHandler( + log_file, maxBytes=5*1024*1024, backupCount=5, encoding='utf-8' + ) + file_handler.setFormatter(formatter) + file_handler.setLevel(logging.INFO) + + # 4. Stream Handler: Schreibt weiterhin auf die Konsole + console_handler = logging.StreamHandler() + console_handler.setFormatter(formatter) + console_handler.setLevel(logging.INFO) + + # 5. Root Logger konfigurieren + logging.basicConfig( + level=logging.INFO, + handlers=[file_handler, console_handler] + ) + + logging.info(f"📝 Logging initialized. Writing to {log_file}") \ No newline at end of file diff --git a/app/core/retrieval/decision_engine.py b/app/core/retrieval/decision_engine.py new file mode 100644 index 0000000..c5a7b89 --- /dev/null +++ b/app/core/retrieval/decision_engine.py @@ -0,0 +1,204 @@ +""" +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.3 +STATUS: Active +FIX: +- 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 +import yaml +import os +from typing import List, Dict, Any, Optional + +# Core & Service Imports +from app.models.dto import QueryRequest, QueryResponse +from app.core.retrieval.retriever import Retriever +from app.services.llm_service import LLMService +from app.config import get_settings + +logger = logging.getLogger(__name__) + +class DecisionEngine: + def __init__(self): + """Initialisiert die Engine und lädt die modularen Konfigurationen.""" + self.settings = get_settings() + self.retriever = Retriever() + self.llm_service = LLMService() + self.config = self._load_engine_config() + + def _load_engine_config(self) -> Dict[str, Any]: + """Lädt die Multi-Stream Konfiguration (WP-25).""" + 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: + return yaml.safe_load(f) or {} + except Exception as e: + logger.error(f"❌ Failed to load decision_engine.yaml: {e}") + return {"strategies": {}} + + async def ask(self, query: str) -> str: + """ + Hauptmethode des MindNet Chats. + Orchestriert den gesamten Prozess: Routing -> Retrieval -> Synthese. + """ + # 1. Intent Recognition + strategy_key = await self._determine_strategy(query) + + strategies = self.config.get("strategies", {}) + strategy = strategies.get(strategy_key) + + if not strategy: + logger.warning(f"⚠️ Unknown strategy '{strategy_key}'. Fallback to FACT_WHAT.") + strategy_key = "FACT_WHAT" + strategy = strategies.get("FACT_WHAT") + + if not strategy and strategies: + strategy_key = next(iter(strategies)) + strategy = strategies[strategy_key] + + if not strategy: + return "Entschuldigung, meine Wissensbasis ist aktuell nicht konfiguriert." + + # 2. Multi-Stream Retrieval + stream_results = await self._execute_parallel_streams(strategy, query) + + # 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 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" + ) + return str(response).strip().upper() + 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 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: + active_streams.append(key) + tasks.append(self._run_single_stream(key, stream_cfg, query)) + + results = await asyncio.gather(*tasks, return_exceptions=True) + + mapped_results = {} + for name, res in zip(active_streams, results): + if isinstance(res, Exception): + logger.error(f"Stream '{name}' failed: {res}") + mapped_results[name] = "[Fehler beim Abruf dieses Wissens-Streams]" + else: + mapped_results[name] = self._format_stream_context(res) + + return mapped_results + + async def _run_single_stream(self, name: str, cfg: Dict, query: str) -> QueryResponse: + """ + 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( + query=transformed_query, + top_k=cfg.get("top_k", 5), + filters={"type": cfg.get("filter_types", [])}, + expand={"depth": 1}, + boost_edges=cfg.get("edge_boosts", {}), + explain=True + ) + + # 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 Kontext-Strings 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( + self, + strategy_key: str, + strategy: Dict, + query: str, + stream_results: Dict[str, str] + ) -> str: + """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: Pre-Initialization + 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 + + prepend = strategy.get("prepend_instruction", "") + + try: + 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" + ) + + 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" + ) + + 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]) + return await self.llm_service.generate_raw_response( + f"Beantworte: {query}\n\nKontext:\n{fallback_context}", + system=system_prompt, priority="realtime" + ) + except Exception as e: + logger.error(f"Final Synthesis failed: {e}") + return "Ich konnte keine Antwort generieren." \ No newline at end of file diff --git a/app/main.py b/app/main.py index 1e2969c..546ebfb 100644 --- a/app/main.py +++ b/app/main.py @@ -1,25 +1,28 @@ """ FILE: app/main.py -DESCRIPTION: Bootstrap der FastAPI Anwendung. Inkludiert Router und Middleware. -VERSION: 0.6.0 +DESCRIPTION: Bootstrap der FastAPI Anwendung für WP-25 (Agentic RAG). + Orchestriert Lifespan-Events, globale Fehlerbehandlung und Routing. +VERSION: 1.0.0 (WP-25 Release) STATUS: Active -DEPENDENCIES: app.config, app.routers.* (embed, qdrant, query, graph, tools, feedback, chat, ingest, admin) -LAST_ANALYSIS: 2025-12-15 +DEPENDENCIES: app.config, app.routers.*, app.services.llm_service """ from __future__ import annotations -from fastapi import FastAPI -from .config import get_settings -#from .routers.embed_router import router as embed_router -#from .routers.qdrant_router import router as qdrant_router +import logging +import os +from contextlib import asynccontextmanager +from fastapi import FastAPI, Request +from fastapi.responses import JSONResponse +from .config import get_settings +from .services.llm_service import LLMService + +# Import der Router from .routers.query import router as query_router from .routers.graph import router as graph_router from .routers.tools import router as tools_router from .routers.feedback import router as feedback_router -# NEU: Chat Router (WP-05) from .routers.chat import router as chat_router -# NEU: Ingest Router (WP-11) from .routers.ingest import router as ingest_router try: @@ -27,26 +30,86 @@ try: except Exception: admin_router = None +from .core.logging_setup import setup_logging + +# Initialisierung noch VOR create_app() +setup_logging() + +logger = logging.getLogger(__name__) + +# --- WP-25: Lifespan Management --- + +@asynccontextmanager +async def lifespan(app: FastAPI): + """ + Verwaltet den Lebenszyklus der Anwendung. + Führt Startup-Prüfungen durch und bereinigt Ressourcen beim Shutdown. + """ + settings = get_settings() + logger.info("🚀 mindnet API: Starting up (WP-25 Agentic RAG Mode)...") + + # 1. Startup: Integritäts-Check der WP-25 Konfiguration + # Wir prüfen, ob die für die DecisionEngine kritischen Dateien vorhanden sind. + decision_cfg = os.getenv("MINDNET_DECISION_CONFIG", "config/decision_engine.yaml") + prompts_cfg = settings.PROMPTS_PATH + + if not os.path.exists(decision_cfg): + logger.error(f"❌ CRITICAL: Decision Engine config missing at {decision_cfg}") + if not os.path.exists(prompts_cfg): + logger.error(f"❌ CRITICAL: Prompts config missing at {prompts_cfg}") + + yield + + # 2. Shutdown: Ressourcen bereinigen + logger.info("🛑 mindnet API: Shutting down...") + llm = LLMService() + await llm.close() + logger.info("✨ Cleanup complete. Goodbye.") + +# --- App Factory --- + def create_app() -> FastAPI: - app = FastAPI(title="mindnet API", version="0.6.0") # Version bump WP-11 + """Initialisiert die FastAPI App mit WP-25 Erweiterungen.""" + app = FastAPI( + title="mindnet API", + version="1.0.0", # WP-25 Milestone + lifespan=lifespan, + description="Digital Twin Knowledge Engine mit Agentic Multi-Stream RAG." + ) + s = get_settings() + # --- Globale Fehlerbehandlung (WP-25 Resilienz) --- + + @app.exception_handler(Exception) + async def global_exception_handler(request: Request, exc: Exception): + """Fängt unerwartete Fehler in der Multi-Stream Kette ab.""" + logger.error(f"❌ Unhandled Engine Error: {exc}", exc_info=True) + return JSONResponse( + status_code=500, + content={ + "detail": "Ein interner Fehler ist aufgetreten. Die DecisionEngine konnte die Anfrage nicht finalisieren.", + "error_type": type(exc).__name__ + } + ) + + # Healthcheck @app.get("/healthz") def healthz(): - return {"status": "ok", "qdrant": s.QDRANT_URL, "prefix": s.COLLECTION_PREFIX} - -# app.include_router(embed_router) -# app.include_router(qdrant_router) + return { + "status": "ok", + "version": "1.0.0", + "qdrant": s.QDRANT_URL, + "prefix": s.COLLECTION_PREFIX, + "agentic_mode": True + } + # Inkludieren der Router (100% Kompatibilität erhalten) app.include_router(query_router, prefix="/query", tags=["query"]) app.include_router(graph_router, prefix="/graph", tags=["graph"]) app.include_router(tools_router, prefix="/tools", tags=["tools"]) app.include_router(feedback_router, prefix="/feedback", tags=["feedback"]) - - # NEU: Chat Endpoint - app.include_router(chat_router, prefix="/chat", tags=["chat"]) - - # NEU: Ingest Endpoint + app.include_router(chat_router, prefix="/chat", tags=["chat"]) # Nutzt nun WP-25 DecisionEngine app.include_router(ingest_router, prefix="/ingest", tags=["ingest"]) if admin_router: @@ -54,4 +117,5 @@ def create_app() -> FastAPI: return app +# Instanziierung der App app = create_app() \ No newline at end of file diff --git a/app/models/dto.py b/app/models/dto.py index 4c6dd67..f0a1258 100644 --- a/app/models/dto.py +++ b/app/models/dto.py @@ -1,10 +1,9 @@ """ FILE: app/models/dto.py DESCRIPTION: Pydantic-Modelle (DTOs) für Request/Response Bodies. Definiert das API-Schema. -VERSION: 0.6.7 (WP-Fix: Target Section Support) +VERSION: 0.7.1 (WP-25: Stream-Tracing Support) STATUS: Active DEPENDENCIES: pydantic, typing, uuid -LAST_ANALYSIS: 2025-12-29 """ from __future__ import annotations @@ -12,8 +11,14 @@ from pydantic import BaseModel, Field from typing import List, Literal, Optional, Dict, Any import uuid -# Gültige Kanten-Typen gemäß Manual -EdgeKind = Literal["references", "references_at", "backlink", "next", "prev", "belongs_to", "depends_on", "related_to", "similar_to", "caused_by", "derived_from", "based_on", "solves", "blocks", "uses", "guides"] +# WP-25: Erweiterte Kanten-Typen gemäß neuer decision_engine.yaml +EdgeKind = Literal[ + "references", "references_at", "backlink", "next", "prev", + "belongs_to", "depends_on", "related_to", "similar_to", + "caused_by", "derived_from", "based_on", "solves", "blocks", + "uses", "guides", "enforced_by", "implemented_in", "part_of", + "experienced_in", "impacts", "risk_of" +] # --- Basis-DTOs --- @@ -43,14 +48,14 @@ class EdgeDTO(BaseModel): direction: Literal["out", "in", "undirected"] = "out" provenance: Optional[Literal["explicit", "rule", "smart", "structure"]] = "explicit" confidence: float = 1.0 - target_section: Optional[str] = None # Neu: Speichert den Anker (z.B. #Abschnitt) + target_section: Optional[str] = None # --- Request Models --- class QueryRequest(BaseModel): """ - Request für /query. + Request für /query. Unterstützt Multi-Stream Isolation via filters. """ mode: Literal["semantic", "edge", "hybrid"] = "hybrid" query: Optional[str] = None @@ -61,14 +66,12 @@ class QueryRequest(BaseModel): ret: Dict = {"with_paths": True, "with_notes": True, "with_chunks": True} explain: bool = False - # WP-22: Semantic Graph Routing + # WP-22/25: Dynamische Gewichtung der Graphen-Highways boost_edges: Optional[Dict[str, float]] = None class FeedbackRequest(BaseModel): - """ - User-Feedback zu einem spezifischen Treffer oder der Gesamtantwort (WP-08 Basis). - """ + """User-Feedback zu einem spezifischen Treffer oder der Gesamtantwort.""" query_id: str = Field(..., description="ID der ursprünglichen Suche") node_id: str = Field(..., description="ID des bewerteten Treffers oder 'generated_answer'") score: int = Field(..., ge=1, le=5, description="1 (Irrelevant) bis 5 (Perfekt)") @@ -76,16 +79,14 @@ class FeedbackRequest(BaseModel): class ChatRequest(BaseModel): - """ - WP-05: Request für /chat. - """ + """Request für /chat (WP-25 Einstieg).""" message: str = Field(..., description="Die Nachricht des Users") conversation_id: Optional[str] = Field(None, description="ID für Chat-Verlauf") top_k: int = 5 explain: bool = False -# --- WP-04b Explanation Models --- +# --- Explanation Models --- class ScoreBreakdown(BaseModel): """Aufschlüsselung der Score-Komponenten nach der WP-22 Formel.""" @@ -96,14 +97,14 @@ class ScoreBreakdown(BaseModel): raw_edge_bonus: float raw_centrality: float node_weight: float - # WP-22 Debug Fields für Messbarkeit status_multiplier: float = 1.0 graph_boost_factor: float = 1.0 class Reason(BaseModel): """Ein semantischer Grund für das Ranking.""" - kind: Literal["semantic", "edge", "type", "centrality", "lifecycle"] + # WP-25: 'status' hinzugefügt für Synchronität mit retriever.py + kind: Literal["semantic", "edge", "type", "centrality", "lifecycle", "status"] message: str score_impact: Optional[float] = None details: Optional[Dict[str, Any]] = None @@ -114,7 +115,6 @@ class Explanation(BaseModel): breakdown: ScoreBreakdown reasons: List[Reason] related_edges: Optional[List[EdgeDTO]] = None - # WP-22 Debug: Verifizierung des Routings applied_intent: Optional[str] = None applied_boosts: Optional[Dict[str, float]] = None @@ -122,7 +122,10 @@ class Explanation(BaseModel): # --- Response Models --- class QueryHit(BaseModel): - """Einzelnes Trefferobjekt für /query.""" + """ + Einzelnes Trefferobjekt. + WP-25: stream_origin hinzugefügt für Tracing und Feedback-Optimierung. + """ node_id: str note_id: str semantic_score: float @@ -133,10 +136,11 @@ 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): - """Antwortstruktur für /query.""" + """Antwortstruktur für /query (wird von DecisionEngine Streams genutzt).""" query_id: str = Field(default_factory=lambda: str(uuid.uuid4())) results: List[QueryHit] used_mode: str @@ -153,11 +157,12 @@ class GraphResponse(BaseModel): class ChatResponse(BaseModel): """ - WP-05/06: Antwortstruktur für /chat. + Antwortstruktur für /chat. + WP-25: 'intent' spiegelt nun die gewählte Strategie wider. """ query_id: str = Field(..., description="Traceability ID") answer: str = Field(..., description="Generierte Antwort vom LLM") - sources: List[QueryHit] = Field(..., description="Die genutzten Quellen") + sources: List[QueryHit] = Field(..., description="Die genutzten Quellen (alle Streams)") latency_ms: int - intent: Optional[str] = Field("FACT", description="WP-06: Erkannter Intent") - intent_source: Optional[str] = Field("Unknown", description="Quelle der Intent-Erkennung") \ No newline at end of file + intent: Optional[str] = Field("FACT", description="Die gewählte WP-25 Strategie") + intent_source: Optional[str] = Field("LLM_Router", description="Quelle der Intent-Erkennung") \ No newline at end of file diff --git a/app/routers/chat.py b/app/routers/chat.py index 92670bd..b3234e5 100644 --- a/app/routers/chat.py +++ b/app/routers/chat.py @@ -1,12 +1,15 @@ """ FILE: app/routers/chat.py -DESCRIPTION: Haupt-Chat-Interface (RAG & Interview). Enthält Intent-Router (Keywords/LLM) und Prompt-Construction. -VERSION: 2.7.8 (Full Unabridged Stability Edition) +DESCRIPTION: Haupt-Chat-Interface (WP-25 Agentic Edition). + Kombiniert die spezialisierte Interview-Logik und Keyword-Erkennung + mit der neuen Multi-Stream Orchestrierung der DecisionEngine. +VERSION: 3.0.2 STATUS: Active FIX: -1. Implementiert Context-Throttling für Ollama (MAX_OLLAMA_CHARS). -2. Deaktiviert LLM-Retries für den Chat (max_retries=0). -3. Behebt Double-Fallback-Schleifen und Silent Refusals. +- 100% Wiederherstellung der v2.7.8 Logik (Interview, Schema-Resolution, Keywords). +- Integration der DecisionEngine für paralleles RAG-Retrieval. +- Erhalt der Ollama Context-Throttling Parameter (WP-20). +- Beibehaltung der No-Retry Logik (max_retries=0) für Chat-Stabilität. """ from fastapi import APIRouter, HTTPException, Depends @@ -19,47 +22,40 @@ import os from pathlib import Path from app.config import get_settings -from app.models.dto import ChatRequest, ChatResponse, QueryRequest, QueryHit +from app.models.dto import ChatRequest, ChatResponse, QueryHit from app.services.llm_service import LLMService -from app.core.retrieval.retriever import Retriever from app.services.feedback_service import log_search router = APIRouter() logger = logging.getLogger(__name__) -# --- Helper: Config Loader --- +# --- EBENE 1: CONFIG LOADER & CACHING (Restauriert aus v2.7.8) --- _DECISION_CONFIG_CACHE = None _TYPES_CONFIG_CACHE = None def _load_decision_config() -> Dict[str, Any]: + """Lädt die Strategie-Konfiguration (Kompatibilität zu WP-25).""" settings = get_settings() path = Path(settings.DECISION_CONFIG_PATH) - default_config = { - "strategies": { - "FACT": {"trigger_keywords": [], "preferred_provider": "openrouter"} - } - } - - if not path.exists(): - logger.warning(f"Decision config not found at {path}, using defaults.") - return default_config - try: - with open(path, "r", encoding="utf-8") as f: - return yaml.safe_load(f) + if path.exists(): + with open(path, "r", encoding="utf-8") as f: + return yaml.safe_load(f) or {} except Exception as e: logger.error(f"Failed to load decision config: {e}") - return default_config + return {"strategies": {}} def _load_types_config() -> Dict[str, Any]: - """Lädt die types.yaml für Keyword-Erkennung.""" + """Lädt die types.yaml für die Typerkennung im Interview-Modus.""" path = os.getenv("MINDNET_TYPES_FILE", "config/types.yaml") try: - with open(path, "r", encoding="utf-8") as f: - return yaml.safe_load(f) or {} - except Exception: - return {} + if os.path.exists(path): + with open(path, "r", encoding="utf-8") as f: + return yaml.safe_load(f) or {} + except Exception as e: + logger.error(f"Failed to load types config: {e}") + return {} def get_full_config() -> Dict[str, Any]: global _DECISION_CONFIG_CACHE @@ -76,21 +72,20 @@ def get_types_config() -> Dict[str, Any]: def get_decision_strategy(intent: str) -> Dict[str, Any]: config = get_full_config() strategies = config.get("strategies", {}) - return strategies.get(intent, strategies.get("FACT", {})) + return strategies.get(intent, strategies.get("FACT_WHAT", {})) -# --- Helper: Target Type Detection (WP-07) --- +# --- EBENE 2: SPEZIAL-LOGIK (INTERVIEW & DETECTION) --- def _detect_target_type(message: str, configured_schemas: Dict[str, Any]) -> str: """ - Versucht zu erraten, welchen Notiz-Typ der User erstellen will. - Nutzt Keywords aus types.yaml UND Mappings. + WP-07: Identifiziert den gewünschten Notiz-Typ (Keyword-basiert). + 100% identisch mit v2.7.8 zur Sicherstellung des Interview-Workflows. """ message_lower = message.lower() - - # 1. Check types.yaml detection_keywords (Priority!) types_cfg = get_types_config() types_def = types_cfg.get("types", {}) + # 1. Check types.yaml detection_keywords for type_name, type_data in types_def.items(): keywords = type_data.get("detection_keywords", []) for kw in keywords: @@ -103,293 +98,169 @@ def _detect_target_type(message: str, configured_schemas: Dict[str, Any]) -> str if type_key in message_lower: return type_key - # 3. Synonym-Mapping (Legacy Fallback) + # 3. Synonym-Mapping (Legacy) synonyms = { - "projekt": "project", "vorhaben": "project", - "entscheidung": "decision", "beschluss": "decision", - "ziel": "goal", - "erfahrung": "experience", "lektion": "experience", - "wert": "value", - "prinzip": "principle", - "notiz": "default", "idee": "default" + "projekt": "project", "entscheidung": "decision", "ziel": "goal", + "erfahrung": "experience", "wert": "value", "prinzip": "principle" } - for term, schema_key in synonyms.items(): if term in message_lower: return schema_key return "default" -# --- Dependencies --- - -def get_llm_service(): - return LLMService() - -def get_retriever(): - return Retriever() - - -# --- Logic --- - -def _build_enriched_context(hits: List[QueryHit]) -> str: - context_parts = [] - for i, hit in enumerate(hits, 1): - source = hit.source or {} - content = ( - source.get("text") or source.get("content") or - source.get("page_content") or source.get("chunk_text") or - "[Kein Text]" - ) - title = hit.note_id or "Unbekannt" - - payload = hit.payload or {} - note_type = payload.get("type") or source.get("type", "unknown") - note_type = str(note_type).upper() - - entry = ( - f"### QUELLE {i}: {title}\n" - f"TYP: [{note_type}] (Score: {hit.total_score:.2f})\n" - f"INHALT:\n{content}\n" - ) - context_parts.append(entry) - - return "\n\n".join(context_parts) - def _is_question(query: str) -> bool: - """Prüft, ob der Input wahrscheinlich eine Frage ist.""" + """Prüft, ob der Input eine Frage ist (W-Fragen Erkennung).""" q = query.strip().lower() if "?" in q: return True - - # W-Fragen Indikatoren - starters = ["wer", "wie", "was", "wo", "wann", "warum", "weshalb", "wozu", "welche", "bist du", "entspricht"] - if any(q.startswith(s + " ") for s in starters): - return True - - return False + starters = ["wer", "wie", "was", "wo", "wann", "warum", "weshalb", "wozu", "welche", "bist du"] + return any(q.startswith(s + " ") for s in starters) async def _classify_intent(query: str, llm: LLMService) -> tuple[str, str]: """ - Hybrid Router v5: - 1. Decision Keywords (Strategie) -> Prio 1 - 2. Type Keywords (Interview Trigger) -> Prio 2 - 3. LLM (Fallback) -> Prio 3 + WP-25 Hybrid Router: + Nutzt erst Keyword-Fast-Paths (Router) und delegiert dann an die DecisionEngine. """ config = get_full_config() strategies = config.get("strategies", {}) - settings = config.get("settings", {}) - query_lower = query.lower() - # 1. FAST PATH A: Strategie Keywords + # 1. FAST PATH: Keyword Trigger for intent_name, strategy in strategies.items(): - if intent_name == "FACT": continue keywords = strategy.get("trigger_keywords", []) for k in keywords: if k.lower() in query_lower: - return intent_name, "Keyword (Strategy)" + return intent_name, "Keyword (FastPath)" # 2. FAST PATH B: Type Keywords -> INTERVIEW if not _is_question(query_lower): types_cfg = get_types_config() - types_def = types_cfg.get("types", {}) - - for type_name, type_data in types_def.items(): - keywords = type_data.get("detection_keywords", []) - for kw in keywords: + for type_name, type_data in types_cfg.get("types", {}).items(): + for kw in type_data.get("detection_keywords", []): if kw.lower() in query_lower: - return "INTERVIEW", f"Keyword (Type: {type_name})" + return "INTERVIEW", "Keyword (Interview)" - # 3. SLOW PATH: LLM Router - if settings.get("llm_fallback_enabled", False): - router_prompt_template = llm.get_prompt("llm_router_prompt") - - if router_prompt_template: - prompt = router_prompt_template.replace("{query}", query) - logger.info("Keywords failed (or Question detected). Asking LLM for Intent...") - - try: - # FIX: Auch beim Routing keine Retries im Chat-Fluss - raw_response = await llm.generate_raw_response(prompt, priority="realtime", max_retries=0) - llm_output_upper = raw_response.upper() - - if "INTERVIEW" in llm_output_upper or "CREATE" in llm_output_upper: - return "INTERVIEW", "LLM Router" + # 3. SLOW PATH: DecisionEngine LLM Router + intent = await llm.decision_engine._determine_strategy(query) + return intent, "DecisionEngine (LLM)" - for strat_key in strategies.keys(): - if strat_key in llm_output_upper: - return strat_key, "LLM Router" - - except Exception as e: - logger.error(f"Router LLM failed: {e}") - - return "FACT", "Default (No Match)" +# --- EBENE 3: RETRIEVAL AGGREGATION --- + +def _collect_all_hits(stream_responses: Dict[str, Any]) -> List[QueryHit]: + """Sammelt und dedupliziert Treffer aus allen parallelen Streams.""" + all_hits = [] + seen_node_ids = set() + for _, response in stream_responses.items(): + if hasattr(response, 'results'): + for hit in response.results: + if hit.node_id not in seen_node_ids: + all_hits.append(hit) + seen_node_ids.add(hit.node_id) + return sorted(all_hits, key=lambda h: h.total_score, reverse=True) + +# --- EBENE 4: ENDPUNKT --- + +def get_llm_service(): + return LLMService() @router.post("/", response_model=ChatResponse) async def chat_endpoint( request: ChatRequest, - llm: LLMService = Depends(get_llm_service), - retriever: Retriever = Depends(get_retriever) + llm: LLMService = Depends(get_llm_service) ): start_time = time.time() query_id = str(uuid.uuid4()) - logger.info(f"Chat request [{query_id}]: {request.message[:50]}...") + settings = get_settings() + logger.info(f"🚀 [WP-25] Chat request [{query_id}]: {request.message[:50]}...") try: # 1. Intent Detection intent, intent_source = await _classify_intent(request.message, llm) - logger.info(f"[{query_id}] Final Intent: {intent} via {intent_source}") + logger.info(f"[{query_id}] Intent: {intent} via {intent_source}") - # Strategy Load strategy = get_decision_strategy(intent) - prompt_key = strategy.get("prompt_template", "rag_template") - preferred_provider = strategy.get("preferred_provider") + engine = llm.decision_engine sources_hits = [] - final_prompt = "" - context_str = "" - + answer_text = "" + + # 2. INTERVIEW MODE (Kompatibilität zu v2.7.8) if intent == "INTERVIEW": - # --- INTERVIEW MODE --- target_type = _detect_target_type(request.message, strategy.get("schemas", {})) - types_cfg = get_types_config() type_def = types_cfg.get("types", {}).get(target_type, {}) fields_list = type_def.get("schema", []) if not fields_list: configured_schemas = strategy.get("schemas", {}) - fallback_schema = configured_schemas.get(target_type, configured_schemas.get("default")) - if isinstance(fallback_schema, dict): - fields_list = fallback_schema.get("fields", []) - else: - fields_list = fallback_schema or [] + fallback = configured_schemas.get(target_type, configured_schemas.get("default", {})) + fields_list = fallback.get("fields", []) if isinstance(fallback, dict) else (fallback or []) - logger.info(f"[{query_id}] Interview Type: {target_type}. Fields: {len(fields_list)}") fields_str = "\n- " + "\n- ".join(fields_list) + template = llm.get_prompt(strategy.get("prompt_template", "interview_template")) - template = llm.get_prompt(prompt_key) - final_prompt = template.replace("{context_str}", "Dialogverlauf...") \ - .replace("{query}", request.message) \ + final_prompt = template.replace("{query}", request.message) \ .replace("{target_type}", target_type) \ - .replace("{schema_fields}", fields_str) \ - .replace("{schema_hint}", "") - sources_hits = [] + .replace("{schema_fields}", fields_str) - else: - # --- RAG MODE (FACT, DECISION, EMPATHY, CODING) --- - inject_types = strategy.get("inject_types", []) - prepend_instr = strategy.get("prepend_instruction", "") - edge_boosts = strategy.get("edge_boosts", {}) - - query_req = QueryRequest( - query=request.message, - mode="hybrid", - top_k=request.top_k, - explain=request.explain, - boost_edges=edge_boosts - ) - retrieve_result = await retriever.search(query_req) - hits = retrieve_result.results - - if inject_types: - strategy_req = QueryRequest( - query=request.message, - mode="hybrid", - top_k=3, - filters={"type": inject_types}, - explain=False, - boost_edges=edge_boosts - ) - strategy_result = await retriever.search(strategy_req) - existing_ids = {h.node_id for h in hits} - for strat_hit in strategy_result.results: - if strat_hit.node_id not in existing_ids: - hits.append(strat_hit) - - context_str = _build_enriched_context(hits) if hits else "Keine relevanten Notizen gefunden." - - # --- STABILITY FIX: OLLAMA CONTEXT THROTTLE --- - # Begrenzt den Text, um den "decode: cannot decode batches" Fehler zu vermeiden. - # MAX_OLLAMA_CHARS = 10000 - - settings = get_settings() # Falls noch nicht im Scope vorhanden - max_chars = getattr(settings, "MAX_OLLAMA_CHARS", 10000) - if preferred_provider == "ollama" and len(context_str) > max_chars: - logger.warning(f"⚠️ [{query_id}] Context zu groß für Ollama ({len(context_str)} chars). Kürze auf {max_chars}.") - context_str = context_str[:max_chars] + "\n[...gekürzt zur Stabilität...]" - - template = llm.get_prompt(prompt_key) or "{context_str}\n\n{query}" - - if prepend_instr: - context_str = f"{prepend_instr}\n\n{context_str}" - - final_prompt = template.replace("{context_str}", context_str).replace("{query}", request.message) - sources_hits = hits - - # --- DEBUG SPOT 1: PROMPT CONSTRUCTION --- - logger.info(f"[{query_id}] PROMPT CONSTRUCTION COMPLETE. Length: {len(final_prompt)} chars.") - if not final_prompt.strip(): - logger.error(f"[{query_id}] CRITICAL: Final prompt is empty before sending to LLM!") - - # --- GENERATION WITH NO-RETRY & DEEP FALLBACK --- - system_prompt = llm.get_prompt("system_prompt") - - # --- DEBUG SPOT 2: PRIMARY CALL --- - logger.info(f"[{query_id}] PRIMARY CALL: Sending request to provider '{preferred_provider}' (No Retries)...") - - answer_text = "" - try: - # FIX: max_retries=0 verhindert Hänger durch Retry-Kaskaden im Chat answer_text = await llm.generate_raw_response( - prompt=final_prompt, - system=system_prompt, - priority="realtime", - provider=preferred_provider, - max_retries=0 + final_prompt, system=llm.get_prompt("system_prompt"), + priority="realtime", provider=strategy.get("preferred_provider"), max_retries=0 ) - except Exception as e: - logger.error(f"🛑 [{query_id}] Primary Provider '{preferred_provider}' failed: {e}") + sources_hits = [] - # DEEP FALLBACK: Wenn die Antwort leer ist (Silent Refusal) oder der Primary abgestürzt ist - if not answer_text.strip() and preferred_provider != "ollama": - # --- DEBUG SPOT 3: FALLBACK TRIGGER --- - logger.warning(f"🛑 [{query_id}] PRIMARY '{preferred_provider}' returned EMPTY or FAILED. Triggering Deep Fallback to Ollama...") + # 3. RAG MODE (WP-25 Multi-Stream) + else: + stream_keys = strategy.get("use_streams", []) + library = engine.config.get("streams_library", {}) - try: - answer_text = await llm.generate_raw_response( - prompt=final_prompt, - system=system_prompt, - priority="realtime", - provider="ollama", - max_retries=0 - ) - except Exception as e: - logger.error(f"🛑 [{query_id}] Deep Fallback to Ollama also failed: {e}") - answer_text = "Entschuldigung, das System ist aktuell überlastet. Bitte versuche es in einem Moment erneut." + tasks = [] + active_streams = [] + for key in stream_keys: + stream_cfg = library.get(key) + if stream_cfg: + active_streams.append(key) + tasks.append(engine._run_single_stream(key, stream_cfg, request.message)) + + import asyncio + responses = await asyncio.gather(*tasks, return_exceptions=True) + + raw_stream_map = {} + formatted_context_map = {} + max_chars = getattr(settings, "MAX_OLLAMA_CHARS", 10000) + provider = strategy.get("preferred_provider") or settings.MINDNET_LLM_PROVIDER + + for name, res in zip(active_streams, responses): + if not isinstance(res, Exception): + raw_stream_map[name] = res + context_text = engine._format_stream_context(res) + + # WP-20 Stability Fix: Throttling + if provider == "ollama" and len(context_text) > max_chars: + context_text = context_text[:max_chars] + "\n[...]" + + formatted_context_map[name] = context_text + + answer_text = await engine._generate_final_answer( + intent, strategy, request.message, formatted_context_map + ) + sources_hits = _collect_all_hits(raw_stream_map) duration_ms = int((time.time() - start_time) * 1000) - + # Logging try: log_search( - query_id=query_id, - query_text=request.message, - results=sources_hits, - mode="interview" if intent == "INTERVIEW" else "chat_rag", - metadata={"intent": intent, "source": intent_source, "provider": preferred_provider} + query_id=query_id, query_text=request.message, results=sources_hits, + mode=f"wp25_{intent.lower()}", metadata={"strategy": intent, "source": intent_source} ) except: pass return ChatResponse( - query_id=query_id, - answer=answer_text, - sources=sources_hits, - latency_ms=duration_ms, - intent=intent, - intent_source=intent_source + query_id=query_id, answer=answer_text, sources=sources_hits, + latency_ms=duration_ms, intent=intent, intent_source=intent_source ) except Exception as e: - logger.error(f"Error in chat endpoint: {e}", exc_info=True) - # Wir geben eine benutzerfreundliche Meldung zurück, statt nur den Error-Stack - raise HTTPException(status_code=500, detail="Das System konnte die Anfrage nicht verarbeiten.") \ No newline at end of file + logger.error(f"❌ Chat Endpoint Failure: {e}", exc_info=True) + raise HTTPException(status_code=500, detail="Fehler bei der Verarbeitung.") \ No newline at end of file diff --git a/app/services/llm_service.py b/app/services/llm_service.py index 8027c3c..be74a8c 100644 --- a/app/services/llm_service.py +++ b/app/services/llm_service.py @@ -3,14 +3,14 @@ FILE: app/services/llm_service.py DESCRIPTION: Hybrid-Client für Ollama, Google GenAI (Gemini) und OpenRouter. Verwaltet provider-spezifische Prompts und Background-Last. WP-20: Optimiertes Fallback-Management zum Schutz von Cloud-Quoten. - WP-20 Fix: Bulletproof Prompt-Auflösung für format() Aufrufe. - WP-22/JSON: Optionales JSON-Schema + strict (für OpenRouter structured outputs). - FIX: Intelligente Rate-Limit Erkennung (429 Handling), v1-API Sync & Timeouts. -VERSION: 3.3.9 + WP-22/JSON: Optionales JSON-Schema + strict (für OpenRouter). + WP-25: Integration der DecisionEngine für Agentic Multi-Stream RAG. +VERSION: 3.4.2 (WP-25: Ingest-Stability Patch) STATUS: Active FIX: -- Importiert clean_llm_text von app.core.registry zur Vermeidung von Circular Imports. -- Wendet clean_llm_text auf Text-Antworten in generate_raw_response an. +- Ingest-Stability: Entfernung des <5-Zeichen Guards (ermöglicht YES/NO Validierungen). +- OpenRouter-Fix: Sicherung gegen leere 'choices' zur Vermeidung von JSON-Errors. +- Erhalt der vollständigen v3.3.9 Logik für Rate-Limits, Retries und Background-Tasks. """ import httpx import yaml @@ -29,7 +29,6 @@ from app.core.registry import clean_llm_text logger = logging.getLogger(__name__) - class LLMService: # GLOBALER SEMAPHOR für Hintergrund-Last Steuerung (WP-06) _background_semaphore = None @@ -37,6 +36,9 @@ class LLMService: def __init__(self): self.settings = get_settings() self.prompts = self._load_prompts() + + # WP-25: Lazy Initialization der DecisionEngine zur Vermeidung von Circular Imports + self._decision_engine = None # Initialisiere Semaphore einmalig auf Klassen-Ebene if LLMService._background_semaphore is None: @@ -71,6 +73,14 @@ class LLMService: ) logger.info("🛰️ LLMService: OpenRouter Integration active.") + @property + def decision_engine(self): + """Lazy Initialization der Decision Engine (WP-25).""" + if self._decision_engine is None: + from app.core.retrieval.decision_engine import DecisionEngine + self._decision_engine = DecisionEngine() + return self._decision_engine + def _load_prompts(self) -> dict: """Lädt die Prompt-Konfiguration aus der YAML-Datei.""" path = Path(self.settings.PROMPTS_PATH) @@ -87,17 +97,13 @@ class LLMService: def get_prompt(self, key: str, provider: str = None) -> str: """ Hole provider-spezifisches Template mit intelligenter Text-Kaskade. - HINWEIS: Dies ist nur ein Text-Lookup und verbraucht kein API-Kontingent. - Kaskade: Gewählter Provider -> Gemini (Cloud-Stil) -> Ollama (Basis-Stil). + Kaskade: Gewählter Provider -> Gemini -> Ollama. """ active_provider = provider or self.settings.MINDNET_LLM_PROVIDER data = self.prompts.get(key, "") if isinstance(data, dict): - # Wir versuchen erst den Provider, dann Gemini, dann Ollama val = data.get(active_provider, data.get("gemini", data.get("ollama", ""))) - - # Falls val durch YAML-Fehler immer noch ein Dict ist, extrahiere ersten String if isinstance(val, dict): logger.warning(f"⚠️ [LLMService] Nested dictionary detected for key '{key}'. Using first entry.") val = next(iter(val.values()), "") if val else "" @@ -120,8 +126,8 @@ class LLMService: strict_json_schema: bool = True ) -> str: """ - Haupteinstiegspunkt für LLM-Anfragen mit Priorisierung. - Wendet die Bereinigung auf Text-Antworten an. + Haupteinstiegspunkt für LLM-Anfragen. + WP-25 FIX: Schwellenwert entfernt, um kurze Ingest-Validierungen (YES/NO) zu unterstützen. """ target_provider = provider or self.settings.MINDNET_LLM_PROVIDER @@ -132,14 +138,18 @@ class LLMService: max_retries, base_delay, model_override, json_schema, json_schema_name, strict_json_schema ) - # WP-14 Fix: Bereinige Text-Antworten vor Rückgabe - return clean_llm_text(res) if not force_json else res + else: + res = await self._dispatch( + target_provider, prompt, system, force_json, + max_retries, base_delay, model_override, + json_schema, json_schema_name, strict_json_schema + ) + + # WP-25 FIX: Nur noch auf absolut leere Antwort prüfen (ermöglicht YES/NO Antworten). + if not res and target_provider != "ollama": + logger.warning(f"⚠️ [WP-25] Empty response from {target_provider}. Falling back to OLLAMA.") + res = await self._execute_ollama(prompt, system, force_json, max_retries, base_delay) - res = await self._dispatch( - target_provider, prompt, system, force_json, - max_retries, base_delay, model_override, - json_schema, json_schema_name, strict_json_schema - ) # WP-14 Fix: Bereinige Text-Antworten vor Rückgabe return clean_llm_text(res) if not force_json else res @@ -156,12 +166,8 @@ class LLMService: json_schema_name: str, strict_json_schema: bool ) -> str: - """ - Routet die Anfrage mit intelligenter Rate-Limit Erkennung. - Nutzt max_retries um die Rate-Limit Schleife zu begrenzen. - """ + """Routet die Anfrage mit intelligenter Rate-Limit Erkennung.""" rate_limit_attempts = 0 - # FIX: Wir nutzen max_retries als Limit für Rate-Limit Versuche, wenn explizit klein gewählt (z.B. Chat) max_rate_retries = min(max_retries, getattr(self.settings, "LLM_RATE_LIMIT_RETRIES", 3)) wait_time = getattr(self.settings, "LLM_RATE_LIMIT_WAIT", 60.0) @@ -181,33 +187,24 @@ class LLMService: if provider == "gemini" and self.google_client: return await self._execute_google(prompt, system, force_json, model_override) - # Default/Fallback zu Ollama return await self._execute_ollama(prompt, system, force_json, max_retries, base_delay) except Exception as e: err_str = str(e) - # Intelligente 429 Erkennung is_rate_limit = any(x in err_str for x in ["429", "RESOURCE_EXHAUSTED", "rate_limited", "Too Many Requests"]) if is_rate_limit and rate_limit_attempts < max_rate_retries: rate_limit_attempts += 1 - logger.warning( - f"⏳ [LLMService] Rate Limit detected from {provider}. " - f"Attempt {rate_limit_attempts}/{max_rate_retries}. Waiting {wait_time}s..." - ) + logger.warning(f"⏳ Rate Limit from {provider}. Attempt {rate_limit_attempts}. Waiting {wait_time}s...") await asyncio.sleep(wait_time) continue - # Wenn kein Rate-Limit oder Retries erschöpft -> Fallback zu Ollama (falls aktiviert) if self.settings.LLM_FALLBACK_ENABLED and provider != "ollama": - logger.warning( - f"🔄 Provider {provider} failed ({err_str}). Falling back to LOCAL OLLAMA." - ) + logger.warning(f"🔄 Provider {provider} failed ({err_str}). Falling back to OLLAMA.") return await self._execute_ollama(prompt, system, force_json, max_retries, base_delay) raise e async def _execute_google(self, prompt, system, force_json, model_override): - """Native Google SDK Integration (Gemini) mit v1 Fix.""" model = model_override or self.settings.GEMINI_MODEL clean_model = model.replace("models/", "") @@ -234,7 +231,7 @@ class LLMService: json_schema_name: str = "mindnet_json", strict_json_schema: bool = True ) -> str: - """OpenRouter API Integration (OpenAI-kompatibel).""" + """OpenRouter API Integration. WP-25 FIX: Sicherung gegen leere 'choices'.""" model = model_override or self.settings.OPENROUTER_MODEL messages = [] if system: @@ -247,9 +244,7 @@ class LLMService: kwargs["response_format"] = { "type": "json_schema", "json_schema": { - "name": json_schema_name, - "strict": strict_json_schema, - "schema": json_schema + "name": json_schema_name, "strict": strict_json_schema, "schema": json_schema } } else: @@ -260,23 +255,23 @@ class LLMService: messages=messages, **kwargs ) - return response.choices[0].message.content.strip() + + # WP-25 FIX: Sicherung gegen leere Antwort-Arrays + if not response.choices or len(response.choices) == 0: + logger.warning(f"🛰️ OpenRouter returned no choices for model {model}") + 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): - """Lokaler Ollama Call mit striktem Retry-Limit.""" payload = { "model": self.settings.LLM_MODEL, "prompt": prompt, "stream": False, - "options": { - "temperature": 0.1 if force_json else 0.7, - "num_ctx": 8192 # Begrenzung für Stabilität (WP-20) - } + "options": {"temperature": 0.1 if force_json else 0.7, "num_ctx": 8192} } - if force_json: - payload["format"] = "json" - if system: - payload["system"] = system + if force_json: payload["format"] = "json" + if system: payload["system"] = system attempt = 0 while True: @@ -286,32 +281,17 @@ class LLMService: return res.json().get("response", "").strip() except Exception as e: attempt += 1 - # WICHTIG: Wenn max_retries=0 (Chat), bricht dies nach dem 1. Versuch (attempt=1) sofort ab. if attempt > max_retries: - logger.error(f"❌ Ollama request failed after {attempt} attempt(s): {e}") + logger.error(f"❌ Ollama request failed: {e}") raise e - wait_time = base_delay * (2 ** (attempt - 1)) - logger.warning(f"⚠️ Ollama attempt {attempt} failed. Retrying in {wait_time}s...") await asyncio.sleep(wait_time) - async def generate_rag_response(self, query: str, context_str: str) -> str: - """Vollständiges RAG Chat-Interface.""" - provider = self.settings.MINDNET_LLM_PROVIDER - system_prompt = self.get_prompt("system_prompt", provider) - rag_template = self.get_prompt("rag_template", provider) - - final_prompt = rag_template.format(context_str=context_str, query=query) - - # RAG Aufrufe im Chat nutzen nun standardmäßig max_retries=2 (überschreibbar) - # Durch den Aufruf von generate_raw_response wird die Bereinigung automatisch angewendet. - return await self.generate_raw_response( - final_prompt, - system=system_prompt, - priority="realtime" - ) + async def generate_rag_response(self, query: str, context_str: Optional[str] = None) -> str: + """WP-25: Orchestrierung via DecisionEngine.""" + logger.info(f"🚀 [WP-25] Chat Query: {query[:50]}...") + return await self.decision_engine.ask(query) async def close(self): - """Schließt die HTTP-Verbindungen.""" if self.ollama_client: await self.ollama_client.aclose() \ No newline at end of file diff --git a/config/decision_engine.yaml b/config/decision_engine.yaml index ffd1d56..23fb221 100644 --- a/config/decision_engine.yaml +++ b/config/decision_engine.yaml @@ -1,145 +1,144 @@ # config/decision_engine.yaml -# Steuerung der Decision Engine (Intent Recognition & Graph Routing) -# VERSION: 2.6.1 (WP-20: Hybrid LLM & WP-22: Semantic Graph Routing) +# VERSION: 3.1.6 (WP-25: Multi-Stream Agentic RAG - Final Release) # STATUS: Active -# DoD: Keine Hardcoded Modelle, volle Integration der strategischen Boosts. +# DoD: +# - Strikte Nutzung der Typen aus types.yaml (v2.7.0). +# - Fix für Projekt-Klassifizierung via Keyword-Fast-Path (Auflösung Kollision). +# - 100% Erhalt aller Stream-Parameter und Edge-Boosts. -version: 2.6 +version: 3.1 settings: llm_fallback_enabled: true - - # Strategie für den Router selbst (Welches Modell erkennt den Intent?) - # "auto" nutzt den in MINDNET_LLM_PROVIDER gesetzten Standard (z.B. openrouter). + # "auto" nutzt den in MINDNET_LLM_PROVIDER gesetzten Standard. router_provider: "auto" + # Verweist auf das Template in prompts.yaml + router_prompt_key: "intent_router_v1" - # Few-Shot Prompting für den LLM-Router - llm_router_prompt: | - Du bist der zentrale Intent-Klassifikator für Mindnet, einen digitalen Zwilling. - Analysiere die Nachricht und wähle die passende Strategie. - Antworte NUR mit dem Namen der Strategie. - - STRATEGIEN: - - INTERVIEW: User will Wissen erfassen, Notizen anlegen oder Dinge festhalten. - - DECISION: Rat, Strategie, Abwägung von Werten, "Soll ich tun X?". - - EMPATHY: Gefühle, Reflexion der eigenen Verfassung, Frust, Freude. - - CODING: Code-Erstellung, Debugging, technische Dokumentation. - - FACT: Reine Wissensabfrage, Definitionen, Suchen von Informationen. - - BEISPIELE: - User: "Wie funktioniert die Qdrant-Vektor-DB?" -> FACT - User: "Soll ich mein Startup jetzt verkaufen?" -> DECISION - User: "Notiere mir kurz meine Gedanken zum Meeting." -> INTERVIEW - User: "Ich fühle mich heute sehr erschöpft." -> EMPATHY - User: "Schreibe eine FastAPI-Route für den Ingest." -> CODING - - NACHRICHT: "{query}" - - STRATEGIE: +# --- EBENE 1: STREAM-LIBRARY (Bausteine basierend auf types.yaml) --- +# Synchronisiert mit types.yaml v2.7.0 -strategies: - # 1. Fakten-Abfrage (Turbo-Modus via OpenRouter / Primary) - FACT: - description: "Reine Wissensabfrage." - preferred_provider: "openrouter" - trigger_keywords: [] - inject_types: [] - # WP-22: Definitionen & Hierarchien im Graphen bevorzugen +streams_library: + values_stream: + name: "Identität & Ethik" + query_template: "Welche meiner Werte und Prinzipien betreffen: {query}" + # Nur Typen aus types.yaml + filter_types: ["value", "principle", "belief", "trait", "boundary", "need", "motivation"] + top_k: 5 + edge_boosts: + guides: 3.0 + enforced_by: 2.5 + based_on: 2.0 + + facts_stream: + name: "Operative Realität" + query_template: "Status, Ressourcen und Fakten zu: {query}" + # Nur Typen aus types.yaml + filter_types: ["project", "decision", "task", "goal", "event", "state"] + top_k: 5 edge_boosts: part_of: 2.0 - composed_of: 2.0 - similar_to: 1.5 - caused_by: 0.5 - prompt_template: "rag_template" - prepend_instruction: null + depends_on: 1.5 + implemented_in: 1.5 - # 2. Entscheidungs-Frage (Power-Strategie via Gemini) - DECISION: - description: "Der User sucht Rat, Strategie oder Abwägung." - preferred_provider: "gemini" - trigger_keywords: - - "soll ich" - - "meinung" - - "besser" - - "empfehlung" - - "strategie" - - "entscheidung" - - "abwägung" - - "vergleich" - inject_types: ["value", "principle", "goal", "risk"] - # WP-22: Risiken und Konsequenzen im Graphen priorisieren + biography_stream: + name: "Persönliche Erfahrung" + query_template: "Welche Erlebnisse habe ich im Kontext von {query} gemacht?" + # Nur Typen aus types.yaml + filter_types: ["experience", "journal", "profile", "person"] + top_k: 3 + edge_boosts: + related_to: 1.5 + experienced_in: 2.0 + + risk_stream: + name: "Risiko-Radar" + query_template: "Gefahren, Hindernisse oder Risiken bei: {query}" + # Nur Typen aus types.yaml + filter_types: ["risk", "obstacle", "bias"] + top_k: 3 edge_boosts: blocks: 2.5 - solves: 2.0 - depends_on: 1.5 - risk_of: 2.5 impacts: 2.0 - prompt_template: "decision_template" - prepend_instruction: | - !!! ENTSCHEIDUNGS-MODUS (HYBRID AI) !!! - BITTE WÄGE FAKTEN GEGEN FOLGENDE WERTE, PRINZIPIEN UND ZIELE AB: + risk_of: 2.5 - # 3. Empathie / "Ich"-Modus (Lokal & Privat via Ollama) - EMPATHY: - description: "Reaktion auf emotionale Zustände." - preferred_provider: "openrouter" - trigger_keywords: - - "ich fühle" - - "traurig" - - "glücklich" - - "gestresst" - - "angst" - - "nervt" - - "überfordert" - - "müde" - inject_types: ["experience", "belief", "profile"] - edge_boosts: - based_on: 2.0 - related_to: 2.0 - experienced_in: 2.5 - blocks: 0.1 - prompt_template: "empathy_template" - prepend_instruction: null - - # 4. Coding / Technical (Gemini Power) - CODING: - description: "Technische Anfragen und Programmierung." - preferred_provider: "gemini" - trigger_keywords: - - "code" - - "python" - - "script" - - "funktion" - - "bug" - - "syntax" - - "json" - - "yaml" - - "bash" - inject_types: ["snippet", "reference", "source"] - # WP-22: Technische Abhängigkeiten priorisieren + tech_stream: + name: "Wissen & Technik" + query_template: "Inhaltliche Details und Definitionen zu: {query}" + # Nur Typen aus types.yaml + filter_types: ["concept", "source", "glossary", "idea", "insight", "skill", "habit"] + top_k: 5 edge_boosts: uses: 2.5 - depends_on: 2.0 implemented_in: 3.0 - prompt_template: "technical_template" - prepend_instruction: null - # 5. Interview / Datenerfassung (Lokal) +# --- EBENE 2: STRATEGIEN (Komposition & Routing) --- +# Orchestriert das Zusammenspiel der Streams basierend auf dem Intent. + +strategies: + # Spezialisierte Fact-Strategie für zeitliche Fragen + FACT_WHEN: + description: "Abfrage von exakten Zeitpunkten und Terminen." + preferred_provider: "openrouter" + # FAST PATH: Harte Keywords für zeitliche Fragen + trigger_keywords: ["wann", "datum", "uhrzeit", "zeitpunkt"] + use_streams: + - "facts_stream" + - "biography_stream" + - "tech_stream" + prompt_template: "fact_synthesis_v1" + + # Spezialisierte Fact-Strategie für inhaltliche Fragen & Listen + FACT_WHAT: + description: "Abfrage von Definitionen, Listen und Inhalten." + preferred_provider: "openrouter" + # FIX v3.1.6: "projekt" entfernt, um Kollision mit DECISION ("Soll ich Projekt...") zu vermeiden. + trigger_keywords: ["was ist", "welche sind", "liste", "übersicht", "zusammenfassung"] + use_streams: + - "facts_stream" + - "tech_stream" + - "biography_stream" + prompt_template: "fact_synthesis_v1" + + # Entscheidungs-Frage + DECISION: + description: "Der User sucht Rat, Strategie oder Abwägung." + preferred_provider: "gemini" + # FIX v3.1.6: Trigger erweitert, um "Soll ich... Projekt..." sicher zu fangen. + trigger_keywords: ["soll ich", "sollte ich", "entscheidung", "abwägen", "priorität", "empfehlung"] + use_streams: + - "values_stream" + - "facts_stream" + - "risk_stream" + prompt_template: "decision_synthesis_v1" + prepend_instruction: | + !!! ENTSCHEIDUNGS-MODUS (AGENTIC MULTI-STREAM) !!! + Analysiere die Fakten vor dem Hintergrund meiner Werte und evaluiere die Risiken. + Wäge ab, ob das Vorhaben mit meiner langfristigen Identität kompatibel ist. + + # Emotionale Reflexion + EMPATHY: + description: "Reaktion auf emotionale Zustände." + preferred_provider: "openrouter" + trigger_keywords: ["fühle", "traurig", "glücklich", "stress", "angst"] + use_streams: + - "biography_stream" + - "values_stream" + prompt_template: "empathy_template" + + # Technischer Support + CODING: + description: "Technische Anfragen und Programmierung." + preferred_provider: "gemini" + trigger_keywords: ["code", "python", "script", "bug", "syntax"] + use_streams: + - "tech_stream" + - "facts_stream" + prompt_template: "technical_template" + + # Eingabe-Modus (WP-07) INTERVIEW: - description: "Der User möchte Wissen erfassen." - preferred_provider: "openrouter" - trigger_keywords: - - "neue notiz" - - "etwas notieren" - - "festhalten" - - "erstellen" - - "dokumentieren" - - "anlegen" - - "interview" - - "erfassen" - - "idee speichern" - - "draft" - inject_types: [] - edge_boosts: {} - prompt_template: "interview_template" - prepend_instruction: null \ No newline at end of file + description: "Der User möchte Wissen erfassen (Eingabemodus)." + preferred_provider: "openrouter" + use_streams: [] + prompt_template: "interview_template" \ No newline at end of file diff --git a/config/prompts.yaml b/config/prompts.yaml index f554155..526ad8c 100644 --- a/config/prompts.yaml +++ b/config/prompts.yaml @@ -1,8 +1,9 @@ -# config/prompts.yaml — Final V2.6.0 (WP-15b Candidate-Validation) -# WP-20: Optimierte Cloud-Templates zur Unterdrückung von Modell-Geschwätz. -# FIX: Explizite Verbote für Einleitungstexte zur Vermeidung von JSON-Parsing-Fehlern. -# WP-15b: Integration der binären edge_validation für den Two-Pass Workflow. -# 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. @@ -17,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: @@ -58,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: @@ -66,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: @@ -87,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: @@ -118,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: | @@ -160,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: | @@ -200,7 +230,7 @@ edge_allocation_template: OUTPUT: # --------------------------------------------------------- -# 7. SMART EDGE ALLOCATION: Extraktion (Intent: INGEST) +# 7. SMART EDGE ALLOCATION: Extraktion (Ingest) # --------------------------------------------------------- edge_extraction: ollama: | @@ -240,7 +270,7 @@ edge_extraction: OUTPUT: # --------------------------------------------------------- -# 8. WP-15b: EDGE VALIDATION (Intent: VALIDATE) +# 8. WP-15b: EDGE VALIDATION (Ingest/Validate) # --------------------------------------------------------- edge_validation: gemini: | @@ -270,4 +300,38 @@ edge_validation: QUELLE: {chunk_text} ZIEL: {target_title} ({target_summary}) BEZIEHUNG: {edge_kind} - Ist diese Verbindung valide? Antworte NUR mit YES oder NO. \ No newline at end of file + 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/docs/00_General/00_documentation_map.md b/docs/00_General/00_documentation_map.md index fef368d..97d9265 100644 --- a/docs/00_General/00_documentation_map.md +++ b/docs/00_General/00_documentation_map.md @@ -151,8 +151,8 @@ Damit dieses System wartbar bleibt (auch für KI-Agenten wie NotebookLM), gelten ## 6. Dokumentations-Status -**Aktuelle Version:** 2.9.1 -**Letzte Aktualisierung:** 2025-01-XX +**Aktuelle Version:** 2.9.3 +**Letzte Aktualisierung:** 2025-12-31 **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 3ff270b..ae2a13e 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: 2.9.1 -context: "Zentrales Glossar für Mindnet v2.8. Enthält Definitionen zu Hybrid-Cloud Resilienz, WP-14 Modularisierung, WP-15b Two-Pass Ingestion und Mistral-safe Parsing." +version: 2.9.3 +context: "Zentrales Glossar für Mindnet v2.9.3. 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 und Mistral-safe Parsing." --- # Mindnet Glossar @@ -23,7 +23,11 @@ context: "Zentrales Glossar für Mindnet v2.8. Enthält Definitionen zu Hybrid-C * **Edge Registry:** Der zentrale Dienst (SSOT), der Kanten-Typen validiert und Aliase in kanonische Typen auflöst. Nutzt `01_edge_vocabulary.md` als Basis. * **LLM Service:** Der Hybrid-Client (v3.3.6), der Anfragen zwischen OpenRouter, Google Gemini und lokalem Ollama routet. Verwaltet Cloud-Timeouts und Quoten-Management. Nutzt zur Text-Bereinigung nun die neutrale `registry.py`, um Circular Imports zu vermeiden. * **Retriever:** Besteht in v2.7+ aus der Orchestrierung (`retriever.py`) und der mathematischen Scoring-Engine (`retriever_scoring.py`). Seit WP-14 im Paket `app.core.retrieval` gekapselt. -* **Decision Engine:** Teil des Routers, der Intents erkennt und entsprechende **Boost-Faktoren** für das Retrieval injiziert. +* **Decision Engine (WP-25):** Der zentrale **Agentic Orchestrator**, der Intents erkennt, parallele Wissens-Streams orchestriert und die Ergebnisse synthetisiert. Implementiert Multi-Stream Retrieval und Intent-basiertes Routing. +* **Agentic Multi-Stream RAG (WP-25):** Architektur-Paradigma, bei dem Nutzeranfragen in parallele, spezialisierte Wissens-Streams aufgeteilt werden (Values, Facts, Biography, Risk, Tech), die gleichzeitig abgefragt und zu einer kontextreichen Antwort synthetisiert werden. +* **Stream-Tracing (WP-25):** Kennzeichnung jedes Treffers mit seinem Ursprungs-Stream (`stream_origin`), um Feedback-Optimierung pro Wissensbereich zu ermöglichen. +* **Intent-basiertes Routing (WP-25):** Hybrid-Modus zur Intent-Erkennung mit Keyword Fast-Path (sofortige Erkennung von Triggern) und LLM Slow-Path (semantische Analyse für unklare Anfragen). +* **Wissens-Synthese (WP-25):** Template-basierte Zusammenführung der Ergebnisse aus parallelen Streams mit expliziten Stream-Variablen (z.B. `{values_stream}`, `{risk_stream}`), um dem LLM eine differenzierte Abwägung zu ermöglichen. * **Traffic Control:** Verwaltet Prioritäten und drosselt Hintergrund-Tasks (z.B. Smart Edges) mittels Semaphoren und Timeouts (45s) zur Vermeidung von System-Hangs. * **Unknown Edges Log:** Die Datei `unknown_edges.jsonl`, in der das System Kanten-Typen protokolliert, die nicht im Dictionary gefunden wurden. * **Database Package (WP-14):** Zentralisiertes Infrastruktur-Paket (`app.core.database`), das den Qdrant-Client (`qdrant.py`) und das Point-Mapping (`qdrant_points.py`) verwaltet. diff --git a/docs/01_User_Manual/01_chat_usage_guide.md b/docs/01_User_Manual/01_chat_usage_guide.md index c560530..eeb1f5b 100644 --- a/docs/01_User_Manual/01_chat_usage_guide.md +++ b/docs/01_User_Manual/01_chat_usage_guide.md @@ -3,8 +3,8 @@ doc_type: user_manual audience: user, mindmaster scope: chat, ui, feedback, graph status: active -version: 2.6 -context: "Anleitung zur Nutzung der Web-Oberfläche, der Chat-Personas und des Graph Explorers." +version: 2.9.3 +context: "Anleitung zur Nutzung der Web-Oberfläche, der Chat-Personas, Multi-Stream RAG und des Graph Explorers." --- # Chat & Graph Usage Guide @@ -60,21 +60,49 @@ Ein Editor mit **"File System First"** Garantie. --- -## 3. Den Chat steuern (Intents) +## 3. Den Chat steuern (Intents & Multi-Stream RAG) -Du steuerst die Persönlichkeit von Mindnet durch deine Wortwahl. Der **Hybrid Router v5** unterscheidet intelligent: +Du steuerst die Persönlichkeit von Mindnet durch deine Wortwahl. Seit WP-25 nutzt Mindnet **Agentic Multi-Stream RAG**, das deine Anfrage in parallele Wissens-Streams aufteilt: -### 3.1 Frage-Modus (Wissen abrufen) -Ausgelöst durch `?` oder W-Wörter. +### 3.1 Intent-Erkennung (Hybrid-Router) + +Der Router erkennt deine Absicht auf zwei Wegen: + +**Schnelle Erkennung (Keyword Fast-Path):** +* **"Soll ich..."** → Sofortige Erkennung als `DECISION` (Berater) +* **"Wann..."** → Sofortige Erkennung als `FACT_WHEN` (Zeitpunkte) +* **"Was ist..."** → Sofortige Erkennung als `FACT_WHAT` (Wissen) +* **"Ich fühle..."** → Sofortige Erkennung als `EMPATHY` (Spiegel) + +**Intelligente Analyse (LLM Slow-Path):** +* Bei unklaren Anfragen analysiert die KI semantisch deine Absicht + +### 3.2 Multi-Stream Retrieval (WP-25) + +Anstelle einer einzelnen Suche führt Mindnet nun **parallele Abfragen** in spezialisierten Wissens-Streams aus: + +**Die Streams:** +* **Values Stream:** Deine Identität, Ethik und Prinzipien (`value`, `principle`, `belief`) +* **Facts Stream:** Operative Daten zu Projekten, Tasks und Status (`project`, `decision`, `task`) +* **Biography Stream:** Persönliche Erfahrungen und Journal-Einträge (`experience`, `journal`) +* **Risk Stream:** Hindernisse und potenzielle Gefahren (`risk`, `obstacle`) +* **Tech Stream:** Technisches Wissen, Code und Dokumentation (`concept`, `source`, `glossary`) + +**Vorteil:** Jeder Stream fokussiert auf spezifische Wissensbereiche, was zu präziseren und kontextreicheren Antworten führt. + +### 3.3 Frage-Modi (Strategien) * **Entscheidung ("Soll ich?"):** Der **Berater**. - * Mindnet lädt deine Werte (`type: value`) und Ziele (`type: goal`). + * Nutzt: Values Stream, Facts Stream, Risk Stream + * Wägt Fakten gegen deine Werte ab und evaluiert Risiken * *Beispiel:* "Soll ich Tool X nutzen?" -> "Nein, Tool X speichert Daten in den USA. Das verstößt gegen dein Prinzip 'Privacy First'." * **Empathie ("Ich fühle..."):** Der **Spiegel**. - * Mindnet lädt deine Erfahrungen (`type: experience`). + * Nutzt: Biography Stream, Values Stream + * Greift auf deine Erfahrungen und Werte zurück * *Beispiel:* "Ich bin frustriert." -> "Das erinnert mich an Projekt Y, da ging es uns ähnlich..." -* **Fakten ("Was ist?"):** Der **Bibliothekar**. - * Liefert präzise Definitionen. +* **Fakten ("Was ist?", "Wann..."):** Der **Bibliothekar**. + * Nutzt: Facts Stream, Tech Stream, Biography Stream + * Liefert präzise Definitionen und zeitliche Informationen ### 3.2 Befehls-Modus (Interview) Ausgelöst durch Aussagen wie "Neues Projekt", "Ich will festhalten". @@ -84,13 +112,17 @@ Ausgelöst durch Aussagen wie "Neues Projekt", "Ich will festhalten". --- -## 4. Ergebnisse interpretieren (Explanation Layer) +## 4. Ergebnisse interpretieren (Explanation Layer & Stream-Tracing) Mindnet liefert eine **Begründung** ("Reasons"), warum es etwas gefunden hat. Öffne eine Quellen-Karte, um zu sehen: * *"Hohe textuelle Übereinstimmung."* (Semantik) * *"Bevorzugt aufgrund des Typs 'decision'."* (Wichtigkeit) * *"Verweist auf 'Projekt X' via 'depends_on'."* (Graph-Kontext) +* *"Quelle: Values Stream"* (Stream-Tracing - WP-25) + +**Stream-Tracing (WP-25):** +Jede Quelle wird mit ihrem Ursprungs-Stream markiert (z.B. "Values Stream", "Facts Stream"). Dies hilft dir zu verstehen, aus welchem Wissensbereich die Information stammt. --- diff --git a/docs/02_concepts/02_concept_ai_personality.md b/docs/02_concepts/02_concept_ai_personality.md index 403e2ef..633f443 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 +scope: ai, router, personas, resilience, agentic_rag status: active -version: 2.8.1 -context: "Fachkonzept der hybriden KI-Persönlichkeit, der Provider-Kaskade und der kognitiven Resilienz (Deep Fallback)." +version: 2.9.3 +context: "Fachkonzept der hybriden KI-Persönlichkeit, Agentic Multi-Stream RAG, Provider-Kaskade und kognitiven Resilienz (Deep Fallback)." --- # Konzept: KI-Persönlichkeit & Router @@ -13,13 +13,45 @@ context: "Fachkonzept der hybriden KI-Persönlichkeit, der Provider-Kaskade und Mindnet soll nicht wie eine Suchmaschine wirken, sondern wie ein **Digitaler Zwilling**. Dazu muss das System erkennen, **was** der Nutzer will, und seine „Persönlichkeit“ sowie seine technische Infrastruktur dynamisch anpassen. -## 1. Der Hybrid Router (Das Gehirn) +## 1. Der Hybrid Router & Agentic Multi-Stream RAG (Das Gehirn) -Jede Eingabe durchläuft den **Hybrid Router**. Er entscheidet über die fachliche Strategie und die technische Ausführung. +Jede Eingabe durchläuft den **Hybrid Router**. 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. -### Modus A: RAG (Retrieval Augmented Generation) -* **Intent:** Der Nutzer hat eine Frage oder ein Problem (`FACT`, `DECISION`, `EMPATHY`). -* **Aktion:** Das System sucht im Gedächtnis und generiert eine Antwort. +### Intent-basiertes Routing (WP-25) + +Der Router nutzt einen **Hybrid-Modus** mit Keyword Fast-Path und LLM Slow-Path: + +**Keyword Fast-Path:** +* Sofortige Erkennung von Triggern wie "Soll ich", "Wann", "Was ist" +* Reduziert Latenz durch schnelle Keyword-Erkennung ohne LLM-Call + +**LLM Slow-Path:** +* Komplexe semantische Analyse für unklare Anfragen +* Nutzt `intent_router_v1` Prompt zur Klassifizierung + +**Strategien:** +* **FACT_WHAT/FACT_WHEN:** Wissensabfrage (Wissen/Listen, Zeitpunkte) +* **DECISION:** Beratung (Rat, Strategie, Abwägung) +* **EMPATHY:** Reflexion (Emotionale Resonanz) +* **CODING:** Technik (Programmierung, Syntax) +* **INTERVIEW:** Datenerfassung (Wissen speichern) + +### Modus A: Agentic Multi-Stream RAG (WP-25) + +Anstelle einer einzelnen Suche führt das System **parallele Abfragen** in spezialisierten Wissens-Streams aus: + +**Stream-Library:** +* **Values Stream:** Identität, Ethik und Prinzipien (`value`, `principle`, `belief`, `trait`, `boundary`, `need`, `motivation`) +* **Facts Stream:** Operative Daten (`project`, `decision`, `task`, `goal`, `event`, `state`) +* **Biography Stream:** Persönliche Erfahrungen (`experience`, `journal`, `profile`, `person`) +* **Risk Stream:** Hindernisse und Gefahren (`risk`, `obstacle`, `bias`) +* **Tech Stream:** Technisches Wissen (`concept`, `source`, `glossary`, `idea`, `insight`, `skill`, `habit`) + +**Wissens-Synthese:** +Die Zusammenführung erfolgt über spezialisierte Templates mit expliziten Stream-Variablen (z.B. `{values_stream}`, `{risk_stream}`). Dies ermöglicht dem LLM eine differenzierte Abwägung zwischen Fakten und persönlichen Werten. + +**Stream-Tracing:** +Jeder Treffer wird mit `stream_origin` markiert, um Feedback-Optimierung pro Wissensbereich zu ermöglichen. ### Modus B: Interview (Knowledge Capture) * **Intent:** Der Nutzer will Wissen speichern (`INTERVIEW`). @@ -45,18 +77,22 @@ Ein intelligenter Zwilling muss jederzeit verfügbar sein. Mindnet v2.8.1 nutzt Mindnet wechselt den Hut, je nach Situation. ### 3.1 Der Berater (Strategy: DECISION) -* **Auslöser:** Fragen wie „Soll ich...?“, „Was ist besser?“. -* **Strategic Retrieval:** Lädt aktiv Notizen der Typen `value` (Werte), `goal` (Ziele) und `risk` (Risiken), auch wenn sie im Text nicht direkt vorkommen. -* **Reasoning:** *„Wäge die Fakten gegen meine Werte ab. Sei strikt bei Risiken.“* +* **Auslöser:** Fragen wie „Soll ich...?", „Was ist besser?", „Empfehlung...". +* **Multi-Stream Retrieval (WP-25):** Führt parallele Abfragen in Values Stream, Facts Stream und Risk Stream aus. +* **Wissens-Synthese:** Wägt Fakten gegen Werte ab, evaluiert Risiken und prüft Kompatibilität mit langfristiger Identität. +* **Reasoning:** *„Wäge die Fakten gegen meine Werte ab. Sei strikt bei Risiken."* ### 3.2 Der Spiegel (Strategy: EMPATHY) -* **Auslöser:** Emotionale Aussagen („Ich bin frustriert“). -* **Strategic Retrieval:** Lädt `experience` (Erfahrungen) und `belief` (Glaubenssätze). -* **Reasoning:** *„Nutze meine eigenen Erfahrungen, um die Situation einzuordnen.“* +* **Auslöser:** Emotionale Aussagen („Ich bin frustriert", „Ich fühle...", „Stress..."). +* **Multi-Stream Retrieval (WP-25):** Führt parallele Abfragen in Biography Stream und Values Stream aus. +* **Wissens-Synthese:** Greift auf persönliche Erfahrungen und Werte zurück, um emotionale Resonanz zu schaffen. +* **Reasoning:** *„Nutze meine eigenen Erfahrungen, um die Situation einzuordnen."* -### 3.3 Der Bibliothekar (Strategy: FACT) -* **Auslöser:** Sachfragen („Was ist Qdrant?“). -* **Behavior:** Präzise, neutral, kurz. +### 3.3 Der Bibliothekar (Strategy: FACT_WHAT / FACT_WHEN) +* **Auslöser:** Sachfragen („Was ist...?", „Welche sind...?", „Wann...?", „Datum..."). +* **Multi-Stream Retrieval (WP-25):** Führt parallele Abfragen in Facts Stream, Tech Stream und Biography Stream aus. +* **Wissens-Synthese:** Kombiniert harte Fakten mit persönlichen Erfahrungen, falls vorhanden. +* **Behavior:** Präzise, neutral, strukturiert. --- diff --git a/docs/03_Technical_References/03_tech_api_reference.md b/docs/03_Technical_References/03_tech_api_reference.md index 6e1d856..05c60a1 100644 --- a/docs/03_Technical_References/03_tech_api_reference.md +++ b/docs/03_Technical_References/03_tech_api_reference.md @@ -1,10 +1,10 @@ --- doc_type: technical_reference audience: developer, integrator -scope: api, endpoints, integration +scope: api, endpoints, integration, agentic_rag status: active -version: 2.9.1 -context: "Vollständige API-Referenz für alle Mindnet-Endpunkte. Basis für Integration und Entwicklung." +version: 2.9.3 +context: "Vollständige API-Referenz für alle Mindnet-Endpunkte inklusive WP-25 Agentic Multi-Stream RAG. Basis für Integration und Entwicklung." --- # API Reference @@ -96,21 +96,37 @@ Hauptendpunkt für RAG-Chat und Interview-Modus. Unterstützt Streaming. **Response (Non-Streaming):** ```json { - "intent": "FACT", - "response": "Mindnet ist ein persönliches KI-Gedächtnis...", - "sources": [...], - "query_id": "uuid" + "query_id": "uuid", + "answer": "Mindnet ist ein persönliches KI-Gedächtnis...", + "sources": [ + { + "node_id": "uuid", + "note_id": "uuid", + "semantic_score": 0.85, + "total_score": 0.92, + "stream_origin": "values_stream", // WP-25: Ursprungs-Stream + "explanation": {...} + } + ], + "latency_ms": 450, + "intent": "DECISION", // WP-25: Gewählte Strategie + "intent_source": "Keyword (FastPath)" // WP-25: Quelle der Intent-Erkennung } ``` **Response (Streaming):** Server-Sent Events (SSE) mit Chunks der Antwort. -**Intent-Typen:** -- `FACT`: Wissensabfrage -- `DECISION`: Entscheidungsfrage -- `EMPATHY`: Emotionale Anfrage -- `INTERVIEW`: Wissen erfassen +**Intent-Typen (WP-25):** +- `FACT_WHAT`: Wissensabfrage (Wissen/Listen) +- `FACT_WHEN`: Zeitpunkte (Termine, Daten) +- `DECISION`: Entscheidungsfrage (Beratung, Strategie) +- `EMPATHY`: Emotionale Anfrage (Reflexion) +- `CODING`: Technische Anfrage (Programmierung) +- `INTERVIEW`: Wissen erfassen (Datenerfassung) + +**Stream-Tracing (WP-25):** +Jeder Treffer in `sources` enthält `stream_origin`, um die Zuordnung zum Quell-Stream zu ermöglichen (z.B. "values_stream", "facts_stream", "risk_stream"). --- diff --git a/docs/03_Technical_References/03_tech_chat_backend.md b/docs/03_Technical_References/03_tech_chat_backend.md index bb20742..7c865af 100644 --- a/docs/03_Technical_References/03_tech_chat_backend.md +++ b/docs/03_Technical_References/03_tech_chat_backend.md @@ -1,30 +1,32 @@ --- doc_type: technical_reference audience: developer, architect -scope: backend, chat, llm_service, traffic_control, resilience +scope: backend, chat, llm_service, traffic_control, resilience, agentic_rag status: active -version: 2.9.1 -context: "Technische Implementierung des FastAPI-Routers, des hybriden LLMService (v3.3.6) und der WP-20 Resilienz-Logik." +version: 2.9.3 +context: "Technische Implementierung des FastAPI-Routers, des hybriden LLMService (v3.4.2), WP-25 Agentic Multi-Stream RAG und WP-20 Resilienz-Logik." --- -# Chat Backend & Traffic Control +# Chat Backend & Agentic Multi-Stream RAG -## 1. Hybrid Router (Decision Engine) +## 1. Hybrid Router & Intent-basiertes Routing (WP-25) -Der zentrale Einstiegspunkt für jede Chatanfrage ist der **Hybrid Router** (`app/routers/chat.py`). Er entscheidet dynamisch über die Strategie und nutzt den `LLMService` zur provider-agnostischen Generierung. +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 (Logik) +### 1.1 Intent-Erkennung (Hybrid-Modus) -Der Router prüft den Input in drei Stufen (Wasserfall-Prinzip): +Der Router nutzt einen **Hybrid-Modus** mit Keyword-Fast-Path und LLM-Slow-Path: -1. **Question Detection (Regelbasiert):** - * Prüfung auf Vorhandensein von `?` oder W-Wörtern (Wer, Wie, Was, Soll ich). - * Wenn positiv: **RAG Modus** (Interview wird blockiert). -2. **Keyword Scan (Fast Path):** - * Lädt `types.yaml` (Objekte) und `decision_engine.yaml` (Handlungen). - * Wenn Match (z.B. "Projekt" + "neu"): **INTERVIEW Modus**. -3. **LLM Fallback (Slow Path):** - * Wenn unklar: Anfrage an LLM zur Klassifizierung mittels `router_prompt`. +1. **Keyword Fast-Path (Sofortige Erkennung):** + * Prüft `trigger_keywords` aus `decision_engine.yaml` (z.B. "Soll ich", "Wann", "Was ist"). + * Wenn Match: Sofortige Intent-Zuordnung ohne LLM-Call. + * **Strategien:** FACT_WHAT, FACT_WHEN, DECISION, EMPATHY, CODING, INTERVIEW. +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):** + * Wenn unklar: Anfrage an `DecisionEngine._determine_strategy()` zur LLM-basierten Klassifizierung. + * Nutzt `intent_router_v1` Prompt aus `prompts.yaml`. ### 1.2 Prompt-Auflösung (Bulletproof Resolution) @@ -34,24 +36,70 @@ Um Kompatibilitätsprobleme mit verschachtelten YAML-Prompts zu vermeiden, nutzt * **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. -### 1.3 RAG Flow (Technisch) +### 1.2 Multi-Stream Retrieval (WP-25) -Wenn der Intent `FACT` oder `DECISION` ist, wird folgender Flow ausgeführt: +Anstelle einer einzelnen Suche führt die `DecisionEngine` nun **parallele Abfragen** in spezialisierten Streams aus: -1. **Pre-Processing:** Query Rewriting (optional). -2. **Context Enrichment:** - * Abruf via `retriever.py` (Hybrid Search). - * Integration von **Edge Boosts** aus der `decision_engine.yaml` zur Beeinflussung der Graph-Gewichtung. - * Injection von Metadaten (`[TYPE]`, `[SCORE]`) in den Prompt. -3. **Prompt Construction:** Assembly aus System-Prompt (Persona) + Context + Query. -4. **Streaming:** LLM-Antwort wird via **SSE (Server-Sent Events)** an den Client gestreamt. -5. **Post-Processing:** Anhängen des `Explanation` Layers (JSON-Breakdown) an das Ende des Streams. +**Stream-Library (definiert in `decision_engine.yaml`):** +* **Values Stream:** Extrahiert Identität, Ethik und Prinzipien (`value`, `principle`, `belief`, etc.). +* **Facts Stream:** Liefert operative Daten zu Projekten, Tasks und Status (`project`, `decision`, `task`, etc.). +* **Biography Stream:** Greift auf persönliche Erfahrungen und Journal-Einträge zu (`experience`, `journal`, `profile`). +* **Risk Stream:** Identifiziert Hindernisse und potenzielle Gefahren (`risk`, `obstacle`, `bias`). +* **Tech Stream:** Bündelt technisches Wissen, Code und Dokumentation (`concept`, `source`, `glossary`, etc.). + +**Stream-Konfiguration:** +* Jeder Stream nutzt individuelle **Edge-Boosts** (z.B. `guides: 3.0` für Values Stream). +* **Filter-Types** sind strikt mit `types.yaml` (v2.7.0) synchronisiert. +* **Query-Templates** transformieren die ursprüngliche Anfrage für spezialisierte Suche. + +**Parallele Ausführung:** +* `asyncio.gather()` führt alle aktiven Streams gleichzeitig aus. +* **Stream-Tracing:** Jeder Treffer wird mit `stream_origin` markiert für Feedback-Optimierung. +* **Fehlerbehandlung:** Einzelne Stream-Fehler blockieren nicht die gesamte Anfrage. + +### 1.3 Wissens-Synthese (WP-25) + +Die Zusammenführung der Daten erfolgt über spezialisierte Templates in der `prompts.yaml`: + +**Template-Struktur:** +* Explizite Variablen für jeden Stream (z.B. `{values_stream}`, `{risk_stream}`). +* **Pre-Initialization:** Alle möglichen Stream-Variablen werden vorab initialisiert (verhindert KeyErrors). +* **Provider-spezifische Templates:** Separate Versionen für Ollama, Gemini und OpenRouter. + +**Synthese-Strategien:** +* **FACT_WHAT/FACT_WHEN:** Kombiniert Fakten, Biographie und Technik. +* **DECISION:** Wägt Fakten gegen Werte ab, evaluiert Risiken. +* **EMPATHY:** Fokus auf Biographie und Werte. +* **CODING:** Technik und Fakten. + +### 1.4 RAG Flow (Technisch) + +Wenn der Intent nicht `INTERVIEW` ist, wird folgender Flow ausgeführt: + +1. **Intent Detection:** Hybrid Router klassifiziert die Anfrage. +2. **Multi-Stream Retrieval:** + * Parallele Abfragen in spezialisierten Streams via `DecisionEngine._execute_parallel_streams()`. + * Jeder Stream nutzt individuelle Filter, Edge-Boosts und Query-Templates. +3. **Context Formatting:** + * Stream-Ergebnisse werden in formatierte Kontext-Strings umgewandelt. + * **Ollama Context-Throttling:** Kontext wird auf `MAX_OLLAMA_CHARS` begrenzt (Standard: 10.000). +4. **Synthese:** + * `DecisionEngine._generate_final_answer()` kombiniert alle Streams. + * Template-basierte Prompt-Konstruktion mit Stream-Variablen. +5. **Response:** + * LLM-Antwort wird generiert (provider-spezifisch). + * **Sources:** Alle Treffer aus allen Streams werden dedupliziert und zurückgegeben. --- -## 2. LLM Service & Traffic Control (WP-20) +## 2. LLM Service & Traffic Control (WP-20 / WP-25) -Der `LLMService` (`app/services/llm_service.py`) fungiert als zentraler Hybrid-Client für OpenRouter, Google Gemini und Ollama. Er schützt das System vor Überlastung und verwaltet Quoten. +Der `LLMService` (`app/services/llm_service.py`, v3.4.2) fungiert als zentraler Hybrid-Client für OpenRouter, Google Gemini und Ollama. Er schützt das System vor Überlastung und verwaltet Quoten. + +**WP-25 Integration:** +* **Lazy Initialization:** `DecisionEngine` wird erst bei Bedarf initialisiert (verhindert Circular Imports). +* **Ingest-Stability Patch:** Entfernung des <5-Zeichen Guards ermöglicht YES/NO Validierungen beim Vault-Import. +* **Empty Response Guard:** Sicherung gegen leere `choices` Arrays bei OpenRouter (verhindert JSON-Errors). Mit Version 2.8.1 wurde die Architektur der Antwort-Generierung grundlegend gehärtet: @@ -107,10 +155,27 @@ In v2.8 wurde ein intelligentes Fehler-Handling für Cloud-Provider implementier --- -## 4. Feedback Traceability +## 4. Feedback Traceability & Stream-Tracing (WP-25) -Unterstützt das geplante Self-Tuning (WP08). +Unterstützt das geplante Self-Tuning (WP08) und ermöglicht Stream-spezifische Optimierung. -1. **Query ID:** Generiert bei jedem `/query` Call eine `UUIDv4`. -2. **Logging:** Speichert einen Snapshot in `data/logs/query_snapshot.jsonl` (Input + Retrieved Context). -3. **Feedback:** Der `/feedback` Endpoint verknüpft das User-Rating (1-5) mit der `query_id`. \ No newline at end of file +1. **Query ID:** Generiert bei jedem `/chat` Call eine `UUIDv4`. +2. **Stream-Tracing:** Jeder Treffer enthält `stream_origin` für Zuordnung zum Quell-Stream. +3. **Logging:** Speichert einen Snapshot in `data/logs/query_snapshot.jsonl` (Input + Retrieved Context + Intent). +4. **Feedback:** Der `/feedback` Endpoint verknüpft das User-Rating (1-5) mit der `query_id` und `stream_origin`. + +## 5. Lifespan Management (WP-25) + +Die FastAPI-Anwendung (`app/main.py`, v1.0.0) implementiert **Lifespan-Management** für sauberen Startup und Shutdown: + +**Startup:** +* Integritäts-Check der WP-25 Konfiguration (`decision_engine.yaml`, `prompts.yaml`). +* Validierung kritischer Dateien vor dem Start. + +**Shutdown:** +* Ressourcen-Cleanup (LLMService-Connections schließen). +* Graceful Shutdown für asynchrone Prozesse. + +**Globale Fehlerbehandlung:** +* Fängt unerwartete Fehler in der Multi-Stream Kette ab. +* Strukturierte JSON-Responses bei Engine-Fehlern. \ No newline at end of file diff --git a/docs/03_Technical_References/03_tech_configuration.md b/docs/03_Technical_References/03_tech_configuration.md index 8553f00..1951594 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 +scope: configuration, env, registry, scoring, resilience, modularization, agentic_rag status: active -version: 2.9.1 -context: "Umfassende Referenztabellen für Umgebungsvariablen (inkl. Hybrid-Cloud & WP-76), YAML-Konfigurationen und die Edge Registry Struktur unter Berücksichtigung von WP-14." +version: 2.9.3 +context: "Umfassende Referenztabellen für Umgebungsvariablen (inkl. Hybrid-Cloud & WP-76), YAML-Konfigurationen, Edge Registry Struktur und WP-25 Multi-Stream RAG unter Berücksichtigung von WP-14." --- # Konfigurations-Referenz @@ -224,15 +224,16 @@ Die Datei muss eine Markdown-Tabelle enthalten, die vom Regex-Parser gelesen wir --- -## 5. Decision Engine (`decision_engine.yaml`) +## 5. Decision Engine (`decision_engine.yaml` v3.1.6) -Die Decision Engine fungiert als zentraler Orchestrator für die Intent-Erkennung und das dynamische Retrieval-Routing. Sie bestimmt, wie das System auf eine Nutzeranfrage reagiert, welche Informationstypen bevorzugt werden und wie der Wissensgraph für die spezifische Situation verformt wird. +Die Decision Engine fungiert als zentraler **Agentic Orchestrator** für die Intent-Erkennung und das dynamische Multi-Stream Retrieval-Routing (WP-25). Sie bestimmt, wie das System auf eine Nutzeranfrage reagiert, welche Wissens-Streams aktiviert werden und wie die Ergebnisse synthetisiert werden. -### 5.1 Intent Recognition: Dual-Path Routing -Das System nutzt ein zweistufiges Verfahren, um die Absicht des Nutzers zu identifizieren: +### 5.1 Intent Recognition: Hybrid-Routing (WP-25) +Das System nutzt einen **Hybrid-Modus** mit Keyword Fast-Path und LLM Slow-Path: -1. **Fast Path (Keyword Trigger):** Das System scannt die Anfrage nach definierten `trigger_keywords`. Wird ein Treffer gefunden, wird die entsprechende Strategie sofort ohne LLM-Einsatz gewählt. -2. **Slow Path (LLM Router):** Wenn kein Keyword matcht und `llm_fallback_enabled: true` gesetzt ist, analysiert ein LLM die Nachricht mittels Few-Shot Prompting. +1. **Fast Path (Keyword Trigger):** Das System scannt die Anfrage nach definierten `trigger_keywords`. Wird ein Treffer gefunden, wird die entsprechende Strategie sofort ohne LLM-Einsatz gewählt (z.B. "Soll ich" → `DECISION`). +2. **Type Keywords:** Prüft `detection_keywords` aus `types.yaml` für Interview-Modus (z.B. "Projekt" + "neu" → `INTERVIEW`). +3. **Slow Path (LLM Router):** Wenn kein Keyword matcht und `llm_fallback_enabled: true` gesetzt ist, analysiert ein LLM die Nachricht mittels Few-Shot Prompting (`intent_router_v1`). #### LLM Router Konfiguration Der Router nutzt den `llm_router_prompt`, um Anfragen in eine der fünf Kern-Strategien (`FACT`, `DECISION`, `EMPATHY`, `CODING`, `INTERVIEW`) zu klassifizieren. @@ -244,36 +245,90 @@ Der Router nutzt den `llm_router_prompt`, um Anfragen in eine der fünf Kern-Str --- -### 5.2 Strategie-Mechaniken (Graph Shaping) -Jede Strategie definiert drei Hebel, um das Ergebnis des Retrievers zu beeinflussen: +### 5.2 Multi-Stream Konfiguration (WP-25) -* **`inject_types`:** Erzwingt die Einbindung bestimmter Notiz-Typen (z. B. `value` bei Entscheidungen), auch wenn diese semantisch eine geringere Ähnlichkeit aufweisen. +Seit WP-25 nutzt die Decision Engine eine **Stream-Library** mit spezialisierten Wissens-Streams: + +**Stream-Library (`streams_library`):** +* **`values_stream`:** Identität, Ethik und Prinzipien (filter_types: `value`, `principle`, `belief`, `trait`, `boundary`, `need`, `motivation`) +* **`facts_stream`:** Operative Daten (filter_types: `project`, `decision`, `task`, `goal`, `event`, `state`) +* **`biography_stream`:** Persönliche Erfahrungen (filter_types: `experience`, `journal`, `profile`, `person`) +* **`risk_stream`:** Hindernisse und Gefahren (filter_types: `risk`, `obstacle`, `bias`) +* **`tech_stream`:** Technisches Wissen (filter_types: `concept`, `source`, `glossary`, `idea`, `insight`, `skill`, `habit`) + +**Stream-Parameter:** +* **`query_template`:** Transformiert die ursprüngliche Anfrage für spezialisierte Suche (z.B. "Welche meiner Werte und Prinzipien betreffen: {query}") +* **`filter_types`:** Strikte Synchronisation mit `types.yaml` (v2.7.0) +* **`top_k`:** Anzahl der Treffer pro Stream (z.B. 5 für Values, 3 für Risk) +* **`edge_boosts`:** Individuelle Edge-Gewichtung pro Stream (z.B. `guides: 3.0` für Values Stream) + +**Strategie-Komposition (`strategies`):** +Jede Strategie definiert, welche Streams aktiviert werden: + +* **`use_streams`:** Liste der Stream-Keys, die parallel abgefragt werden (z.B. `["values_stream", "facts_stream", "risk_stream"]` für `DECISION`) +* **`prompt_template`:** Template-Key aus `prompts.yaml` für die Wissens-Synthese (z.B. `decision_synthesis_v1`) +* **`prepend_instruction`:** Optional: Zusätzliche Anweisung für das LLM (z.B. "Analysiere die Fakten vor dem Hintergrund meiner Werte") +* **`preferred_provider`:** Optional: Provider-Präferenz für diese Strategie (z.B. `gemini` für DECISION) + +### 5.3 Strategie-Mechaniken (Graph Shaping) +Jede Strategie definiert mehrere Hebel, um das Ergebnis zu beeinflussen: + +* **`use_streams`:** Aktiviert parallele Wissens-Streams (WP-25). * **`edge_boosts`:** Erhöht die Gewichtung spezifischer Kanten-Typen in der Scoring-Formel. Dies ermöglicht es dem Graphen, die Textsuche situativ zu "überstimmen". * **`prepend_instruction`:** Injiziert eine spezifische Systemanweisung in das LLM-Prompt, um den Antwortstil anzupassen (z. B. "Wäge Fakten gegen Werte ab"). --- -### 5.3 Übersicht der Strategien +### 5.4 Übersicht der Strategien (WP-25) -| Strategie | Fokus | Bevorzugte Kanten (`edge_boosts`) | Injektionstypen | +| Strategie | Fokus | Aktive Streams | Bevorzugte Kanten (`edge_boosts`) | | :--- | :--- | :--- | :--- | -| **FACT** | Wissensabfrage & Definitionen | `part_of` (2.0), `composed_of` (2.0), `similar_to` (1.5) | *(Keine)* | -| **DECISION** | Rat, Strategie & Abwägung | `blocks` (2.5), `solves` (2.0), `risk_of` (2.5) | `value`, `principle`, `goal`, `risk` | -| **EMPATHY** | Emotionale Resonanz | `based_on` (2.0), `experienced_in` (2.5), `related_to` (2.0) | `experience`, `belief`, `profile` | -| **CODING** | Programmierung & Syntax | `implemented_in` (3.0), `uses` (2.5), `depends_on` (2.0) | `snippet`, `reference`, `source` | -| **INTERVIEW** | Erfassung neuer Daten | *(Keine)* | *(Keine)* | +| **FACT_WHAT** | Wissensabfrage & Listen | `facts_stream`, `tech_stream`, `biography_stream` | `part_of` (2.0), `depends_on` (1.5), `implemented_in` (1.5) | +| **FACT_WHEN** | Zeitpunkte & Termine | `facts_stream`, `biography_stream`, `tech_stream` | `part_of` (2.0), `depends_on` (1.5) | +| **DECISION** | Rat, Strategie & Abwägung | `values_stream`, `facts_stream`, `risk_stream` | `blocks` (2.5), `impacts` (2.0), `risk_of` (2.5) | +| **EMPATHY** | Emotionale Resonanz | `biography_stream`, `values_stream` | `related_to` (1.5), `experienced_in` (2.0) | +| **CODING** | Programmierung & Syntax | `tech_stream`, `facts_stream` | `uses` (2.5), `implemented_in` (3.0) | +| **INTERVIEW** | Erfassung neuer Daten | *(Keine Streams)* | *(Keine)* | --- -### 5.4 Der Interview-Modus & Schemas +### 5.5 Der Interview-Modus & Schemas Die Strategie `INTERVIEW` dient der strukturierten Datenerfassung. -* **Trigger:** Aktiviert durch Phrasen wie "neue notiz", "festhalten" oder "dokumentieren". +* **Trigger:** Aktiviert durch Phrasen wie "neue notiz", "festhalten" oder "dokumentieren" (Type Keywords aus `types.yaml`). * **Schema-Logik:** Nutzt das `default`-Schema mit den Feldern `Titel`, `Thema/Inhalt` und `Tags`, sofern kein spezifisches Typ-Schema aus der `types.yaml` greift. * **Dynamik:** In diesem Modus wird der Fokus vom Retrieval (Wissen finden) auf die Extraktion (Wissen speichern) verschoben. +* **Streams:** Keine Streams aktiviert (leere `use_streams` Liste). > **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) + +Seit WP-25 nutzen die Synthese-Templates explizite Stream-Variablen: + +**Template-Struktur:** +```yaml +decision_synthesis_v1: + ollama: | + WERTE & PRINZIPIEN (Identität): + {values_stream} + + OPERATIVE FAKTEN (Realität): + {facts_stream} + + RISIKO-RADAR (Konsequenzen): + {risk_stream} + + ENTSCHEIDUNGSFRAGE: + {query} +``` + +**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. + Auszug aus der decision_engine.yaml ```yaml diff --git a/docs/05_Development/05_developer_guide.md b/docs/05_Development/05_developer_guide.md index 427d163..0f8e5a2 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 +scope: workflow, testing, architecture, modules, modularization, agentic_rag status: active -version: 2.9.1 -context: "Umfassender Guide für Entwickler: Modularisierte Architektur (WP-14), Two-Pass Ingestion (WP-15b), Modul-Interna, Setup und Git-Workflow." +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." --- # Mindnet Developer Guide & Workflow @@ -236,7 +236,7 @@ Das Backend ist das Herzstück. Es stellt die Logik via REST-API bereit. | :--- | :--- | :--- | :--- | | **Entry** | `app/main.py` | 🟢 **Core** | **Entrypoint.** Initialisiert FastAPI, CORS, und bindet alle Router ein. | | **Config** | `app/config.py` | 🟢 **Core** | **Settings.** Zentrale Konfiguration (Pydantic). Lädt Env-Vars für Qdrant, LLM und Pfade. | -| **Router** | `app/routers/chat.py` | 🟢 **API** | **Conversation API.** Haupt-Endpunkt für Chat. Entscheidet zwischen Interview- und RAG-Modus. | +| **Router** | `app/routers/chat.py` | 🟢 **API** | **Conversation API (WP-25).** Haupt-Endpunkt für Chat. Hybrid Router mit Intent-Erkennung, Multi-Stream Orchestration und Wissens-Synthese. | | | `app/routers/ingest.py` | 🟢 **API** | **Write API.** Nimmt Markdown entgegen, steuert Ingestion und Discovery-Analyse. | | | `app/routers/query.py` | 🟢 **API** | **Search API.** Klassischer Hybrid-Retriever Endpunkt. | | | `app/routers/graph.py` | 🟢 **API** | **Viz API.** Liefert Knoten/Kanten für Frontend-Graphen (Cytoscape). | @@ -393,12 +393,13 @@ Mindnet lernt nicht durch Training (Fine-Tuning), sondern durch **Konfiguration* edge_defaults: ["blocks"] # Automatische Kante detection_keywords: ["gefahr", "risiko"] ``` -2. **Strategie (`config/decision_engine.yaml`):** +2. **Strategie (`config/decision_engine.yaml` v3.1.6, WP-25):** ```yaml DECISION: - inject_types: ["value", "risk"] # <--- "risk" hinzufügen + use_streams: ["values_stream", "facts_stream", "risk_stream"] # WP-25: Multi-Stream + inject_types: ["value", "risk"] # Legacy: Fallback für nicht-Stream-Typen ``` - *Ergebnis:* Wenn der Intent `DECISION` erkannt wird, sucht das System nun auch aktiv nach Risiken. + *Ergebnis (WP-25):* Wenn der Intent `DECISION` erkannt wird, führt das System parallele Abfragen in Values, Facts und Risk Streams aus und synthetisiert die Ergebnisse. ### Workflow B: Graph-Farben ändern 1. Öffne `app/frontend/ui_config.py`. diff --git a/docs/05_Development/05_testing_guide.md b/docs/05_Development/05_testing_guide.md index 3310877..445b0de 100644 --- a/docs/05_Development/05_testing_guide.md +++ b/docs/05_Development/05_testing_guide.md @@ -3,8 +3,8 @@ doc_type: developer_guide audience: developer, tester scope: testing, quality_assurance, test_strategies status: active -version: 2.9.1 -context: "Umfassender Test-Guide für Mindnet: Test-Strategien, Test-Frameworks, Test-Daten und Best Practices." +version: 2.9.3 +context: "Umfassender Test-Guide für Mindnet: Test-Strategien, Test-Frameworks, Test-Daten und Best Practices inklusive WP-25 Multi-Stream RAG." --- # Testing Guide @@ -245,19 +245,29 @@ class TestIngest(unittest.IsolatedAsyncioTestCase): - `tests/test_edges_defaults_smoke.py` - `scripts/edges_full_check.py` -### 4.4 Chat & Intent-Tests +### 4.4 Chat & Intent-Tests (WP-25) **Was wird getestet:** -- Intent-Erkennung (FACT, DECISION, EMPATHY, INTERVIEW) -- Decision Engine +- Intent-Erkennung (FACT_WHAT, FACT_WHEN, DECISION, EMPATHY, CODING, INTERVIEW) +- Hybrid Router (Keyword Fast-Path + LLM Slow-Path) +- Decision Engine (Multi-Stream Orchestration) +- Parallele Stream-Abfragen (Values, Facts, Biography, Risk, Tech) +- Stream-Tracing (`stream_origin` Markierung) +- Wissens-Synthese (Template-basierte Zusammenführung) - Interview-Modus - Feedback-Loop **Tests:** -- `tests/test_wp06_decision.py` -- `tests/test_interview_intent.py` -- `tests/test_chat_wp05.py` -- `tests/test_feedback_smoke.py` +- `tests/test_wp06_decision.py` - Decision Engine (Legacy) +- `tests/test_interview_intent.py` - Interview-Modus +- `tests/test_chat_wp05.py` - Chat-Backend (Legacy) +- `tests/test_feedback_smoke.py` - Feedback-Loop + +**WP-25 Spezifische Tests (geplant):** +- Multi-Stream Retrieval (parallele Abfragen) +- Stream-Tracing (stream_origin Zuordnung) +- Template-Robustheit (Pre-Initialization) +- Intent-Kollision (Keyword-Fast-Path Präzision) ### 4.5 Ingestion-Tests diff --git a/docs/06_Roadmap/06_active_roadmap.md b/docs/06_Roadmap/06_active_roadmap.md index 7b9be49..cb61b71 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.1 -context: "Aktuelle Planung für kommende Features (ab WP16), Release-Strategie und Historie der abgeschlossenen WPs nach WP-14/15b." +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." --- # Mindnet Active Roadmap -**Aktueller Stand:** v2.9.1 (Post-WP14 / WP-15b) -**Fokus:** Modularisierung, Two-Pass Ingestion & Graph Intelligence. +**Aktueller Stand:** v2.9.3 (Post-WP25: Agentic Multi-Stream RAG) +**Fokus:** Agentic Orchestration, Multi-Stream Retrieval & Wissens-Synthese. | Phase | Fokus | Status | | :--- | :--- | :--- | @@ -48,6 +48,8 @@ Eine Übersicht der implementierten Features zum schnellen Auffinden von Funktio | **WP-20** | **Cloud Hybrid Mode & Resilienz** | **Ergebnis:** Integration von OpenRouter (Mistral 7B) & Gemini 2.5 Lite. Implementierung von WP-76 (Rate-Limit Wait) & Mistral-safe JSON Parsing. | | **WP-21** | Semantic Graph Routing & Canonical Edges | Transformation des Graphen von statischen Verbindungen zu dynamischen, kontextsensitiven Pfaden. Das System soll verstehen, *welche* Art von Verbindung für die aktuelle Frage relevant ist ("Warum?" vs. "Was kommt danach?"). | | **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. | ### 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. @@ -191,53 +193,29 @@ Der bisherige WP-15 Ansatz litt unter Halluzinationen (erfundene Kantentypen), h 2. **Single Source of Truth (SSOT):** Die Registry nutzt `01_edge_vocabulary.md` als führende Konfiguration. 3. **Self-Learning Loop:** Protokollierung unbekannter Kanten in `unknown_edges.jsonl`. -### WP-23: Agentic Multi-Stream Reasoning (Mindnet 2025) +### WP-25: Agentic Multi-Stream RAG Orchestration +**Status:** ✅ Fertig (v2.9.3) -#### 1. Zielsetzung & Problemstellung -Das bisherige System basiert auf einem globalen Scoring-Modell, bei dem Notizen unterschiedlicher Typen (z. B. `insight` vs. `belief`) in einem einzigen Retrieval-Topf konkurrieren. Dies führt dazu, dass leiser gewichtete, aber fundamentale Identitätsmerkmale oft durch hochgewichtete aktuelle Erkenntnisse verdrängt werden. Ziel dieses Pakets ist die Einführung einer parallelen **Stream-Architektur**, um die Vielschichtigkeit menschlicher Entscheidungsprozesse (Werte + Erfahrung + Absicht) im LLM-Kontext zu garantieren. +**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. -#### 2. Funktionsbeschreibung: Die Streams -Die Daten aus der `types.yaml` werden in drei logische Verarbeitungseinheiten unterteilt: +**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 -##### A. Identity Stream (Die Wahrheitsebene) -* **Inhalt:** `value`, `belief`, `trait`, `principle`, `need`, `boundary`, `bias`. -* **Zweck:** Definition des moralischen Kompasses, der psychologischen Grundbedürfnisse und kognitiven Muster. -* **Wirkung:** Liefert das "Warum" hinter jeder Handlung. +**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 -##### B. History Stream (Die Evidenzebene) -* **Inhalt:** `experience`, `event`, `source`, `journal`, `person`. -* **Zweck:** Bereitstellung empirischer Belege aus der Vergangenheit und sozialer Kontexte. -* **Wirkung:** Verankert die Antwort in real erlebten Mustern und Fakten. - -##### C. Action Stream (Die Dynamikebene) -* **Inhalt:** `project`, `decision`, `goal`, `task`, `risk`, `motivation`, `habit`, `state`. -* **Zweck:** Analyse der aktuellen Richtung, geplanter Vorhaben und des gegenwärtigen Zustands. -* **Wirkung:** Liefert den Kontext für die Umsetzung und zukünftige Ziele. - - -#### 3. Technische Wirkungsweise (Solution Sketch) - -##### Schritt 1: Query-Decomposition -Ein initialer Klassifizierungs-Agent analysiert die Nutzeranfrage und bestimmt, welcher Stream primär angesprochen werden muss (z. B. "Wie soll ich mich entscheiden?" boostet den Identity Stream). - -##### Schritt 2: Parallel Stream Retrieval -Anstelle einer Suche werden drei unabhängige Vektor-Suchen mit Typ-Filtern durchgeführt: -* **Search_A (Identity):** Top-5 Ergebnisse aus Identitäts-Notizen. -* **Search_B (History):** Top-5 Ergebnisse aus biografischen/externen Notizen. -* **Search_C (Action):** Top-5 Ergebnisse aus operativen/strategischen Notizen. - -##### Schritt 3: Agentic Synthesis (The Reasoning) -Ein Synthese-Agent (LLM) erhält die aggregierten Ergebnisse in getrennten Sektionen. Die Anweisung lautet: -1. **Prüfung:** Steht das aktuelle Vorhaben (Action) im Einklang mit den Werten (Identity)? -2. **Abgleich:** Welche vergangenen Erfahrungen (History) stützen oder widersprechen diesem Weg? -3. **Korrektur:** Identifiziere mögliche Biases oder Grenzüberschreitungen (Boundary). - - - -#### 4. Erwartete Ergebnisse -* **Höhere Resonanz:** Antworten wirken authentischer, da sie explizit auf das Wertesystem des Nutzers Bezug nehmen. -* **Widerspruchs-Erkennung:** Das System kann den Nutzer aktiv warnen, wenn ein Projekt gegen seine `principles` oder `needs` verstößt. -* **Robustes Retrieval:** Wichtige Identitäts-Informationen gehen nicht mehr im "Rauschen" von hunderten Journal-Einträgen verloren. +**Ausblick (WP-25a):** +- Pre-Synthesis: LLM-basierte Komprimierung überlanger Streams +- Kontext-Budgeting: Intelligente Token-Verteilung +- Stream-specific Provider: Unterschiedliche KI-Modelle pro Wissensbereich --- ### WP-24 – Proactive Discovery & Agentic Knowledge Mining diff --git a/docs/99_Archive/WP15c_release_notes.md b/docs/99_Archive/WP15c_release_notes.md index 26cff1a..62cb8c9 100644 --- a/docs/99_Archive/WP15c_release_notes.md +++ b/docs/99_Archive/WP15c_release_notes.md @@ -1,4 +1,4 @@ -# Release Notes: Mindnet v2.9.1 (WP15c) +# Release Notes: Mindnet v2.9.3 (WP15c) **Release Date:** 2025-12-31 **Type:** Feature Release - Multigraph & Diversity Engine diff --git a/docs/99_Archive/WP25_merge_commit.md b/docs/99_Archive/WP25_merge_commit.md new file mode 100644 index 0000000..66acec9 --- /dev/null +++ b/docs/99_Archive/WP25_merge_commit.md @@ -0,0 +1,138 @@ +# Branch Merge Commit Message: WP25 + +``` +feat: Agentic Multi-Stream RAG Orchestration (v3.0) + +## Architektur-Transformation (WP-25) + +### Agentic Multi-Stream RAG Orchestration +- Übergang von linearer RAG-Architektur zu paralleler Multi-Stream Engine +- Parallele Abfragen in spezialisierten Wissens-Streams (Values, Facts, Biography, Risk, Tech) +- Stream-Tracing: Jeder Treffer wird mit `stream_origin` markiert +- Fehler-Resilienz: Einzelne Stream-Fehler blockieren nicht die gesamte Anfrage + +**Geänderte Dateien:** +- `app/core/retrieval/decision_engine.py`: Multi-Stream Orchestrator (v1.0.3) +- `app/routers/chat.py`: Hybrid Router Integration (v3.0.2) +- `app/models/dto.py`: Stream-Tracing Support (v0.7.1) + +### Intent-basiertes Routing ("The Brain") +- Hybrid-Modus: Keyword Fast-Path + LLM Slow-Path +- Strategien: FACT_WHAT, FACT_WHEN, DECISION, EMPATHY, CODING, INTERVIEW +- Sofortige Erkennung von Triggern wie "Soll ich" oder "Wann" ohne LLM-Call +- Komplexe semantische Analyse für unklare Anfragen + +**Geänderte Dateien:** +- `app/routers/chat.py`: Hybrid Router mit Keyword-Fast-Path (v3.0.2) +- `app/core/retrieval/decision_engine.py`: LLM-basiertes Routing (v1.0.3) + +### Wissens-Synthese +- Spezialisierte Templates in `prompts.yaml` mit expliziten Stream-Variablen +- Pre-Initialization aller Stream-Variablen (verhindert KeyErrors) +- Provider-spezifische Templates für Ollama, Gemini und OpenRouter +- Differenzierte Abwägung zwischen Fakten und persönlichen Werten + +**Geänderte Dateien:** +- `config/prompts.yaml`: Stream-Templates (v3.1.2) +- `app/core/retrieval/decision_engine.py`: Synthese-Logik (v1.0.3) + +## Stream-Konfiguration & Typ-Synchronisation + +### Stream-Library (decision_engine.yaml v3.1.6) +- **Values Stream:** Identität, Ethik und Prinzipien (filter_types: value, principle, belief, etc.) +- **Facts Stream:** Operative Daten (filter_types: project, decision, task, etc.) +- **Biography Stream:** Persönliche Erfahrungen (filter_types: experience, journal, profile) +- **Risk Stream:** Hindernisse und Gefahren (filter_types: risk, obstacle, bias) +- **Tech Stream:** Technisches Wissen (filter_types: concept, source, glossary, etc.) + +**Edge-Boosts pro Stream:** +- Values: `guides: 3.0`, `enforced_by: 2.5`, `based_on: 2.0` +- Facts: `part_of: 2.0`, `depends_on: 1.5`, `implemented_in: 1.5` +- Risk: `blocks: 2.5`, `impacts: 2.0`, `risk_of: 2.5` + +**Geänderte Dateien:** +- `config/decision_engine.yaml`: Multi-Stream Konfiguration (v3.1.6) + +## Bugfixes & Optimierungen + +### Vault-Import Performance +- "Empty Response Guard" korrigiert: Kurze Ingest-Antworten (YES/NO) lösen nicht mehr fälschlicherweise langsame Fallbacks aus +- Entfernung des <5-Zeichen Guards ermöglicht YES/NO Validierungen + +**Geänderte Dateien:** +- `app/services/llm_service.py`: Ingest-Stability Patch (v3.4.2) + +### Cloud-Resilienz +- Sicherheitscheck für das `choices`-Array in `_execute_openrouter` implementiert +- Verhindert JSON-Errors bei überlasteten APIs + +**Geänderte Dateien:** +- `app/services/llm_service.py`: Empty Response Guard (v3.4.2) + +### Template-Robustheit +- Automatische Vor-Initialisierung aller Stream-Variablen in der `DecisionEngine` +- Verhindert `KeyError`-Abstürze bei unvollständigen Konfigurationen +- Fallback-Mechanismus bei Template-Fehlern + +**Geänderte Dateien:** +- `app/core/retrieval/decision_engine.py`: Pre-Initialization (v1.0.3) + +### Intent-Kollision +- Generische Begriffe (wie "Projekt") wurden aus den Keyword-Triggern entfernt +- Sicherstellt, dass strategische Fragen (z.B. "Soll ich...?") korrekt als `DECISION` geroutet werden + +**Geänderte Dateien:** +- `config/decision_engine.yaml`: Keyword-Fix (v3.1.6) + +## Ollama Context-Throttling + +- Vor der Übergabe an Ollama prüft der Chat-Router, ob der Kontext die Grenze von `MAX_OLLAMA_CHARS` überschreitet (Standard: 10.000) +- Automatische Kürzung bei großen Kontexten + +**Geänderte Dateien:** +- `app/routers/chat.py`: Context-Throttling (v3.0.2) + +## Lifespan Management + +- FastAPI-Anwendung implementiert Lifespan-Management für sauberen Startup und Shutdown +- Integritäts-Check der WP-25 Konfiguration beim Startup +- Ressourcen-Cleanup beim Shutdown +- Globale Fehlerbehandlung für asynchrone Prozesse + +**Geänderte Dateien:** +- `app/main.py`: Lifespan Management (v1.0.0) + +## Impact & Breaking Changes + +### Keine Migration erforderlich +**WICHTIG:** Diese Version ist **rückwärtskompatibel**. Bestehende Vaults funktionieren ohne Re-Import. + +**Konfigurations-Updates:** +- `decision_engine.yaml` wurde auf v3.1.6 aktualisiert (Multi-Stream Struktur) +- `prompts.yaml` wurde auf v3.1.2 aktualisiert (Stream-Templates) +- **Empfehlung:** Backup der alten Konfigurationsdateien vor dem Update + +### API-Erweiterungen +- `QueryHit.stream_origin`: Name des Ursprungs-Streams (optional) +- `ChatResponse.intent`: Die gewählte WP-25 Strategie (optional) +- `ChatResponse.intent_source`: Quelle der Intent-Erkennung (optional) + +## Dokumentation + +Alle relevanten Dokumente aktualisiert: +- `03_tech_chat_backend.md`: Agentic Multi-Stream RAG, Intent-basiertes Routing, Wissens-Synthese +- `03_tech_configuration.md`: decision_engine.yaml, prompts.yaml (Stream-Struktur) +- `03_tech_api_reference.md`: Erweiterte DTOs (stream_origin, intent) + +## Versionen + +- Decision Engine: v1.0.3 +- Chat Router: v3.0.2 +- LLM Service: v3.4.2 +- DTOs: v0.7.1 +- Main: v1.0.0 +- decision_engine.yaml: v3.1.6 +- prompts.yaml: v3.1.2 + +Closes #[issue-number] +``` diff --git a/docs/99_Archive/WP25_release_notes.md b/docs/99_Archive/WP25_release_notes.md new file mode 100644 index 0000000..e7acffe --- /dev/null +++ b/docs/99_Archive/WP25_release_notes.md @@ -0,0 +1,268 @@ +# Release Notes: Mindnet v3.0.0 (WP25) + +**Release Date:** 2026-01-01 +**Type:** Feature Release - Agentic Multi-Stream RAG Orchestration +**Branch:** WP25 + +--- + +## 🎯 Übersicht + +Diese Version markiert den Übergang 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. + +--- + +## ✨ Neue Features + +### Agentic Multi-Stream RAG Orchestration + +Mindnet führt nun **parallele Abfragen** in spezialisierten Wissens-Streams aus, anstatt einer einzelnen Suche: + +**Stream-Library:** +* **Values Stream:** Extrahiert Identität, Ethik und Prinzipien (`value`, `principle`, `belief`, `trait`, `boundary`, `need`, `motivation`). +* **Facts Stream:** Liefert operative Daten zu Projekten, Tasks und Status (`project`, `decision`, `task`, `goal`, `event`, `state`). +* **Biography Stream:** Greift auf persönliche Erfahrungen und Journal-Einträge zu (`experience`, `journal`, `profile`, `person`). +* **Risk Stream:** Identifiziert Hindernisse und potenzielle Gefahren (`risk`, `obstacle`, `bias`). +* **Tech Stream:** Bündelt technisches Wissen, Code und Dokumentation (`concept`, `source`, `glossary`, `idea`, `insight`, `skill`, `habit`). + +**Vorteile:** +* **Präzise Kontext-Ladung:** Jeder Stream fokussiert auf spezifische Wissensbereiche. +* **Parallele Ausführung:** Alle Streams werden gleichzeitig abgefragt (asyncio.gather). +* **Stream-Tracing:** Jeder Treffer wird mit `stream_origin` markiert für Feedback-Optimierung. +* **Fehler-Resilienz:** Einzelne Stream-Fehler blockieren nicht die gesamte Anfrage. + +### Intent-basiertes Routing ("The Brain") + +Der Router wurde zu einem **hybriden System** erweitert, das Anfragen klassifiziert, bevor die Suche beginnt: + +**Strategien:** +* **FACT_WHAT:** Wissen/Listen (z.B. "Was ist...", "Welche sind..."). +* **FACT_WHEN:** Zeitpunkte (z.B. "Wann...", "Datum..."). +* **DECISION:** Beratung (z.B. "Soll ich...", "Sollte ich...", "Empfehlung..."). +* **EMPATHY:** Reflexion (z.B. "Fühle...", "Stress...", "Angst..."). +* **CODING:** Technik (z.B. "Code...", "Python...", "Bug..."). +* **INTERVIEW:** Datenerfassung (z.B. "Neues Projekt...", "Erfahrung..."). + +**Hybrid-Modus:** +* **Keyword Fast-Path:** Sofortige Erkennung von Triggern wie "Soll ich" oder "Wann" ohne LLM-Call. +* **LLM Slow-Path:** Komplexe semantische Analyse für unklare Anfragen. +* **Type Keywords:** Automatische Erkennung von Objekt-Typen für Interview-Modus. + +**Vorteil:** Reduziert Latenz durch schnelle Keyword-Erkennung, nutzt LLM nur bei Bedarf. + +### Wissens-Synthese + +Die Zusammenführung der Daten erfolgt über spezialisierte Templates in der `prompts.yaml`: + +**Template-Struktur:** +* Explizite Variablen für jeden Stream (z.B. `{values_stream}`, `{risk_stream}`). +* **Pre-Initialization:** Alle möglichen Stream-Variablen werden vorab initialisiert (verhindert KeyErrors). +* **Provider-spezifische Templates:** Separate Versionen für Ollama, Gemini und OpenRouter. + +**Synthese-Strategien:** +* **FACT_WHAT/FACT_WHEN:** Kombiniert Fakten, Biographie und Technik. +* **DECISION:** Wägt Fakten gegen Werte ab, evaluiert Risiken. +* **EMPATHY:** Fokus auf Biographie und Werte. +* **CODING:** Technik und Fakten. + +**Vorteil:** Ermöglicht dem LLM eine differenzierte Abwägung zwischen Fakten und persönlichen Werten. + +--- + +## 🔧 Verbesserungen + +### Stream-Konfiguration & Typ-Synchronisation + +Jeder Stream nutzt individuelle **Edge-Boosts** und **Filter-Types**, die strikt mit der `types.yaml` (v2.7.0) synchronisiert sind: + +**Beispiele:** +* **Values Stream:** `guides: 3.0`, `enforced_by: 2.5`, `based_on: 2.0`. +* **Facts Stream:** `part_of: 2.0`, `depends_on: 1.5`, `implemented_in: 1.5`. +* **Risk Stream:** `blocks: 2.5`, `impacts: 2.0`, `risk_of: 2.5`. + +**Vorteil:** Präzise Gewichtung der Graph-Kanten je nach Wissensbereich. + +### Template-Robustheit + +**Pre-Initialization:** Automatische Vor-Initialisierung aller Stream-Variablen in der `DecisionEngine` verhindert `KeyError`-Abstürze bei unvollständigen Konfigurationen. + +**Fallback-Mechanismus:** Bei Template-Fehlern wird ein vereinfachter Prompt mit allen verfügbaren Stream-Ergebnissen verwendet. + +### Ollama Context-Throttling + +Vor der Übergabe an Ollama prüft der Chat-Router, ob der Kontext (RAG-Hits) die Grenze von `MAX_OLLAMA_CHARS` überschreitet (Standard: 10.000) und kürzt diesen ggf. + +**Vorteil:** Verhindert VRAM-Überlastung bei großen Kontexten. + +--- + +## 🐛 Bugfixes + +- ✅ **Behoben:** Vault-Import Performance - "Empty Response Guard" korrigiert, damit kurze Ingest-Antworten (YES/NO) nicht mehr fälschlicherweise langsame Fallbacks auslösen. +- ✅ **Behoben:** Cloud-Resilienz - Sicherheitscheck für das `choices`-Array in `_execute_openrouter` implementiert, um JSON-Errors bei überlasteten APIs zu verhindern. +- ✅ **Behoben:** Template-Robustheit - Automatische Vor-Initialisierung aller Stream-Variablen verhindert `KeyError`-Abstürze. +- ✅ **Behoben:** Intent-Kollision - Generische Begriffe (wie "Projekt") wurden aus den Keyword-Triggern entfernt, um sicherzustellen, dass strategische Fragen (z.B. "Soll ich...?") korrekt als `DECISION` geroutet werden. + +--- + +## ⚠️ Breaking Changes & Migration + +### Keine Migration erforderlich + +**WICHTIG:** Diese Version ist **rückwärtskompatibel**. Bestehende Vaults funktionieren ohne Re-Import. + +**Konfigurations-Updates:** +* `decision_engine.yaml` wurde auf v3.1.6 aktualisiert (Multi-Stream Struktur). +* `prompts.yaml` wurde auf v3.1.2 aktualisiert (Stream-Templates). +* **Empfehlung:** Backup der alten Konfigurationsdateien vor dem Update. + +--- + +## 📚 API-Änderungen + +### Erweiterte DTOs + +**QueryHit (erweitert):** +```python +class QueryHit(BaseModel): + # ... bestehende Felder ... + stream_origin: Optional[str] = None # Neu: Name des Ursprungs-Streams +``` + +**ChatResponse (erweitert):** +```python +class ChatResponse(BaseModel): + # ... bestehende Felder ... + intent: Optional[str] = "FACT" # Neu: Die gewählte WP-25 Strategie + intent_source: Optional[str] = "LLM_Router" # Neu: Quelle der Intent-Erkennung +``` + +**Impact für API-Consumer:** +* Frontend kann `stream_origin` für visuelle Kennzeichnung der Quellen nutzen. +* `intent` und `intent_source` ermöglichen Debugging und Analytics. + +--- + +## 📖 Dokumentation + +Alle relevanten Dokumente wurden aktualisiert: + +- ✅ `03_tech_chat_backend.md`: Agentic Multi-Stream RAG, Intent-basiertes Routing, Wissens-Synthese +- ✅ `03_tech_configuration.md`: decision_engine.yaml, prompts.yaml (Stream-Struktur) +- ✅ `03_tech_api_reference.md`: Erweiterte DTOs (stream_origin, intent) + +--- + +## 🔄 Technische Details + +### Geänderte Module + +**Decision Engine:** +- `app/core/retrieval/decision_engine.py`: Multi-Stream Orchestrator (v1.0.3) + - Paralleles Retrieval via `_execute_parallel_streams()` + - Stream-Tracing mit `stream_origin` + - Pre-Initialization der Stream-Variablen + +**Chat Router:** +- `app/routers/chat.py`: Hybrid Router Integration (v3.0.2) + - Keyword Fast-Path + LLM Slow-Path + - Multi-Stream Orchestrierung + - Ollama Context-Throttling + +**LLM Service:** +- `app/services/llm_service.py`: Ingest-Stability Patch (v3.4.2) + - Entfernung des <5-Zeichen Guards + - Empty Response Guard für OpenRouter + - Lazy Initialization der DecisionEngine + +**DTOs:** +- `app/models/dto.py`: Stream-Tracing Support (v0.7.1) + - `stream_origin` in `QueryHit` + - `intent` und `intent_source` in `ChatResponse` + +**Main:** +- `app/main.py`: Lifespan Management (v1.0.0) + - Startup-Integritäts-Check + - Ressourcen-Cleanup beim Shutdown + - Globale Fehlerbehandlung + +**Konfiguration:** +- `config/decision_engine.yaml`: Multi-Stream Konfiguration (v3.1.6) +- `config/prompts.yaml`: Stream-Templates (v3.1.2) + +### Versionsnummern + +- Decision Engine: **v1.0.3** +- Chat Router: **v3.0.2** +- LLM Service: **v3.4.2** +- DTOs: **v0.7.1** +- Main: **v1.0.0** +- decision_engine.yaml: **v3.1.6** +- prompts.yaml: **v3.1.2** + +--- + +## 🚀 Upgrade-Pfad + +### Für Administratoren + +1. **Code aktualisieren:** + ```bash + git pull origin main + source .venv/bin/activate + pip install -r requirements.txt + ``` + +2. **Konfiguration prüfen:** + ```bash + # Backup der alten Konfigurationen + cp config/decision_engine.yaml config/decision_engine.yaml.backup + cp config/prompts.yaml config/prompts.yaml.backup + + # Prüfen, ob neue Konfigurationen vorhanden sind + ls -la config/decision_engine.yaml config/prompts.yaml + ``` + +3. **Services neu starten:** + ```bash + sudo systemctl restart mindnet-prod + sudo systemctl restart mindnet-ui-prod + ``` + +### Für Entwickler + +- Keine Code-Änderungen erforderlich, wenn nur API genutzt wird +- Frontend kann neue Felder (`stream_origin`, `intent`, `intent_source`) optional nutzen +- Stream-Tracing ermöglicht Feedback-Optimierung pro Wissensbereich + +--- + +## 📝 Bekannte Einschränkungen + +- **Stream-Parallelität:** Maximale Anzahl paralleler Streams ist durch asyncio-Limits begrenzt (typischerweise 5-10 Streams). +- **Kontext-Größe:** Große Kontexte (>10.000 Zeichen) werden bei Ollama automatisch gekürzt. + +--- + +## 🔮 Ausblick (WP-25a: Agentic Refinement) + +Auf Basis des stabilen WP-25 Fundaments sind folgende Erweiterungen geplant: + +* **Pre-Synthesis:** LLM-basierte Komprimierung überlanger Streams vor der finalen Antwortgenerierung. +* **Kontext-Budgeting:** Intelligente Verteilung der verfügbaren Token auf die aktivierten Streams. +* **Stream-specific Provider:** Zuweisung unterschiedlicher KI-Modelle pro Wissensbereich (z.B. lokales Ollama für Identitäts-Werte, Gemini für technische Fakten). + +--- + +## 🙏 Danksagungen + +Diese Version wurde durch umfangreiche Architektur-Überarbeitung ermöglicht. Besonderer Fokus lag auf: +- Transformation zu einer Agentic Multi-Stream Engine +- Intelligente Intent-Erkennung mit Hybrid-Routing +- Parallele Wissens-Synthese mit wertebasierter Abwägung +- Robustheit und Fehler-Resilienz + +--- + +**Vollständige Changelog:** Siehe Git-Commits für detaillierte Änderungen +**Support:** Bei Fragen siehe [Admin Operations Guide](../04_Operations/04_admin_operations.md) diff --git a/docs/README.md b/docs/README.md index b9d04f4..a4de11d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,13 +2,13 @@ doc_type: documentation_index audience: all status: active -version: 2.9.1 +version: 2.9.3 context: "Zentraler Einstiegspunkt für die Mindnet-Dokumentation" --- # Mindnet Dokumentation -Willkommen in der Dokumentation von Mindnet v2.9.1! Diese Dokumentation hilft dir dabei, das System zu verstehen, zu nutzen und weiterzuentwickeln. +Willkommen in der Dokumentation von Mindnet v2.9.3! Diese Dokumentation hilft dir dabei, das System zu verstehen, zu nutzen und weiterzuentwickeln. ## 🚀 Schnellstart