Hybrider Chat (mit und ohne LLM Einordung des Intents)

This commit is contained in:
Lars 2025-12-09 13:08:04 +01:00
parent 97985371ca
commit 03594424a1
4 changed files with 285 additions and 139 deletions

View File

@ -1,10 +1,5 @@
""" """
app/routers/chat.py RAG Endpunkt (WP-06 Decision Engine - Full Config Refactor) app/routers/chat.py RAG Endpunkt (WP-06 Hybrid Router)
Zweck:
Verbindet Retrieval mit LLM-Generation.
WP-06: Implementiert Intent Detection und Strategic Retrieval.
Update: Konfiguration via decision_engine.yaml (Late Binding) mit 'Best Match' Logik.
""" """
from fastapi import APIRouter, HTTPException, Depends from fastapi import APIRouter, HTTPException, Depends
@ -25,23 +20,15 @@ logger = logging.getLogger(__name__)
# --- Helper: Config Loader --- # --- Helper: Config Loader ---
# Cache für die Config (damit wir nicht bei jedem Request lesen)
_DECISION_CONFIG_CACHE = None _DECISION_CONFIG_CACHE = None
def _load_decision_config() -> Dict[str, Any]: def _load_decision_config() -> Dict[str, Any]:
"""Lädt die Decision-Engine Konfiguration (Late Binding).""" """Lädt die Decision-Engine Konfiguration (Late Binding)."""
settings = get_settings() settings = get_settings()
path = Path(settings.DECISION_CONFIG_PATH) path = Path(settings.DECISION_CONFIG_PATH)
# Default Fallback, falls YAML kaputt/weg
default_config = { default_config = {
"strategies": { "strategies": {
"FACT": {"trigger_keywords": []}, "FACT": {"trigger_keywords": []}
"DECISION": {
"trigger_keywords": ["soll ich", "meinung"],
"inject_types": ["value", "principle"],
"prompt_template": "decision_template"
}
} }
} }
@ -57,17 +44,14 @@ def _load_decision_config() -> Dict[str, Any]:
return default_config return default_config
def get_full_config() -> Dict[str, Any]: def get_full_config() -> Dict[str, Any]:
"""Gibt die ganze Config zurück (für Intent Detection)."""
global _DECISION_CONFIG_CACHE global _DECISION_CONFIG_CACHE
if _DECISION_CONFIG_CACHE is None: if _DECISION_CONFIG_CACHE is None:
_DECISION_CONFIG_CACHE = _load_decision_config() _DECISION_CONFIG_CACHE = _load_decision_config()
return _DECISION_CONFIG_CACHE return _DECISION_CONFIG_CACHE
def get_decision_strategy(intent: str) -> Dict[str, Any]: def get_decision_strategy(intent: str) -> Dict[str, Any]:
"""Gibt die Strategie für einen spezifischen Intent zurück."""
config = get_full_config() config = get_full_config()
strategies = config.get("strategies", {}) strategies = config.get("strategies", {})
# Fallback auf FACT, wenn Intent unbekannt
return strategies.get(intent, strategies.get("FACT", {})) return strategies.get(intent, strategies.get("FACT", {}))
@ -83,30 +67,17 @@ def get_retriever():
# --- Logic --- # --- Logic ---
def _build_enriched_context(hits: List[QueryHit]) -> str: 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 = [] context_parts = []
for i, hit in enumerate(hits, 1): for i, hit in enumerate(hits, 1):
source = hit.source or {} source = hit.source or {}
# 1. Content extrahieren
content = ( content = (
source.get("text") or source.get("text") or source.get("content") or
source.get("content") or source.get("page_content") or source.get("chunk_text") or
source.get("page_content") or "[Kein Text]"
source.get("chunk_text") or
"[Kein Textinhalt verfügbar]"
) )
title = hit.note_id or "Unbekannt"
# 2. Metadaten für "Context Intelligence"
title = hit.note_id or "Unbekannte Notiz"
note_type = source.get("type", "unknown").upper() note_type = source.get("type", "unknown").upper()
# 3. Formatierung
entry = ( entry = (
f"### QUELLE {i}: {title}\n" f"### QUELLE {i}: {title}\n"
f"TYP: [{note_type}] (Score: {hit.total_score:.2f})\n" f"TYP: [{note_type}] (Score: {hit.total_score:.2f})\n"
@ -118,38 +89,58 @@ def _build_enriched_context(hits: List[QueryHit]) -> str:
async def _classify_intent(query: str, llm: LLMService) -> str: async def _classify_intent(query: str, llm: LLMService) -> str:
""" """
WP-06: Intent Detection (Best Match / Longest Keyword Wins). Hybrid Router:
1. Keyword Check (Best/Longest Match) -> FAST
Prüft Keywords aus der YAML gegen die Query. 2. LLM Fallback (wenn in config aktiv) -> SMART
Wenn mehrere Strategien passen, gewinnt die mit dem längsten Keyword (Spezifität).
""" """
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()
best_intent = None
best_intent = "FACT"
max_match_length = 0 max_match_length = 0
# Iteriere über alle Strategien # 1. FAST PATH: Keywords
for intent_name, strategy in strategies.items(): for intent_name, strategy in strategies.items():
if intent_name == "FACT": if intent_name == "FACT": continue
continue
keywords = strategy.get("trigger_keywords", []) keywords = strategy.get("trigger_keywords", [])
# Prüfe jedes Keyword
for k in keywords: for k in keywords:
# Wenn Keyword im Text ist...
if k.lower() in query_lower: if k.lower() in query_lower:
# ... prüfen wir, ob es spezifischer (länger) ist als der bisherige Favorit if len(k) > max_match_length:
current_len = len(k) max_match_length = len(k)
if current_len > max_match_length:
max_match_length = current_len
best_intent = intent_name best_intent = intent_name
# Wir brechen hier NICHT ab, sondern suchen weiter nach noch längeren Matches
if best_intent:
logger.info(f"Intent detected via KEYWORD: {best_intent}")
return best_intent
# 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...")
return best_intent # Kurzer Raw Call
llm_decision = await llm.generate_raw_response(prompt)
# Cleaning
llm_decision = llm_decision.strip().upper()
if ":" in llm_decision:
llm_decision = llm_decision.split(":")[-1].strip()
# Validierung: Nur bekannte Intents zulassen
# Entferne Satzzeichen
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
else:
logger.warning(f"LLM predicted unknown intent '{llm_decision}', falling back to FACT.")
return "FACT"
@router.post("/", response_model=ChatResponse) @router.post("/", response_model=ChatResponse)
async def chat_endpoint( async def chat_endpoint(
@ -159,21 +150,20 @@ async def chat_endpoint(
): ):
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]}...") logger.info(f"Chat request [{query_id}]: {request.message[:50]}...")
try: try:
# 1. Intent Detection (Config-Driven & Best Match) # 1. Intent Detection
intent = await _classify_intent(request.message, llm) intent = await _classify_intent(request.message, llm)
logger.info(f"[{query_id}] Detected Intent: {intent}") logger.info(f"[{query_id}] Final Intent: {intent}")
# Lade Strategie aus Config (Late Binding) # Strategy Load
strategy = get_decision_strategy(intent) strategy = get_decision_strategy(intent)
inject_types = strategy.get("inject_types", []) inject_types = strategy.get("inject_types", [])
prompt_key = strategy.get("prompt_template", "rag_template") prompt_key = strategy.get("prompt_template", "rag_template")
prepend_instr = strategy.get("prepend_instruction", "") prepend_instr = strategy.get("prepend_instruction", "")
# 2. Primary Retrieval (Fakten) # 2. Primary Retrieval
query_req = QueryRequest( query_req = QueryRequest(
query=request.message, query=request.message,
mode="hybrid", mode="hybrid",
@ -183,19 +173,19 @@ async def chat_endpoint(
retrieve_result = await retriever.search(query_req) retrieve_result = await retriever.search(query_req)
hits = retrieve_result.results hits = retrieve_result.results
# 3. Strategic Retrieval (Konfigurierbar) # 3. Strategic Retrieval
if inject_types: if inject_types:
logger.info(f"[{query_id}] Executing Strategic Retrieval for types: {inject_types}...") logger.info(f"[{query_id}] Executing Strategic Retrieval for types: {inject_types}...")
strategy_req = QueryRequest( strategy_req = QueryRequest(
query=request.message, query=request.message,
mode="hybrid", mode="hybrid",
top_k=3, top_k=3,
filters={"type": inject_types}, # Dynamische Liste aus YAML filters={"type": inject_types},
explain=False explain=False
) )
strategy_result = await retriever.search(strategy_req) strategy_result = await retriever.search(strategy_req)
# Merge Results (Deduplication via node_id) # Merge
existing_ids = {h.node_id for h in hits} existing_ids = {h.node_id for h in hits}
for strat_hit in strategy_result.results: for strat_hit in strategy_result.results:
if strat_hit.node_id not in existing_ids: if strat_hit.node_id not in existing_ids:
@ -207,20 +197,29 @@ async def chat_endpoint(
else: else:
context_str = _build_enriched_context(hits) context_str = _build_enriched_context(hits)
# 5. Generation Setup # 5. Generation
# Wir laden das Template aus dem Service (da dort die prompts.yaml geladen ist)
template = llm.prompts.get(prompt_key, "{context_str}\n\n{query}") template = llm.prompts.get(prompt_key, "{context_str}\n\n{query}")
system_prompt = llm.prompts.get("system_prompt", "")
# Injection der Instruktion (falls konfiguriert)
if prepend_instr: if prepend_instr:
context_str = f"{prepend_instr}\n\n{context_str}" context_str = f"{prepend_instr}\n\n{context_str}"
# Manuelles Bauen des finalen Prompts für volle Kontrolle
final_prompt = template.replace("{context_str}", context_str).replace("{query}", request.message)
# Aufruf via Raw Response (da wir den Prompt schon fertig haben)
# Wir müssen den System-Prompt manuell mitgeben?
# generate_raw_response in llm_service unterstützt aktuell kein 'system'.
# -> Wir erweitern generate_raw_response oder nutzen einen Hack: System + Prompt.
# SAUBERER WEG: Wir bauen den Payload für Ollama hier manuell zusammen und rufen eine generische Methode.
# Da LLMService.generate_raw_response keine System-Msg nimmt, packen wir sie davor.
full_text_prompt = f"{system_prompt}\n\n{final_prompt}"
logger.info(f"[{query_id}] Sending to LLM (Intent: {intent}, Template: {prompt_key})...") logger.info(f"[{query_id}] Sending to LLM (Intent: {intent}, Template: {prompt_key})...")
answer_text = await llm.generate_rag_response( answer_text = await llm.generate_raw_response(full_text_prompt)
query=request.message,
context_str=context_str
)
# 6. Response
duration_ms = int((time.time() - start_time) * 1000) duration_ms = int((time.time() - start_time) * 1000)
return ChatResponse( return ChatResponse(

View File

@ -2,7 +2,7 @@
app/services/llm_service.py LLM Client (Ollama) app/services/llm_service.py LLM Client (Ollama)
Version: Version:
0.1.2 (WP-05 Fix: Increased Timeout for CPU Inference) 0.2.0 (WP-06 Hybrid Router Support)
""" """
import httpx import httpx
@ -18,18 +18,19 @@ 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()
# FIX: Timeout auf 120 Sekunden erhöht für CPU-Only Server
self.client = httpx.AsyncClient(base_url=self.settings.OLLAMA_URL, timeout=120.0) # Timeout aus Config nutzen (Default 120s)
self.client = httpx.AsyncClient(
base_url=self.settings.OLLAMA_URL,
timeout=self.settings.LLM_TIMEOUT
)
def _load_prompts(self) -> dict: def _load_prompts(self) -> dict:
"""Lädt Prompts aus der konfigurierten YAML-Datei.""" """Lädt Prompts aus der konfigurierten YAML-Datei."""
path = Path(self.settings.PROMPTS_PATH) path = Path(self.settings.PROMPTS_PATH)
if not path.exists(): if not path.exists():
logger.warning(f"Prompt config not found at {path}, using defaults.") logger.warning(f"Prompt config not found at {path}, using defaults.")
return { return {}
"system_prompt": "You are a helpful AI assistant.",
"rag_template": "Context: {context_str}\nQuestion: {query}"
}
try: try:
with open(path, "r", encoding="utf-8") as f: with open(path, "r", encoding="utf-8") as f:
@ -38,15 +39,76 @@ class LLMService:
logger.error(f"Failed to load prompts: {e}") logger.error(f"Failed to load prompts: {e}")
return {} return {}
async def generate_raw_response(self, prompt: str) -> str:
"""
Führt einen direkten LLM Call ohne RAG-Template aus.
Ideal für Classification/Routing.
"""
payload = {
"model": self.settings.LLM_MODEL,
"prompt": prompt,
"stream": False,
"options": {
"temperature": 0.0, # Deterministisch für Routing!
"num_ctx": 512 # Kleines Fenster reicht für Classification
}
}
try:
response = await self.client.post("/api/generate", json=payload)
if response.status_code != 200:
logger.error(f"Ollama Error ({response.status_code}): {response.text}")
return "FACT" # Fallback bei Fehler
data = response.json()
return data.get("response", "").strip()
except Exception as e:
logger.error(f"LLM Raw Gen Error: {e}")
return "FACT"
async def generate_rag_response(self, query: str, context_str: str) -> str: async def generate_rag_response(self, query: str, context_str: str) -> str:
""" """
Generiert eine Antwort basierend auf Query und Kontext. Generiert eine Antwort basierend auf Query und Kontext (RAG).
""" """
system_prompt = self.prompts.get("system_prompt", "") # Template hier ist nur Fallback, falls im Router nichts übergeben wird.
template = self.prompts.get("rag_template", "{context_str}\n\n{query}") # Im Normalfall formatiert der Router den context_str bereits vor oder übergibt das Template.
# Hier nutzen wir simple substitution, da der Prompt meist schon vom Router aufbereitet ist
# oder wir nutzen das Standard-Template aus der YAML.
# Template füllen # HINWEIS: In der neuen Architektur (chat.py) wird das Template bereits VOR diesem Aufruf
final_prompt = template.format(context_str=context_str, query=query) # geladen und formatiert, und als 'prompt' übergeben?
# Nein, chat.py ruft generate_rag_response(query, context_str) auf.
# Wir müssen sicherstellen, dass wir das *richtige* Template nutzen.
# Da generate_rag_response aktuell KEINEN 'template_key' Parameter hat,
# gehen wir davon aus, dass 'context_str' bereits Instruktionen enthält ODER
# dass chat.py den Prompt komplett baut.
# Um die API sauber zu halten: chat.py übergibt jetzt den FERTIGEN Prompt als context_str?
# Nein, chat.py baut den Prompt mit replace().
# Wir ändern diese Methode leicht ab, um flexibler zu sein:
# Wir erwarten, dass der Aufrufer (chat.py) die volle Kontrolle hat.
# Damit es 100% zusammenpasst mit dem chat.py unten:
# chat.py baut den finalen Prompt selbst zusammen!
# Wir nutzen daher generate_raw_response eigentlich auch für RAG,
# ODER wir passen generate_rag_response an, dass es "dumm" ist.
# Legacy Support für generate_rag_response:
# Wir bauen den Prompt zusammen, falls noch nicht geschehen.
# ABER: chat.py in meiner Version unten macht das Template-Handling.
# Daher ist der sauberste Weg: chat.py ruft generate_raw_response auf für die Antwort!
# FIX für Kompatibilität: Wir leiten rag_response intern auf raw um,
# bauen aber vorher den Prompt, falls context_str übergeben wird.
# Da wir chat.py kontrollieren (siehe unten), ändern wir chat.py so,
# dass es generate_raw_response nutzt! Das ist viel sauberer.
# Diese Methode bleibt für Backward Compatibility.
system_prompt = self.prompts.get("system_prompt", "")
rag_template = self.prompts.get("rag_template", "{context_str}\n\n{query}")
final_prompt = rag_template.format(context_str=context_str, query=query)
payload = { payload = {
"model": self.settings.LLM_MODEL, "model": self.settings.LLM_MODEL,
@ -55,29 +117,17 @@ class LLMService:
"stream": False, "stream": False,
"options": { "options": {
"temperature": 0.7, "temperature": 0.7,
# Kleinerer Context spart Rechenzeit, falls 4096 zu viel ist
"num_ctx": 2048 "num_ctx": 2048
} }
} }
try: try:
response = await self.client.post("/api/generate", json=payload) response = await self.client.post("/api/generate", json=payload)
if response.status_code != 200: if response.status_code != 200:
error_msg = response.text return f"Error: {response.text}"
logger.error(f"Ollama API Error ({response.status_code}): {error_msg}") return response.json().get("response", "")
return f"Fehler vom LLM (Modell '{self.settings.LLM_MODEL}' vorhanden?): {error_msg}"
data = response.json()
return data.get("response", "")
except httpx.ReadTimeout:
return "Timeout: Das Modell braucht zu lange zum Antworten (>120s). Hardware-Limit erreicht?"
except httpx.ConnectError:
return "Verbindungsfehler: Ist Ollama gestartet (Port 11434)?"
except Exception as e: except Exception as e:
logger.error(f"LLM Service Exception: {e}") return f"Error: {str(e)}"
return f"Interner Fehler: {str(e)}"
async def close(self): async def close(self):
await self.client.aclose() await self.client.aclose()

View File

@ -1,21 +1,83 @@
version: 1.0 # config/decision_engine.yaml
# Steuerung der Decision Engine (WP-06)
# Hybrid-Modus: Keywords (Fast) + LLM Router (Smart Fallback)
version: 1.1
settings:
# Schalter: Soll das LLM gefragt werden, wenn kein Keyword passt?
llm_fallback_enabled: true
# Der Prompt für den "Semantic Router" (Slow Path)
llm_router_prompt: |
Analysiere die folgende Nachricht und entscheide, welche Strategie passt.
Antworte NUR mit dem Namen der Strategie (ein Wort).
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).
NACHRICHT: "{query}"
STRATEGIE:
strategies: strategies:
# Strategie 1: Der Berater (Das haben wir gebaut) # 1. Fakten-Abfrage (Fallback & Default)
FACT:
description: "Reine Wissensabfrage."
trigger_keywords: []
inject_types: []
prompt_template: "rag_template"
prepend_instruction: null
# 2. Entscheidungs-Frage
DECISION: DECISION:
trigger_keywords: ["soll ich", "empfehlung", "strategie"] description: "Der User sucht Rat, Strategie oder Abwägung."
trigger_keywords:
- "soll ich"
- "meinung"
- "besser"
- "empfehlung"
- "strategie"
- "entscheidung"
- "wert"
- "prinzip"
- "vor- und nachteile"
- "abwägung"
inject_types: ["value", "principle", "goal"] inject_types: ["value", "principle", "goal"]
prompt_template: "decision_template" # Nutzt das "Abwägen"-Template prompt_template: "decision_template"
prepend_instruction: |
!!! ENTSCHEIDUNGS-MODUS !!!
BITTE WÄGE FAKTEN GEGEN FOLGENDE WERTE, PRINZIPIEN UND ZIELE AB:
# Strategie 2: Der empathische Zuhörer (NEU - Konzept) # 3. Empathie / "Ich"-Modus
EMPATHY: EMPATHY:
trigger_keywords: ["ich fühle", "traurig", "gestresst", "angst"] description: "Reaktion auf emotionale Zustände."
inject_types: ["belief", "experience"] # Lädt Glaubenssätze & eigene Erfahrungen trigger_keywords:
prompt_template: "empathy_template" # Ein Template, das auf "Zuhören" getrimmt ist - "ich fühle"
prepend_instruction: "SEI EMPATHISCH. SPIEGEL DIE GEFÜHLE." - "traurig"
- "glücklich"
- "gestresst"
- "angst"
- "nervt"
- "überfordert"
inject_types: ["experience", "belief", "profile"]
prompt_template: "empathy_template"
prepend_instruction: null
# Strategie 3: Der Coder (NEU - Konzept) # 4. Coding / Technical
CODING: CODING:
trigger_keywords: ["code", "python", "funktion", "bug"] description: "Technische Anfragen und Programmierung."
inject_types: ["snippet", "reference"] # Lädt nur technische Schnipsel trigger_keywords:
prompt_template: "technical_template" # Ein Template, das Codeblöcke erzwingt - "code"
- "python"
- "script"
- "funktion"
- "bug"
- "syntax"
- "json"
- "yaml"
inject_types: ["snippet", "reference", "source"]
prompt_template: "technical_template"
prepend_instruction: null

View File

@ -1,30 +1,20 @@
# config/prompts.yaml — Final V2.3 (WP-06 Decision Engine) # config/prompts.yaml — Final V2.3.1 (Multi-Personality Support)
# Optimiert für Phi-3 Mini (Small Language Model)
system_prompt: | system_prompt: |
Du bist 'mindnet', mein digitaler Zwilling und strategischer Partner. Du bist 'mindnet', mein digitaler Zwilling und strategischer Partner.
DEINE IDENTITÄT: DEINE IDENTITÄT:
- Du bist nicht nur eine Datenbank, sondern handelst nach MEINEN Werten. - Du bist nicht nur eine Datenbank, sondern handelst nach MEINEN Werten und Zielen.
- Du bist objektiv bei Fakten, aber subjektiv (in meinem Sinne) bei Entscheidungen. - Du passt deinen Stil dynamisch an die Situation an (Analytisch, Empathisch oder Technisch).
DEINE REGELN: DEINE REGELN:
1. Deine Antwort muss auf dem bereitgestellten KONTEXT basieren. 1. Deine Antwort muss zu 100% auf dem bereitgestellten KONTEXT basieren.
2. Unterscheide klar zwischen FAKTEN (externe Welt) und PRINZIPIEN (meine innere Welt). 2. Halluziniere keine Fakten, die nicht in den Quellen stehen.
3. Wenn Quellen vom Typ [VALUE] oder [PRINCIPLE] vorliegen, haben diese Vorrang bei der Entscheidungsfindung. 3. Antworte auf Deutsch (außer bei Code/Fachbegriffen).
4. Antworte auf Deutsch.
# Neuer Prompt für WP-06: Intent Detection
intent_prompt: |
Klassifiziere die folgende User-Anfrage.
Antworte NUR mit einem einzigen Wort: 'FACT' oder 'DECISION'.
'FACT': Der User fragt nach Wissen, Definitionen, Syntax oder Inhalten (z.B. "Was ist...", "Wie funktioniert...", "Zusammenfassung von...").
'DECISION': Der User fragt nach Rat, Meinung, Strategie oder Abwägung (z.B. "Soll ich...", "Was ist besser...", "Lohnt sich...", "Wie gehe ich vor...").
ANFRAGE: "{query}"
KLASSE:
# ---------------------------------------------------------
# 1. STANDARD: Fakten & Wissen (Intent: FACT)
# ---------------------------------------------------------
rag_template: | rag_template: |
QUELLEN (WISSEN): QUELLEN (WISSEN):
========================================= =========================================
@ -35,12 +25,14 @@ rag_template: |
{query} {query}
ANWEISUNG: ANWEISUNG:
Beantworte die Frage basierend auf den Quellen. Beantworte die Frage präzise basierend auf den Quellen.
Nenne die spezifischen Gründe, die im Text stehen (besonders aus [DECISION] Quellen). Fasse die Informationen zusammen. Sei objektiv und neutral.
# Neues Template für WP-06: Reasoning & Decision Making # ---------------------------------------------------------
# 2. DECISION: Strategie & Abwägung (Intent: DECISION)
# ---------------------------------------------------------
decision_template: | decision_template: |
KONTEXT (FAKTEN & WERTE): KONTEXT (FAKTEN & STRATEGIE):
========================================= =========================================
{context_str} {context_str}
========================================= =========================================
@ -51,11 +43,54 @@ 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 gegen meine [VALUE] und [PRINCIPLE] Quellen (falls vorhanden). 2. Prüfe dies hart gegen meine strategischen Notizen (Typ [VALUE], [PRINCIPLE], [GOAL]).
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?
4. Gib eine klare Empfehlung ab.
FORMAT: FORMAT:
- **Analyse:** (Faktenlage) - **Analyse:** (Kurze Zusammenfassung der Fakten)
- **Werte-Check:** (Konflikt oder Übereinstimmung mit Prinzipien) - **Abgleich:** (Gibt es Konflikte mit Werten/Zielen? Nenne die Quelle!)
- **Fazit:** (Deine Empfehlung) - **Empfehlung:** (Klare Meinung: Ja/Nein/Vielleicht mit Begründung)
# ---------------------------------------------------------
# 3. EMPATHY: Der Spiegel / "Ich"-Modus (Intent: EMPATHY)
# ---------------------------------------------------------
empathy_template: |
KONTEXT (ERFAHRUNGEN & GLAUBENSSÄTZE):
=========================================
{context_str}
=========================================
SITUATION:
{query}
ANWEISUNG:
Du agierst jetzt als mein empathischer Spiegel.
1. Versuche nicht sofort, das Problem technisch zu lösen.
2. Zeige Verständnis für die Situation basierend auf meinen eigenen Erfahrungen ([EXPERIENCE]) oder Glaubenssätzen ([BELIEF]), falls im Kontext vorhanden.
3. Antworte in der "Ich"-Form oder "Wir"-Form. Sei unterstützend.
TONFALL:
Ruhig, verständnisvoll, reflektiert. Keine Aufzählungszeichen, sondern fließender Text.
# ---------------------------------------------------------
# 4. TECHNICAL: Der Coder (Intent: CODING)
# ---------------------------------------------------------
technical_template: |
KONTEXT (DOCS & SNIPPETS):
=========================================
{context_str}
=========================================
TASK:
{query}
ANWEISUNG:
Du bist Senior Developer.
1. Ignoriere Smalltalk. Komm sofort zum Punkt.
2. Generiere validen, performanten Code basierend auf den Quellen.
3. Wenn Quellen fehlen, nutze dein allgemeines Programmierwissen, aber weise darauf hin.
FORMAT:
- Kurze Erklärung des Ansatzes.
- Markdown Code-Block (Copy-Paste fertig).
- Wichtige Edge-Cases.