neue engine mit mehreren typen
This commit is contained in:
parent
3c5c01998f
commit
97985371ca
|
|
@ -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}")
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
Loading…
Reference in New Issue
Block a user