From ee0d792b543733afb3e8005eb99728eabb6e2712 Mon Sep 17 00:00:00 2001 From: Juan Pablo Mesa Lopez Date: Fri, 26 Apr 2024 00:57:30 -0500 Subject: [PATCH 01/11] Added fix to _encode_documents within rolling_window.py when len(docs)> 2048 openai limit --- semantic_router/splitters/rolling_window.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/semantic_router/splitters/rolling_window.py b/semantic_router/splitters/rolling_window.py index a2809ff5..2e02253d 100644 --- a/semantic_router/splitters/rolling_window.py +++ b/semantic_router/splitters/rolling_window.py @@ -100,12 +100,19 @@ def __call__(self, docs: List[str]) -> List[DocumentSplit]: return splits def _encode_documents(self, docs: List[str]) -> np.ndarray: - try: - embeddings = self.encoder(docs) - return np.array(embeddings) - except Exception as e: - logger.error(f"Error encoding documents {docs}: {e}") - raise + max_docs_per_batch = 2000 # OpenAI limit is 2048 + embeddings = [] + + for i in range(0, len(docs), max_docs_per_batch): + batch_docs = docs[i : i + max_docs_per_batch] + try: + batch_embeddings = self.encoder(batch_docs) + embeddings.extend(batch_embeddings) + except Exception as e: + logger.error(f"Error encoding documents {batch_docs}: {e}") + raise + + return np.array(embeddings) def _calculate_similarity_scores(self, encoded_docs: np.ndarray) -> List[float]: raw_similarities = [] From ff1161f2cedd1dd90a6a81c6cc91837990912a27 Mon Sep 17 00:00:00 2001 From: Simonas <20096648+simjak@users.noreply.github.com> Date: Fri, 26 Apr 2024 20:22:56 +0300 Subject: [PATCH 02/11] Revert "fix: Added fix to encode documents within rolling window" This reverts commit 7bb47b3efa90c8ea21b7ac756158a7f70f4bc04d. --- semantic_router/splitters/rolling_window.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/semantic_router/splitters/rolling_window.py b/semantic_router/splitters/rolling_window.py index 2e02253d..a2809ff5 100644 --- a/semantic_router/splitters/rolling_window.py +++ b/semantic_router/splitters/rolling_window.py @@ -100,19 +100,12 @@ def __call__(self, docs: List[str]) -> List[DocumentSplit]: return splits def _encode_documents(self, docs: List[str]) -> np.ndarray: - max_docs_per_batch = 2000 # OpenAI limit is 2048 - embeddings = [] - - for i in range(0, len(docs), max_docs_per_batch): - batch_docs = docs[i : i + max_docs_per_batch] - try: - batch_embeddings = self.encoder(batch_docs) - embeddings.extend(batch_embeddings) - except Exception as e: - logger.error(f"Error encoding documents {batch_docs}: {e}") - raise - - return np.array(embeddings) + try: + embeddings = self.encoder(docs) + return np.array(embeddings) + except Exception as e: + logger.error(f"Error encoding documents {docs}: {e}") + raise def _calculate_similarity_scores(self, encoded_docs: np.ndarray) -> List[float]: raw_similarities = [] From b9cb061958dad3ff0c718d102b1e2013ca3e3eb4 Mon Sep 17 00:00:00 2001 From: Juan Pablo Mesa Lopez Date: Fri, 26 Apr 2024 13:45:14 -0500 Subject: [PATCH 03/11] fix: Split list of documents before embedding them --- semantic_router/splitters/rolling_window.py | 27 ++++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/semantic_router/splitters/rolling_window.py b/semantic_router/splitters/rolling_window.py index a2809ff5..89336a2b 100644 --- a/semantic_router/splitters/rolling_window.py +++ b/semantic_router/splitters/rolling_window.py @@ -100,12 +100,27 @@ def __call__(self, docs: List[str]) -> List[DocumentSplit]: return splits def _encode_documents(self, docs: List[str]) -> np.ndarray: - try: - embeddings = self.encoder(docs) - return np.array(embeddings) - except Exception as e: - logger.error(f"Error encoding documents {docs}: {e}") - raise + """ + Encodes a list of documents into embeddings. If the number of documents exceeds 2000, + the documents are split into batches to avoid overloading the encoder. OpenAI has a + limit of len(array) < 2048. + + :param docs: List of text documents to be encoded. + :return: A numpy array of embeddings for the given documents. + """ + max_docs_per_batch = 2000 + embeddings = [] + + for i in range(0, len(docs), max_docs_per_batch): + batch_docs = docs[i : i + max_docs_per_batch] + try: + batch_embeddings = self.encoder(batch_docs) + embeddings.extend(batch_embeddings) + except Exception as e: + logger.error(f"Error encoding documents {batch_docs}: {e}") + raise + + return np.array(embeddings) def _calculate_similarity_scores(self, encoded_docs: np.ndarray) -> List[float]: raw_similarities = [] From 78857b5456ad7af9f9b835fcc8f56b650c5f7d3e Mon Sep 17 00:00:00 2001 From: James Briggs Date: Sat, 27 Apr 2024 18:24:12 +0800 Subject: [PATCH 04/11] fix for pytests --- semantic_router/index/pinecone.py | 37 ++++++++++++++++++++++++++----- semantic_router/layer.py | 8 +++---- tests/unit/test_layer.py | 26 ++++++++++++++++++++++ 3 files changed, 62 insertions(+), 9 deletions(-) diff --git a/semantic_router/index/pinecone.py b/semantic_router/index/pinecone.py index e240ed31..148c9070 100644 --- a/semantic_router/index/pinecone.py +++ b/semantic_router/index/pinecone.py @@ -49,12 +49,27 @@ class PineconeIndex(BaseIndex): ServerlessSpec: Any = Field(default=None, exclude=True) namespace: Optional[str] = "" - def __init__(self, **data): - super().__init__(**data) - self._initialize_client() + def __init__( + self, + api_key: Optional[str] = None, + index_name: str = "index", + dimensions: Optional[int] = None, + metric: str = "cosine", + cloud: str = "aws", + region: str = "us-west-2", + host: str = "", + namespace: Optional[str] = "", + ): + super().__init__() + self.index_name = index_name + self.dimensions = dimensions + self.metric = metric + self.cloud = cloud + self.region = region + self.host = host + self.namespace = namespace self.type = "pinecone" - self.client = self._initialize_client() - self.index = self._init_index(force_create=True) + self.client = self._initialize_client(api_key=api_key) def _initialize_client(self, api_key: Optional[str] = None): try: @@ -77,6 +92,18 @@ def _initialize_client(self, api_key: Optional[str] = None): return Pinecone(**pinecone_args) def _init_index(self, force_create: bool = False) -> Union[Any, None]: + """Initializing the index can be done after the object has been created + to allow for the user to set the dimensions and other parameters. + + If the index doesn't exist and the dimensions are given, the index will + be created. If the index exists, it will be returned. If the index doesn't + exist and the dimensions are not given, the index will not be created and + None will be returned. + + :param force_create: If True, the index will be created even if the + dimensions are not given (which will raise an error). + :type force_create: bool, optional + """ index_exists = self.index_name in self.client.list_indexes().names() dimensions_given = self.dimensions is not None if dimensions_given and not index_exists: diff --git a/semantic_router/layer.py b/semantic_router/layer.py index a138893a..d9781820 100644 --- a/semantic_router/layer.py +++ b/semantic_router/layer.py @@ -354,7 +354,7 @@ def from_config(cls, config: LayerConfig, index: Optional[BaseIndex] = None): def add(self, route: Route): logger.info(f"Adding `{route.name}` route") # create embeddings - embeds = self.encoder(route.utterances) # type: ignore + embeds = self.encoder(route.utterances) # if route has no score_threshold, use default if route.score_threshold is None: route.score_threshold = self.score_threshold @@ -363,7 +363,7 @@ def add(self, route: Route): self.index.add( embeddings=embeds, routes=[route.name] * len(route.utterances), - utterances=route.utterances, # type: ignore + utterances=route.utterances, ) self.routes.append(route) @@ -409,14 +409,14 @@ def _add_routes(self, routes: List[Route]): all_utterances = [ utterance for route in routes for utterance in route.utterances ] - embedded_utterances = self.encoder(all_utterances) # type: ignore + embedded_utterances = self.encoder(all_utterances) # create route array route_names = [route.name for route in routes for _ in route.utterances] # add everything to the index self.index.add( embeddings=embedded_utterances, routes=route_names, - utterances=all_utterances, # type: ignore + utterances=all_utterances, ) def _encode(self, text: str) -> Any: diff --git a/tests/unit/test_layer.py b/tests/unit/test_layer.py index 8f4833f0..fb5a1439 100644 --- a/tests/unit/test_layer.py +++ b/tests/unit/test_layer.py @@ -4,6 +4,7 @@ from unittest.mock import mock_open, patch import pytest +import time from semantic_router.encoders import BaseEncoder, CohereEncoder, OpenAIEncoder from semantic_router.index.local import LocalIndex @@ -279,12 +280,37 @@ def test_query_filter_pinecone(self, openai_encoder, routes, index_cls): route_layer = RouteLayer( encoder=openai_encoder, routes=routes, index=pineconeindex ) + time.sleep(5) # allow for index to be populated + print(routes) query_result = route_layer(text="Hello", route_filter=["Route 1"]).name + print(query_result) try: route_layer(text="Hello", route_filter=["Route 8"]).name except ValueError: assert True + + # delete index + pineconeindex.delete_index() + + assert query_result in ["Route 1"] + + def test_namespace_pinecone_index(self, openai_encoder, routes, index_cls): + pinecone_api_key = os.environ["PINECONE_API_KEY"] + pineconeindex = PineconeIndex(api_key=pinecone_api_key, namespace="test") + route_layer = RouteLayer( + encoder=openai_encoder, routes=routes, index=pineconeindex + ) + time.sleep(5) # allow for index to be populated + query_result = route_layer(text="Hello", route_filter=["Route 1"]).name + + try: + route_layer(text="Hello", route_filter=["Route 8"]).name + except ValueError: + assert True + + # delete index + pineconeindex.delete_index() assert query_result in ["Route 1"] From ceb67ceb22a860d0b33b9e4a50534ae8cc5aaa7b Mon Sep 17 00:00:00 2001 From: James Briggs Date: Sat, 27 Apr 2024 18:32:17 +0800 Subject: [PATCH 05/11] type fixes for linter --- semantic_router/encoders/base.py | 4 ++-- semantic_router/index/base.py | 2 +- tests/unit/test_layer.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/semantic_router/encoders/base.py b/semantic_router/encoders/base.py index 5c26633f..b1e1311e 100644 --- a/semantic_router/encoders/base.py +++ b/semantic_router/encoders/base.py @@ -1,4 +1,4 @@ -from typing import List +from typing import Any, List from pydantic.v1 import BaseModel, Field @@ -11,5 +11,5 @@ class BaseEncoder(BaseModel): class Config: arbitrary_types_allowed = True - def __call__(self, docs: List[str]) -> List[List[float]]: + def __call__(self, docs: List[Any]) -> List[List[float]]: raise NotImplementedError("Subclasses must implement this method") diff --git a/semantic_router/index/base.py b/semantic_router/index/base.py index f8a965df..d95a62a8 100644 --- a/semantic_router/index/base.py +++ b/semantic_router/index/base.py @@ -20,7 +20,7 @@ class BaseIndex(BaseModel): type: str = "base" def add( - self, embeddings: List[List[float]], routes: List[str], utterances: List[str] + self, embeddings: List[List[float]], routes: List[str], utterances: List[Any] ): """ Add embeddings to the index. diff --git a/tests/unit/test_layer.py b/tests/unit/test_layer.py index fb5a1439..1cee98ab 100644 --- a/tests/unit/test_layer.py +++ b/tests/unit/test_layer.py @@ -289,7 +289,7 @@ def test_query_filter_pinecone(self, openai_encoder, routes, index_cls): route_layer(text="Hello", route_filter=["Route 8"]).name except ValueError: assert True - + # delete index pineconeindex.delete_index() From b6c2aeb179bb1e90ddf0d5d9644e7b2c02991869 Mon Sep 17 00:00:00 2001 From: James Briggs Date: Sat, 27 Apr 2024 18:52:00 +0800 Subject: [PATCH 06/11] fixes --- semantic_router/index/pinecone.py | 4 ++-- tests/unit/test_layer.py | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/semantic_router/index/pinecone.py b/semantic_router/index/pinecone.py index 148c9070..31fbfac8 100644 --- a/semantic_router/index/pinecone.py +++ b/semantic_router/index/pinecone.py @@ -122,7 +122,7 @@ def _init_index(self, force_create: bool = False) -> Union[Any, None]: time.sleep(0.5) elif index_exists: # if the index exists we just return it - index = self.client.Index(self.index_name, namespace=self.namespace) + index = self.client.Index(self.index_name) # grab the dimensions from the index self.dimensions = index.describe_index_stats()["dimension"] elif force_create and not dimensions_given: @@ -234,7 +234,7 @@ def get_routes(self) -> List[Tuple]: def delete(self, route_name: str): route_vec_ids = self._get_route_ids(route_name=route_name) if self.index is not None: - self.index.delete(ids=route_vec_ids) + self.index.delete(ids=route_vec_ids, namespace=self.namespace) else: raise ValueError("Index is None, could not delete.") diff --git a/tests/unit/test_layer.py b/tests/unit/test_layer.py index 1cee98ab..7607d37c 100644 --- a/tests/unit/test_layer.py +++ b/tests/unit/test_layer.py @@ -281,9 +281,7 @@ def test_query_filter_pinecone(self, openai_encoder, routes, index_cls): encoder=openai_encoder, routes=routes, index=pineconeindex ) time.sleep(5) # allow for index to be populated - print(routes) query_result = route_layer(text="Hello", route_filter=["Route 1"]).name - print(query_result) try: route_layer(text="Hello", route_filter=["Route 8"]).name From a510d9167bbe4a9fb8d51e83ad6c092016240ea5 Mon Sep 17 00:00:00 2001 From: James Briggs Date: Sat, 27 Apr 2024 18:59:31 +0800 Subject: [PATCH 07/11] extend sleep --- tests/unit/test_layer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/test_layer.py b/tests/unit/test_layer.py index 7607d37c..241e3e50 100644 --- a/tests/unit/test_layer.py +++ b/tests/unit/test_layer.py @@ -280,7 +280,7 @@ def test_query_filter_pinecone(self, openai_encoder, routes, index_cls): route_layer = RouteLayer( encoder=openai_encoder, routes=routes, index=pineconeindex ) - time.sleep(5) # allow for index to be populated + time.sleep(10) # allow for index to be populated query_result = route_layer(text="Hello", route_filter=["Route 1"]).name try: @@ -299,7 +299,7 @@ def test_namespace_pinecone_index(self, openai_encoder, routes, index_cls): route_layer = RouteLayer( encoder=openai_encoder, routes=routes, index=pineconeindex ) - time.sleep(5) # allow for index to be populated + time.sleep(10) # allow for index to be populated query_result = route_layer(text="Hello", route_filter=["Route 1"]).name try: From 5e06cd18b10d0ab16bb3693cef94aa5f753b62eb Mon Sep 17 00:00:00 2001 From: James Briggs Date: Sat, 27 Apr 2024 19:06:47 +0800 Subject: [PATCH 08/11] decrease sleep --- tests/unit/test_layer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/test_layer.py b/tests/unit/test_layer.py index 241e3e50..7607d37c 100644 --- a/tests/unit/test_layer.py +++ b/tests/unit/test_layer.py @@ -280,7 +280,7 @@ def test_query_filter_pinecone(self, openai_encoder, routes, index_cls): route_layer = RouteLayer( encoder=openai_encoder, routes=routes, index=pineconeindex ) - time.sleep(10) # allow for index to be populated + time.sleep(5) # allow for index to be populated query_result = route_layer(text="Hello", route_filter=["Route 1"]).name try: @@ -299,7 +299,7 @@ def test_namespace_pinecone_index(self, openai_encoder, routes, index_cls): route_layer = RouteLayer( encoder=openai_encoder, routes=routes, index=pineconeindex ) - time.sleep(10) # allow for index to be populated + time.sleep(5) # allow for index to be populated query_result = route_layer(text="Hello", route_filter=["Route 1"]).name try: From c4dd453a8c5ce3a1a7c98a6bbaca515ba18246e6 Mon Sep 17 00:00:00 2001 From: James Briggs Date: Sat, 27 Apr 2024 19:10:50 +0800 Subject: [PATCH 09/11] remove delete --- .github/workflows/test.yml | 1 + tests/unit/test_layer.py | 6 ------ 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4e13b938..2a957e00 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -48,6 +48,7 @@ jobs: - name: Pytest env: PINECONE_API_KEY: ${{ secrets.PINECONE_API_KEY }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} run: | make test - name: Upload coverage to Codecov diff --git a/tests/unit/test_layer.py b/tests/unit/test_layer.py index 7607d37c..284bde0b 100644 --- a/tests/unit/test_layer.py +++ b/tests/unit/test_layer.py @@ -288,9 +288,6 @@ def test_query_filter_pinecone(self, openai_encoder, routes, index_cls): except ValueError: assert True - # delete index - pineconeindex.delete_index() - assert query_result in ["Route 1"] def test_namespace_pinecone_index(self, openai_encoder, routes, index_cls): @@ -307,9 +304,6 @@ def test_namespace_pinecone_index(self, openai_encoder, routes, index_cls): except ValueError: assert True - # delete index - pineconeindex.delete_index() - assert query_result in ["Route 1"] def test_query_with_no_index(self, openai_encoder, index_cls): From 9f16810c7cad3c74c2a9f95f051ba5ceab08ef84 Mon Sep 17 00:00:00 2001 From: James Briggs Date: Sat, 27 Apr 2024 19:17:45 +0800 Subject: [PATCH 10/11] add delete and increase sleep --- tests/unit/test_layer.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/unit/test_layer.py b/tests/unit/test_layer.py index 284bde0b..241e3e50 100644 --- a/tests/unit/test_layer.py +++ b/tests/unit/test_layer.py @@ -280,7 +280,7 @@ def test_query_filter_pinecone(self, openai_encoder, routes, index_cls): route_layer = RouteLayer( encoder=openai_encoder, routes=routes, index=pineconeindex ) - time.sleep(5) # allow for index to be populated + time.sleep(10) # allow for index to be populated query_result = route_layer(text="Hello", route_filter=["Route 1"]).name try: @@ -288,6 +288,9 @@ def test_query_filter_pinecone(self, openai_encoder, routes, index_cls): except ValueError: assert True + # delete index + pineconeindex.delete_index() + assert query_result in ["Route 1"] def test_namespace_pinecone_index(self, openai_encoder, routes, index_cls): @@ -296,7 +299,7 @@ def test_namespace_pinecone_index(self, openai_encoder, routes, index_cls): route_layer = RouteLayer( encoder=openai_encoder, routes=routes, index=pineconeindex ) - time.sleep(5) # allow for index to be populated + time.sleep(10) # allow for index to be populated query_result = route_layer(text="Hello", route_filter=["Route 1"]).name try: @@ -304,6 +307,9 @@ def test_namespace_pinecone_index(self, openai_encoder, routes, index_cls): except ValueError: assert True + # delete index + pineconeindex.delete_index() + assert query_result in ["Route 1"] def test_query_with_no_index(self, openai_encoder, index_cls): From 3b78805acb5bb5ca4ff55b72fb84b3778e9f1662 Mon Sep 17 00:00:00 2001 From: James Briggs Date: Sat, 27 Apr 2024 19:18:31 +0800 Subject: [PATCH 11/11] remove delete --- tests/unit/test_layer.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/unit/test_layer.py b/tests/unit/test_layer.py index 241e3e50..da85c10a 100644 --- a/tests/unit/test_layer.py +++ b/tests/unit/test_layer.py @@ -288,9 +288,6 @@ def test_query_filter_pinecone(self, openai_encoder, routes, index_cls): except ValueError: assert True - # delete index - pineconeindex.delete_index() - assert query_result in ["Route 1"] def test_namespace_pinecone_index(self, openai_encoder, routes, index_cls): @@ -307,9 +304,6 @@ def test_namespace_pinecone_index(self, openai_encoder, routes, index_cls): except ValueError: assert True - # delete index - pineconeindex.delete_index() - assert query_result in ["Route 1"] def test_query_with_no_index(self, openai_encoder, index_cls):