WP07 #7
|
|
@ -1,10 +1,11 @@
|
||||||
"""
|
"""
|
||||||
app/routers/chat.py — RAG Endpunkt (WP-06 Hybrid Router + WP-04c Feedback)
|
app/routers/chat.py — RAG Endpunkt (WP-06 Hybrid Router + WP-07 Interview Mode)
|
||||||
Version: 2.3.2 (Merged Stability Patch)
|
Version: 2.4.0 (Interview Support)
|
||||||
|
|
||||||
Features:
|
Features:
|
||||||
- Hybrid Intent Router (Keyword + LLM)
|
- Hybrid Intent Router (Keyword + LLM)
|
||||||
- Strategic Retrieval (Late Binding via Config)
|
- Strategic Retrieval (Late Binding via Config)
|
||||||
|
- Interview Loop (Schema-driven Data Collection)
|
||||||
- Context Enrichment (Payload/Source Fallback)
|
- Context Enrichment (Payload/Source Fallback)
|
||||||
- Data Flywheel (Feedback Logging Integration)
|
- Data Flywheel (Feedback Logging Integration)
|
||||||
"""
|
"""
|
||||||
|
|
@ -21,7 +22,6 @@ from app.config import get_settings
|
||||||
from app.models.dto import ChatRequest, ChatResponse, QueryRequest, QueryHit
|
from app.models.dto import ChatRequest, ChatResponse, QueryRequest, QueryHit
|
||||||
from app.services.llm_service import LLMService
|
from app.services.llm_service import LLMService
|
||||||
from app.core.retriever import Retriever
|
from app.core.retriever import Retriever
|
||||||
# [MERGE] Integration Feedback Service (WP-04c)
|
|
||||||
from app.services.feedback_service import log_search
|
from app.services.feedback_service import log_search
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
@ -62,6 +62,47 @@ def get_decision_strategy(intent: str) -> Dict[str, Any]:
|
||||||
strategies = config.get("strategies", {})
|
strategies = config.get("strategies", {})
|
||||||
return strategies.get(intent, strategies.get("FACT", {}))
|
return strategies.get(intent, strategies.get("FACT", {}))
|
||||||
|
|
||||||
|
# --- Helper: Target Type Detection (WP-07) ---
|
||||||
|
|
||||||
|
def _detect_target_type(message: str, configured_schemas: Dict[str, Any]) -> str:
|
||||||
|
"""
|
||||||
|
Versucht zu erraten, welchen Notiz-Typ der User erstellen will.
|
||||||
|
Nutzt Keywords und Mappings.
|
||||||
|
"""
|
||||||
|
message_lower = message.lower()
|
||||||
|
|
||||||
|
# 1. Direkter Match mit Schema-Keys (z.B. "projekt", "entscheidung")
|
||||||
|
# Ignoriere 'default' hier
|
||||||
|
for type_key in configured_schemas.keys():
|
||||||
|
if type_key == "default":
|
||||||
|
continue
|
||||||
|
if type_key in message_lower:
|
||||||
|
return type_key
|
||||||
|
|
||||||
|
# 2. Synonym-Mapping (Deutsch -> Schema Key)
|
||||||
|
# Dies verbessert die UX, falls User deutsche Begriffe nutzen
|
||||||
|
synonyms = {
|
||||||
|
"projekt": "project",
|
||||||
|
"vorhaben": "project",
|
||||||
|
"entscheidung": "decision",
|
||||||
|
"beschluss": "decision",
|
||||||
|
"ziel": "goal",
|
||||||
|
"erfahrung": "experience",
|
||||||
|
"lektion": "experience",
|
||||||
|
"wert": "value",
|
||||||
|
"prinzip": "principle",
|
||||||
|
"grundsatz": "principle",
|
||||||
|
"notiz": "default",
|
||||||
|
"idee": "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
for term, schema_key in synonyms.items():
|
||||||
|
if term in message_lower:
|
||||||
|
# Prüfen, ob der gemappte Key auch konfiguriert ist
|
||||||
|
if schema_key in configured_schemas:
|
||||||
|
return schema_key
|
||||||
|
|
||||||
|
return "default"
|
||||||
|
|
||||||
# --- Dependencies ---
|
# --- Dependencies ---
|
||||||
|
|
||||||
|
|
@ -167,67 +208,118 @@ async def chat_endpoint(
|
||||||
|
|
||||||
# Strategy Load
|
# Strategy Load
|
||||||
strategy = get_decision_strategy(intent)
|
strategy = get_decision_strategy(intent)
|
||||||
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", "")
|
|
||||||
|
|
||||||
# 2. Primary Retrieval
|
|
||||||
query_req = QueryRequest(
|
|
||||||
query=request.message,
|
|
||||||
mode="hybrid",
|
|
||||||
top_k=request.top_k,
|
|
||||||
explain=request.explain
|
|
||||||
)
|
|
||||||
retrieve_result = await retriever.search(query_req)
|
|
||||||
hits = retrieve_result.results
|
|
||||||
|
|
||||||
# 3. Strategic Retrieval (WP-06 Kernfeature)
|
# --- SPLIT LOGIC: INTERVIEW vs. RAG ---
|
||||||
if inject_types:
|
|
||||||
logger.info(f"[{query_id}] Executing Strategic Retrieval for types: {inject_types}...")
|
sources_hits = []
|
||||||
strategy_req = QueryRequest(
|
final_prompt = ""
|
||||||
query=request.message,
|
|
||||||
mode="hybrid",
|
if intent == "INTERVIEW":
|
||||||
top_k=3,
|
# --- WP-07: INTERVIEW MODE ---
|
||||||
filters={"type": inject_types},
|
# Kein Retrieval. Wir nutzen den Dialog-Kontext.
|
||||||
explain=False
|
|
||||||
)
|
# 1. Schema Loading (Late Binding)
|
||||||
strategy_result = await retriever.search(strategy_req)
|
schemas = strategy.get("schemas", {})
|
||||||
|
target_type = _detect_target_type(request.message, schemas)
|
||||||
|
active_schema = schemas.get(target_type, schemas.get("default"))
|
||||||
|
|
||||||
|
logger.info(f"[{query_id}] Starting Interview for Type: {target_type}")
|
||||||
|
|
||||||
|
# Robustes Schema-Parsing (Dict vs List)
|
||||||
|
if isinstance(active_schema, dict):
|
||||||
|
fields_list = active_schema.get("fields", [])
|
||||||
|
hint_str = active_schema.get("hint", "")
|
||||||
|
else:
|
||||||
|
fields_list = active_schema # Fallback falls nur Liste definiert
|
||||||
|
hint_str = ""
|
||||||
|
|
||||||
|
fields_str = "\n- " + "\n- ".join(fields_list)
|
||||||
|
|
||||||
|
# 2. Context Logic
|
||||||
|
# Hinweis: In einer Stateless-API ist {context_str} idealerweise die History.
|
||||||
|
# Da ChatRequest (noch) kein History-Feld hat, nutzen wir einen Placeholder
|
||||||
|
# oder verlassen uns darauf, dass der Client die History im Prompt mitschickt
|
||||||
|
# (Streamlit Pattern: Appends history to prompt).
|
||||||
|
# Wir labeln es hier explizit.
|
||||||
|
context_str = "Bisheriger Verlauf (falls vorhanden): Siehe oben/unten."
|
||||||
|
|
||||||
|
# 3. Prompt Assembly
|
||||||
|
template = llm.prompts.get(prompt_key, "")
|
||||||
|
final_prompt = template.replace("{context_str}", context_str) \
|
||||||
|
.replace("{query}", request.message) \
|
||||||
|
.replace("{target_type}", target_type) \
|
||||||
|
.replace("{schema_fields}", fields_str) \
|
||||||
|
.replace("{schema_hint}", hint_str)
|
||||||
|
|
||||||
|
# Keine Hits im Interview
|
||||||
|
sources_hits = []
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
# 4. Context Building
|
|
||||||
if not hits:
|
|
||||||
context_str = "Keine relevanten Notizen gefunden."
|
|
||||||
else:
|
else:
|
||||||
context_str = _build_enriched_context(hits)
|
# --- WP-06: STANDARD RAG MODE ---
|
||||||
|
inject_types = strategy.get("inject_types", [])
|
||||||
|
prepend_instr = strategy.get("prepend_instruction", "")
|
||||||
|
|
||||||
# 5. Generation
|
# 2. Primary Retrieval
|
||||||
template = llm.prompts.get(prompt_key, "{context_str}\n\n{query}")
|
query_req = QueryRequest(
|
||||||
system_prompt = llm.prompts.get("system_prompt", "")
|
query=request.message,
|
||||||
|
mode="hybrid",
|
||||||
|
top_k=request.top_k,
|
||||||
|
explain=request.explain
|
||||||
|
)
|
||||||
|
retrieve_result = await retriever.search(query_req)
|
||||||
|
hits = retrieve_result.results
|
||||||
|
|
||||||
|
# 3. Strategic Retrieval (WP-06 Kernfeature)
|
||||||
|
if inject_types:
|
||||||
|
logger.info(f"[{query_id}] Executing Strategic Retrieval for types: {inject_types}...")
|
||||||
|
strategy_req = QueryRequest(
|
||||||
|
query=request.message,
|
||||||
|
mode="hybrid",
|
||||||
|
top_k=3,
|
||||||
|
filters={"type": inject_types},
|
||||||
|
explain=False
|
||||||
|
)
|
||||||
|
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)
|
||||||
|
|
||||||
|
# 4. Context Building
|
||||||
|
if not hits:
|
||||||
|
context_str = "Keine relevanten Notizen gefunden."
|
||||||
|
else:
|
||||||
|
context_str = _build_enriched_context(hits)
|
||||||
|
|
||||||
|
# 5. Generation Setup
|
||||||
|
template = llm.prompts.get(prompt_key, "{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
|
||||||
|
|
||||||
if prepend_instr:
|
# --- COMMON GENERATION ---
|
||||||
context_str = f"{prepend_instr}\n\n{context_str}"
|
|
||||||
|
system_prompt = llm.prompts.get("system_prompt", "")
|
||||||
final_prompt = template.replace("{context_str}", context_str).replace("{query}", request.message)
|
|
||||||
|
|
||||||
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})...")
|
||||||
|
|
||||||
# System-Prompt separat übergeben (WP-06a Fix)
|
# System-Prompt separat übergeben
|
||||||
answer_text = await llm.generate_raw_response(prompt=final_prompt, system=system_prompt)
|
answer_text = await llm.generate_raw_response(prompt=final_prompt, system=system_prompt)
|
||||||
|
|
||||||
duration_ms = int((time.time() - start_time) * 1000)
|
duration_ms = int((time.time() - start_time) * 1000)
|
||||||
|
|
||||||
# 6. Logging (Fire & Forget) - [MERGE POINT]
|
# 6. Logging (Fire & Forget)
|
||||||
# Wir loggen alles für das Data Flywheel (WP-08 Self-Tuning)
|
|
||||||
try:
|
try:
|
||||||
log_search(
|
log_search(
|
||||||
query_id=query_id,
|
query_id=query_id,
|
||||||
query_text=request.message,
|
query_text=request.message,
|
||||||
results=hits,
|
results=sources_hits,
|
||||||
mode="chat_rag",
|
mode="interview" if intent == "INTERVIEW" else "chat_rag",
|
||||||
metadata={
|
metadata={
|
||||||
"intent": intent,
|
"intent": intent,
|
||||||
"intent_source": intent_source,
|
"intent_source": intent_source,
|
||||||
|
|
@ -242,7 +334,7 @@ async def chat_endpoint(
|
||||||
return ChatResponse(
|
return ChatResponse(
|
||||||
query_id=query_id,
|
query_id=query_id,
|
||||||
answer=answer_text,
|
answer=answer_text,
|
||||||
sources=hits,
|
sources=sources_hits,
|
||||||
latency_ms=duration_ms,
|
latency_ms=duration_ms,
|
||||||
intent=intent,
|
intent=intent,
|
||||||
intent_source=intent_source
|
intent_source=intent_source
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,19 @@
|
||||||
# config/decision_engine.yaml
|
# config/decision_engine.yaml
|
||||||
# Steuerung der Decision Engine (WP-06)
|
# Steuerung der Decision Engine (WP-06 + WP-07)
|
||||||
# Hybrid-Modus: Keywords (Fast) + LLM Router (Smart Fallback)
|
# Hybrid-Modus: Keywords (Fast) + LLM Router (Smart Fallback)
|
||||||
version: 1.2
|
version: 1.3
|
||||||
|
|
||||||
settings:
|
settings:
|
||||||
llm_fallback_enabled: true
|
llm_fallback_enabled: true
|
||||||
|
|
||||||
# Few-Shot Prompting für bessere SLM-Performance
|
# Few-Shot Prompting für bessere SLM-Performance
|
||||||
|
# Erweitert um INTERVIEW Beispiele
|
||||||
llm_router_prompt: |
|
llm_router_prompt: |
|
||||||
Du bist ein Klassifikator. Analysiere die Nachricht und wähle die passende Strategie.
|
Du bist ein Klassifikator. Analysiere die Nachricht und wähle die passende Strategie.
|
||||||
Antworte NUR mit dem Namen der Strategie.
|
Antworte NUR mit dem Namen der Strategie.
|
||||||
|
|
||||||
STRATEGIEN:
|
STRATEGIEN:
|
||||||
|
- INTERVIEW: User will Wissen strukturieren, Notizen anlegen, Projekte starten ("Neu", "Festhalten").
|
||||||
- DECISION: Rat, Strategie, Vor/Nachteile, "Soll ich".
|
- DECISION: Rat, Strategie, Vor/Nachteile, "Soll ich".
|
||||||
- EMPATHY: Gefühle, Frust, Freude, Probleme, "Alles ist sinnlos", "Ich bin traurig".
|
- EMPATHY: Gefühle, Frust, Freude, Probleme, "Alles ist sinnlos", "Ich bin traurig".
|
||||||
- CODING: Code, Syntax, Programmierung, Python.
|
- CODING: Code, Syntax, Programmierung, Python.
|
||||||
|
|
@ -20,6 +22,8 @@ settings:
|
||||||
BEISPIELE:
|
BEISPIELE:
|
||||||
User: "Wie funktioniert Qdrant?" -> FACT
|
User: "Wie funktioniert Qdrant?" -> FACT
|
||||||
User: "Soll ich Qdrant nutzen?" -> DECISION
|
User: "Soll ich Qdrant nutzen?" -> DECISION
|
||||||
|
User: "Ich möchte ein neues Projekt anlegen" -> INTERVIEW
|
||||||
|
User: "Lass uns eine Entscheidung festhalten" -> INTERVIEW
|
||||||
User: "Schreibe ein Python Script" -> CODING
|
User: "Schreibe ein Python Script" -> CODING
|
||||||
User: "Alles ist grau und sinnlos" -> EMPATHY
|
User: "Alles ist grau und sinnlos" -> EMPATHY
|
||||||
User: "Mir geht es heute gut" -> EMPATHY
|
User: "Mir geht es heute gut" -> EMPATHY
|
||||||
|
|
@ -86,4 +90,54 @@ strategies:
|
||||||
- "yaml"
|
- "yaml"
|
||||||
inject_types: ["snippet", "reference", "source"]
|
inject_types: ["snippet", "reference", "source"]
|
||||||
prompt_template: "technical_template"
|
prompt_template: "technical_template"
|
||||||
prepend_instruction: null
|
prepend_instruction: null
|
||||||
|
|
||||||
|
# 5. Interview / Datenerfassung (WP-07)
|
||||||
|
INTERVIEW:
|
||||||
|
description: "Der User möchte strukturiertes Wissen erfassen (Projekt, Notiz, Idee)."
|
||||||
|
trigger_keywords:
|
||||||
|
- "neue notiz"
|
||||||
|
- "neues projekt"
|
||||||
|
- "neue entscheidung"
|
||||||
|
- "neues ziel"
|
||||||
|
- "festhalten"
|
||||||
|
- "entwurf erstellen"
|
||||||
|
- "interview"
|
||||||
|
- "dokumentieren"
|
||||||
|
- "erfassen"
|
||||||
|
- "idee speichern"
|
||||||
|
inject_types: [] # Keine RAG-Suche, reiner Kontext-Dialog
|
||||||
|
prompt_template: "interview_template"
|
||||||
|
prepend_instruction: null
|
||||||
|
|
||||||
|
# LATE BINDING SCHEMAS:
|
||||||
|
# Definition der Pflichtfelder pro Typ (korrespondiert mit types.yaml)
|
||||||
|
# Wenn ein Typ hier fehlt, wird 'default' genutzt.
|
||||||
|
schemas:
|
||||||
|
default:
|
||||||
|
fields: ["Titel", "Thema/Inhalt", "Tags"]
|
||||||
|
hint: "Halte es einfach und übersichtlich."
|
||||||
|
|
||||||
|
project:
|
||||||
|
fields: ["Titel", "Zielsetzung (Goal)", "Status (draft/active)", "Wichtige Stakeholder", "Nächste Schritte"]
|
||||||
|
hint: "Achte darauf, Abhängigkeiten zu anderen Projekten mit [[rel:depends_on]] zu erfragen."
|
||||||
|
|
||||||
|
decision:
|
||||||
|
fields: ["Titel", "Kontext (Warum entscheiden wir?)", "Getroffene Entscheidung", "Betrachtete Alternativen", "Status (proposed/final)"]
|
||||||
|
hint: "Wichtig: Frage explizit nach den Gründen gegen die Alternativen."
|
||||||
|
|
||||||
|
goal:
|
||||||
|
fields: ["Titel", "Zeitrahmen (Deadline)", "Messkriterien (KPIs)", "Verbundene Werte"]
|
||||||
|
hint: "Ziele sollten SMART formuliert sein."
|
||||||
|
|
||||||
|
experience:
|
||||||
|
fields: ["Titel", "Situation (Kontext)", "Erkenntnis (Learning)", "Emotionale Keywords (für Empathie-Suche)"]
|
||||||
|
hint: "Fokussiere dich auf die persönliche Lektion."
|
||||||
|
|
||||||
|
value:
|
||||||
|
fields: ["Titel (Name des Werts)", "Definition (Was bedeutet das für uns?)", "Anti-Beispiel (Was ist es nicht?)"]
|
||||||
|
hint: "Werte dienen als Entscheidungsgrundlage."
|
||||||
|
|
||||||
|
principle:
|
||||||
|
fields: ["Titel", "Handlungsanweisung", "Begründung"]
|
||||||
|
hint: "Prinzipien sind härter als Werte."
|
||||||
|
|
@ -93,4 +93,47 @@ technical_template: |
|
||||||
FORMAT:
|
FORMAT:
|
||||||
- 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.
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# 5. INTERVIEW: Der Analyst (Intent: INTERVIEW)
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
interview_template: |
|
||||||
|
CHAT_HISTORIE (BISHERIGER KONTEXT):
|
||||||
|
=========================================
|
||||||
|
{context_str}
|
||||||
|
=========================================
|
||||||
|
|
||||||
|
AKTUELLE USER-EINGABE:
|
||||||
|
{query}
|
||||||
|
|
||||||
|
DEINE ROLLE:
|
||||||
|
Du bist 'Mindnet Analyst'. Dein Ziel ist es, einen strukturierten Entwurf für eine Notiz vom Typ '{target_type}' zu erstellen.
|
||||||
|
|
||||||
|
PFLICHTFELDER (SCHEMA):
|
||||||
|
{schema_fields}
|
||||||
|
|
||||||
|
HINWEIS ZUM TYP:
|
||||||
|
{schema_hint}
|
||||||
|
|
||||||
|
ANWEISUNG:
|
||||||
|
1. Analysiere den bisherigen Verlauf und die Eingabe. Welche der Pflichtfelder sind bereits bekannt?
|
||||||
|
2. STATUS CHECK:
|
||||||
|
- Fehlen Pflichtfelder? -> Stelle GENAU EINE gezielte Frage, um das nächste fehlende Feld zu klären. Warte auf die Antwort.
|
||||||
|
- Sind alle Felder grob geklärt? -> Generiere den finalen Entwurf.
|
||||||
|
|
||||||
|
OUTPUT FORMAT (Nur wenn alle Infos da sind):
|
||||||
|
Erstelle einen Markdown-Codeblock. Nutze Frontmatter.
|
||||||
|
Verlinke erkannte Entitäten aggressiv mit [[Wikilinks]] oder [[rel:relation Ziel]].
|
||||||
|
|
||||||
|
Beispiel Output:
|
||||||
|
"Danke, ich habe alle Infos. Hier ist dein Entwurf:"
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
type: {target_type}
|
||||||
|
status: draft
|
||||||
|
tags: [...]
|
||||||
|
---
|
||||||
|
# Titel
|
||||||
|
...
|
||||||
48
tests/test_interview_intent.py
Normal file
48
tests/test_interview_intent.py
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
|
||||||
|
# URL anpassen, falls du auf Port 8001 (Prod) oder 8002 (Dev) bist
|
||||||
|
API_URL = "http://localhost:8002/chat/"
|
||||||
|
|
||||||
|
def test_intent(message, expected_intent, expected_type_hint):
|
||||||
|
payload = {
|
||||||
|
"message": message,
|
||||||
|
"top_k": 0, # Für Interview irrelevant
|
||||||
|
"explain": False
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.post(API_URL, json=payload)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
intent = data.get("intent")
|
||||||
|
answer = data.get("answer")
|
||||||
|
|
||||||
|
print(f"--- TEST: '{message}' ---")
|
||||||
|
print(f"Erkannter Intent: {intent}")
|
||||||
|
print(f"Antwort-Snippet: {answer[:100]}...")
|
||||||
|
|
||||||
|
if intent == expected_intent:
|
||||||
|
print("✅ Intent SUCCESS")
|
||||||
|
else:
|
||||||
|
print(f"❌ Intent FAILED (Erwartet: {expected_intent})")
|
||||||
|
|
||||||
|
if expected_type_hint.lower() in answer.lower():
|
||||||
|
print(f"✅ Context Check SUCCESS (Typ '{expected_type_hint}' erkannt)")
|
||||||
|
else:
|
||||||
|
print(f"⚠️ Context Check WARNING (Typ '{expected_type_hint}' nicht explizit im Start-Prompt gefunden)")
|
||||||
|
print("\n")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error: {e}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Test 1: Projekt-Start
|
||||||
|
test_intent("Ich möchte ein neues Projekt anlegen", "INTERVIEW", "project")
|
||||||
|
|
||||||
|
# Test 2: Entscheidungs-Doku
|
||||||
|
test_intent("Lass uns eine Entscheidung festhalten", "INTERVIEW", "decision")
|
||||||
|
|
||||||
|
# Test 3: Standard Chat (Gegenprobe)
|
||||||
|
test_intent("Was ist ein Vektor?", "FACT", "")
|
||||||
Loading…
Reference in New Issue
Block a user