diff --git a/semantic_router/index/base.py b/semantic_router/index/base.py index 452a18c65d24eecd4a3d3f12eecd75b1fae1df43..933e22942d592cb8515869ddea9217b6089e17fc 100644 --- a/semantic_router/index/base.py +++ b/semantic_router/index/base.py @@ -160,6 +160,13 @@ class BaseIndex(BaseModel): """ raise NotImplementedError("This method should be implemented by subclasses.") + def is_ready(self) -> bool: + """ + Checks if the index is ready to be used. + This method should be implemented by subclasses. + """ + raise NotImplementedError("This method should be implemented by subclasses.") + def query( self, vector: np.ndarray, diff --git a/semantic_router/index/local.py b/semantic_router/index/local.py index 10b77bea8772c4b4716e1b46fb970cbdd5a49771..61b2c3b5ffd5110048de8c7b5f8edd747734e5c3 100644 --- a/semantic_router/index/local.py +++ b/semantic_router/index/local.py @@ -82,6 +82,12 @@ class LocalIndex(BaseIndex): vectors=self.index.shape[0] if self.index is not None else 0, ) + def is_ready(self) -> bool: + """ + Checks if the index is ready to be used. + """ + return self.index is not None and self.routes is not None + def query( self, vector: np.ndarray, diff --git a/semantic_router/index/pinecone.py b/semantic_router/index/pinecone.py index b07063186198ff7bf24d1418f8327a6f295fe58e..f885fbf757b8223d1a12b2cf68d89a55bbb74937 100644 --- a/semantic_router/index/pinecone.py +++ b/semantic_router/index/pinecone.py @@ -464,6 +464,12 @@ class PineconeIndex(BaseIndex): vectors=0, ) + def is_ready(self) -> bool: + """ + Checks if the index is ready to be used. + """ + return self.index is not None + def query( self, vector: np.ndarray, diff --git a/semantic_router/index/postgres.py b/semantic_router/index/postgres.py index 54054c848459a6b01488c7fee2b8a5f0a2ed8042..6f4a9f2a1e56eaaeb890e5850c79acb0d61039ac 100644 --- a/semantic_router/index/postgres.py +++ b/semantic_router/index/postgres.py @@ -352,6 +352,12 @@ class PostgresIndex(BaseIndex): vectors=count, ) + def is_ready(self) -> bool: + """ + Checks if the index is ready to be used. + """ + return isinstance(self.conn, psycopg2.extensions.connection) + def query( self, vector: np.ndarray, diff --git a/semantic_router/index/qdrant.py b/semantic_router/index/qdrant.py index 5986f2c0c71d8ee9e26e6d21b93c48da26d5f8da..5b2eac80d2914163cd7b9247ebd55bbdb5c74266 100644 --- a/semantic_router/index/qdrant.py +++ b/semantic_router/index/qdrant.py @@ -196,6 +196,10 @@ class QdrantIndex(BaseIndex): List[Tuple]: A list of (route_name, utterance, function_schema, metadata) objects. """ + # Check if collection exists first + if not self.client.collection_exists(self.index_name): + return [] + from qdrant_client import grpc results = [] @@ -255,6 +259,12 @@ class QdrantIndex(BaseIndex): vectors=collection_info.points_count, ) + def is_ready(self) -> bool: + """ + Checks if the index is ready to be used. + """ + return self.client.collection_exists(self.index_name) + def query( self, vector: np.ndarray, diff --git a/semantic_router/routers/base.py b/semantic_router/routers/base.py index c551b124fa5aee12a1f3c544dc9fbcec755180cd..e42f16305781583163001d276cfc908f74b5cedd 100644 --- a/semantic_router/routers/base.py +++ b/semantic_router/routers/base.py @@ -422,9 +422,8 @@ class BaseRouter(BaseModel): simulate_static: bool = False, route_filter: Optional[List[str]] = None, ) -> RouteChoice: - ready = self._index_ready() - if not ready: - raise ValueError("Index or routes are not populated.") + if not self.index or not self.index.is_ready(): + raise ValueError("Index is not ready.") # if no vector provided, encode text to get vector if vector is None: if text is None: @@ -481,9 +480,9 @@ class BaseRouter(BaseModel): simulate_static: bool = False, route_filter: Optional[List[str]] = None, ) -> RouteChoice: - ready = self._index_ready() # TODO: need async version for qdrant - if not ready: - raise ValueError("Index or routes are not populated.") + if not self.index or not self.index.is_ready(): + # TODO: need async version for qdrant + raise ValueError("Index is not ready.") # if no vector provided, encode text to get vector if vector is None: if text is None: diff --git a/semantic_router/routers/hybrid.py b/semantic_router/routers/hybrid.py index 3d810576855ffa791e8dcf1056053aebc489c7ed..cb8b5f51b440e429b12983a20399ecbfb778d312 100644 --- a/semantic_router/routers/hybrid.py +++ b/semantic_router/routers/hybrid.py @@ -218,8 +218,8 @@ class HybridRouter(BaseRouter): route_filter: Optional[List[str]] = None, sparse_vector: dict[int, float] | SparseEmbedding | None = None, ) -> RouteChoice: - if self.index.index is None or self.routes is None: - raise ValueError("Index or routes are not populated.") + if not self.index or not self.index.is_ready(): + raise ValueError("Index is not ready.") potential_sparse_vector: List[SparseEmbedding] | None = None # if no vector provided, encode text to get vector if vector is None: diff --git a/tests/unit/test_router.py b/tests/unit/test_router.py index 799a978d296c77dd486d572d8b99f5323d115fb0..985898473397722cbc16845840a7a92641df56ac 100644 --- a/tests/unit/test_router.py +++ b/tests/unit/test_router.py @@ -282,7 +282,7 @@ class TestIndexEncoders: try: assert len(route_layer.index) == 5 break - except AssertionError: + except Exception: logger.warning(f"Index not populated, waiting for retry (try {count})") time.sleep(PINECONE_SLEEP) count += 1 @@ -733,7 +733,7 @@ class TestSemanticRouter: try: assert query_result in ["Route 1", "Route 2"] break - except AssertionError: + except Exception: logger.warning( f"Query result not in expected routes, waiting for retry (try {count})" ) @@ -770,7 +770,7 @@ class TestSemanticRouter: try: assert query_result in ["Route 1"] break - except AssertionError: + except Exception: logger.warning( f"Query result not in expected routes, waiting for retry (try {count})" ) @@ -800,7 +800,7 @@ class TestSemanticRouter: ).name assert query_result in ["Route 1"] break - except AssertionError: + except Exception: logger.warning( f"Query result not in expected routes, waiting for retry (try {count})" ) @@ -830,7 +830,11 @@ class TestSemanticRouter: if index_cls is PineconeIndex: time.sleep(PINECONE_SLEEP) # allow for index to be populated vector = encoder(["hello"]) - query_result = route_layer(vector=vector).name + if router_cls is HybridRouter: + sparse_vector = route_layer.sparse_encoder(["hello"])[0] + query_result = route_layer(vector=vector, sparse_vector=sparse_vector).name + else: + query_result = route_layer(vector=vector).name assert query_result in ["Route 1", "Route 2"] def test_query_with_no_text_or_vector(