PMFlow/backend/app/api/rag_api/routes_qa.py

234 lines
8.2 KiB
Python

from __future__ import annotations
from sqlalchemy import and_, select
from .shared import *
from .helpers import _resolve_tenant_id
from ...models import QAFeedback, QARecord
from ...services.feedback_center import FeedbackCenterService
@router.post("/qa-records/{qa_id}/feedback")
async def submit_qa_feedback(
qa_id: int,
request: QAFeedbackCreateRequest,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
service = AdaptiveRAGService(db, tenant_id=_resolve_tenant_id(current_user, tenant_id))
try:
result = await service.submit_feedback(
qa_id=qa_id,
rating=request.rating,
helpfulness=request.helpfulness,
error_types=request.error_types,
correct_answer=request.correct_answer,
submitted_by=int(current_user.id),
)
except ValueError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
try:
feedback_row = (
await db.execute(
select(QAFeedback)
.where(
and_(
QAFeedback.tenant_id == _resolve_tenant_id(current_user, tenant_id),
QAFeedback.qa_id == qa_id,
QAFeedback.submitted_by == int(current_user.id),
)
)
.order_by(QAFeedback.created_at.desc(), QAFeedback.id.desc())
)
).scalars().first()
qa_row = (
await db.execute(
select(QARecord).where(
and_(
QARecord.tenant_id == _resolve_tenant_id(current_user, tenant_id),
QARecord.id == qa_id,
)
)
)
).scalar_one_or_none()
if feedback_row and qa_row:
await FeedbackCenterService(db).ingest_qa_feedback(
qa_feedback=feedback_row,
qa_record=qa_row,
commit=True,
)
except Exception:
await db.rollback()
return success_response(result)
@router.get("/qa-records")
async def list_qa_records(
project_id: int | None = Query(default=None),
date_from: datetime | None = Query(default=None),
date_to: datetime | None = Query(default=None),
rating: str | None = Query(default=None),
page: int = Query(default=1, ge=1),
page_size: int = Query(default=20, ge=1, le=100),
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
_ = current_user
query = QAListQuery(
project_id=project_id,
date_from=date_from,
date_to=date_to,
rating=rating,
page=page,
page_size=page_size,
)
service = AdaptiveRAGService(db, tenant_id=_resolve_tenant_id(current_user, tenant_id))
result = await service.list_qa_records(
project_id=query.project_id,
date_from=query.date_from,
date_to=query.date_to,
rating=query.rating,
page=query.page,
page_size=query.page_size,
)
return success_response(result)
@router.get("/qa-sessions")
async def list_qa_sessions(
date_from: datetime | None = Query(default=None),
date_to: datetime | None = Query(default=None),
page: int = Query(default=1, ge=1),
page_size: int = Query(default=20, ge=1, le=100),
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
query = QASessionListQuery(
date_from=date_from,
date_to=date_to,
page=page,
page_size=page_size,
)
service = AdaptiveRAGService(db, tenant_id=_resolve_tenant_id(current_user, tenant_id))
result = await service.list_qa_sessions(
user_id=int(current_user.id),
date_from=query.date_from,
date_to=query.date_to,
page=query.page,
page_size=query.page_size,
)
return success_response(result)
@router.get("/qa-sessions/{session_id}")
async def qa_session_detail(
session_id: int,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
service = AdaptiveRAGService(db, tenant_id=_resolve_tenant_id(current_user, tenant_id))
try:
result = await service.get_qa_session_detail(session_id=session_id, user_id=int(current_user.id))
except ValueError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
return success_response(result)
@router.get("/qa-sessions/{session_id}/state")
async def qa_session_state_replay(
session_id: int,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
service = AdaptiveRAGService(db, tenant_id=_resolve_tenant_id(current_user, tenant_id))
try:
result = await service.get_session_state_replay(
session_id=session_id,
user_id=int(current_user.id),
)
except ValueError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
return success_response(result)
@router.patch("/qa-sessions/{session_id}/title")
async def rename_qa_session(
session_id: int,
request: QASessionRenameRequest,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
service = AdaptiveRAGService(db, tenant_id=_resolve_tenant_id(current_user, tenant_id))
try:
result = await service.rename_qa_session(
session_id=session_id,
user_id=int(current_user.id),
title=request.title,
)
except ValueError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
return success_response(result)
@router.delete("/qa-sessions/{session_id}")
async def delete_qa_session(
session_id: int,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
service = AdaptiveRAGService(db, tenant_id=_resolve_tenant_id(current_user, tenant_id))
deleted = await service.delete_qa_session(session_id=session_id, user_id=int(current_user.id))
if not deleted:
raise HTTPException(status_code=404, detail="qa session 不存在")
return success_response({"deleted": True, "session_id": session_id})
@router.get("/qa-records/stats")
async def qa_records_stats(
project_id: int | None = Query(default=None),
date_from: datetime | None = Query(default=None),
date_to: datetime | None = Query(default=None),
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
service = AdaptiveRAGService(db, tenant_id=_resolve_tenant_id(current_user, tenant_id))
result = await service.get_qa_stats(
project_id=project_id,
date_from=date_from,
date_to=date_to,
)
return success_response(result)
@router.get("/qa-records/export")
async def export_qa_records_jsonl(
include_unrated: bool = Query(default=False),
project_id: int | None = Query(default=None),
rating: str | None = Query(default=None),
domain: str | None = Query(default=None),
date_from: datetime | None = Query(default=None),
date_to: datetime | None = Query(default=None),
current_user: User = Depends(require_roles("ADMIN", "BOSS")),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
_ = current_user
query = QAExportQuery(
include_unrated=include_unrated,
project_id=project_id,
rating=rating,
domain=domain,
date_from=date_from,
date_to=date_to,
)
service = AdaptiveRAGService(db, tenant_id=_resolve_tenant_id(current_user, tenant_id))
payload = await service.export_qa_jsonl(
include_unrated=query.include_unrated,
project_id=query.project_id,
rating=query.rating,
domain=query.domain,
date_from=query.date_from,
date_to=query.date_to,
)
return Response(content=payload, media_type="application/x-ndjson")