From 999957259504317f32d89328c2013314994b27a5 Mon Sep 17 00:00:00 2001 From: george Date: Sat, 6 Jun 2026 13:01:52 +0100 Subject: [PATCH] Support modern Qdrant search API --- backend/app/search.py | 9 ++------- backend/app/vector_store.py | 38 +++++++++++++++++++++++++++++++------ tests/test_search.py | 34 +++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 13 deletions(-) diff --git a/backend/app/search.py b/backend/app/search.py index ce7e825..460e30c 100644 --- a/backend/app/search.py +++ b/backend/app/search.py @@ -3,7 +3,7 @@ from typing import List, Dict, Any, Optional from pathlib import Path from .config import settings -from .vector_store import get_client, _collection_name as VECTOR_COLLECTION +from .vector_store import get_client, query_points from .embeddings import embed_text, get_embedding_size from .db import get_chunks_for_library, list_libraries @@ -55,12 +55,7 @@ def search_docs( search_filter = None # Perform vector search - results = client.search( - collection_name=VECTOR_COLLECTION, - query_vector=query_embedding, - limit=limit, - search_filter=search_filter - ) + results = query_points(client, query_embedding, limit, search_filter) # Format and return results formatted_results = [] diff --git a/backend/app/vector_store.py b/backend/app/vector_store.py index feb93c8..cffc912 100644 --- a/backend/app/vector_store.py +++ b/backend/app/vector_store.py @@ -64,6 +64,37 @@ def get_embedding_size() -> int: return 384 +def query_points( + client: Any, + query_vector: List[float], + limit: int, + search_filter: Optional[Any] = None, +) -> List[Any]: + """Run a vector query across old and new qdrant-client APIs.""" + legacy_search = getattr(client, "search", None) + if callable(legacy_search): + return legacy_search( + collection_name=_collection_name, + query_vector=query_vector, + limit=limit, + search_filter=search_filter, + ) + + modern_query = getattr(client, "query_points", None) + if not callable(modern_query): + raise RuntimeError( + "Installed qdrant-client supports neither search() nor query_points()" + ) + + response = modern_query( + collection_name=_collection_name, + query=query_vector, + limit=limit, + query_filter=search_filter, + ) + return list(response.points) + + def create_collection(client: Any, collection_name: str, size: int, distance: Any) -> None: """Create a Qdrant collection across qdrant-client keyword changes.""" vector_params = VectorParams(size=size, distance=distance) @@ -255,12 +286,7 @@ async def search_vectors( ) # Perform vector search - results = client.search( - collection_name=_collection_name, - query_vector=query_vector, - limit=limit, - search_filter=search_filter - ) + results = query_points(client, query_vector, limit, search_filter) # Format results formatted_results = [] diff --git a/tests/test_search.py b/tests/test_search.py index f6fa6ad..c72fe3f 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -178,6 +178,40 @@ class TestSearchDocs: assert "title" in result assert "chunk_index" in result + def test_uses_modern_qdrant_query_points_api(self): + """New qdrant-client versions expose query_points instead of search.""" + from unittest.mock import patch + from backend.app.search import search_docs + + point = type("ScoredPoint", (), { + "score": 0.91, + "payload": { + "id": "modern-result", + "library_id": "documentation", + "path": "docs/index.md", + "title": "Index", + "chunk_index": 0, + }, + })() + + class Response: + points = [point] + + class ModernClient: + def query_points(self, **kwargs): + assert kwargs["query"] == [0.1, 0.2] + assert kwargs["limit"] == 3 + return Response() + + with ( + patch("backend.app.search.embed_text", return_value=[0.1, 0.2]), + patch("backend.app.search.get_client", return_value=ModernClient()), + ): + results = search_docs("world generation", limit=3) + + assert len(results) == 1 + assert results[0]["id"] == "modern-result" + class TestGetLibraryDocs: """Tests for get_library_docs() - document retrieval."""