neue engine mit mehreren typen

This commit is contained in:
Lars 2025-12-09 09:58:35 +01:00
parent 3c5c01998f
commit 97985371ca
2 changed files with 72 additions and 39 deletions

View File

@ -1,10 +1,10 @@
"""
app/routers/chat.py RAG Endpunkt (WP-06 Decision Engine - Late Binding Refactor)
app/routers/chat.py RAG Endpunkt (WP-06 Decision Engine - Full Config Refactor)
Zweck:
Verbindet Retrieval mit LLM-Generation.
WP-06: Implementiert Intent Detection und Strategic Retrieval.
Update: Konfiguration via decision_engine.yaml (Late Binding).
Update: Konfiguration via decision_engine.yaml (Late Binding) mit 'Best Match' Logik.
"""
from fastapi import APIRouter, HTTPException, Depends
@ -25,14 +25,23 @@ logger = logging.getLogger(__name__)
# --- Helper: Config Loader ---
# Cache für die Config (damit wir nicht bei jedem Request lesen)
_DECISION_CONFIG_CACHE = None
def _load_decision_config() -> Dict[str, Any]:
"""Lädt die Decision-Engine Konfiguration (Late Binding)."""
settings = get_settings()
path = Path(settings.DECISION_CONFIG_PATH)
# Default Fallback, falls YAML kaputt/weg
default_config = {
"strategies": {
"FACT": {"inject_types": [], "prompt_template": "rag_template"},
"DECISION": {"inject_types": ["value", "principle"], "prompt_template": "decision_template"}
"FACT": {"trigger_keywords": []},
"DECISION": {
"trigger_keywords": ["soll ich", "meinung"],
"inject_types": ["value", "principle"],
"prompt_template": "decision_template"
}
}
}
@ -47,15 +56,17 @@ def _load_decision_config() -> Dict[str, Any]:
logger.error(f"Failed to load decision config: {e}")
return default_config
# Cache für die Config (damit wir nicht bei jedem Request lesen)
_DECISION_CONFIG_CACHE = None
def get_decision_strategy(intent: str) -> Dict[str, Any]:
def get_full_config() -> Dict[str, Any]:
"""Gibt die ganze Config zurück (für Intent Detection)."""
global _DECISION_CONFIG_CACHE
if _DECISION_CONFIG_CACHE is None:
_DECISION_CONFIG_CACHE = _load_decision_config()
strategies = _DECISION_CONFIG_CACHE.get("strategies", {})
return _DECISION_CONFIG_CACHE
def get_decision_strategy(intent: str) -> Dict[str, Any]:
"""Gibt die Strategie für einen spezifischen Intent zurück."""
config = get_full_config()
strategies = config.get("strategies", {})
# Fallback auf FACT, wenn Intent unbekannt
return strategies.get(intent, strategies.get("FACT", {}))
@ -74,6 +85,8 @@ def get_retriever():
def _build_enriched_context(hits: List[QueryHit]) -> str:
"""
Baut einen 'Rich Context' String.
Statt nur Text, injizieren wir Metadaten (Typ, Tags), damit das LLM
die semantische Rolle des Schnipsels versteht.
"""
context_parts = []
@ -105,14 +118,38 @@ def _build_enriched_context(hits: List[QueryHit]) -> str:
async def _classify_intent(query: str, llm: LLMService) -> str:
"""
WP-06: Intent Detection (Simple Keyword Heuristic for Speed).
TODO: Move keywords to config if needed later.
WP-06: Intent Detection (Best Match / Longest Keyword Wins).
Prüft Keywords aus der YAML gegen die Query.
Wenn mehrere Strategien passen, gewinnt die mit dem längsten Keyword (Spezifität).
"""
# Performance-Optimierung: Keywords statt LLM Call
keywords = ["soll ich", "meinung", "besser", "empfehlung", "strategie", "entscheidung", "wert", "prinzip"]
if any(k in query.lower() for k in keywords):
return "DECISION"
return "FACT"
config = get_full_config()
strategies = config.get("strategies", {})
query_lower = query.lower()
best_intent = "FACT"
max_match_length = 0
# Iteriere über alle Strategien
for intent_name, strategy in strategies.items():
if intent_name == "FACT":
continue
keywords = strategy.get("trigger_keywords", [])
# Prüfe jedes Keyword
for k in keywords:
# Wenn Keyword im Text ist...
if k.lower() in query_lower:
# ... prüfen wir, ob es spezifischer (länger) ist als der bisherige Favorit
current_len = len(k)
if current_len > max_match_length:
max_match_length = current_len
best_intent = intent_name
# Wir brechen hier NICHT ab, sondern suchen weiter nach noch längeren Matches
return best_intent
@router.post("/", response_model=ChatResponse)
async def chat_endpoint(
@ -126,7 +163,7 @@ async def chat_endpoint(
logger.info(f"Chat request [{query_id}]: {request.message[:50]}...")
try:
# 1. Intent Detection
# 1. Intent Detection (Config-Driven & Best Match)
intent = await _classify_intent(request.message, llm)
logger.info(f"[{query_id}] Detected Intent: {intent}")

View File

@ -1,25 +1,21 @@
# config/decision_engine.yaml
# Steuerung der Decision Engine (WP-06)
# Hier wird definiert, wie auf verschiedene Intents reagiert wird.
version: 1.0
strategies:
# 1. Fakten-Abfrage (Standard)
FACT:
description: "Reine Wissensabfrage."
inject_types: [] # Keine speziellen Typen erzwingen
prompt_template: "rag_template"
prepend_instruction: null # Keine spezielle Anweisung im Context
# 2. Entscheidungs-Frage (WP-06)
# Strategie 1: Der Berater (Das haben wir gebaut)
DECISION:
description: "Der User sucht Rat, Strategie oder Abwägung."
# HIER definierst du, was das 'Gewissen' ausmacht:
# Aktuell: Werte & Prinzipien.
# Später einfach ergänzen um: "goal", "experience", "belief"
inject_types: ["value", "principle", "goal"]
prompt_template: "decision_template"
prepend_instruction: |
!!! ENTSCHEIDUNGS-MODUS !!!
BITTE WÄGE FAKTEN GEGEN FOLGENDE WERTE/PRINZIPIEN AB:
trigger_keywords: ["soll ich", "empfehlung", "strategie"]
inject_types: ["value", "principle", "goal"]
prompt_template: "decision_template" # Nutzt das "Abwägen"-Template
# Strategie 2: Der empathische Zuhörer (NEU - Konzept)
EMPATHY:
trigger_keywords: ["ich fühle", "traurig", "gestresst", "angst"]
inject_types: ["belief", "experience"] # Lädt Glaubenssätze & eigene Erfahrungen
prompt_template: "empathy_template" # Ein Template, das auf "Zuhören" getrimmt ist
prepend_instruction: "SEI EMPATHISCH. SPIEGEL DIE GEFÜHLE."
# Strategie 3: Der Coder (NEU - Konzept)
CODING:
trigger_keywords: ["code", "python", "funktion", "bug"]
inject_types: ["snippet", "reference"] # Lädt nur technische Schnipsel
prompt_template: "technical_template" # Ein Template, das Codeblöcke erzwingt