import uuid from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.auth.deps import get_current_user from app.database import get_db from app.models.collection import Collection from app.models.search_schemas import SearchRequest, SearchResponse, SourceItem from app.models.user import User from app.services.embedding import embed_query from app.services.vector_store import search as qdrant_search from app.services.reranking import rerank from app.services.llm import generate_answer router = APIRouter(prefix="/api", tags=["search"]) FETCH_K = 20 FINAL_K = 5 @router.post("/search", response_model=SearchResponse) async def search( body: SearchRequest, user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): result = await db.execute( select(Collection).where( Collection.id == uuid.UUID(body.collection_id), Collection.user_id == user.id, ) ) collection = result.scalar_one_or_none() if not collection: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Collection not found") query_vector = await embed_query(body.query) qdrant_results = await qdrant_search( tenant_id=str(user.id), query_vector=query_vector, collection_id=body.collection_id, k=FETCH_K, ) if not qdrant_results: return SearchResponse( answer="Недостаточно информации в истории обращений" if body.generate_answer else None, sources=[], ) reranked = rerank(body.query, qdrant_results, k=FINAL_K) sources = [ SourceItem(ticket_id=r["ticket_id"], score=r["score"], category=r["category"]) for r in reranked ] answer = None if body.generate_answer: context = "\n\n".join( f"Тикет №{r['ticket_id']} ({r['category']}):\n{r['full_text']}" for r in reranked ) answer = await generate_answer(body.query, context) return SearchResponse(answer=answer, sources=sources)