Spaces:
Sleeping
Sleeping
| """Workspace API — query history and feedback, stored in Redis.""" | |
| from __future__ import annotations | |
| import json | |
| from typing import Literal | |
| import redis.asyncio as aioredis | |
| from fastapi import APIRouter, HTTPException, Query | |
| from pydantic import BaseModel | |
| from src.config import settings | |
| from src.utils.logger import get_logger | |
| logger = get_logger(__name__) | |
| router = APIRouter(tags=["workspace"]) | |
| HISTORY_KEY = "gs:queries" # shared with analytics | |
| FEEDBACK_KEY = "gs:feedback:{id}" # one key per query_id | |
| async def _redis() -> aioredis.Redis: | |
| return aioredis.from_url(settings.redis_url, decode_responses=True) | |
| # --------------------------------------------------------------------------- | |
| # GET /api/workspace/history | |
| # --------------------------------------------------------------------------- | |
| async def get_history( | |
| page: int = Query(default=1, ge=1), | |
| limit: int = Query(default=20, ge=1, le=100), | |
| ) -> dict: | |
| r = await _redis() | |
| try: | |
| total = await r.llen(HISTORY_KEY) | |
| start = (page - 1) * limit | |
| raw_list = await r.lrange(HISTORY_KEY, start, start + limit - 1) | |
| except Exception as exc: | |
| logger.warning("workspace_history_redis_failed", extra={"error": str(exc)}) | |
| return {"items": [], "total": 0} | |
| finally: | |
| await r.aclose() | |
| items = [] | |
| for raw in raw_list: | |
| try: | |
| ev = json.loads(raw) | |
| items.append({ | |
| "id": ev.get("id", ""), | |
| "query": ev.get("query", ""), | |
| "answer_brief": ev.get("answer_brief", ""), | |
| "created_at": ev.get("created_at", ""), | |
| "success": ev.get("success", True), | |
| "duration_ms": ev.get("duration_ms", 0), | |
| }) | |
| except Exception: | |
| continue | |
| return {"items": items, "total": total} | |
| # --------------------------------------------------------------------------- | |
| # POST /api/query/{query_id}/feedback | |
| # --------------------------------------------------------------------------- | |
| Sentiment = Literal["helpful", "not_helpful", "hallucinated"] | |
| class FeedbackBody(BaseModel): | |
| sentiment: Sentiment | |
| text: str | None = None | |
| async def post_feedback(query_id: str, body: FeedbackBody) -> dict: | |
| r = await _redis() | |
| try: | |
| key = FEEDBACK_KEY.format(id=query_id) | |
| payload = {"sentiment": body.sentiment, "text": body.text or ""} | |
| await r.set(key, json.dumps(payload), ex=86400 * 30) | |
| except Exception as exc: | |
| logger.warning("workspace_feedback_redis_failed", extra={"query_id": query_id, "error": str(exc)}) | |
| finally: | |
| await r.aclose() | |
| logger.info("feedback_received", extra={"query_id": query_id, "sentiment": body.sentiment}) | |
| return {"ok": True} | |