From 6c2074166c553f51562664c69f96a01ede0d4f9e Mon Sep 17 00:00:00 2001 From: Lars Date: Tue, 9 Dec 2025 13:50:18 +0100 Subject: [PATCH] verbessertes Prompt, und chat-Router optimiert --- app/routers/chat.py | 53 ++++++++++++++++++++++++------------- config/decision_engine.yaml | 24 ++++++++++------- 2 files changed, 50 insertions(+), 27 deletions(-) diff --git a/app/routers/chat.py b/app/routers/chat.py index 60339fc..0e01dc6 100644 --- a/app/routers/chat.py +++ b/app/routers/chat.py @@ -1,6 +1,6 @@ """ -app/routers/chat.py — RAG Endpunkt (WP-06 Hybrid Router) -Version: 0.2.1 (Fix: System Prompt Separation) +app/routers/chat.py — RAG Endpunkt (WP-06 Hybrid Router v2) +Update: Robusteres LLM-Parsing für Small Language Models (SLMs). """ from fastapi import APIRouter, HTTPException, Depends @@ -88,6 +88,11 @@ def _build_enriched_context(hits: List[QueryHit]) -> str: return "\n\n".join(context_parts) async def _classify_intent(query: str, llm: LLMService) -> str: + """ + Hybrid Router v2: + 1. Keyword Check (Best/Longest Match) -> FAST + 2. LLM Fallback (Robust Parsing) -> SMART + """ config = get_full_config() strategies = config.get("strategies", {}) settings = config.get("settings", {}) @@ -96,7 +101,7 @@ async def _classify_intent(query: str, llm: LLMService) -> str: best_intent = None max_match_length = 0 - # 1. FAST PATH + # 1. FAST PATH: Keywords for intent_name, strategy in strategies.items(): if intent_name == "FACT": continue keywords = strategy.get("trigger_keywords", []) @@ -110,29 +115,41 @@ async def _classify_intent(query: str, llm: LLMService) -> str: logger.info(f"Intent detected via KEYWORD: {best_intent}") return best_intent - # 2. SLOW PATH + # 2. SLOW PATH: LLM Router if settings.get("llm_fallback_enabled", False): router_prompt_template = settings.get("llm_router_prompt", "") if router_prompt_template: prompt = router_prompt_template.replace("{query}", query) logger.info("Keywords failed. Asking LLM for Intent...") - # Router braucht keinen System-Prompt, nur den Classifier-Prompt - llm_decision = await llm.generate_raw_response(prompt) + # Kurzer Raw Call + raw_response = await llm.generate_raw_response(prompt) - llm_decision = llm_decision.strip().upper() - if ":" in llm_decision: - llm_decision = llm_decision.split(":")[-1].strip() + # --- Robust Parsing für SLMs --- + # Wir suchen nach den bekannten Strategie-Namen im Output + llm_output_upper = raw_response.upper() + logger.info(f"LLM Router Raw Output: '{raw_response}'") # Debugging - # Satzzeichen entfernen für sauberen Match - llm_decision = ''.join(filter(str.isalnum, llm_decision)) - - if llm_decision in strategies: - logger.info(f"Intent detected via LLM: {llm_decision}") - return llm_decision + found_intents = [] + for strat_key in strategies.keys(): + # Wir prüfen, ob der Strategie-Name (z.B. "EMPATHY") im Text vorkommt + if strat_key in llm_output_upper: + found_intents.append(strat_key) + + # Entscheidung + final_intent = "FACT" + if len(found_intents) == 1: + # Eindeutiger Treffer + final_intent = found_intents[0] + logger.info(f"Intent detected via LLM (Parsed): {final_intent}") + return final_intent + elif len(found_intents) > 1: + # Mehrere Treffer (z.B. "Es ist FACT oder DECISION") -> Nimm den ersten oder Fallback + logger.warning(f"LLM returned multiple intents {found_intents}. Using first match: {found_intents[0]}") + return found_intents[0] else: - logger.warning(f"LLM predicted unknown intent '{llm_decision}', falling back to FACT.") - + logger.warning(f"LLM did not return a valid strategy name. Falling back to FACT.") + return "FACT" @router.post("/", response_model=ChatResponse) @@ -200,7 +217,7 @@ async def chat_endpoint( logger.info(f"[{query_id}] Sending to LLM (Intent: {intent}, Template: {prompt_key})...") - # FIX: System-Prompt separat übergeben! + # System-Prompt separat übergeben answer_text = await llm.generate_raw_response(prompt=final_prompt, system=system_prompt) duration_ms = int((time.time() - start_time) * 1000) diff --git a/config/decision_engine.yaml b/config/decision_engine.yaml index 5c9bb7c..f0f9e2d 100644 --- a/config/decision_engine.yaml +++ b/config/decision_engine.yaml @@ -1,22 +1,28 @@ # config/decision_engine.yaml # Steuerung der Decision Engine (WP-06) # Hybrid-Modus: Keywords (Fast) + LLM Router (Smart Fallback) -version: 1.1 +version: 1.2 settings: - # Schalter: Soll das LLM gefragt werden, wenn kein Keyword passt? llm_fallback_enabled: true - # Der Prompt für den "Semantic Router" (Slow Path) + # Few-Shot Prompting für bessere SLM-Performance llm_router_prompt: | - Analysiere die folgende Nachricht und entscheide, welche Strategie passt. - Antworte NUR mit dem Namen der Strategie (ein Wort). + Du bist ein Klassifikator. Analysiere die Nachricht und wähle die passende Strategie. + Antworte NUR mit dem Namen der Strategie. STRATEGIEN: - - DECISION: User fragt nach Rat, Meinung, Strategie, Vor/Nachteilen. - - EMPATHY: User äußert Gefühle, Frust, Freude oder persönliche Probleme. - - CODING: User fragt nach Code, Syntax oder Programmierung. - - FACT: User fragt nach Wissen, Definitionen oder Fakten (Default). + - DECISION: Rat, Strategie, Vor/Nachteile, "Soll ich". + - EMPATHY: Gefühle, Frust, Freude, Probleme, "Alles ist sinnlos", "Ich bin traurig". + - CODING: Code, Syntax, Programmierung, Python. + - FACT: Wissen, Fakten, Definitionen. + + BEISPIELE: + User: "Wie funktioniert Qdrant?" -> FACT + User: "Soll ich Qdrant nutzen?" -> DECISION + User: "Schreibe ein Python Script" -> CODING + User: "Alles ist grau und sinnlos" -> EMPATHY + User: "Mir geht es heute gut" -> EMPATHY NACHRICHT: "{query}"