mindnet/app/routers/chat.py
2025-12-08 10:32:57 +01:00

112 lines
3.7 KiB
Python

"""
app/routers/chat.py — RAG Endpunkt (WP-05)
Zweck:
Verbindet Retrieval (WP-04) mit LLM-Generation (WP-05).
1. Empfängt User-Frage.
2. Sucht relevante Chunks (Retriever).
3. Baut Kontext-String.
4. Generiert Antwort via Ollama.
Version:
0.1.0
"""
from fastapi import APIRouter, HTTPException, Depends
from typing import List
import time
import uuid
import logging
from app.models.dto import ChatRequest, ChatResponse, QueryRequest, QueryHit
from app.services.llm_service import LLMService
# Annahme: Der Retriever aus WP-04 liegt hier.
# Falls Import-Fehler: Bitte Pfad prüfen (z.B. app.services.retriever oder app.core.retriever)
from app.core.retriever import Retriever
router = APIRouter()
logger = logging.getLogger(__name__)
# Dependency für Services (Singletons oder Factory wäre sauberer, hier pragmatisch instanziiert)
def get_llm_service():
return LLMService()
def get_retriever():
return Retriever()
def _build_context_from_hits(hits: List[QueryHit]) -> str:
"""
Formatiert die Suchtreffer zu einem String für den Prompt.
Extrahiert Text aus hit.source (wo der Chunk-Inhalt liegt).
"""
context_parts = []
for i, hit in enumerate(hits, 1):
# Wir versuchen, den Text aus verschiedenen gängigen Feldern zu holen
source = hit.source or {}
content = source.get("text") or source.get("content") or "No text content available."
title = hit.note_id or "Unknown Note"
# Formatierung:
# [1] Titel der Notiz (Score: 0.85)
# Inhalt...
entry = (
f"SOURCE [{i}]: {title} (Score: {hit.total_score:.2f})\n"
f"CONTENT: {content}\n"
)
context_parts.append(entry)
return "\n---\n".join(context_parts)
@router.post("/", response_model=ChatResponse)
async def chat_endpoint(
request: ChatRequest,
llm: LLMService = Depends(get_llm_service),
retriever: Retriever = Depends(get_retriever)
):
start_time = time.time()
query_id = str(uuid.uuid4())
logger.info(f"Chat request [{query_id}]: {request.message}")
try:
# 1. Retrieval: Wir nutzen den existierenden Retriever
# Wir mappen ChatRequest auf QueryRequest (WP-04 Logik)
query_req = QueryRequest(
query=request.message,
mode="hybrid", # Hybrid ist am robustesten für RAG
top_k=request.top_k,
explain=request.explain # Traceability weitergeben
)
# Retrieval ausführen (retriever.search erwartet QueryRequest)
# Hinweis: retrieve_result ist vom Typ QueryResponse (aus DTO)
retrieve_result = await retriever.search(query_req)
hits = retrieve_result.results
# 2. Kontext bauen
if not hits:
logger.info(f"[{query_id}] No hits found for context.")
context_str = "Keine relevanten Notizen gefunden."
else:
context_str = _build_context_from_hits(hits)
# 3. LLM Generation
logger.info(f"[{query_id}] Generating answer with {len(hits)} context chunks...")
answer_text = await llm.generate_rag_response(
query=request.message,
context_str=context_str
)
# 4. Response bauen
duration_ms = int((time.time() - start_time) * 1000)
return ChatResponse(
query_id=retrieve_result.query_id, # Wir nutzen die ID vom Retriever für Konsistenz
answer=answer_text,
sources=hits,
latency_ms=duration_ms
)
except Exception as e:
logger.error(f"Error in chat endpoint: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e))