LeomordKaly commited on
Commit
aba4f71
Β·
verified Β·
1 Parent(s): b07536a

deploy: phase 3 BYOK backend (Dockerfile.hf, FastAPI on 7860)

Browse files
Files changed (2) hide show
  1. interfaces/api.py +39 -0
  2. utils/audit.py +37 -0
interfaces/api.py CHANGED
@@ -779,6 +779,45 @@ if _FASTAPI_AVAILABLE:
779
  "items": [e.model_dump(mode="json") for e in entries],
780
  }
781
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
782
  # ── BYOK upload endpoints ────────────────────────────────────────
783
  from fastapi import File, UploadFile
784
  from qdrant_client.models import FieldCondition, Filter, MatchValue
 
779
  "items": [e.model_dump(mode="json") for e in entries],
780
  }
781
 
782
+ # ── BYOK answer feedback (πŸ‘/πŸ‘Ž β†’ hash-chained audit row) ─────────
783
+ class _ByokFeedbackBody(_ByokBaseModel):
784
+ """Public-demo feedback payload β€” a rating on the last answer."""
785
+
786
+ rating: str
787
+ query: str = ""
788
+ answer_summary: str = ""
789
+
790
+ @app.post("/byok/feedback", tags=["byok"])
791
+ async def byok_feedback_endpoint(
792
+ request: _FastApiRequest,
793
+ body: _ByokFeedbackBody,
794
+ creds: Annotated[ByokCreds, Depends(extract_byok)],
795
+ ) -> dict:
796
+ """Record a thumbs-up/down on an answer as a session-scoped audit row.
797
+
798
+ The rating lands on the same SHA-256 hash chain as every query, so it
799
+ is itself tamper-evident and shows up in ``/byok/audit``. No LLM call,
800
+ no throttle β€” it is a cheap write.
801
+ """
802
+ rating = (body.rating or "").strip().lower()
803
+ if rating not in ("up", "down"):
804
+ raise HTTPException(
805
+ status.HTTP_400_BAD_REQUEST,
806
+ detail={"reason": "rating must be 'up' or 'down'"},
807
+ )
808
+ try:
809
+ audit_logger.log_feedback(
810
+ user_id=f"demo-{creds.session_id}",
811
+ org_id=_DEMO_ORG_ID,
812
+ rating=rating,
813
+ query=body.query,
814
+ answer_summary=(body.answer_summary or "")[:200],
815
+ persona=creds.demo_persona or "anonymous",
816
+ )
817
+ except Exception as exc: # pragma: no cover -- defensive
818
+ logger.warning("byok_feedback_persist_failed", error=str(exc))
819
+ return {"session_id": creds.session_id, "rating": rating, "recorded": True}
820
+
821
  # ── BYOK upload endpoints ────────────────────────────────────────
822
  from fastapi import File, UploadFile
823
  from qdrant_client.models import FieldCondition, Filter, MatchValue
utils/audit.py CHANGED
@@ -261,6 +261,43 @@ class AuditLogger:
261
  )
262
  return entry
263
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
264
  def log_security_event(
265
  self,
266
  *,
 
261
  )
262
  return entry
263
 
264
+ def log_feedback(
265
+ self,
266
+ *,
267
+ user_id: str,
268
+ org_id: str = "",
269
+ rating: str,
270
+ query: str = "",
271
+ **kwargs: Any,
272
+ ) -> AuditEntry:
273
+ """Record a user thumbs-up/down on an answer.
274
+
275
+ Lands on the same SHA-256 hash chain as every other event (PII-redacted
276
+ before persistence), so feedback is itself tamper-evident and exportable
277
+ via ``/byok/audit``. ``rating`` is normalised to ``up`` / ``down``.
278
+
279
+ Args:
280
+ user_id: Identifier of the user giving feedback.
281
+ org_id: Organization identifier.
282
+ rating: ``up`` or ``down``.
283
+ query: The question the rated answer responded to.
284
+ **kwargs: Extra metadata (e.g. answer_summary).
285
+
286
+ Returns:
287
+ The persisted AuditEntry.
288
+ """
289
+ entry = AuditEntry(
290
+ action="feedback",
291
+ user_id=user_id,
292
+ org_id=org_id,
293
+ details={"rating": rating, "query": query},
294
+ status="success",
295
+ metadata=kwargs,
296
+ )
297
+ self._persist(entry)
298
+ _audit_log.info("feedback_recorded", user_id=user_id, rating=rating)
299
+ return entry
300
+
301
  def log_security_event(
302
  self,
303
  *,