# backend/app/api/feedback.py # Feedback endpoint. Lets the widget surface thumbs-up / thumbs-down for any # completed interaction. Ratings are stored against the SQLite interaction row. # # Self-improvement loop: # rating=1 → positive signal written to DB; used by data_prep.py to build # high-quality reranker triplets (in-scope chunks that helped). # rating=-1 → negative signal; used by data_prep.py to build hard-negative # triplets (chunks the model surfaced that didn't help the user). # The retrain_reranker.yml GitHub Actions workflow aggregates these signals # and auto-triggers reranker fine-tuning once enough triplets accumulate. import sqlite3 from fastapi import APIRouter, Depends, HTTPException, Request, status from pydantic import BaseModel, Field from app.core.logging import get_logger from app.security.jwt_auth import verify_jwt router = APIRouter() logger = get_logger(__name__) class FeedbackRequest(BaseModel): interaction_id: int = Field(..., description="Row ID returned in the SSE done event.") rating: int = Field(..., description="1 for thumbs-up, -1 for thumbs-down.") model_config = {"json_schema_extra": {"example": {"interaction_id": 42, "rating": 1}}} @router.post("/feedback", status_code=status.HTTP_204_NO_CONTENT) async def submit_feedback( request: Request, body: FeedbackRequest, _token: dict = Depends(verify_jwt), ) -> None: """Record user feedback on a completed interaction. Idempotent — re-rating overwrites.""" if body.rating not in (1, -1): raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="rating must be 1 (positive) or -1 (negative)", ) db_path = request.app.state.settings.DB_PATH try: with sqlite3.connect(db_path) as conn: cursor = conn.execute( "UPDATE interactions SET feedback = ? WHERE id = ?", (body.rating, body.interaction_id), ) if cursor.rowcount == 0: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Interaction {body.interaction_id} not found.", ) except HTTPException: raise except Exception as exc: logger.error("Feedback write failed for interaction %d: %s", body.interaction_id, exc) raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Feedback store unavailable.", )