WP25 #19
37
app/core/logging_setup.py
Normal file
37
app/core/logging_setup.py
Normal file
|
|
@ -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}")
|
||||||
204
app/core/retrieval/decision_engine.py
Normal file
204
app/core/retrieval/decision_engine.py
Normal file
|
|
@ -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."
|
||||||
104
app/main.py
104
app/main.py
|
|
@ -1,25 +1,28 @@
|
||||||
"""
|
"""
|
||||||
FILE: app/main.py
|
FILE: app/main.py
|
||||||
DESCRIPTION: Bootstrap der FastAPI Anwendung. Inkludiert Router und Middleware.
|
DESCRIPTION: Bootstrap der FastAPI Anwendung für WP-25 (Agentic RAG).
|
||||||
VERSION: 0.6.0
|
Orchestriert Lifespan-Events, globale Fehlerbehandlung und Routing.
|
||||||
|
VERSION: 1.0.0 (WP-25 Release)
|
||||||
STATUS: Active
|
STATUS: Active
|
||||||
DEPENDENCIES: app.config, app.routers.* (embed, qdrant, query, graph, tools, feedback, chat, ingest, admin)
|
DEPENDENCIES: app.config, app.routers.*, app.services.llm_service
|
||||||
LAST_ANALYSIS: 2025-12-15
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from fastapi import FastAPI
|
import logging
|
||||||
from .config import get_settings
|
import os
|
||||||
#from .routers.embed_router import router as embed_router
|
from contextlib import asynccontextmanager
|
||||||
#from .routers.qdrant_router import router as qdrant_router
|
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.query import router as query_router
|
||||||
from .routers.graph import router as graph_router
|
from .routers.graph import router as graph_router
|
||||||
from .routers.tools import router as tools_router
|
from .routers.tools import router as tools_router
|
||||||
from .routers.feedback import router as feedback_router
|
from .routers.feedback import router as feedback_router
|
||||||
# NEU: Chat Router (WP-05)
|
|
||||||
from .routers.chat import router as chat_router
|
from .routers.chat import router as chat_router
|
||||||
# NEU: Ingest Router (WP-11)
|
|
||||||
from .routers.ingest import router as ingest_router
|
from .routers.ingest import router as ingest_router
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -27,26 +30,86 @@ try:
|
||||||
except Exception:
|
except Exception:
|
||||||
admin_router = None
|
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:
|
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()
|
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")
|
@app.get("/healthz")
|
||||||
def healthz():
|
def healthz():
|
||||||
return {"status": "ok", "qdrant": s.QDRANT_URL, "prefix": s.COLLECTION_PREFIX}
|
return {
|
||||||
|
"status": "ok",
|
||||||
# app.include_router(embed_router)
|
"version": "1.0.0",
|
||||||
# app.include_router(qdrant_router)
|
"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(query_router, prefix="/query", tags=["query"])
|
||||||
app.include_router(graph_router, prefix="/graph", tags=["graph"])
|
app.include_router(graph_router, prefix="/graph", tags=["graph"])
|
||||||
app.include_router(tools_router, prefix="/tools", tags=["tools"])
|
app.include_router(tools_router, prefix="/tools", tags=["tools"])
|
||||||
app.include_router(feedback_router, prefix="/feedback", tags=["feedback"])
|
app.include_router(feedback_router, prefix="/feedback", tags=["feedback"])
|
||||||
|
app.include_router(chat_router, prefix="/chat", tags=["chat"]) # Nutzt nun WP-25 DecisionEngine
|
||||||
# NEU: Chat Endpoint
|
|
||||||
app.include_router(chat_router, prefix="/chat", tags=["chat"])
|
|
||||||
|
|
||||||
# NEU: Ingest Endpoint
|
|
||||||
app.include_router(ingest_router, prefix="/ingest", tags=["ingest"])
|
app.include_router(ingest_router, prefix="/ingest", tags=["ingest"])
|
||||||
|
|
||||||
if admin_router:
|
if admin_router:
|
||||||
|
|
@ -54,4 +117,5 @@ def create_app() -> FastAPI:
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
# Instanziierung der App
|
||||||
app = create_app()
|
app = create_app()
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
"""
|
"""
|
||||||
FILE: app/models/dto.py
|
FILE: app/models/dto.py
|
||||||
DESCRIPTION: Pydantic-Modelle (DTOs) für Request/Response Bodies. Definiert das API-Schema.
|
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
|
STATUS: Active
|
||||||
DEPENDENCIES: pydantic, typing, uuid
|
DEPENDENCIES: pydantic, typing, uuid
|
||||||
LAST_ANALYSIS: 2025-12-29
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
@ -12,8 +11,14 @@ from pydantic import BaseModel, Field
|
||||||
from typing import List, Literal, Optional, Dict, Any
|
from typing import List, Literal, Optional, Dict, Any
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
# Gültige Kanten-Typen gemäß Manual
|
# 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"]
|
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 ---
|
# --- Basis-DTOs ---
|
||||||
|
|
@ -43,14 +48,14 @@ class EdgeDTO(BaseModel):
|
||||||
direction: Literal["out", "in", "undirected"] = "out"
|
direction: Literal["out", "in", "undirected"] = "out"
|
||||||
provenance: Optional[Literal["explicit", "rule", "smart", "structure"]] = "explicit"
|
provenance: Optional[Literal["explicit", "rule", "smart", "structure"]] = "explicit"
|
||||||
confidence: float = 1.0
|
confidence: float = 1.0
|
||||||
target_section: Optional[str] = None # Neu: Speichert den Anker (z.B. #Abschnitt)
|
target_section: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
# --- Request Models ---
|
# --- Request Models ---
|
||||||
|
|
||||||
class QueryRequest(BaseModel):
|
class QueryRequest(BaseModel):
|
||||||
"""
|
"""
|
||||||
Request für /query.
|
Request für /query. Unterstützt Multi-Stream Isolation via filters.
|
||||||
"""
|
"""
|
||||||
mode: Literal["semantic", "edge", "hybrid"] = "hybrid"
|
mode: Literal["semantic", "edge", "hybrid"] = "hybrid"
|
||||||
query: Optional[str] = None
|
query: Optional[str] = None
|
||||||
|
|
@ -61,14 +66,12 @@ class QueryRequest(BaseModel):
|
||||||
ret: Dict = {"with_paths": True, "with_notes": True, "with_chunks": True}
|
ret: Dict = {"with_paths": True, "with_notes": True, "with_chunks": True}
|
||||||
explain: bool = False
|
explain: bool = False
|
||||||
|
|
||||||
# WP-22: Semantic Graph Routing
|
# WP-22/25: Dynamische Gewichtung der Graphen-Highways
|
||||||
boost_edges: Optional[Dict[str, float]] = None
|
boost_edges: Optional[Dict[str, float]] = None
|
||||||
|
|
||||||
|
|
||||||
class FeedbackRequest(BaseModel):
|
class FeedbackRequest(BaseModel):
|
||||||
"""
|
"""User-Feedback zu einem spezifischen Treffer oder der Gesamtantwort."""
|
||||||
User-Feedback zu einem spezifischen Treffer oder der Gesamtantwort (WP-08 Basis).
|
|
||||||
"""
|
|
||||||
query_id: str = Field(..., description="ID der ursprünglichen Suche")
|
query_id: str = Field(..., description="ID der ursprünglichen Suche")
|
||||||
node_id: str = Field(..., description="ID des bewerteten Treffers oder 'generated_answer'")
|
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)")
|
score: int = Field(..., ge=1, le=5, description="1 (Irrelevant) bis 5 (Perfekt)")
|
||||||
|
|
@ -76,16 +79,14 @@ class FeedbackRequest(BaseModel):
|
||||||
|
|
||||||
|
|
||||||
class ChatRequest(BaseModel):
|
class ChatRequest(BaseModel):
|
||||||
"""
|
"""Request für /chat (WP-25 Einstieg)."""
|
||||||
WP-05: Request für /chat.
|
|
||||||
"""
|
|
||||||
message: str = Field(..., description="Die Nachricht des Users")
|
message: str = Field(..., description="Die Nachricht des Users")
|
||||||
conversation_id: Optional[str] = Field(None, description="ID für Chat-Verlauf")
|
conversation_id: Optional[str] = Field(None, description="ID für Chat-Verlauf")
|
||||||
top_k: int = 5
|
top_k: int = 5
|
||||||
explain: bool = False
|
explain: bool = False
|
||||||
|
|
||||||
|
|
||||||
# --- WP-04b Explanation Models ---
|
# --- Explanation Models ---
|
||||||
|
|
||||||
class ScoreBreakdown(BaseModel):
|
class ScoreBreakdown(BaseModel):
|
||||||
"""Aufschlüsselung der Score-Komponenten nach der WP-22 Formel."""
|
"""Aufschlüsselung der Score-Komponenten nach der WP-22 Formel."""
|
||||||
|
|
@ -96,14 +97,14 @@ class ScoreBreakdown(BaseModel):
|
||||||
raw_edge_bonus: float
|
raw_edge_bonus: float
|
||||||
raw_centrality: float
|
raw_centrality: float
|
||||||
node_weight: float
|
node_weight: float
|
||||||
# WP-22 Debug Fields für Messbarkeit
|
|
||||||
status_multiplier: float = 1.0
|
status_multiplier: float = 1.0
|
||||||
graph_boost_factor: float = 1.0
|
graph_boost_factor: float = 1.0
|
||||||
|
|
||||||
|
|
||||||
class Reason(BaseModel):
|
class Reason(BaseModel):
|
||||||
"""Ein semantischer Grund für das Ranking."""
|
"""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
|
message: str
|
||||||
score_impact: Optional[float] = None
|
score_impact: Optional[float] = None
|
||||||
details: Optional[Dict[str, Any]] = None
|
details: Optional[Dict[str, Any]] = None
|
||||||
|
|
@ -114,7 +115,6 @@ class Explanation(BaseModel):
|
||||||
breakdown: ScoreBreakdown
|
breakdown: ScoreBreakdown
|
||||||
reasons: List[Reason]
|
reasons: List[Reason]
|
||||||
related_edges: Optional[List[EdgeDTO]] = None
|
related_edges: Optional[List[EdgeDTO]] = None
|
||||||
# WP-22 Debug: Verifizierung des Routings
|
|
||||||
applied_intent: Optional[str] = None
|
applied_intent: Optional[str] = None
|
||||||
applied_boosts: Optional[Dict[str, float]] = None
|
applied_boosts: Optional[Dict[str, float]] = None
|
||||||
|
|
||||||
|
|
@ -122,7 +122,10 @@ class Explanation(BaseModel):
|
||||||
# --- Response Models ---
|
# --- Response Models ---
|
||||||
|
|
||||||
class QueryHit(BaseModel):
|
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
|
node_id: str
|
||||||
note_id: str
|
note_id: str
|
||||||
semantic_score: float
|
semantic_score: float
|
||||||
|
|
@ -133,10 +136,11 @@ class QueryHit(BaseModel):
|
||||||
source: Optional[Dict] = None
|
source: Optional[Dict] = None
|
||||||
payload: Optional[Dict] = None
|
payload: Optional[Dict] = None
|
||||||
explanation: Optional[Explanation] = None
|
explanation: Optional[Explanation] = None
|
||||||
|
stream_origin: Optional[str] = Field(None, description="Name des Ursprungs-Streams")
|
||||||
|
|
||||||
|
|
||||||
class QueryResponse(BaseModel):
|
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()))
|
query_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
||||||
results: List[QueryHit]
|
results: List[QueryHit]
|
||||||
used_mode: str
|
used_mode: str
|
||||||
|
|
@ -153,11 +157,12 @@ class GraphResponse(BaseModel):
|
||||||
|
|
||||||
class ChatResponse(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")
|
query_id: str = Field(..., description="Traceability ID")
|
||||||
answer: str = Field(..., description="Generierte Antwort vom LLM")
|
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
|
latency_ms: int
|
||||||
intent: Optional[str] = Field("FACT", description="WP-06: Erkannter Intent")
|
intent: Optional[str] = Field("FACT", description="Die gewählte WP-25 Strategie")
|
||||||
intent_source: Optional[str] = Field("Unknown", description="Quelle der Intent-Erkennung")
|
intent_source: Optional[str] = Field("LLM_Router", description="Quelle der Intent-Erkennung")
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
"""
|
"""
|
||||||
FILE: app/routers/chat.py
|
FILE: app/routers/chat.py
|
||||||
DESCRIPTION: Haupt-Chat-Interface (RAG & Interview). Enthält Intent-Router (Keywords/LLM) und Prompt-Construction.
|
DESCRIPTION: Haupt-Chat-Interface (WP-25 Agentic Edition).
|
||||||
VERSION: 2.7.8 (Full Unabridged Stability Edition)
|
Kombiniert die spezialisierte Interview-Logik und Keyword-Erkennung
|
||||||
|
mit der neuen Multi-Stream Orchestrierung der DecisionEngine.
|
||||||
|
VERSION: 3.0.2
|
||||||
STATUS: Active
|
STATUS: Active
|
||||||
FIX:
|
FIX:
|
||||||
1. Implementiert Context-Throttling für Ollama (MAX_OLLAMA_CHARS).
|
- 100% Wiederherstellung der v2.7.8 Logik (Interview, Schema-Resolution, Keywords).
|
||||||
2. Deaktiviert LLM-Retries für den Chat (max_retries=0).
|
- Integration der DecisionEngine für paralleles RAG-Retrieval.
|
||||||
3. Behebt Double-Fallback-Schleifen und Silent Refusals.
|
- 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
|
from fastapi import APIRouter, HTTPException, Depends
|
||||||
|
|
@ -19,47 +22,40 @@ import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from app.config import get_settings
|
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.services.llm_service import LLMService
|
||||||
from app.core.retrieval.retriever import Retriever
|
|
||||||
from app.services.feedback_service import log_search
|
from app.services.feedback_service import log_search
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# --- Helper: Config Loader ---
|
# --- EBENE 1: CONFIG LOADER & CACHING (Restauriert aus v2.7.8) ---
|
||||||
|
|
||||||
_DECISION_CONFIG_CACHE = None
|
_DECISION_CONFIG_CACHE = None
|
||||||
_TYPES_CONFIG_CACHE = None
|
_TYPES_CONFIG_CACHE = None
|
||||||
|
|
||||||
def _load_decision_config() -> Dict[str, Any]:
|
def _load_decision_config() -> Dict[str, Any]:
|
||||||
|
"""Lädt die Strategie-Konfiguration (Kompatibilität zu WP-25)."""
|
||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
path = Path(settings.DECISION_CONFIG_PATH)
|
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:
|
try:
|
||||||
with open(path, "r", encoding="utf-8") as f:
|
if path.exists():
|
||||||
return yaml.safe_load(f)
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
|
return yaml.safe_load(f) or {}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to load decision config: {e}")
|
logger.error(f"Failed to load decision config: {e}")
|
||||||
return default_config
|
return {"strategies": {}}
|
||||||
|
|
||||||
def _load_types_config() -> Dict[str, Any]:
|
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")
|
path = os.getenv("MINDNET_TYPES_FILE", "config/types.yaml")
|
||||||
try:
|
try:
|
||||||
with open(path, "r", encoding="utf-8") as f:
|
if os.path.exists(path):
|
||||||
return yaml.safe_load(f) or {}
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
except Exception:
|
return yaml.safe_load(f) or {}
|
||||||
return {}
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to load types config: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
def get_full_config() -> Dict[str, Any]:
|
def get_full_config() -> Dict[str, Any]:
|
||||||
global _DECISION_CONFIG_CACHE
|
global _DECISION_CONFIG_CACHE
|
||||||
|
|
@ -76,21 +72,20 @@ def get_types_config() -> Dict[str, Any]:
|
||||||
def get_decision_strategy(intent: str) -> Dict[str, Any]:
|
def get_decision_strategy(intent: str) -> Dict[str, Any]:
|
||||||
config = get_full_config()
|
config = get_full_config()
|
||||||
strategies = config.get("strategies", {})
|
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:
|
def _detect_target_type(message: str, configured_schemas: Dict[str, Any]) -> str:
|
||||||
"""
|
"""
|
||||||
Versucht zu erraten, welchen Notiz-Typ der User erstellen will.
|
WP-07: Identifiziert den gewünschten Notiz-Typ (Keyword-basiert).
|
||||||
Nutzt Keywords aus types.yaml UND Mappings.
|
100% identisch mit v2.7.8 zur Sicherstellung des Interview-Workflows.
|
||||||
"""
|
"""
|
||||||
message_lower = message.lower()
|
message_lower = message.lower()
|
||||||
|
|
||||||
# 1. Check types.yaml detection_keywords (Priority!)
|
|
||||||
types_cfg = get_types_config()
|
types_cfg = get_types_config()
|
||||||
types_def = types_cfg.get("types", {})
|
types_def = types_cfg.get("types", {})
|
||||||
|
|
||||||
|
# 1. Check types.yaml detection_keywords
|
||||||
for type_name, type_data in types_def.items():
|
for type_name, type_data in types_def.items():
|
||||||
keywords = type_data.get("detection_keywords", [])
|
keywords = type_data.get("detection_keywords", [])
|
||||||
for kw in 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:
|
if type_key in message_lower:
|
||||||
return type_key
|
return type_key
|
||||||
|
|
||||||
# 3. Synonym-Mapping (Legacy Fallback)
|
# 3. Synonym-Mapping (Legacy)
|
||||||
synonyms = {
|
synonyms = {
|
||||||
"projekt": "project", "vorhaben": "project",
|
"projekt": "project", "entscheidung": "decision", "ziel": "goal",
|
||||||
"entscheidung": "decision", "beschluss": "decision",
|
"erfahrung": "experience", "wert": "value", "prinzip": "principle"
|
||||||
"ziel": "goal",
|
|
||||||
"erfahrung": "experience", "lektion": "experience",
|
|
||||||
"wert": "value",
|
|
||||||
"prinzip": "principle",
|
|
||||||
"notiz": "default", "idee": "default"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for term, schema_key in synonyms.items():
|
for term, schema_key in synonyms.items():
|
||||||
if term in message_lower:
|
if term in message_lower:
|
||||||
return schema_key
|
return schema_key
|
||||||
|
|
||||||
return "default"
|
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:
|
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()
|
q = query.strip().lower()
|
||||||
if "?" in q: return True
|
if "?" in q: return True
|
||||||
|
starters = ["wer", "wie", "was", "wo", "wann", "warum", "weshalb", "wozu", "welche", "bist du"]
|
||||||
# W-Fragen Indikatoren
|
return any(q.startswith(s + " ") for s in starters)
|
||||||
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
|
|
||||||
|
|
||||||
async def _classify_intent(query: str, llm: LLMService) -> tuple[str, str]:
|
async def _classify_intent(query: str, llm: LLMService) -> tuple[str, str]:
|
||||||
"""
|
"""
|
||||||
Hybrid Router v5:
|
WP-25 Hybrid Router:
|
||||||
1. Decision Keywords (Strategie) -> Prio 1
|
Nutzt erst Keyword-Fast-Paths (Router) und delegiert dann an die DecisionEngine.
|
||||||
2. Type Keywords (Interview Trigger) -> Prio 2
|
|
||||||
3. LLM (Fallback) -> Prio 3
|
|
||||||
"""
|
"""
|
||||||
config = get_full_config()
|
config = get_full_config()
|
||||||
strategies = config.get("strategies", {})
|
strategies = config.get("strategies", {})
|
||||||
settings = config.get("settings", {})
|
|
||||||
|
|
||||||
query_lower = query.lower()
|
query_lower = query.lower()
|
||||||
|
|
||||||
# 1. FAST PATH A: Strategie Keywords
|
# 1. FAST PATH: Keyword Trigger
|
||||||
for intent_name, strategy in strategies.items():
|
for intent_name, strategy in strategies.items():
|
||||||
if intent_name == "FACT": continue
|
|
||||||
keywords = strategy.get("trigger_keywords", [])
|
keywords = strategy.get("trigger_keywords", [])
|
||||||
for k in keywords:
|
for k in keywords:
|
||||||
if k.lower() in query_lower:
|
if k.lower() in query_lower:
|
||||||
return intent_name, "Keyword (Strategy)"
|
return intent_name, "Keyword (FastPath)"
|
||||||
|
|
||||||
# 2. FAST PATH B: Type Keywords -> INTERVIEW
|
# 2. FAST PATH B: Type Keywords -> INTERVIEW
|
||||||
if not _is_question(query_lower):
|
if not _is_question(query_lower):
|
||||||
types_cfg = get_types_config()
|
types_cfg = get_types_config()
|
||||||
types_def = types_cfg.get("types", {})
|
for type_name, type_data in types_cfg.get("types", {}).items():
|
||||||
|
for kw in type_data.get("detection_keywords", []):
|
||||||
for type_name, type_data in types_def.items():
|
|
||||||
keywords = type_data.get("detection_keywords", [])
|
|
||||||
for kw in keywords:
|
|
||||||
if kw.lower() in query_lower:
|
if kw.lower() in query_lower:
|
||||||
return "INTERVIEW", f"Keyword (Type: {type_name})"
|
return "INTERVIEW", "Keyword (Interview)"
|
||||||
|
|
||||||
# 3. SLOW PATH: LLM Router
|
# 3. SLOW PATH: DecisionEngine LLM Router
|
||||||
if settings.get("llm_fallback_enabled", False):
|
intent = await llm.decision_engine._determine_strategy(query)
|
||||||
router_prompt_template = llm.get_prompt("llm_router_prompt")
|
return intent, "DecisionEngine (LLM)"
|
||||||
|
|
||||||
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"
|
|
||||||
|
|
||||||
for strat_key in strategies.keys():
|
# --- EBENE 3: RETRIEVAL AGGREGATION ---
|
||||||
if strat_key in llm_output_upper:
|
|
||||||
return strat_key, "LLM Router"
|
def _collect_all_hits(stream_responses: Dict[str, Any]) -> List[QueryHit]:
|
||||||
|
"""Sammelt und dedupliziert Treffer aus allen parallelen Streams."""
|
||||||
except Exception as e:
|
all_hits = []
|
||||||
logger.error(f"Router LLM failed: {e}")
|
seen_node_ids = set()
|
||||||
|
for _, response in stream_responses.items():
|
||||||
return "FACT", "Default (No Match)"
|
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)
|
@router.post("/", response_model=ChatResponse)
|
||||||
async def chat_endpoint(
|
async def chat_endpoint(
|
||||||
request: ChatRequest,
|
request: ChatRequest,
|
||||||
llm: LLMService = Depends(get_llm_service),
|
llm: LLMService = Depends(get_llm_service)
|
||||||
retriever: Retriever = Depends(get_retriever)
|
|
||||||
):
|
):
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
query_id = str(uuid.uuid4())
|
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:
|
try:
|
||||||
# 1. Intent Detection
|
# 1. Intent Detection
|
||||||
intent, intent_source = await _classify_intent(request.message, llm)
|
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)
|
strategy = get_decision_strategy(intent)
|
||||||
prompt_key = strategy.get("prompt_template", "rag_template")
|
engine = llm.decision_engine
|
||||||
preferred_provider = strategy.get("preferred_provider")
|
|
||||||
|
|
||||||
sources_hits = []
|
sources_hits = []
|
||||||
final_prompt = ""
|
answer_text = ""
|
||||||
context_str = ""
|
|
||||||
|
# 2. INTERVIEW MODE (Kompatibilität zu v2.7.8)
|
||||||
if intent == "INTERVIEW":
|
if intent == "INTERVIEW":
|
||||||
# --- INTERVIEW MODE ---
|
|
||||||
target_type = _detect_target_type(request.message, strategy.get("schemas", {}))
|
target_type = _detect_target_type(request.message, strategy.get("schemas", {}))
|
||||||
|
|
||||||
types_cfg = get_types_config()
|
types_cfg = get_types_config()
|
||||||
type_def = types_cfg.get("types", {}).get(target_type, {})
|
type_def = types_cfg.get("types", {}).get(target_type, {})
|
||||||
fields_list = type_def.get("schema", [])
|
fields_list = type_def.get("schema", [])
|
||||||
|
|
||||||
if not fields_list:
|
if not fields_list:
|
||||||
configured_schemas = strategy.get("schemas", {})
|
configured_schemas = strategy.get("schemas", {})
|
||||||
fallback_schema = configured_schemas.get(target_type, configured_schemas.get("default"))
|
fallback = configured_schemas.get(target_type, configured_schemas.get("default", {}))
|
||||||
if isinstance(fallback_schema, dict):
|
fields_list = fallback.get("fields", []) if isinstance(fallback, dict) else (fallback or [])
|
||||||
fields_list = fallback_schema.get("fields", [])
|
|
||||||
else:
|
|
||||||
fields_list = fallback_schema or []
|
|
||||||
|
|
||||||
logger.info(f"[{query_id}] Interview Type: {target_type}. Fields: {len(fields_list)}")
|
|
||||||
fields_str = "\n- " + "\n- ".join(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("{query}", request.message) \
|
||||||
final_prompt = template.replace("{context_str}", "Dialogverlauf...") \
|
|
||||||
.replace("{query}", request.message) \
|
|
||||||
.replace("{target_type}", target_type) \
|
.replace("{target_type}", target_type) \
|
||||||
.replace("{schema_fields}", fields_str) \
|
.replace("{schema_fields}", fields_str)
|
||||||
.replace("{schema_hint}", "")
|
|
||||||
sources_hits = []
|
|
||||||
|
|
||||||
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(
|
answer_text = await llm.generate_raw_response(
|
||||||
prompt=final_prompt,
|
final_prompt, system=llm.get_prompt("system_prompt"),
|
||||||
system=system_prompt,
|
priority="realtime", provider=strategy.get("preferred_provider"), max_retries=0
|
||||||
priority="realtime",
|
|
||||||
provider=preferred_provider,
|
|
||||||
max_retries=0
|
|
||||||
)
|
)
|
||||||
except Exception as e:
|
sources_hits = []
|
||||||
logger.error(f"🛑 [{query_id}] Primary Provider '{preferred_provider}' failed: {e}")
|
|
||||||
|
|
||||||
# DEEP FALLBACK: Wenn die Antwort leer ist (Silent Refusal) oder der Primary abgestürzt ist
|
# 3. RAG MODE (WP-25 Multi-Stream)
|
||||||
if not answer_text.strip() and preferred_provider != "ollama":
|
else:
|
||||||
# --- DEBUG SPOT 3: FALLBACK TRIGGER ---
|
stream_keys = strategy.get("use_streams", [])
|
||||||
logger.warning(f"🛑 [{query_id}] PRIMARY '{preferred_provider}' returned EMPTY or FAILED. Triggering Deep Fallback to Ollama...")
|
library = engine.config.get("streams_library", {})
|
||||||
|
|
||||||
try:
|
tasks = []
|
||||||
answer_text = await llm.generate_raw_response(
|
active_streams = []
|
||||||
prompt=final_prompt,
|
for key in stream_keys:
|
||||||
system=system_prompt,
|
stream_cfg = library.get(key)
|
||||||
priority="realtime",
|
if stream_cfg:
|
||||||
provider="ollama",
|
active_streams.append(key)
|
||||||
max_retries=0
|
tasks.append(engine._run_single_stream(key, stream_cfg, request.message))
|
||||||
)
|
|
||||||
except Exception as e:
|
import asyncio
|
||||||
logger.error(f"🛑 [{query_id}] Deep Fallback to Ollama also failed: {e}")
|
responses = await asyncio.gather(*tasks, return_exceptions=True)
|
||||||
answer_text = "Entschuldigung, das System ist aktuell überlastet. Bitte versuche es in einem Moment erneut."
|
|
||||||
|
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)
|
duration_ms = int((time.time() - start_time) * 1000)
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
try:
|
try:
|
||||||
log_search(
|
log_search(
|
||||||
query_id=query_id,
|
query_id=query_id, query_text=request.message, results=sources_hits,
|
||||||
query_text=request.message,
|
mode=f"wp25_{intent.lower()}", metadata={"strategy": intent, "source": intent_source}
|
||||||
results=sources_hits,
|
|
||||||
mode="interview" if intent == "INTERVIEW" else "chat_rag",
|
|
||||||
metadata={"intent": intent, "source": intent_source, "provider": preferred_provider}
|
|
||||||
)
|
)
|
||||||
except: pass
|
except: pass
|
||||||
|
|
||||||
return ChatResponse(
|
return ChatResponse(
|
||||||
query_id=query_id,
|
query_id=query_id, answer=answer_text, sources=sources_hits,
|
||||||
answer=answer_text,
|
latency_ms=duration_ms, intent=intent, intent_source=intent_source
|
||||||
sources=sources_hits,
|
|
||||||
latency_ms=duration_ms,
|
|
||||||
intent=intent,
|
|
||||||
intent_source=intent_source
|
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in chat endpoint: {e}", exc_info=True)
|
logger.error(f"❌ Chat Endpoint Failure: {e}", exc_info=True)
|
||||||
# Wir geben eine benutzerfreundliche Meldung zurück, statt nur den Error-Stack
|
raise HTTPException(status_code=500, detail="Fehler bei der Verarbeitung.")
|
||||||
raise HTTPException(status_code=500, detail="Das System konnte die Anfrage nicht verarbeiten.")
|
|
||||||
|
|
@ -3,14 +3,14 @@ FILE: app/services/llm_service.py
|
||||||
DESCRIPTION: Hybrid-Client für Ollama, Google GenAI (Gemini) und OpenRouter.
|
DESCRIPTION: Hybrid-Client für Ollama, Google GenAI (Gemini) und OpenRouter.
|
||||||
Verwaltet provider-spezifische Prompts und Background-Last.
|
Verwaltet provider-spezifische Prompts und Background-Last.
|
||||||
WP-20: Optimiertes Fallback-Management zum Schutz von Cloud-Quoten.
|
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).
|
||||||
WP-22/JSON: Optionales JSON-Schema + strict (für OpenRouter structured outputs).
|
WP-25: Integration der DecisionEngine für Agentic Multi-Stream RAG.
|
||||||
FIX: Intelligente Rate-Limit Erkennung (429 Handling), v1-API Sync & Timeouts.
|
VERSION: 3.4.2 (WP-25: Ingest-Stability Patch)
|
||||||
VERSION: 3.3.9
|
|
||||||
STATUS: Active
|
STATUS: Active
|
||||||
FIX:
|
FIX:
|
||||||
- Importiert clean_llm_text von app.core.registry zur Vermeidung von Circular Imports.
|
- Ingest-Stability: Entfernung des <5-Zeichen Guards (ermöglicht YES/NO Validierungen).
|
||||||
- Wendet clean_llm_text auf Text-Antworten in generate_raw_response an.
|
- 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 httpx
|
||||||
import yaml
|
import yaml
|
||||||
|
|
@ -29,7 +29,6 @@ from app.core.registry import clean_llm_text
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class LLMService:
|
class LLMService:
|
||||||
# GLOBALER SEMAPHOR für Hintergrund-Last Steuerung (WP-06)
|
# GLOBALER SEMAPHOR für Hintergrund-Last Steuerung (WP-06)
|
||||||
_background_semaphore = None
|
_background_semaphore = None
|
||||||
|
|
@ -37,6 +36,9 @@ class LLMService:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.settings = get_settings()
|
self.settings = get_settings()
|
||||||
self.prompts = self._load_prompts()
|
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
|
# Initialisiere Semaphore einmalig auf Klassen-Ebene
|
||||||
if LLMService._background_semaphore is None:
|
if LLMService._background_semaphore is None:
|
||||||
|
|
@ -71,6 +73,14 @@ class LLMService:
|
||||||
)
|
)
|
||||||
logger.info("🛰️ LLMService: OpenRouter Integration active.")
|
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:
|
def _load_prompts(self) -> dict:
|
||||||
"""Lädt die Prompt-Konfiguration aus der YAML-Datei."""
|
"""Lädt die Prompt-Konfiguration aus der YAML-Datei."""
|
||||||
path = Path(self.settings.PROMPTS_PATH)
|
path = Path(self.settings.PROMPTS_PATH)
|
||||||
|
|
@ -87,17 +97,13 @@ class LLMService:
|
||||||
def get_prompt(self, key: str, provider: str = None) -> str:
|
def get_prompt(self, key: str, provider: str = None) -> str:
|
||||||
"""
|
"""
|
||||||
Hole provider-spezifisches Template mit intelligenter Text-Kaskade.
|
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 -> Ollama.
|
||||||
Kaskade: Gewählter Provider -> Gemini (Cloud-Stil) -> Ollama (Basis-Stil).
|
|
||||||
"""
|
"""
|
||||||
active_provider = provider or self.settings.MINDNET_LLM_PROVIDER
|
active_provider = provider or self.settings.MINDNET_LLM_PROVIDER
|
||||||
data = self.prompts.get(key, "")
|
data = self.prompts.get(key, "")
|
||||||
|
|
||||||
if isinstance(data, dict):
|
if isinstance(data, dict):
|
||||||
# Wir versuchen erst den Provider, dann Gemini, dann Ollama
|
|
||||||
val = data.get(active_provider, data.get("gemini", data.get("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):
|
if isinstance(val, dict):
|
||||||
logger.warning(f"⚠️ [LLMService] Nested dictionary detected for key '{key}'. Using first entry.")
|
logger.warning(f"⚠️ [LLMService] Nested dictionary detected for key '{key}'. Using first entry.")
|
||||||
val = next(iter(val.values()), "") if val else ""
|
val = next(iter(val.values()), "") if val else ""
|
||||||
|
|
@ -120,8 +126,8 @@ class LLMService:
|
||||||
strict_json_schema: bool = True
|
strict_json_schema: bool = True
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
Haupteinstiegspunkt für LLM-Anfragen mit Priorisierung.
|
Haupteinstiegspunkt für LLM-Anfragen.
|
||||||
Wendet die Bereinigung auf Text-Antworten an.
|
WP-25 FIX: Schwellenwert entfernt, um kurze Ingest-Validierungen (YES/NO) zu unterstützen.
|
||||||
"""
|
"""
|
||||||
target_provider = provider or self.settings.MINDNET_LLM_PROVIDER
|
target_provider = provider or self.settings.MINDNET_LLM_PROVIDER
|
||||||
|
|
||||||
|
|
@ -132,14 +138,18 @@ class LLMService:
|
||||||
max_retries, base_delay, model_override,
|
max_retries, base_delay, model_override,
|
||||||
json_schema, json_schema_name, strict_json_schema
|
json_schema, json_schema_name, strict_json_schema
|
||||||
)
|
)
|
||||||
# WP-14 Fix: Bereinige Text-Antworten vor Rückgabe
|
else:
|
||||||
return clean_llm_text(res) if not force_json else res
|
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
|
# WP-14 Fix: Bereinige Text-Antworten vor Rückgabe
|
||||||
return clean_llm_text(res) if not force_json else res
|
return clean_llm_text(res) if not force_json else res
|
||||||
|
|
||||||
|
|
@ -156,12 +166,8 @@ class LLMService:
|
||||||
json_schema_name: str,
|
json_schema_name: str,
|
||||||
strict_json_schema: bool
|
strict_json_schema: bool
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""Routet die Anfrage mit intelligenter Rate-Limit Erkennung."""
|
||||||
Routet die Anfrage mit intelligenter Rate-Limit Erkennung.
|
|
||||||
Nutzt max_retries um die Rate-Limit Schleife zu begrenzen.
|
|
||||||
"""
|
|
||||||
rate_limit_attempts = 0
|
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))
|
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)
|
wait_time = getattr(self.settings, "LLM_RATE_LIMIT_WAIT", 60.0)
|
||||||
|
|
||||||
|
|
@ -181,33 +187,24 @@ class LLMService:
|
||||||
if provider == "gemini" and self.google_client:
|
if provider == "gemini" and self.google_client:
|
||||||
return await self._execute_google(prompt, system, force_json, model_override)
|
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)
|
return await self._execute_ollama(prompt, system, force_json, max_retries, base_delay)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
err_str = str(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"])
|
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:
|
if is_rate_limit and rate_limit_attempts < max_rate_retries:
|
||||||
rate_limit_attempts += 1
|
rate_limit_attempts += 1
|
||||||
logger.warning(
|
logger.warning(f"⏳ Rate Limit from {provider}. Attempt {rate_limit_attempts}. Waiting {wait_time}s...")
|
||||||
f"⏳ [LLMService] Rate Limit detected from {provider}. "
|
|
||||||
f"Attempt {rate_limit_attempts}/{max_rate_retries}. Waiting {wait_time}s..."
|
|
||||||
)
|
|
||||||
await asyncio.sleep(wait_time)
|
await asyncio.sleep(wait_time)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Wenn kein Rate-Limit oder Retries erschöpft -> Fallback zu Ollama (falls aktiviert)
|
|
||||||
if self.settings.LLM_FALLBACK_ENABLED and provider != "ollama":
|
if self.settings.LLM_FALLBACK_ENABLED and provider != "ollama":
|
||||||
logger.warning(
|
logger.warning(f"🔄 Provider {provider} failed ({err_str}). Falling back to OLLAMA.")
|
||||||
f"🔄 Provider {provider} failed ({err_str}). Falling back to LOCAL OLLAMA."
|
|
||||||
)
|
|
||||||
return await self._execute_ollama(prompt, system, force_json, max_retries, base_delay)
|
return await self._execute_ollama(prompt, system, force_json, max_retries, base_delay)
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
async def _execute_google(self, prompt, system, force_json, model_override):
|
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
|
model = model_override or self.settings.GEMINI_MODEL
|
||||||
clean_model = model.replace("models/", "")
|
clean_model = model.replace("models/", "")
|
||||||
|
|
||||||
|
|
@ -234,7 +231,7 @@ class LLMService:
|
||||||
json_schema_name: str = "mindnet_json",
|
json_schema_name: str = "mindnet_json",
|
||||||
strict_json_schema: bool = True
|
strict_json_schema: bool = True
|
||||||
) -> str:
|
) -> str:
|
||||||
"""OpenRouter API Integration (OpenAI-kompatibel)."""
|
"""OpenRouter API Integration. WP-25 FIX: Sicherung gegen leere 'choices'."""
|
||||||
model = model_override or self.settings.OPENROUTER_MODEL
|
model = model_override or self.settings.OPENROUTER_MODEL
|
||||||
messages = []
|
messages = []
|
||||||
if system:
|
if system:
|
||||||
|
|
@ -247,9 +244,7 @@ class LLMService:
|
||||||
kwargs["response_format"] = {
|
kwargs["response_format"] = {
|
||||||
"type": "json_schema",
|
"type": "json_schema",
|
||||||
"json_schema": {
|
"json_schema": {
|
||||||
"name": json_schema_name,
|
"name": json_schema_name, "strict": strict_json_schema, "schema": json_schema
|
||||||
"strict": strict_json_schema,
|
|
||||||
"schema": json_schema
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
|
|
@ -260,23 +255,23 @@ class LLMService:
|
||||||
messages=messages,
|
messages=messages,
|
||||||
**kwargs
|
**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):
|
async def _execute_ollama(self, prompt, system, force_json, max_retries, base_delay):
|
||||||
"""Lokaler Ollama Call mit striktem Retry-Limit."""
|
|
||||||
payload = {
|
payload = {
|
||||||
"model": self.settings.LLM_MODEL,
|
"model": self.settings.LLM_MODEL,
|
||||||
"prompt": prompt,
|
"prompt": prompt,
|
||||||
"stream": False,
|
"stream": False,
|
||||||
"options": {
|
"options": {"temperature": 0.1 if force_json else 0.7, "num_ctx": 8192}
|
||||||
"temperature": 0.1 if force_json else 0.7,
|
|
||||||
"num_ctx": 8192 # Begrenzung für Stabilität (WP-20)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if force_json:
|
if force_json: payload["format"] = "json"
|
||||||
payload["format"] = "json"
|
if system: payload["system"] = system
|
||||||
if system:
|
|
||||||
payload["system"] = system
|
|
||||||
|
|
||||||
attempt = 0
|
attempt = 0
|
||||||
while True:
|
while True:
|
||||||
|
|
@ -286,32 +281,17 @@ class LLMService:
|
||||||
return res.json().get("response", "").strip()
|
return res.json().get("response", "").strip()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
attempt += 1
|
attempt += 1
|
||||||
# WICHTIG: Wenn max_retries=0 (Chat), bricht dies nach dem 1. Versuch (attempt=1) sofort ab.
|
|
||||||
if attempt > max_retries:
|
if attempt > max_retries:
|
||||||
logger.error(f"❌ Ollama request failed after {attempt} attempt(s): {e}")
|
logger.error(f"❌ Ollama request failed: {e}")
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
wait_time = base_delay * (2 ** (attempt - 1))
|
wait_time = base_delay * (2 ** (attempt - 1))
|
||||||
logger.warning(f"⚠️ Ollama attempt {attempt} failed. Retrying in {wait_time}s...")
|
|
||||||
await asyncio.sleep(wait_time)
|
await asyncio.sleep(wait_time)
|
||||||
|
|
||||||
async def generate_rag_response(self, query: str, context_str: str) -> str:
|
async def generate_rag_response(self, query: str, context_str: Optional[str] = None) -> str:
|
||||||
"""Vollständiges RAG Chat-Interface."""
|
"""WP-25: Orchestrierung via DecisionEngine."""
|
||||||
provider = self.settings.MINDNET_LLM_PROVIDER
|
logger.info(f"🚀 [WP-25] Chat Query: {query[:50]}...")
|
||||||
system_prompt = self.get_prompt("system_prompt", provider)
|
return await self.decision_engine.ask(query)
|
||||||
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 close(self):
|
async def close(self):
|
||||||
"""Schließt die HTTP-Verbindungen."""
|
|
||||||
if self.ollama_client:
|
if self.ollama_client:
|
||||||
await self.ollama_client.aclose()
|
await self.ollama_client.aclose()
|
||||||
|
|
@ -1,145 +1,144 @@
|
||||||
# config/decision_engine.yaml
|
# config/decision_engine.yaml
|
||||||
# Steuerung der Decision Engine (Intent Recognition & Graph Routing)
|
# VERSION: 3.1.6 (WP-25: Multi-Stream Agentic RAG - Final Release)
|
||||||
# VERSION: 2.6.1 (WP-20: Hybrid LLM & WP-22: Semantic Graph Routing)
|
|
||||||
# STATUS: Active
|
# 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:
|
settings:
|
||||||
llm_fallback_enabled: true
|
llm_fallback_enabled: true
|
||||||
|
# "auto" nutzt den in MINDNET_LLM_PROVIDER gesetzten Standard.
|
||||||
# Strategie für den Router selbst (Welches Modell erkennt den Intent?)
|
|
||||||
# "auto" nutzt den in MINDNET_LLM_PROVIDER gesetzten Standard (z.B. openrouter).
|
|
||||||
router_provider: "auto"
|
router_provider: "auto"
|
||||||
|
# Verweist auf das Template in prompts.yaml
|
||||||
|
router_prompt_key: "intent_router_v1"
|
||||||
|
|
||||||
# Few-Shot Prompting für den LLM-Router
|
# --- EBENE 1: STREAM-LIBRARY (Bausteine basierend auf types.yaml) ---
|
||||||
llm_router_prompt: |
|
# Synchronisiert mit types.yaml v2.7.0
|
||||||
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:
|
|
||||||
|
|
||||||
strategies:
|
streams_library:
|
||||||
# 1. Fakten-Abfrage (Turbo-Modus via OpenRouter / Primary)
|
values_stream:
|
||||||
FACT:
|
name: "Identität & Ethik"
|
||||||
description: "Reine Wissensabfrage."
|
query_template: "Welche meiner Werte und Prinzipien betreffen: {query}"
|
||||||
preferred_provider: "openrouter"
|
# Nur Typen aus types.yaml
|
||||||
trigger_keywords: []
|
filter_types: ["value", "principle", "belief", "trait", "boundary", "need", "motivation"]
|
||||||
inject_types: []
|
top_k: 5
|
||||||
# WP-22: Definitionen & Hierarchien im Graphen bevorzugen
|
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:
|
edge_boosts:
|
||||||
part_of: 2.0
|
part_of: 2.0
|
||||||
composed_of: 2.0
|
depends_on: 1.5
|
||||||
similar_to: 1.5
|
implemented_in: 1.5
|
||||||
caused_by: 0.5
|
|
||||||
prompt_template: "rag_template"
|
|
||||||
prepend_instruction: null
|
|
||||||
|
|
||||||
# 2. Entscheidungs-Frage (Power-Strategie via Gemini)
|
biography_stream:
|
||||||
DECISION:
|
name: "Persönliche Erfahrung"
|
||||||
description: "Der User sucht Rat, Strategie oder Abwägung."
|
query_template: "Welche Erlebnisse habe ich im Kontext von {query} gemacht?"
|
||||||
preferred_provider: "gemini"
|
# Nur Typen aus types.yaml
|
||||||
trigger_keywords:
|
filter_types: ["experience", "journal", "profile", "person"]
|
||||||
- "soll ich"
|
top_k: 3
|
||||||
- "meinung"
|
edge_boosts:
|
||||||
- "besser"
|
related_to: 1.5
|
||||||
- "empfehlung"
|
experienced_in: 2.0
|
||||||
- "strategie"
|
|
||||||
- "entscheidung"
|
risk_stream:
|
||||||
- "abwägung"
|
name: "Risiko-Radar"
|
||||||
- "vergleich"
|
query_template: "Gefahren, Hindernisse oder Risiken bei: {query}"
|
||||||
inject_types: ["value", "principle", "goal", "risk"]
|
# Nur Typen aus types.yaml
|
||||||
# WP-22: Risiken und Konsequenzen im Graphen priorisieren
|
filter_types: ["risk", "obstacle", "bias"]
|
||||||
|
top_k: 3
|
||||||
edge_boosts:
|
edge_boosts:
|
||||||
blocks: 2.5
|
blocks: 2.5
|
||||||
solves: 2.0
|
|
||||||
depends_on: 1.5
|
|
||||||
risk_of: 2.5
|
|
||||||
impacts: 2.0
|
impacts: 2.0
|
||||||
prompt_template: "decision_template"
|
risk_of: 2.5
|
||||||
prepend_instruction: |
|
|
||||||
!!! ENTSCHEIDUNGS-MODUS (HYBRID AI) !!!
|
|
||||||
BITTE WÄGE FAKTEN GEGEN FOLGENDE WERTE, PRINZIPIEN UND ZIELE AB:
|
|
||||||
|
|
||||||
# 3. Empathie / "Ich"-Modus (Lokal & Privat via Ollama)
|
tech_stream:
|
||||||
EMPATHY:
|
name: "Wissen & Technik"
|
||||||
description: "Reaktion auf emotionale Zustände."
|
query_template: "Inhaltliche Details und Definitionen zu: {query}"
|
||||||
preferred_provider: "openrouter"
|
# Nur Typen aus types.yaml
|
||||||
trigger_keywords:
|
filter_types: ["concept", "source", "glossary", "idea", "insight", "skill", "habit"]
|
||||||
- "ich fühle"
|
top_k: 5
|
||||||
- "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
|
|
||||||
edge_boosts:
|
edge_boosts:
|
||||||
uses: 2.5
|
uses: 2.5
|
||||||
depends_on: 2.0
|
|
||||||
implemented_in: 3.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:
|
INTERVIEW:
|
||||||
description: "Der User möchte Wissen erfassen."
|
description: "Der User möchte Wissen erfassen (Eingabemodus)."
|
||||||
preferred_provider: "openrouter"
|
preferred_provider: "openrouter"
|
||||||
trigger_keywords:
|
use_streams: []
|
||||||
- "neue notiz"
|
prompt_template: "interview_template"
|
||||||
- "etwas notieren"
|
|
||||||
- "festhalten"
|
|
||||||
- "erstellen"
|
|
||||||
- "dokumentieren"
|
|
||||||
- "anlegen"
|
|
||||||
- "interview"
|
|
||||||
- "erfassen"
|
|
||||||
- "idee speichern"
|
|
||||||
- "draft"
|
|
||||||
inject_types: []
|
|
||||||
edge_boosts: {}
|
|
||||||
prompt_template: "interview_template"
|
|
||||||
prepend_instruction: null
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
# config/prompts.yaml — Final V2.6.0 (WP-15b Candidate-Validation)
|
# config/prompts.yaml — VERSION 3.1.2 (WP-25 Cleanup: Multi-Stream Sync)
|
||||||
# WP-20: Optimierte Cloud-Templates zur Unterdrückung von Modell-Geschwätz.
|
# STATUS: Active
|
||||||
# FIX: Explizite Verbote für Einleitungstexte zur Vermeidung von JSON-Parsing-Fehlern.
|
# FIX:
|
||||||
# WP-15b: Integration der binären edge_validation für den Two-Pass Workflow.
|
# - 100% Wiederherstellung der Ingest- & Validierungslogik (Sektion 5-8).
|
||||||
# OLLAMA: UNVERÄNDERT laut Benutzeranweisung.
|
# - Ü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: |
|
system_prompt: |
|
||||||
Du bist 'mindnet', mein digitaler Zwilling und strategischer Partner.
|
Du bist 'mindnet', mein digitaler Zwilling und strategischer Partner.
|
||||||
|
|
@ -17,39 +18,57 @@ system_prompt: |
|
||||||
3. Antworte auf Deutsch (außer bei Code/Fachbegriffen).
|
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: |
|
ollama: |
|
||||||
QUELLEN (WISSEN):
|
WISSENS-STREAMS:
|
||||||
=========================================
|
=========================================
|
||||||
{context_str}
|
FAKTEN & STATUS:
|
||||||
|
{facts_stream}
|
||||||
|
|
||||||
|
ERFAHRUNG & BIOGRAFIE:
|
||||||
|
{biography_stream}
|
||||||
|
|
||||||
|
WISSEN & TECHNIK:
|
||||||
|
{tech_stream}
|
||||||
=========================================
|
=========================================
|
||||||
|
|
||||||
FRAGE:
|
FRAGE:
|
||||||
{query}
|
{query}
|
||||||
|
|
||||||
ANWEISUNG:
|
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.
|
Fasse die Informationen zusammen. Sei objektiv und neutral.
|
||||||
gemini: |
|
gemini: |
|
||||||
Kontext meines digitalen Zwillings: {context_str}
|
Beantworte die Wissensabfrage "{query}" basierend auf diesen Streams:
|
||||||
Beantworte strukturiert und präzise: {query}
|
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: |
|
openrouter: |
|
||||||
Kontext-Analyse für den digitalen Zwilling:
|
Synthese der Wissens-Streams für: {query}
|
||||||
{context_str}
|
Inhalt: {facts_stream} | {biography_stream} | {tech_stream}
|
||||||
|
Antworte basierend auf dem bereitgestellten Kontext.
|
||||||
Anfrage: {query}
|
|
||||||
Antworte basierend auf dem Kontext.
|
|
||||||
|
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
# 2. DECISION: Strategie & Abwägung (Intent: DECISION)
|
# 2. DECISION: Strategie & Abwägung (Intent: DECISION)
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
decision_template:
|
# Ersetzt das alte 'decision_template'. Nutzt jetzt parallele Streams.
|
||||||
|
decision_synthesis_v1:
|
||||||
ollama: |
|
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:
|
ENTSCHEIDUNGSFRAGE:
|
||||||
|
|
@ -58,7 +77,7 @@ decision_template:
|
||||||
ANWEISUNG:
|
ANWEISUNG:
|
||||||
Du agierst als mein Entscheidungs-Partner.
|
Du agierst als mein Entscheidungs-Partner.
|
||||||
1. Analysiere die Faktenlage aus den Quellen.
|
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?
|
3. Wäge ab: Passt die technische/faktische Lösung zu meinen Werten?
|
||||||
|
|
||||||
FORMAT:
|
FORMAT:
|
||||||
|
|
@ -66,19 +85,26 @@ decision_template:
|
||||||
- **Abgleich:** (Gibt es Konflikte mit Werten/Zielen? Nenne die Quelle!)
|
- **Abgleich:** (Gibt es Konflikte mit Werten/Zielen? Nenne die Quelle!)
|
||||||
- **Empfehlung:** (Klare Meinung: Ja/No/Vielleicht mit Begründung)
|
- **Empfehlung:** (Klare Meinung: Ja/No/Vielleicht mit Begründung)
|
||||||
gemini: |
|
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: |
|
openrouter: |
|
||||||
Strategische Entscheidungsanalyse: {query}
|
Strategische Multi-Stream Analyse für: {query}
|
||||||
Wertebasis aus dem Graphen: {context_str}
|
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)
|
# 3. EMPATHY: Der Spiegel / "Ich"-Modus (Intent: EMPATHY)
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
empathy_template:
|
empathy_template:
|
||||||
ollama: |
|
ollama: |
|
||||||
KONTEXT (ERFAHRUNGEN & GLAUBENSSÄTZE):
|
KONTEXT (ERFAHRUNGEN & WERTE):
|
||||||
=========================================
|
=========================================
|
||||||
{context_str}
|
ERLEBNISSE & BIOGRAFIE:
|
||||||
|
{biography_stream}
|
||||||
|
|
||||||
|
WERTE & BEDÜRFNISSE:
|
||||||
|
{values_stream}
|
||||||
=========================================
|
=========================================
|
||||||
|
|
||||||
SITUATION:
|
SITUATION:
|
||||||
|
|
@ -87,22 +113,26 @@ empathy_template:
|
||||||
ANWEISUNG:
|
ANWEISUNG:
|
||||||
Du agierst jetzt als mein empathischer Spiegel.
|
Du agierst jetzt als mein empathischer Spiegel.
|
||||||
1. Versuche nicht sofort, das Problem technisch zu lösen.
|
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.
|
3. Antworte in der "Ich"-Form oder "Wir"-Form. Sei unterstützend.
|
||||||
|
|
||||||
TONFALL:
|
TONFALL:
|
||||||
Ruhig, verständnisvoll, reflektiert. Keine Aufzählungszeichen, sondern fließender Text.
|
Ruhig, verständnisvoll, reflektiert. Keine Aufzählungszeichen, sondern fließender Text.
|
||||||
gemini: "Sei mein digitaler Spiegel für {query}. 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: {context_str}"
|
openrouter: "Empathische Reflexion der Situation {query}. Persönlicher Kontext: {biography_stream}, {values_stream}"
|
||||||
|
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
# 4. TECHNICAL: Der Coder (Intent: CODING)
|
# 4. TECHNICAL: Der Coder (Intent: CODING)
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
technical_template:
|
technical_template:
|
||||||
ollama: |
|
ollama: |
|
||||||
KONTEXT (DOCS & SNIPPETS):
|
KONTEXT (WISSEN & PROJEKTE):
|
||||||
=========================================
|
=========================================
|
||||||
{context_str}
|
TECHNIK & SNIPPETS:
|
||||||
|
{tech_stream}
|
||||||
|
|
||||||
|
PROJEKT-STATUS:
|
||||||
|
{facts_stream}
|
||||||
=========================================
|
=========================================
|
||||||
|
|
||||||
TASK:
|
TASK:
|
||||||
|
|
@ -118,11 +148,11 @@ technical_template:
|
||||||
- Kurze Erklärung des Ansatzes.
|
- Kurze Erklärung des Ansatzes.
|
||||||
- Markdown Code-Block (Copy-Paste fertig).
|
- Markdown Code-Block (Copy-Paste fertig).
|
||||||
- Wichtige Edge-Cases.
|
- Wichtige Edge-Cases.
|
||||||
gemini: "Generiere Code für {query} unter Berücksichtigung von {context_str}."
|
gemini: "Generiere Code für {query} unter Berücksichtigung von {tech_stream} und {facts_stream}."
|
||||||
openrouter: "Technischer Support für {query}. Code-Referenzen: {context_str}"
|
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:
|
interview_template:
|
||||||
ollama: |
|
ollama: |
|
||||||
|
|
@ -160,7 +190,7 @@ interview_template:
|
||||||
openrouter: "Strukturiere den Input {query} nach dem Schema {schema_fields} für Typ {target_type}."
|
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:
|
edge_allocation_template:
|
||||||
ollama: |
|
ollama: |
|
||||||
|
|
@ -200,7 +230,7 @@ edge_allocation_template:
|
||||||
OUTPUT:
|
OUTPUT:
|
||||||
|
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
# 7. SMART EDGE ALLOCATION: Extraktion (Intent: INGEST)
|
# 7. SMART EDGE ALLOCATION: Extraktion (Ingest)
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
edge_extraction:
|
edge_extraction:
|
||||||
ollama: |
|
ollama: |
|
||||||
|
|
@ -240,7 +270,7 @@ edge_extraction:
|
||||||
OUTPUT:
|
OUTPUT:
|
||||||
|
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
# 8. WP-15b: EDGE VALIDATION (Intent: VALIDATE)
|
# 8. WP-15b: EDGE VALIDATION (Ingest/Validate)
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
edge_validation:
|
edge_validation:
|
||||||
gemini: |
|
gemini: |
|
||||||
|
|
@ -270,4 +300,38 @@ edge_validation:
|
||||||
QUELLE: {chunk_text}
|
QUELLE: {chunk_text}
|
||||||
ZIEL: {target_title} ({target_summary})
|
ZIEL: {target_title} ({target_summary})
|
||||||
BEZIEHUNG: {edge_kind}
|
BEZIEHUNG: {edge_kind}
|
||||||
Ist diese Verbindung valide? Antworte NUR mit YES oder NO.
|
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:
|
||||||
|
|
@ -151,8 +151,8 @@ Damit dieses System wartbar bleibt (auch für KI-Agenten wie NotebookLM), gelten
|
||||||
|
|
||||||
## 6. Dokumentations-Status
|
## 6. Dokumentations-Status
|
||||||
|
|
||||||
**Aktuelle Version:** 2.9.1
|
**Aktuelle Version:** 2.9.3
|
||||||
**Letzte Aktualisierung:** 2025-01-XX
|
**Letzte Aktualisierung:** 2025-12-31
|
||||||
**Status:** ✅ Vollständig und aktiv gepflegt
|
**Status:** ✅ Vollständig und aktiv gepflegt
|
||||||
|
|
||||||
**Hinweis:** Diese Dokumentation wird kontinuierlich aktualisiert. Bei Fragen oder Verbesserungsvorschlägen bitte im Repository melden.
|
**Hinweis:** Diese Dokumentation wird kontinuierlich aktualisiert. Bei Fragen oder Verbesserungsvorschlägen bitte im Repository melden.
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
doc_type: glossary
|
doc_type: glossary
|
||||||
audience: all
|
audience: all
|
||||||
status: active
|
status: active
|
||||||
version: 2.9.1
|
version: 2.9.3
|
||||||
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."
|
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
|
# 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.
|
* **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.
|
* **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.
|
* **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.
|
* **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.
|
* **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.
|
* **Database Package (WP-14):** Zentralisiertes Infrastruktur-Paket (`app.core.database`), das den Qdrant-Client (`qdrant.py`) und das Point-Mapping (`qdrant_points.py`) verwaltet.
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ doc_type: user_manual
|
||||||
audience: user, mindmaster
|
audience: user, mindmaster
|
||||||
scope: chat, ui, feedback, graph
|
scope: chat, ui, feedback, graph
|
||||||
status: active
|
status: active
|
||||||
version: 2.6
|
version: 2.9.3
|
||||||
context: "Anleitung zur Nutzung der Web-Oberfläche, der Chat-Personas und des Graph Explorers."
|
context: "Anleitung zur Nutzung der Web-Oberfläche, der Chat-Personas, Multi-Stream RAG und des Graph Explorers."
|
||||||
---
|
---
|
||||||
|
|
||||||
# Chat & Graph Usage Guide
|
# 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)
|
### 3.1 Intent-Erkennung (Hybrid-Router)
|
||||||
Ausgelöst durch `?` oder W-Wörter.
|
|
||||||
|
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**.
|
* **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'."
|
* *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**.
|
* **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..."
|
* *Beispiel:* "Ich bin frustriert." -> "Das erinnert mich an Projekt Y, da ging es uns ähnlich..."
|
||||||
* **Fakten ("Was ist?"):** Der **Bibliothekar**.
|
* **Fakten ("Was ist?", "Wann..."):** Der **Bibliothekar**.
|
||||||
* Liefert präzise Definitionen.
|
* Nutzt: Facts Stream, Tech Stream, Biography Stream
|
||||||
|
* Liefert präzise Definitionen und zeitliche Informationen
|
||||||
|
|
||||||
### 3.2 Befehls-Modus (Interview)
|
### 3.2 Befehls-Modus (Interview)
|
||||||
Ausgelöst durch Aussagen wie "Neues Projekt", "Ich will festhalten".
|
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.
|
Mindnet liefert eine **Begründung** ("Reasons"), warum es etwas gefunden hat.
|
||||||
Öffne eine Quellen-Karte, um zu sehen:
|
Öffne eine Quellen-Karte, um zu sehen:
|
||||||
* *"Hohe textuelle Übereinstimmung."* (Semantik)
|
* *"Hohe textuelle Übereinstimmung."* (Semantik)
|
||||||
* *"Bevorzugt aufgrund des Typs 'decision'."* (Wichtigkeit)
|
* *"Bevorzugt aufgrund des Typs 'decision'."* (Wichtigkeit)
|
||||||
* *"Verweist auf 'Projekt X' via 'depends_on'."* (Graph-Kontext)
|
* *"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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
---
|
---
|
||||||
doc_type: concept
|
doc_type: concept
|
||||||
audience: architect, product_owner
|
audience: architect, product_owner
|
||||||
scope: ai, router, personas, resilience
|
scope: ai, router, personas, resilience, agentic_rag
|
||||||
status: active
|
status: active
|
||||||
version: 2.8.1
|
version: 2.9.3
|
||||||
context: "Fachkonzept der hybriden KI-Persönlichkeit, der Provider-Kaskade und der kognitiven Resilienz (Deep Fallback)."
|
context: "Fachkonzept der hybriden KI-Persönlichkeit, Agentic Multi-Stream RAG, Provider-Kaskade und kognitiven Resilienz (Deep Fallback)."
|
||||||
---
|
---
|
||||||
|
|
||||||
# Konzept: KI-Persönlichkeit & Router
|
# 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.
|
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-basiertes Routing (WP-25)
|
||||||
* **Intent:** Der Nutzer hat eine Frage oder ein Problem (`FACT`, `DECISION`, `EMPATHY`).
|
|
||||||
* **Aktion:** Das System sucht im Gedächtnis und generiert eine Antwort.
|
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)
|
### Modus B: Interview (Knowledge Capture)
|
||||||
* **Intent:** Der Nutzer will Wissen speichern (`INTERVIEW`).
|
* **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.
|
Mindnet wechselt den Hut, je nach Situation.
|
||||||
|
|
||||||
### 3.1 Der Berater (Strategy: DECISION)
|
### 3.1 Der Berater (Strategy: DECISION)
|
||||||
* **Auslöser:** Fragen wie „Soll ich...?“, „Was ist besser?“.
|
* **Auslöser:** Fragen wie „Soll ich...?", „Was ist besser?", „Empfehlung...".
|
||||||
* **Strategic Retrieval:** Lädt aktiv Notizen der Typen `value` (Werte), `goal` (Ziele) und `risk` (Risiken), auch wenn sie im Text nicht direkt vorkommen.
|
* **Multi-Stream Retrieval (WP-25):** Führt parallele Abfragen in Values Stream, Facts Stream und Risk Stream aus.
|
||||||
* **Reasoning:** *„Wäge die Fakten gegen meine Werte ab. Sei strikt bei Risiken.“*
|
* **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)
|
### 3.2 Der Spiegel (Strategy: EMPATHY)
|
||||||
* **Auslöser:** Emotionale Aussagen („Ich bin frustriert“).
|
* **Auslöser:** Emotionale Aussagen („Ich bin frustriert", „Ich fühle...", „Stress...").
|
||||||
* **Strategic Retrieval:** Lädt `experience` (Erfahrungen) und `belief` (Glaubenssätze).
|
* **Multi-Stream Retrieval (WP-25):** Führt parallele Abfragen in Biography Stream und Values Stream aus.
|
||||||
* **Reasoning:** *„Nutze meine eigenen Erfahrungen, um die Situation einzuordnen.“*
|
* **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)
|
### 3.3 Der Bibliothekar (Strategy: FACT_WHAT / FACT_WHEN)
|
||||||
* **Auslöser:** Sachfragen („Was ist Qdrant?“).
|
* **Auslöser:** Sachfragen („Was ist...?", „Welche sind...?", „Wann...?", „Datum...").
|
||||||
* **Behavior:** Präzise, neutral, kurz.
|
* **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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
---
|
---
|
||||||
doc_type: technical_reference
|
doc_type: technical_reference
|
||||||
audience: developer, integrator
|
audience: developer, integrator
|
||||||
scope: api, endpoints, integration
|
scope: api, endpoints, integration, agentic_rag
|
||||||
status: active
|
status: active
|
||||||
version: 2.9.1
|
version: 2.9.3
|
||||||
context: "Vollständige API-Referenz für alle Mindnet-Endpunkte. Basis für Integration und Entwicklung."
|
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
|
# API Reference
|
||||||
|
|
@ -96,21 +96,37 @@ Hauptendpunkt für RAG-Chat und Interview-Modus. Unterstützt Streaming.
|
||||||
**Response (Non-Streaming):**
|
**Response (Non-Streaming):**
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"intent": "FACT",
|
"query_id": "uuid",
|
||||||
"response": "Mindnet ist ein persönliches KI-Gedächtnis...",
|
"answer": "Mindnet ist ein persönliches KI-Gedächtnis...",
|
||||||
"sources": [...],
|
"sources": [
|
||||||
"query_id": "uuid"
|
{
|
||||||
|
"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):**
|
**Response (Streaming):**
|
||||||
Server-Sent Events (SSE) mit Chunks der Antwort.
|
Server-Sent Events (SSE) mit Chunks der Antwort.
|
||||||
|
|
||||||
**Intent-Typen:**
|
**Intent-Typen (WP-25):**
|
||||||
- `FACT`: Wissensabfrage
|
- `FACT_WHAT`: Wissensabfrage (Wissen/Listen)
|
||||||
- `DECISION`: Entscheidungsfrage
|
- `FACT_WHEN`: Zeitpunkte (Termine, Daten)
|
||||||
- `EMPATHY`: Emotionale Anfrage
|
- `DECISION`: Entscheidungsfrage (Beratung, Strategie)
|
||||||
- `INTERVIEW`: Wissen erfassen
|
- `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").
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,32 @@
|
||||||
---
|
---
|
||||||
doc_type: technical_reference
|
doc_type: technical_reference
|
||||||
audience: developer, architect
|
audience: developer, architect
|
||||||
scope: backend, chat, llm_service, traffic_control, resilience
|
scope: backend, chat, llm_service, traffic_control, resilience, agentic_rag
|
||||||
status: active
|
status: active
|
||||||
version: 2.9.1
|
version: 2.9.3
|
||||||
context: "Technische Implementierung des FastAPI-Routers, des hybriden LLMService (v3.3.6) und der WP-20 Resilienz-Logik."
|
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):**
|
1. **Keyword Fast-Path (Sofortige Erkennung):**
|
||||||
* Prüfung auf Vorhandensein von `?` oder W-Wörtern (Wer, Wie, Was, Soll ich).
|
* Prüft `trigger_keywords` aus `decision_engine.yaml` (z.B. "Soll ich", "Wann", "Was ist").
|
||||||
* Wenn positiv: **RAG Modus** (Interview wird blockiert).
|
* Wenn Match: Sofortige Intent-Zuordnung ohne LLM-Call.
|
||||||
2. **Keyword Scan (Fast Path):**
|
* **Strategien:** FACT_WHAT, FACT_WHEN, DECISION, EMPATHY, CODING, INTERVIEW.
|
||||||
* Lädt `types.yaml` (Objekte) und `decision_engine.yaml` (Handlungen).
|
2. **Type Keywords (Interview-Modus):**
|
||||||
* Wenn Match (z.B. "Projekt" + "neu"): **INTERVIEW Modus**.
|
* Lädt `types.yaml` und prüft `detection_keywords` für Objekt-Erkennung.
|
||||||
3. **LLM Fallback (Slow Path):**
|
* Wenn Match und keine Frage: **INTERVIEW Modus** (Datenerfassung).
|
||||||
* Wenn unklar: Anfrage an LLM zur Klassifizierung mittels `router_prompt`.
|
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)
|
### 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.
|
* **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.
|
* **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).
|
**Stream-Library (definiert in `decision_engine.yaml`):**
|
||||||
2. **Context Enrichment:**
|
* **Values Stream:** Extrahiert Identität, Ethik und Prinzipien (`value`, `principle`, `belief`, etc.).
|
||||||
* Abruf via `retriever.py` (Hybrid Search).
|
* **Facts Stream:** Liefert operative Daten zu Projekten, Tasks und Status (`project`, `decision`, `task`, etc.).
|
||||||
* Integration von **Edge Boosts** aus der `decision_engine.yaml` zur Beeinflussung der Graph-Gewichtung.
|
* **Biography Stream:** Greift auf persönliche Erfahrungen und Journal-Einträge zu (`experience`, `journal`, `profile`).
|
||||||
* Injection von Metadaten (`[TYPE]`, `[SCORE]`) in den Prompt.
|
* **Risk Stream:** Identifiziert Hindernisse und potenzielle Gefahren (`risk`, `obstacle`, `bias`).
|
||||||
3. **Prompt Construction:** Assembly aus System-Prompt (Persona) + Context + Query.
|
* **Tech Stream:** Bündelt technisches Wissen, Code und Dokumentation (`concept`, `source`, `glossary`, etc.).
|
||||||
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-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:
|
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`.
|
1. **Query ID:** Generiert bei jedem `/chat` Call eine `UUIDv4`.
|
||||||
2. **Logging:** Speichert einen Snapshot in `data/logs/query_snapshot.jsonl` (Input + Retrieved Context).
|
2. **Stream-Tracing:** Jeder Treffer enthält `stream_origin` für Zuordnung zum Quell-Stream.
|
||||||
3. **Feedback:** Der `/feedback` Endpoint verknüpft das User-Rating (1-5) mit der `query_id`.
|
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.
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
---
|
---
|
||||||
doc_type: technical_reference
|
doc_type: technical_reference
|
||||||
audience: developer, admin
|
audience: developer, admin
|
||||||
scope: configuration, env, registry, scoring, resilience, modularization
|
scope: configuration, env, registry, scoring, resilience, modularization, agentic_rag
|
||||||
status: active
|
status: active
|
||||||
version: 2.9.1
|
version: 2.9.3
|
||||||
context: "Umfassende Referenztabellen für Umgebungsvariablen (inkl. Hybrid-Cloud & WP-76), YAML-Konfigurationen und die Edge Registry Struktur unter Berücksichtigung von WP-14."
|
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
|
# 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
|
### 5.1 Intent Recognition: Hybrid-Routing (WP-25)
|
||||||
Das System nutzt ein zweistufiges Verfahren, um die Absicht des Nutzers zu identifizieren:
|
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.
|
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. **Slow Path (LLM Router):** Wenn kein Keyword matcht und `llm_fallback_enabled: true` gesetzt ist, analysiert ein LLM die Nachricht mittels Few-Shot Prompting.
|
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
|
#### 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.
|
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)
|
### 5.2 Multi-Stream Konfiguration (WP-25)
|
||||||
Jede Strategie definiert drei Hebel, um das Ergebnis des Retrievers zu beeinflussen:
|
|
||||||
|
|
||||||
* **`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".
|
* **`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").
|
* **`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)* |
|
| **FACT_WHAT** | Wissensabfrage & Listen | `facts_stream`, `tech_stream`, `biography_stream` | `part_of` (2.0), `depends_on` (1.5), `implemented_in` (1.5) |
|
||||||
| **DECISION** | Rat, Strategie & Abwägung | `blocks` (2.5), `solves` (2.0), `risk_of` (2.5) | `value`, `principle`, `goal`, `risk` |
|
| **FACT_WHEN** | Zeitpunkte & Termine | `facts_stream`, `biography_stream`, `tech_stream` | `part_of` (2.0), `depends_on` (1.5) |
|
||||||
| **EMPATHY** | Emotionale Resonanz | `based_on` (2.0), `experienced_in` (2.5), `related_to` (2.0) | `experience`, `belief`, `profile` |
|
| **DECISION** | Rat, Strategie & Abwägung | `values_stream`, `facts_stream`, `risk_stream` | `blocks` (2.5), `impacts` (2.0), `risk_of` (2.5) |
|
||||||
| **CODING** | Programmierung & Syntax | `implemented_in` (3.0), `uses` (2.5), `depends_on` (2.0) | `snippet`, `reference`, `source` |
|
| **EMPATHY** | Emotionale Resonanz | `biography_stream`, `values_stream` | `related_to` (1.5), `experienced_in` (2.0) |
|
||||||
| **INTERVIEW** | Erfassung neuer Daten | *(Keine)* | *(Keine)* |
|
| **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.
|
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.
|
* **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.
|
* **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.
|
> **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
|
Auszug aus der decision_engine.yaml
|
||||||
```yaml
|
```yaml
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
---
|
---
|
||||||
doc_type: developer_guide
|
doc_type: developer_guide
|
||||||
audience: developer
|
audience: developer
|
||||||
scope: workflow, testing, architecture, modules, modularization
|
scope: workflow, testing, architecture, modules, modularization, agentic_rag
|
||||||
status: active
|
status: active
|
||||||
version: 2.9.1
|
version: 2.9.3
|
||||||
context: "Umfassender Guide für Entwickler: Modularisierte Architektur (WP-14), Two-Pass Ingestion (WP-15b), Modul-Interna, Setup und Git-Workflow."
|
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
|
# 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. |
|
| **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. |
|
| **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/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/query.py` | 🟢 **API** | **Search API.** Klassischer Hybrid-Retriever Endpunkt. |
|
||||||
| | `app/routers/graph.py` | 🟢 **API** | **Viz API.** Liefert Knoten/Kanten für Frontend-Graphen (Cytoscape). |
|
| | `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
|
edge_defaults: ["blocks"] # Automatische Kante
|
||||||
detection_keywords: ["gefahr", "risiko"]
|
detection_keywords: ["gefahr", "risiko"]
|
||||||
```
|
```
|
||||||
2. **Strategie (`config/decision_engine.yaml`):**
|
2. **Strategie (`config/decision_engine.yaml` v3.1.6, WP-25):**
|
||||||
```yaml
|
```yaml
|
||||||
DECISION:
|
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
|
### Workflow B: Graph-Farben ändern
|
||||||
1. Öffne `app/frontend/ui_config.py`.
|
1. Öffne `app/frontend/ui_config.py`.
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ doc_type: developer_guide
|
||||||
audience: developer, tester
|
audience: developer, tester
|
||||||
scope: testing, quality_assurance, test_strategies
|
scope: testing, quality_assurance, test_strategies
|
||||||
status: active
|
status: active
|
||||||
version: 2.9.1
|
version: 2.9.3
|
||||||
context: "Umfassender Test-Guide für Mindnet: Test-Strategien, Test-Frameworks, Test-Daten und Best Practices."
|
context: "Umfassender Test-Guide für Mindnet: Test-Strategien, Test-Frameworks, Test-Daten und Best Practices inklusive WP-25 Multi-Stream RAG."
|
||||||
---
|
---
|
||||||
|
|
||||||
# Testing Guide
|
# Testing Guide
|
||||||
|
|
@ -245,19 +245,29 @@ class TestIngest(unittest.IsolatedAsyncioTestCase):
|
||||||
- `tests/test_edges_defaults_smoke.py`
|
- `tests/test_edges_defaults_smoke.py`
|
||||||
- `scripts/edges_full_check.py`
|
- `scripts/edges_full_check.py`
|
||||||
|
|
||||||
### 4.4 Chat & Intent-Tests
|
### 4.4 Chat & Intent-Tests (WP-25)
|
||||||
|
|
||||||
**Was wird getestet:**
|
**Was wird getestet:**
|
||||||
- Intent-Erkennung (FACT, DECISION, EMPATHY, INTERVIEW)
|
- Intent-Erkennung (FACT_WHAT, FACT_WHEN, DECISION, EMPATHY, CODING, INTERVIEW)
|
||||||
- Decision Engine
|
- 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
|
- Interview-Modus
|
||||||
- Feedback-Loop
|
- Feedback-Loop
|
||||||
|
|
||||||
**Tests:**
|
**Tests:**
|
||||||
- `tests/test_wp06_decision.py`
|
- `tests/test_wp06_decision.py` - Decision Engine (Legacy)
|
||||||
- `tests/test_interview_intent.py`
|
- `tests/test_interview_intent.py` - Interview-Modus
|
||||||
- `tests/test_chat_wp05.py`
|
- `tests/test_chat_wp05.py` - Chat-Backend (Legacy)
|
||||||
- `tests/test_feedback_smoke.py`
|
- `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
|
### 4.5 Ingestion-Tests
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,14 @@
|
||||||
doc_type: roadmap
|
doc_type: roadmap
|
||||||
audience: product_owner, developer
|
audience: product_owner, developer
|
||||||
status: active
|
status: active
|
||||||
version: 2.9.1
|
version: 2.9.3
|
||||||
context: "Aktuelle Planung für kommende Features (ab WP16), Release-Strategie und Historie der abgeschlossenen WPs nach WP-14/15b."
|
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
|
# Mindnet Active Roadmap
|
||||||
|
|
||||||
**Aktueller Stand:** v2.9.1 (Post-WP14 / WP-15b)
|
**Aktueller Stand:** v2.9.3 (Post-WP25: Agentic Multi-Stream RAG)
|
||||||
**Fokus:** Modularisierung, Two-Pass Ingestion & Graph Intelligence.
|
**Fokus:** Agentic Orchestration, Multi-Stream Retrieval & Wissens-Synthese.
|
||||||
|
|
||||||
| Phase | Fokus | Status |
|
| 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-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-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-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
|
### 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.
|
* **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.
|
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`.
|
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
|
**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.
|
||||||
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.
|
|
||||||
|
|
||||||
#### 2. Funktionsbeschreibung: Die Streams
|
**Kern-Features:**
|
||||||
Die Daten aus der `types.yaml` werden in drei logische Verarbeitungseinheiten unterteilt:
|
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)
|
**Technische Details:**
|
||||||
* **Inhalt:** `value`, `belief`, `trait`, `principle`, `need`, `boundary`, `bias`.
|
- Decision Engine v1.0.3: Multi-Stream Orchestrator
|
||||||
* **Zweck:** Definition des moralischen Kompasses, der psychologischen Grundbedürfnisse und kognitiven Muster.
|
- Chat Router v3.0.2: Hybrid Router Integration
|
||||||
* **Wirkung:** Liefert das "Warum" hinter jeder Handlung.
|
- 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)
|
**Ausblick (WP-25a):**
|
||||||
* **Inhalt:** `experience`, `event`, `source`, `journal`, `person`.
|
- Pre-Synthesis: LLM-basierte Komprimierung überlanger Streams
|
||||||
* **Zweck:** Bereitstellung empirischer Belege aus der Vergangenheit und sozialer Kontexte.
|
- Kontext-Budgeting: Intelligente Token-Verteilung
|
||||||
* **Wirkung:** Verankert die Antwort in real erlebten Mustern und Fakten.
|
- Stream-specific Provider: Unterschiedliche KI-Modelle pro Wissensbereich
|
||||||
|
|
||||||
##### 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.
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### WP-24 – Proactive Discovery & Agentic Knowledge Mining
|
### WP-24 – Proactive Discovery & Agentic Knowledge Mining
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Release Notes: Mindnet v2.9.1 (WP15c)
|
# Release Notes: Mindnet v2.9.3 (WP15c)
|
||||||
|
|
||||||
**Release Date:** 2025-12-31
|
**Release Date:** 2025-12-31
|
||||||
**Type:** Feature Release - Multigraph & Diversity Engine
|
**Type:** Feature Release - Multigraph & Diversity Engine
|
||||||
|
|
|
||||||
138
docs/99_Archive/WP25_merge_commit.md
Normal file
138
docs/99_Archive/WP25_merge_commit.md
Normal file
|
|
@ -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]
|
||||||
|
```
|
||||||
268
docs/99_Archive/WP25_release_notes.md
Normal file
268
docs/99_Archive/WP25_release_notes.md
Normal file
|
|
@ -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)
|
||||||
|
|
@ -2,13 +2,13 @@
|
||||||
doc_type: documentation_index
|
doc_type: documentation_index
|
||||||
audience: all
|
audience: all
|
||||||
status: active
|
status: active
|
||||||
version: 2.9.1
|
version: 2.9.3
|
||||||
context: "Zentraler Einstiegspunkt für die Mindnet-Dokumentation"
|
context: "Zentraler Einstiegspunkt für die Mindnet-Dokumentation"
|
||||||
---
|
---
|
||||||
|
|
||||||
# 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
|
## 🚀 Schnellstart
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user