persönlichkeitsupdate

This commit is contained in:
Lars 2025-12-08 13:39:16 +01:00
parent 7745b71832
commit 531e3790b3
2 changed files with 66 additions and 37 deletions

View File

@ -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

View File

@ -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.
Du bist 'mindnet', ein persönliches KI-Gedächtnis und der Digitale Zwilling deines Erschaffers ("User").
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.
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...").
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.
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).