Files
border-red-post-nalati/backend/app/api/routes/retrieval.py
T

105 lines
3.4 KiB
Python

from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi.responses import Response
from sqlalchemy import func, select
from sqlalchemy.orm import Session
from app.db.session import get_db
from app.models import RetrievalRun
from app.schemas.catalog import Page
from app.schemas.exports import ExportHistory
from app.schemas.hybrid import (
ChunkKeywordRequest,
ChunkKeywordResponse,
HybridRequest,
HybridResponse,
)
from app.schemas.retrieval import RetrievalRequest, RetrievalResponse
from app.schemas.vectors import VectorRetrievalResponse
from app.services.auth import require_admin
from app.services.embedding_runtime import get_embedding_provider
from app.services.exports import export_history
from app.services.hybrid import retrieve_chunks, retrieve_hybrid
from app.services.retrieval import retrieve
router = APIRouter(prefix="/retrieval", tags=["retrieval"])
RunResponse = RetrievalResponse | VectorRetrievalResponse | ChunkKeywordResponse | HybridResponse
@router.post(
"/exports",
response_class=Response,
responses={200: {"content": {"application/x-ndjson": {}}, "description": "JSONL 历史快照"}},
dependencies=[Depends(require_admin)],
)
def download_history(payload: ExportHistory, db: Session = Depends(get_db)):
"""管理员按明确编号导出历史;沿用 Origin/CSRF,失败不返回部分文件。"""
content, export_id, digest = export_history(db, payload)
return Response(
content,
media_type="application/x-ndjson; charset=utf-8",
headers={
"Content-Disposition": f'attachment; filename="nalati-retrieval-{export_id}.jsonl"',
"X-Records-SHA256": digest,
"X-Content-Type-Options": "nosniff",
},
)
@router.post("", response_model=RetrievalResponse)
def keyword_retrieval(payload: RetrievalRequest, db: Session = Depends(get_db)):
"""只检索已核验知识;保存原始检索结果供复核,不调用大模型。"""
return retrieve(db, payload)
@router.post("/chunks", response_model=ChunkKeywordResponse)
def chunk_keyword_retrieval(payload: ChunkKeywordRequest, db: Session = Depends(get_db)):
return retrieve_chunks(db, payload)
@router.post("/hybrid", response_model=HybridResponse)
def hybrid_retrieval(
payload: HybridRequest,
db: Session = Depends(get_db),
provider=Depends(get_embedding_provider),
):
return retrieve_hybrid(db, payload, provider)
@router.get(
"/runs",
response_model=Page[RunResponse],
dependencies=[Depends(require_admin)],
)
def list_runs(
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100),
db: Session = Depends(get_db),
):
rows = db.scalars(
select(RetrievalRun)
.order_by(RetrievalRun.created_at.desc(), RetrievalRun.id)
.offset((page - 1) * page_size)
.limit(page_size)
)
return {
"items": [row.response for row in rows],
"total": db.scalar(select(func.count()).select_from(RetrievalRun)),
"page": page,
"page_size": page_size,
}
@router.get(
"/runs/{run_id}",
response_model=RunResponse,
dependencies=[Depends(require_admin)],
)
def read_run(run_id: UUID, db: Session = Depends(get_db)):
row = db.get(RetrievalRun, str(run_id))
if row is None:
raise HTTPException(404, "检索记录不存在")
return row.response