105 lines
3.4 KiB
Python
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
|