support-bot-saas/backend/app/routers/search.py
ed0ss c4924b4da8
Some checks failed
CI / lint-backend (push) Has been cancelled
CI / lint-frontend (push) Has been cancelled
CI / build-backend (push) Has been cancelled
CI / build-frontend (push) Has been cancelled
feat: раскрытие полного текста тикета по клику в результатах поиска
- Добавлено поле full_text в SourceItem (бэкенд)
- Фронтенд: клик по строке тикета раскрывает/скрывает полный текст
2026-06-20 20:11:58 +03:00

69 lines
2.2 KiB
Python

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"], full_text=r["full_text"])
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)