mindnet/app/services/feedback_service.py
2025-12-15 15:40:39 +01:00

99 lines
2.8 KiB
Python

"""
FILE: app/services/feedback_service.py
DESCRIPTION: Schreibt Search- und Feedback-Logs in JSONL-Dateien.
VERSION: 1.1
STATUS: Active
DEPENDENCIES: app.models.dto
LAST_ANALYSIS: 2025-12-15
"""
import json
import os
import time
import logging
from pathlib import Path
from typing import Dict, Any, List, Union
from app.models.dto import QueryRequest, QueryResponse, FeedbackRequest, QueryHit
logger = logging.getLogger(__name__)
# Pfad für Logs (lokal auf dem Beelink/PC)
LOG_DIR = Path("data/logs")
SEARCH_LOG_FILE = LOG_DIR / "search_history.jsonl"
FEEDBACK_LOG_FILE = LOG_DIR / "feedback.jsonl"
def _ensure_log_dir():
if not LOG_DIR.exists():
os.makedirs(LOG_DIR, exist_ok=True)
def _append_jsonl(file_path: Path, data: dict):
try:
with open(file_path, "a", encoding="utf-8") as f:
f.write(json.dumps(data, ensure_ascii=False) + "\n")
except Exception as e:
logger.error(f"Failed to write log: {e}")
def log_search(
query_id: str,
query_text: str,
results: List[QueryHit],
mode: str = "unknown",
metadata: Dict[str, Any] = None
):
"""
Generische Logging-Funktion für Suche UND Chat.
Args:
query_id: UUID der Anfrage.
query_text: User-Eingabe.
results: Liste der Treffer (QueryHit Objekte).
mode: z.B. "semantic", "hybrid", "chat_rag".
metadata: Zusätzliche Infos (z.B. generierte Antwort, Intent).
"""
_ensure_log_dir()
hits_summary = []
for hit in results:
# Pydantic Model Dump für saubere Serialisierung
breakdown = None
if hit.explanation and hit.explanation.breakdown:
breakdown = hit.explanation.breakdown.model_dump()
hits_summary.append({
"node_id": hit.node_id,
"note_id": hit.note_id,
"total_score": hit.total_score,
"breakdown": breakdown,
"rank_semantic": hit.semantic_score,
"rank_edge": hit.edge_bonus,
"type": hit.source.get("type") if hit.source else "unknown"
})
entry = {
"timestamp": time.time(),
"query_id": query_id,
"query_text": query_text,
"mode": mode,
"hits_count": len(hits_summary),
"hits": hits_summary,
"metadata": metadata or {}
}
_append_jsonl(SEARCH_LOG_FILE, entry)
logger.info(f"Logged search/chat interaction {query_id}")
def log_feedback(fb: FeedbackRequest):
"""
Speichert das User-Feedback.
"""
_ensure_log_dir()
entry = {
"timestamp": time.time(),
"query_id": fb.query_id,
"node_id": fb.node_id,
"score": fb.score,
"comment": fb.comment
}
_append_jsonl(FEEDBACK_LOG_FILE, entry)
logger.info(f"Logged feedback for {fb.query_id}")