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

149 lines
5.4 KiB
Python

from __future__ import annotations
from fastapi.responses import JSONResponse
from .shared import *
from .helpers import _resolve_tenant_id
from .. import rag_ask_contract
from ...core.response import ErrorCode, error_response
from ...services.ai_pmo.ai_pmo_kernel import AIPMOKernelService
@router.post("/ask")
async def rag_ask(
request: RAGAskRequest,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
service = AIPMOKernelService(
db,
tenant_id=rag_ask_contract.resolve_effective_tenant_id(current_user, tenant_id),
)
ingress_result = await service.process_ingress(
**rag_ask_contract.build_kernel_ingress_kwargs(
request=request,
current_user=current_user,
)
)
if not bool(ingress_result.get("handled")):
raise HTTPException(
status_code=400,
detail=str(ingress_result.get("reason") or "ai_pmo_kernel_not_handled"),
)
payload = rag_ask_contract.build_rag_response_payload(ingress_result=ingress_result)
if not str(payload.get("answer") or "").strip() and not str(payload.get("reply_text") or "").strip():
return JSONResponse(
status_code=500,
content=error_response(ErrorCode.INTERNAL_ERROR, "RAG 返回空回答,未生成有效响应。"),
)
return success_response(payload)
@router.get("/manifest")
async def rag_manifest(
project_id: int | 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))
data = await service.get_project_manifest(project_id=project_id)
return success_response(data)
@router.get("/features/catalog")
async def rag_feature_catalog(
query: str | None = Query(default=None),
limit: int = Query(default=20, ge=1, le=200),
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))
data = await service.get_feature_catalog(query=query, limit=limit)
return success_response(data)
@router.post("/entity-resolution")
async def rag_entity_resolution(
request: EntityResolutionRequest,
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))
data = await service.entity_resolution(
question=request.question,
project_id=request.project_id,
limit=request.limit,
)
return success_response(data)
@router.post("/multi-step-search")
async def rag_multi_step_search(
request: MultiStepSearchRequest,
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))
data = await service.multi_step_search(
question=request.question,
project_id=request.project_id,
source_chat_id=request.source_chat_id,
session_state=None,
)
return success_response(data)
@router.post("/symbols/sync")
async def rag_symbols_sync(
request: SymbolSyncRequest,
current_user: User = Depends(require_roles("ADMIN", "BOSS")),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
service = SymbolIndexService(db, tenant_id=_resolve_tenant_id(current_user, tenant_id))
data = await service.sync_codebase_symbols(
base_dir=request.base_dir,
max_files=request.max_files,
force_reindex=request.force_reindex,
)
return success_response(data)
@router.post("/symbols/search")
async def rag_symbols_search(
request: SymbolSearchRequest,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
service = SymbolIndexService(db, tenant_id=_resolve_tenant_id(current_user, tenant_id))
data = await service.search_symbols(
query=request.query,
language=request.language,
symbol_type=request.symbol_type,
limit=request.limit,
)
return success_response(data)
@router.get("/symbols/overview")
async def rag_symbols_overview(
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
service = SymbolIndexService(db, tenant_id=_resolve_tenant_id(current_user, tenant_id))
data = await service.get_overview(top_n_files=10, auto_sync_if_empty=False)
return success_response(data)
@router.get("/symbols/{symbol_id}")
async def rag_symbol_detail(
symbol_id: int,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
tenant_id: int = Depends(get_tenant_id),
):
service = SymbolIndexService(db, tenant_id=_resolve_tenant_id(current_user, tenant_id))
try:
data = await service.get_symbol_detail(symbol_id=symbol_id)
except ValueError as exc:
raise HTTPException(status_code=404, detail=str(exc)) from exc
return success_response(data)