diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index 58810c9e3e..93efa56fb9 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -2276,6 +2276,12 @@ QDRANT_TIMEOUT = int(os.environ.get('QDRANT_TIMEOUT', '5')) QDRANT_HNSW_M = int(os.environ.get('QDRANT_HNSW_M', '16')) ENABLE_QDRANT_MULTITENANCY_MODE = os.environ.get('ENABLE_QDRANT_MULTITENANCY_MODE', 'true').lower() == 'true' QDRANT_COLLECTION_PREFIX = os.environ.get('QDRANT_COLLECTION_PREFIX', 'open-webui') +QDRANT_HYBRID_SEARCH_ENABLED = os.environ.get('QDRANT_HYBRID_SEARCH_ENABLED', 'true').lower() == 'true' +QDRANT_SPARSE_EMBEDDING_MODEL = os.environ.get('QDRANT_SPARSE_EMBEDDING_MODEL', 'Qdrant/bm25') +QDRANT_DENSE_VECTOR_NAME = os.environ.get('QDRANT_DENSE_VECTOR_NAME', 'dense') +QDRANT_SPARSE_VECTOR_NAME = os.environ.get('QDRANT_SPARSE_VECTOR_NAME', 'sparse') +QDRANT_HYBRID_SEARCH_RRF_K = int(os.environ.get('QDRANT_HYBRID_SEARCH_RRF_K', '60')) +QDRANT_SPARSE_ON_DISK = os.environ.get('QDRANT_SPARSE_ON_DISK', 'false').lower() == 'true' WEAVIATE_HTTP_HOST = os.environ.get('WEAVIATE_HTTP_HOST', '') WEAVIATE_GRPC_HOST = os.environ.get('WEAVIATE_GRPC_HOST', '') diff --git a/backend/open_webui/retrieval/utils.py b/backend/open_webui/retrieval/utils.py index 4ab8bdf7c0..86568437ec 100644 --- a/backend/open_webui/retrieval/utils.py +++ b/backend/open_webui/retrieval/utils.py @@ -125,6 +125,7 @@ class VectorSearchRetriever(BaseRetriever): collection_name=self.collection_name, vectors=[embedding], limit=self.top_k, + query=query, ) ids = result.ids[0] @@ -144,13 +145,14 @@ class VectorSearchRetriever(BaseRetriever): return results -def query_doc(collection_name: str, query_embedding: list[float], k: int, user: UserModel = None): +def query_doc(collection_name: str, query_embedding: list[float], k: int, user: UserModel = None, query_text: Optional[str] = None): try: log.debug(f'query_doc:doc {collection_name}') result = VECTOR_DB_CLIENT.search( collection_name=collection_name, vectors=[query_embedding], limit=k, + query=query_text, ) if result: @@ -436,13 +438,14 @@ async def query_collection( results = [] error = False - def process_query_collection(collection_name, query_embedding): + def process_query_collection(collection_name, query_embedding, query_text : Optional[str] = None): try: if collection_name: result = query_doc( collection_name=collection_name, k=k, query_embedding=query_embedding, + query_text=query_text, ) if result is not None: return result.model_dump(), None @@ -457,9 +460,9 @@ async def query_collection( with ThreadPoolExecutor() as executor: future_results = [] - for query_embedding in query_embeddings: + for query_text, query_embedding in zip(queries, query_embeddings): for collection_name in collection_names: - result = executor.submit(process_query_collection, collection_name, query_embedding) + result = executor.submit(process_query_collection, collection_name, query_embedding, query_text) future_results.append(result) task_results = [future.result() for future in future_results] diff --git a/backend/open_webui/retrieval/vector/dbs/chroma.py b/backend/open_webui/retrieval/vector/dbs/chroma.py index 4ace732b2d..8ec0a85efd 100755 --- a/backend/open_webui/retrieval/vector/dbs/chroma.py +++ b/backend/open_webui/retrieval/vector/dbs/chroma.py @@ -72,6 +72,7 @@ class ChromaClient(VectorDBBase): vectors: list[list[float | int]], filter: Optional[dict] = None, limit: int = 10, + query: Optional[str] = None, ) -> Optional[SearchResult]: # Search for the nearest neighbor items based on the vectors and return 'limit' number of results. try: diff --git a/backend/open_webui/retrieval/vector/dbs/elasticsearch.py b/backend/open_webui/retrieval/vector/dbs/elasticsearch.py index 201a5e1706..9316bbc69b 100644 --- a/backend/open_webui/retrieval/vector/dbs/elasticsearch.py +++ b/backend/open_webui/retrieval/vector/dbs/elasticsearch.py @@ -160,6 +160,7 @@ class ElasticsearchClient(VectorDBBase): vectors: list[list[float]], filter: Optional[dict] = None, limit: int = 10, + query: Optional[str] = None, ) -> Optional[SearchResult]: query = { 'size': limit, diff --git a/backend/open_webui/retrieval/vector/dbs/mariadb_vector.py b/backend/open_webui/retrieval/vector/dbs/mariadb_vector.py index 1cb3563382..c7ed64da0c 100644 --- a/backend/open_webui/retrieval/vector/dbs/mariadb_vector.py +++ b/backend/open_webui/retrieval/vector/dbs/mariadb_vector.py @@ -384,6 +384,7 @@ class MariaDBVectorClient(VectorDBBase): vectors: List[List[float]], filter: Optional[Dict[str, Any]] = None, limit: int = 10, + query: Optional[str] = None, ) -> Optional[SearchResult]: """ Perform a vector similarity search. diff --git a/backend/open_webui/retrieval/vector/dbs/milvus.py b/backend/open_webui/retrieval/vector/dbs/milvus.py index 2f3d8f3890..99a445bd4e 100644 --- a/backend/open_webui/retrieval/vector/dbs/milvus.py +++ b/backend/open_webui/retrieval/vector/dbs/milvus.py @@ -182,6 +182,7 @@ class MilvusClient(VectorDBBase): vectors: list[list[float | int]], filter: Optional[dict] = None, limit: int = 10, + query: Optional[str] = None, ) -> Optional[SearchResult]: # Search for the nearest neighbor items based on the vectors and return 'limit' number of results. collection_name = collection_name.replace('-', '_') diff --git a/backend/open_webui/retrieval/vector/dbs/milvus_multitenancy.py b/backend/open_webui/retrieval/vector/dbs/milvus_multitenancy.py index 93b4a8cbc4..fc6e5ad79d 100644 --- a/backend/open_webui/retrieval/vector/dbs/milvus_multitenancy.py +++ b/backend/open_webui/retrieval/vector/dbs/milvus_multitenancy.py @@ -160,6 +160,7 @@ class MilvusClient(VectorDBBase): vectors: List[List[float]], filter: Optional[Dict] = None, limit: int = 10, + query: Optional[str] = None, ) -> Optional[SearchResult]: if not vectors: return None diff --git a/backend/open_webui/retrieval/vector/dbs/opengauss.py b/backend/open_webui/retrieval/vector/dbs/opengauss.py index ac97cf01fa..64d8861290 100644 --- a/backend/open_webui/retrieval/vector/dbs/opengauss.py +++ b/backend/open_webui/retrieval/vector/dbs/opengauss.py @@ -222,6 +222,7 @@ class OpenGaussClient(VectorDBBase): vectors: List[List[float]], filter: Optional[Dict[str, Any]] = None, limit: int = 10, + query: Optional[str] = None, ) -> Optional[SearchResult]: try: if not vectors: diff --git a/backend/open_webui/retrieval/vector/dbs/opensearch.py b/backend/open_webui/retrieval/vector/dbs/opensearch.py index a08dca7865..af00e242ab 100644 --- a/backend/open_webui/retrieval/vector/dbs/opensearch.py +++ b/backend/open_webui/retrieval/vector/dbs/opensearch.py @@ -120,6 +120,7 @@ class OpenSearchClient(VectorDBBase): vectors: list[list[float | int]], filter: Optional[dict] = None, limit: int = 10, + query: Optional[str] = None, ) -> Optional[SearchResult]: try: if not self.has_collection(collection_name): diff --git a/backend/open_webui/retrieval/vector/dbs/oracle23ai.py b/backend/open_webui/retrieval/vector/dbs/oracle23ai.py index 9a5bd638d9..2b7a0e3f76 100644 --- a/backend/open_webui/retrieval/vector/dbs/oracle23ai.py +++ b/backend/open_webui/retrieval/vector/dbs/oracle23ai.py @@ -518,6 +518,7 @@ class Oracle23aiClient(VectorDBBase): vectors: List[List[Union[float, int]]], filter: Optional[dict] = None, limit: int = 10, + query: Optional[str] = None, ) -> Optional[SearchResult]: """ Search for similar vectors in the database. diff --git a/backend/open_webui/retrieval/vector/dbs/pgvector.py b/backend/open_webui/retrieval/vector/dbs/pgvector.py index 4775ff21f4..a37f5612de 100644 --- a/backend/open_webui/retrieval/vector/dbs/pgvector.py +++ b/backend/open_webui/retrieval/vector/dbs/pgvector.py @@ -396,6 +396,7 @@ class PgvectorClient(VectorDBBase): vectors: List[List[float]], filter: Optional[Dict[str, Any]] = None, limit: int = 10, + query: Optional[str] = None, ) -> Optional[SearchResult]: try: if not vectors: diff --git a/backend/open_webui/retrieval/vector/dbs/pinecone.py b/backend/open_webui/retrieval/vector/dbs/pinecone.py index 6469ac9172..be998073a4 100644 --- a/backend/open_webui/retrieval/vector/dbs/pinecone.py +++ b/backend/open_webui/retrieval/vector/dbs/pinecone.py @@ -354,6 +354,7 @@ class PineconeClient(VectorDBBase): vectors: List[List[Union[float, int]]], filter: Optional[dict] = None, limit: int = 10, + query: Optional[str] = None, ) -> Optional[SearchResult]: """Search for similar vectors in a collection.""" if not vectors or not vectors[0]: diff --git a/backend/open_webui/retrieval/vector/dbs/qdrant.py b/backend/open_webui/retrieval/vector/dbs/qdrant.py index f050bebeb5..42c11268cd 100644 --- a/backend/open_webui/retrieval/vector/dbs/qdrant.py +++ b/backend/open_webui/retrieval/vector/dbs/qdrant.py @@ -148,6 +148,7 @@ class QdrantClient(VectorDBBase): vectors: list[list[float | int]], filter: Optional[dict] = None, limit: int = 10, + query: Optional[str] = None, ) -> Optional[SearchResult]: # Search for the nearest neighbor items based on the vectors and return 'limit' number of results. if limit is None: diff --git a/backend/open_webui/retrieval/vector/dbs/qdrant_multitenancy.py b/backend/open_webui/retrieval/vector/dbs/qdrant_multitenancy.py index c3c2ba41d0..c048094379 100644 --- a/backend/open_webui/retrieval/vector/dbs/qdrant_multitenancy.py +++ b/backend/open_webui/retrieval/vector/dbs/qdrant_multitenancy.py @@ -16,7 +16,14 @@ from open_webui.config import ( QDRANT_COLLECTION_PREFIX, QDRANT_TIMEOUT, QDRANT_HNSW_M, + QDRANT_HYBRID_SEARCH_ENABLED, + QDRANT_SPARSE_EMBEDDING_MODEL, + QDRANT_DENSE_VECTOR_NAME, + QDRANT_SPARSE_VECTOR_NAME, + QDRANT_HYBRID_SEARCH_RRF_K, + QDRANT_SPARSE_ON_DISK, ) + from open_webui.retrieval.vector.main import ( GetResult, SearchResult, @@ -25,9 +32,16 @@ from open_webui.retrieval.vector.main import ( ) from qdrant_client import QdrantClient as Qclient from qdrant_client.http.exceptions import UnexpectedResponse -from qdrant_client.http.models import PointStruct +from qdrant_client.http.models import PointStruct, SparseVector from qdrant_client.models import models +try: + from fastembed.sparse import SparseTextEmbedding + + FASTEMBED_AVAILABLE = True +except ImportError: + FASTEMBED_AVAILABLE = False + NO_LIMIT = 999999999 TENANT_ID_FIELD = 'tenant_id' DEFAULT_DIMENSION = 384 @@ -53,6 +67,12 @@ class QdrantClient(VectorDBBase): self.GRPC_PORT = QDRANT_GRPC_PORT self.QDRANT_TIMEOUT = QDRANT_TIMEOUT self.QDRANT_HNSW_M = QDRANT_HNSW_M + self.QDRANT_HYBRID_SEARCH_ENABLED = QDRANT_HYBRID_SEARCH_ENABLED + self.QDRANT_SPARSE_EMBEDDING_MODEL = QDRANT_SPARSE_EMBEDDING_MODEL + self.QDRANT_DENSE_VECTOR_NAME = QDRANT_DENSE_VECTOR_NAME + self.QDRANT_SPARSE_VECTOR_NAME = QDRANT_SPARSE_VECTOR_NAME + self.QDRANT_HYBRID_SEARCH_RRF_K = QDRANT_HYBRID_SEARCH_RRF_K + self.QDRANT_SPARSE_ON_DISK = QDRANT_SPARSE_ON_DISK if not self.QDRANT_URI: raise ValueError('QDRANT_URI is not set. Please configure it in the environment variables.') @@ -86,6 +106,42 @@ class QdrantClient(VectorDBBase): self.WEB_SEARCH_COLLECTION = f'{self.collection_prefix}_web-search' self.HASH_BASED_COLLECTION = f'{self.collection_prefix}_hash-based' + # Initialize sparse encoder if hybrid search is enabled + self.sparse_encoder = None + if self.QDRANT_HYBRID_SEARCH_ENABLED: + if FASTEMBED_AVAILABLE: + try: + self.sparse_encoder = SparseTextEmbedding(model_name=self.QDRANT_SPARSE_EMBEDDING_MODEL) + log.info(f'Hybrid search enabled with sparse model: {self.QDRANT_SPARSE_EMBEDDING_MODEL}') + except Exception as e: + log.warning(f'Failed to load sparse encoder, hybrid search disabled: {e}') + else: + log.warning( + "fastembed not installed, hybrid search disabled. Install with: pip install 'fastembed>=0.6.1'" + ) + @property + def _hybrid_enabled(self) -> bool: + return self.QDRANT_HYBRID_SEARCH_ENABLED and self.sparse_encoder is not None + + def _is_hybrid_collection(self, mt_collection_name: str) -> bool: + """Check if an existing collection has sparse vectors (i.e. was created as hybrid).""" + try: + info = self.client.get_collection(mt_collection_name) + return bool(info.config.params.sparse_vectors) + except Exception: + return False + + def _encode_sparse(self, texts: list[str]) -> list[SparseVector]: + """Encode texts into sparse vectors using fastembed.""" + embeddings = list(self.sparse_encoder.embed(texts)) + return [SparseVector(indices=e.indices.tolist(), values=e.values.tolist()) for e in embeddings] + + def _encode_sparse_query(self, text: str) -> SparseVector: + """Encode a query text into a sparse vector.""" + embeddings = list(self.sparse_encoder.query_embed(text)) + e = embeddings[0] + return SparseVector(indices=e.indices.tolist(), values=e.values.tolist()) + def _result_to_get_result(self, points) -> GetResult: ids, documents, metadatas = [], [], [] for point in points: @@ -134,21 +190,46 @@ class QdrantClient(VectorDBBase): """ Creates a collection with multi-tenancy configuration and payload indexes for tenant_id and metadata fields. """ - self.client.create_collection( - collection_name=mt_collection_name, - vectors_config=models.VectorParams( - size=dimension, - distance=models.Distance.COSINE, - on_disk=self.QDRANT_ON_DISK, - ), - # Disable global index building due to multitenancy - # For more details https://qdrant.tech/documentation/guides/multiple-partitions/#calibrate-performance - hnsw_config=models.HnswConfigDiff( - payload_m=self.QDRANT_HNSW_M, - m=0, - ), - ) - log.info(f'Multi-tenant collection {mt_collection_name} created with dimension {dimension}!') + if self._hybrid_enabled: + self.client.create_collection( + collection_name=mt_collection_name, + vectors_config={ + self.QDRANT_DENSE_VECTOR_NAME: models.VectorParams( + size=dimension, + distance=models.Distance.COSINE, + on_disk=self.QDRANT_ON_DISK, + ) + }, + sparse_vectors_config={ + self.QDRANT_SPARSE_VECTOR_NAME: models.SparseVectorParams( + index=models.SparseIndexParams(on_disk=self.QDRANT_SPARSE_ON_DISK) + ) + }, + hnsw_config=models.HnswConfigDiff( + payload_m=self.QDRANT_HNSW_M, + m=0, + ), + ) + log.info( + f'Multi-tenant hybrid collection {mt_collection_name} created with ' + f'dense ({dimension} dims) + sparse vectors' + ) + else: + self.client.create_collection( + collection_name=mt_collection_name, + vectors_config=models.VectorParams( + size=dimension, + distance=models.Distance.COSINE, + on_disk=self.QDRANT_ON_DISK, + ), + # Disable global index building due to multitenancy + # For more details https://qdrant.tech/documentation/guides/multiple-partitions/#calibrate-performance + hnsw_config=models.HnswConfigDiff( + payload_m=self.QDRANT_HNSW_M, + m=0, + ), + ) + log.info(f'Multi-tenant collection {mt_collection_name} created with dimension {dimension}') self.client.create_payload_index( collection_name=mt_collection_name, @@ -170,22 +251,60 @@ class QdrantClient(VectorDBBase): ), ) - def _create_points(self, items: List[VectorItem], tenant_id: str) -> List[PointStruct]: + def _create_points( + self, items: list[VectorItem], tenant_id: str, collection_is_hybrid: bool = False + ) -> list[PointStruct]: """ Create point structs from vector items with tenant ID. + Uses named vectors when collection_is_hybrid is True. + If hybrid is also enabled in config, adds sparse vectors; otherwise dense-only named vector. """ - return [ - PointStruct( - id=item['id'], - vector=item['vector'], - payload={ - 'text': item['text'], - 'metadata': item['metadata'], - TENANT_ID_FIELD: tenant_id, - }, - ) - for item in items - ] + if collection_is_hybrid: + if self._hybrid_enabled: + texts = [item['text'] for item in items] + sparse_vectors = self._encode_sparse(texts) + return [ + PointStruct( + id=item['id'], + vector={ + self.QDRANT_DENSE_VECTOR_NAME: item['vector'], + self.QDRANT_SPARSE_VECTOR_NAME: sparse_vec, + }, + payload={ + 'text': item['text'], + 'metadata': item['metadata'], + TENANT_ID_FIELD: tenant_id, + }, + ) + for item, sparse_vec in zip(items, sparse_vectors) + ] + else: + # Collection is hybrid but encoder unavailable — store only dense named vector + return [ + PointStruct( + id=item['id'], + vector={self.QDRANT_DENSE_VECTOR_NAME: item['vector']}, + payload={ + 'text': item['text'], + 'metadata': item['metadata'], + TENANT_ID_FIELD: tenant_id, + }, + ) + for item in items + ] + else: + return [ + PointStruct( + id=item['id'], + vector=item['vector'], + payload={ + 'text': item['text'], + 'metadata': item['metadata'], + TENANT_ID_FIELD: tenant_id, + }, + ) + for item in items + ] def _ensure_collection(self, mt_collection_name: str, dimension: int = DEFAULT_DIMENSION): """ @@ -245,9 +364,11 @@ class QdrantClient(VectorDBBase): vectors: List[List[float | int]], filter: Optional[Dict] = None, limit: int = 10, + query: Optional[str] = None, ) -> Optional[SearchResult]: """ Search for the nearest neighbor items based on the vectors with tenant isolation. + Uses hybrid search (dense + sparse RRF) when enabled and query is provided. """ if not self.client or not vectors: return None @@ -257,12 +378,42 @@ class QdrantClient(VectorDBBase): return None tenant_filter = _tenant_filter(tenant_id) - query_response = self.client.query_points( - collection_name=mt_collection, - query=vectors[0], - limit=limit, - query_filter=models.Filter(must=[tenant_filter]), - ) + combined_filter = models.Filter(must=[tenant_filter]) + + collection_is_hybrid = self._is_hybrid_collection(mt_collection) + + # Hybrid search with RRF fusion + if collection_is_hybrid and self._hybrid_enabled and query: + sparse_query = self._encode_sparse_query(query) + query_response = self.client.query_points( + collection_name=mt_collection, + query=models.FusionQuery(fusion=models.Fusion.RRF), + prefetch=[ + models.Prefetch( + query=vectors[0], + using=self.QDRANT_DENSE_VECTOR_NAME, + filter=combined_filter, + limit=limit * 2, + ), + models.Prefetch( + query=sparse_query, + using=self.QDRANT_SPARSE_VECTOR_NAME, + filter=combined_filter, + limit=limit * 2, + ), + ], + limit=limit, + ) + else: + # Dense-only search — use named vector if collection schema requires it + query_vector = (self.QDRANT_DENSE_VECTOR_NAME, vectors[0]) if collection_is_hybrid else vectors[0] + query_response = self.client.query_points( + collection_name=mt_collection, + query=query_vector, + limit=limit, + query_filter=combined_filter, + ) + get_result = self._result_to_get_result(query_response.points) return SearchResult( ids=get_result.ids, @@ -320,7 +471,8 @@ class QdrantClient(VectorDBBase): mt_collection, tenant_id = self._get_collection_and_tenant_id(collection_name) dimension = len(items[0]['vector']) self._ensure_collection(mt_collection, dimension) - points = self._create_points(items, tenant_id) + collection_is_hybrid = self._is_hybrid_collection(mt_collection) + points = self._create_points(items, tenant_id, collection_is_hybrid) self.client.upload_points(mt_collection, points) return None diff --git a/backend/open_webui/retrieval/vector/dbs/s3vector.py b/backend/open_webui/retrieval/vector/dbs/s3vector.py index 8877d206e6..1c548aaa03 100644 --- a/backend/open_webui/retrieval/vector/dbs/s3vector.py +++ b/backend/open_webui/retrieval/vector/dbs/s3vector.py @@ -285,6 +285,7 @@ class S3VectorClient(VectorDBBase): vectors: List[List[Union[float, int]]], filter: Optional[dict] = None, limit: int = 10, + query: Optional[str] = None, ) -> Optional[SearchResult]: """ Search for similar vectors in a collection using multiple query vectors. diff --git a/backend/open_webui/retrieval/vector/dbs/weaviate.py b/backend/open_webui/retrieval/vector/dbs/weaviate.py index 2cf4c135c5..d7284b75d7 100644 --- a/backend/open_webui/retrieval/vector/dbs/weaviate.py +++ b/backend/open_webui/retrieval/vector/dbs/weaviate.py @@ -162,6 +162,7 @@ class WeaviateClient(VectorDBBase): vectors: List[List[Union[float, int]]], filter: Optional[dict] = None, limit: int = 10, + query: Optional[str] = None, ) -> Optional[SearchResult]: sane_collection_name = self._sanitize_collection_name(collection_name) if not self.client.collections.exists(sane_collection_name): diff --git a/backend/open_webui/retrieval/vector/main.py b/backend/open_webui/retrieval/vector/main.py index f7904baa20..25cb454f95 100644 --- a/backend/open_webui/retrieval/vector/main.py +++ b/backend/open_webui/retrieval/vector/main.py @@ -58,6 +58,7 @@ class VectorDBBase(ABC): vectors: List[List[Union[float, int]]], filter: Optional[Dict] = None, limit: int = 10, + query: Optional[str] = None, ) -> Optional[SearchResult]: """Search for similar vectors in a collection.""" pass diff --git a/backend/open_webui/routers/memories.py b/backend/open_webui/routers/memories.py index 4557f0c44d..dbd4287efa 100644 --- a/backend/open_webui/routers/memories.py +++ b/backend/open_webui/routers/memories.py @@ -142,6 +142,7 @@ async def query_memory( collection_name=f'user-memory-{user.id}', vectors=[vector], limit=form_data.k, + query=form_data.content, ) return results diff --git a/backend/open_webui/routers/retrieval.py b/backend/open_webui/routers/retrieval.py index 6c9e988dd6..bfb2bac4f2 100644 --- a/backend/open_webui/routers/retrieval.py +++ b/backend/open_webui/routers/retrieval.py @@ -2409,6 +2409,7 @@ async def query_doc_handler( query_embedding=query_embedding, k=form_data.k if form_data.k else request.app.state.config.TOP_K, user=user, + query_text=form_data.query, ) except Exception as e: log.exception(e) diff --git a/backend/open_webui/tools/builtin.py b/backend/open_webui/tools/builtin.py index f02a082c42..3ca311d4dc 100644 --- a/backend/open_webui/tools/builtin.py +++ b/backend/open_webui/tools/builtin.py @@ -2221,6 +2221,7 @@ async def query_knowledge_bases( vectors=[query_embedding], filter={'knowledge_base_id': {'$in': accessible_ids}}, limit=count, + query=query, ) if search_results and search_results.ids and search_results.ids[0]: diff --git a/backend/requirements.txt b/backend/requirements.txt index a9275beaf3..9b8f85a930 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -124,6 +124,7 @@ boto3==1.42.62 pymilvus==2.6.9 qdrant-client==1.17.0 +fastembed==0.8.0 # fastembed for qdrant SparseTextEmbedding playwright==1.58.0 # Caution: version must match docker-compose.playwright.yaml - Update the docker-compose.yaml if necessary elasticsearch==9.3.0 pinecone==6.0.2