""" Vector router for Library Desk API. Endpoints for semantic search and vector operations. """ from fastapi import APIRouter, HTTPException, Depends, Query import logging from src.models.vector import ( SearchRequest, SearchResponse, VectorUpdateSummary, CollectionListResponse, DeletePageChunksResponse ) from src.services.vector_service import VectorService from src.core.dependencies import ( QdrantDep, WikiJSDep, OllamaDep, verify_api_key, RequiredUserQuery ) logger = logging.getLogger(__name__) router = APIRouter(prefix="/vector", tags=["Vector"]) # Dependency to get vector service def get_vector_service( qdrant_client: QdrantDep, wiki_client: WikiJSDep, ollama_client: OllamaDep ) -> VectorService: """Get vector service instance.""" return VectorService(qdrant_client, wiki_client, ollama_client) @router.post("/search", response_model=SearchResponse) async def semantic_search( request: SearchRequest, vector_service: VectorService = Depends(get_vector_service), api_key: str = Depends(verify_api_key) ): """ Perform semantic search across user's documents. Uses Ollama to generate query embedding, then searches Qdrant for similar document chunks. **Example Request:** ```json { "query": "how to configure docker", "user": "", "limit": 10, "score_threshold": 0.5 } ``` **Returns:** List of matching chunks with similarity scores """ try: return await vector_service.search( query=request.query, user=request.user, limit=request.limit, score_threshold=request.score_threshold ) except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: logger.error(f"Semantic search failed: {e}", exc_info=True) raise HTTPException(status_code=500, detail="Search failed") @router.post("/update-from-page/{page_id}", response_model=VectorUpdateSummary) async def update_vectors_from_page( page_id: int, user: RequiredUserQuery, force_refresh: bool = Query(default=False, description="Force re-embedding"), vector_service: VectorService = Depends(get_vector_service), api_key: str = Depends(verify_api_key) ): """ Update vector embeddings from a wiki page. This endpoint: 1. Fetches the page from Wiki.js 2. Chunks the content (500 tokens with 50 token overlap) 3. Generates embeddings via Ollama 4. Upserts chunks to Qdrant with metadata **Use Cases:** - Called automatically after page creation/update (via BackgroundTasks) - Called manually by user/Librarian to refresh vectors - Called by Scheduler for batch processing **Example:** `POST /vector/update-from-page/5?user= (user is REQUIRED)` **Returns:** Summary with chunks created and processing time """ try: summary = await vector_service.update_from_page( page_id=page_id, user=user, force_refresh=force_refresh ) if not summary.success: raise HTTPException( status_code=500, detail=f"Vector update failed: {summary.error_message}" ) return summary except HTTPException: raise except Exception as e: logger.error(f"Failed to update vectors from page {page_id}: {e}", exc_info=True) raise HTTPException(status_code=500, detail="Vector update failed") @router.delete("/pages/{page_id}", response_model=DeletePageChunksResponse) async def delete_page_chunks( page_id: int, user: RequiredUserQuery, vector_service: VectorService = Depends(get_vector_service), api_key: str = Depends(verify_api_key) ): """ Delete all vector chunks for a wiki page. This is automatically called when a page is deleted from the wiki. **Example:** `DELETE /vector/pages/5?user= (user is REQUIRED)` """ try: deleted_count = await vector_service.delete_page_chunks( page_id=page_id, user=user ) return DeletePageChunksResponse( page_id=page_id, chunks_deleted=deleted_count, success=deleted_count > 0, message=f"Deleted {deleted_count} chunks for page {page_id}" ) except Exception as e: logger.error(f"Failed to delete chunks for page {page_id}: {e}", exc_info=True) raise HTTPException(status_code=500, detail="Failed to delete chunks") @router.get("/collections", response_model=CollectionListResponse) async def list_collections( vector_service: VectorService = Depends(get_vector_service), api_key: str = Depends(verify_api_key) ): """ List all Qdrant collections with statistics. Returns collection names, vector counts, and point counts. **Example:** `GET /vector/collections` """ try: return await vector_service.list_collections() except Exception as e: logger.error(f"Failed to list collections: {e}", exc_info=True) raise HTTPException(status_code=500, detail="Failed to list collections")