98 lines
2.8 KiB
Python
98 lines
2.8 KiB
Python
"""
|
|
app/services/feedback_service.py
|
|
Service zum Loggen von Suchanfragen und Feedback (WP-04c).
|
|
Speichert Daten als JSONL für späteres Self-Tuning (WP-08).
|
|
|
|
Version: 1.1 (Chat-Support)
|
|
"""
|
|
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}") |