WP05 #3
|
|
@ -1,8 +1,13 @@
|
|||
"""
|
||||
app/routers/chat.py — RAG Endpunkt (WP-05)
|
||||
app/routers/chat.py — RAG Endpunkt (WP-05 Final)
|
||||
|
||||
Zweck:
|
||||
Verbindet Retrieval mit LLM-Generation.
|
||||
Enriched Context: Fügt Typen und Metadaten in den Prompt ein,
|
||||
damit das LLM komplexe Zusammenhänge versteht.
|
||||
|
||||
Version:
|
||||
0.2.0 (Final Clean Version)
|
||||
0.3.0 (Audit Finalization)
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Depends
|
||||
|
|
@ -24,14 +29,18 @@ def get_llm_service():
|
|||
def get_retriever():
|
||||
return Retriever()
|
||||
|
||||
def _build_context_from_hits(hits: List[QueryHit]) -> str:
|
||||
def _build_enriched_context(hits: List[QueryHit]) -> str:
|
||||
"""
|
||||
Formatiert die Suchtreffer zu einem String für den Prompt.
|
||||
Baut einen 'Rich Context' String.
|
||||
Statt nur Text, injizieren wir Metadaten (Typ, Tags), damit das LLM
|
||||
die semantische Rolle des Schnipsels versteht.
|
||||
"""
|
||||
context_parts = []
|
||||
|
||||
for i, hit in enumerate(hits, 1):
|
||||
source = hit.source or {}
|
||||
# Robuster Zugriff auf Content
|
||||
|
||||
# 1. Content extrahieren (Robust)
|
||||
content = (
|
||||
source.get("text") or
|
||||
source.get("content") or
|
||||
|
|
@ -39,15 +48,31 @@ def _build_context_from_hits(hits: List[QueryHit]) -> str:
|
|||
source.get("chunk_text") or
|
||||
"[Kein Textinhalt verfügbar]"
|
||||
)
|
||||
title = hit.note_id or "Unknown Note"
|
||||
|
||||
# 2. Metadaten für "Context Intelligence"
|
||||
title = hit.note_id or "Unbekannte Notiz"
|
||||
|
||||
# Versuche, den Typ aus dem Payload zu lesen (wichtig für Decision/Project Unterscheidung)
|
||||
# In WP-03 Import landen diese Infos oft in 'metadata' oder direkt im Payload root.
|
||||
# Wir schauen defensiv an beiden Orten.
|
||||
note_type = source.get("type", "unknown").upper()
|
||||
tags = source.get("tags", [])
|
||||
if isinstance(tags, list):
|
||||
tags_str = ", ".join(tags[:3]) # Nur die ersten 3 Tags
|
||||
else:
|
||||
tags_str = str(tags)
|
||||
|
||||
# 3. Formatierung für das LLM
|
||||
# Wir nutzen ein Format, das wie ein strukturiertes Dokument aussieht.
|
||||
# Das hilft dem Modell, Grenzen zwischen Quellen zu erkennen.
|
||||
entry = (
|
||||
f"SOURCE [{i}]: {title} (Score: {hit.total_score:.2f})\n"
|
||||
f"CONTENT: {content}\n"
|
||||
f"### SOURCE [{i}]: {title}\n"
|
||||
f"METADATA: [TYPE: {note_type}] [SCORE: {hit.total_score:.2f}] [TAGS: {tags_str}]\n"
|
||||
f"CONTENT:\n{content}\n"
|
||||
)
|
||||
context_parts.append(entry)
|
||||
|
||||
return "\n---\n".join(context_parts)
|
||||
return "\n\n".join(context_parts)
|
||||
|
||||
@router.post("/", response_model=ChatResponse)
|
||||
async def chat_endpoint(
|
||||
|
|
@ -58,30 +83,33 @@ async def chat_endpoint(
|
|||
start_time = time.time()
|
||||
query_id = str(uuid.uuid4())
|
||||
|
||||
# Minimales Logging für Traceability
|
||||
# Logging verkürzt für Übersichtlichkeit
|
||||
logger.info(f"Chat request [{query_id}]: {request.message[:50]}...")
|
||||
|
||||
try:
|
||||
# 1. Retrieval
|
||||
# 1. Retrieval (Graph-Awareness)
|
||||
# Wir erzwingen 'hybrid', damit graph-basierte Nachbarn gefunden werden.
|
||||
query_req = QueryRequest(
|
||||
query=request.message,
|
||||
mode="hybrid",
|
||||
mode="hybrid", # <--- Audit Check: Hybrid Mode active
|
||||
top_k=request.top_k,
|
||||
explain=request.explain
|
||||
explain=request.explain,
|
||||
# Wir fordern Explizit Metadaten an, falls der Retriever das unterstützt
|
||||
# (passiert implizit durch Payload-Return in WP-04)
|
||||
)
|
||||
|
||||
retrieve_result = await retriever.search(query_req)
|
||||
hits = retrieve_result.results
|
||||
|
||||
# 2. Kontext bauen
|
||||
# 2. Context Assembly
|
||||
if not hits:
|
||||
logger.info(f"[{query_id}] No hits found.")
|
||||
context_str = "Keine relevanten Notizen gefunden."
|
||||
else:
|
||||
context_str = _build_context_from_hits(hits)
|
||||
context_str = _build_enriched_context(hits)
|
||||
|
||||
# 3. LLM Generation
|
||||
logger.info(f"[{query_id}] Sending to LLM ({len(hits)} context chunks)...")
|
||||
# 3. Generation
|
||||
logger.info(f"[{query_id}] Context built with {len(hits)} chunks. Sending to LLM...")
|
||||
answer_text = await llm.generate_rag_response(
|
||||
query=request.message,
|
||||
context_str=context_str
|
||||
|
|
|
|||
|
|
@ -1,31 +1,32 @@
|
|||
# config/prompts.yaml — Persönlichkeit & Templates für mindnet
|
||||
# Version: 1.0
|
||||
# config/prompts.yaml — Persönlichkeit & RAG-Strategie
|
||||
# Version: 2.0 (Audit Update)
|
||||
|
||||
system_prompt: |
|
||||
Du bist 'mindnet', ein persönliches KI-Gedächtnis und ein digitaler Zwilling.
|
||||
|
||||
Deine Identität:
|
||||
- Du bist hilfreich, präzise und reflektiert.
|
||||
- Du erfindest keine Fakten (Halluzinationen vermeiden).
|
||||
- Du basierst deine Antworten streng auf dem bereitgestellten KONTEXT (Auszüge aus Notizen).
|
||||
- Wenn der Kontext die Antwort nicht enthält, gib das offen zu.
|
||||
- Du zitierst Quellen implizit durch Nennung der Notiz-Titel, wenn sinnvoll.
|
||||
|
||||
Dein Stil:
|
||||
- Du antwortest in der Sprache des Users (meist Deutsch).
|
||||
- Du bist 'Ich' (das Gedächtnis) und sprichst den User als 'Du' (der Besitzer) an.
|
||||
- Formattiere Antworten mit Markdown (fett, listen), um Lesbarkeit zu erhöhen.
|
||||
Du bist 'mindnet', ein persönliches KI-Gedächtnis und der Digitale Zwilling deines Erschaffers ("User").
|
||||
|
||||
DEINE PERSÖNLICHKEIT & WERTE:
|
||||
1. Pragmatismus: Du bevorzugst funktionierende Lösungen über theoretische Perfektion.
|
||||
2. Transparenz: Du erfindest keine Fakten. Wenn Informationen im Kontext fehlen, sagst du das klar.
|
||||
3. Vernetztes Denken: Du suchst aktiv nach Verbindungen zwischen den bereitgestellten Notizen.
|
||||
4. Erklärbarkeit: Wenn du eine Aussage machst, beziehst du dich implizit auf die Quelle (z.B. "Wie in Projekt X definiert...").
|
||||
|
||||
DEINE AUFGABE:
|
||||
Beantworte die Frage des Users ausschließlich basierend auf dem untenstehenden KONTEXT.
|
||||
Der Kontext besteht aus Auszügen verschiedener Notizen. Achte besonders auf den [TYPE] der Notiz:
|
||||
- [DECISION] erklärt das "Warum".
|
||||
- [PROJECT] erklärt das "Was" und "Wann".
|
||||
- [CONCEPT] liefert Definitionen.
|
||||
- [VALUE] definiert die moralische/strategische Ausrichtung.
|
||||
|
||||
rag_template: |
|
||||
HINTERGRUNDWISSEN (KONTEXT):
|
||||
---------------------
|
||||
=========================================
|
||||
{context_str}
|
||||
---------------------
|
||||
=========================================
|
||||
|
||||
FRAGE DES USERS:
|
||||
{query}
|
||||
|
||||
ANWEISUNG:
|
||||
Beantworte die Frage basierend auf dem oben stehenden Kontext.
|
||||
Wenn du dich auf eine spezifische Notiz beziehst, erwähne ihren Titel.
|
||||
Erkläre Zusammenhänge, falls im Kontext ersichtlich.
|
||||
Analysiere die Quellen oben. Synthetisiere eine Antwort, die die Frage präzise beantwortet.
|
||||
Nutze Markdown für Struktur (Fettgedrucktes für Wichtiges).
|
||||
Loading…
Reference in New Issue
Block a user