Skip to content
Snippets Groups Projects
Commit 8a3fb3b8 authored by Simonas's avatar Simonas
Browse files

bump openai version to ^1.0.0

parent e4ffbb82
No related branches found
No related tags found
No related merge requests found
<?xml version="1.0" ?> <?xml version="1.0" ?>
<coverage version="7.3.2" timestamp="1702463592393" lines-valid="334" lines-covered="334" line-rate="1" branches-covered="0" branches-valid="0" branch-rate="0" complexity="0"> <coverage version="7.3.2" timestamp="1702485028371" lines-valid="344" lines-covered="344" line-rate="1" branches-covered="0" branches-valid="0" branch-rate="0" complexity="0">
<!-- Generated by coverage.py: https://coverage.readthedocs.io/en/7.3.2 --> <!-- Generated by coverage.py: https://coverage.readthedocs.io/en/7.3.2 -->
<!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd --> <!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd -->
<sources> <sources>
...@@ -353,27 +353,37 @@ ...@@ -353,27 +353,37 @@
<line number="8" hits="1"/> <line number="8" hits="1"/>
<line number="11" hits="1"/> <line number="11" hits="1"/>
<line number="12" hits="1"/> <line number="12" hits="1"/>
<line number="13" hits="1"/>
<line number="14" hits="1"/> <line number="14" hits="1"/>
<line number="15" hits="1"/> <line number="19" hits="1"/>
<line number="16" hits="1"/> <line number="20" hits="1"/>
<line number="18" hits="1"/> <line number="21" hits="1"/>
<line number="22" hits="1"/> <line number="22" hits="1"/>
<line number="23" hits="1"/> <line number="23" hits="1"/>
<line number="24" hits="1"/>
<line number="25" hits="1"/>
<line number="26" hits="1"/> <line number="26" hits="1"/>
<line number="27" hits="1"/>
<line number="28" hits="1"/> <line number="28" hits="1"/>
<line number="29" hits="1"/> <line number="29" hits="1"/>
<line number="30" hits="1"/> <line number="30" hits="1"/>
<line number="31" hits="1"/> <line number="31" hits="1"/>
<line number="32" hits="1"/> <line number="32" hits="1"/>
<line number="33" hits="1"/>
<line number="34" hits="1"/>
<line number="35" hits="1"/> <line number="35" hits="1"/>
<line number="36" hits="1"/> <line number="36" hits="1"/>
<line number="37" hits="1"/> <line number="37" hits="1"/>
<line number="38" hits="1"/>
<line number="39" hits="1"/> <line number="39" hits="1"/>
<line number="40" hits="1"/> <line number="40" hits="1"/>
<line number="41" hits="1"/>
<line number="42" hits="1"/>
<line number="43" hits="1"/>
<line number="44" hits="1"/>
<line number="45" hits="1"/>
<line number="46" hits="1"/>
<line number="47" hits="1"/>
<line number="49" hits="1"/>
<line number="50" hits="1"/>
<line number="52" hits="1"/>
<line number="53" hits="1"/>
</lines> </lines>
</class> </class>
</classes> </classes>
......
This diff is collapsed.
...@@ -14,7 +14,7 @@ readme = "README.md" ...@@ -14,7 +14,7 @@ readme = "README.md"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.10" python = "^3.10"
pydantic = "^1.8.2" pydantic = "^1.8.2"
openai = "^0.28.1" openai = "^1.0.0"
cohere = "^4.32" cohere = "^4.32"
numpy = "^1.25.2" numpy = "^1.25.2"
pinecone-text = "^0.7.0" pinecone-text = "^0.7.0"
......
...@@ -2,39 +2,52 @@ import os ...@@ -2,39 +2,52 @@ import os
from time import sleep from time import sleep
import openai import openai
from openai.error import OpenAIError, RateLimitError, ServiceUnavailableError from openai import OpenAIError
from semantic_router.encoders import BaseEncoder from semantic_router.encoders import BaseEncoder
from semantic_router.utils.logger import logger from semantic_router.utils.logger import logger
class OpenAIEncoder(BaseEncoder): class OpenAIEncoder(BaseEncoder):
def __init__(self, name: str, openai_api_key: str | None = None): client: openai.Client | None
def __init__(
self,
name: str = os.getenv("OPENAI_MODEL_NAME", "text-embedding-ada-002"),
openai_api_key: str | None = None,
):
super().__init__(name=name) super().__init__(name=name)
openai.api_key = openai_api_key or os.getenv("OPENAI_API_KEY") openai.api_key = openai_api_key or os.getenv("OPENAI_API_KEY")
if openai.api_key is None: if openai.api_key is None:
raise ValueError("OpenAI API key cannot be 'None'.") raise ValueError("OpenAI API key cannot be 'None'.")
try:
self.client = openai.Client()
except Exception as e:
raise ValueError(f"OpenAI API client failed to initialize. Error: {e}")
def __call__(self, docs: list[str]) -> list[list[float]]: def __call__(self, docs: list[str]) -> list[list[float]]:
"""Encode a list of texts using the OpenAI API. Returns a list of if self.client is None:
vector embeddings. raise ValueError("OpenAI client is not initialized.")
""" embeds = None
res = None
error_message = "" error_message = ""
# exponential backoff # Exponential backoff
for j in range(5): for j in range(3):
try: try:
logger.info(f"Encoding {len(docs)} documents...") logger.info(f"Encoding {len(docs)} documents...")
res = openai.Embedding.create(input=docs, engine=self.name) embeds = self.client.embeddings.create(input=docs, model=self.name)
if isinstance(res, dict) and "data" in res: if isinstance(embeds, dict) and "data" in embeds:
break break
except (RateLimitError, ServiceUnavailableError, OpenAIError) as e: except OpenAIError as e:
logger.warning(f"Retrying in {2**j} seconds...")
sleep(2**j) sleep(2**j)
error_message = str(e) error_message = str(e)
if not res or not isinstance(res, dict) or "data" not in res: logger.warning(f"Retrying in {2**j} seconds...")
raise ValueError(f"OpenAI API call failed. Error: {error_message}") except Exception as e:
logger.error(f"OpenAI API call failed. Error: {error_message}")
raise ValueError(f"OpenAI API call failed. Error: {e}")
if not embeds or not isinstance(embeds, dict) or "data" not in embeds:
raise ValueError(f"No embeddings returned. Error: {error_message}")
embeds = [r["embedding"] for r in res["data"]] embeddings = [r["embedding"] for r in embeds["data"]]
return embeds return embeddings
import openai
import pytest import pytest
from openai.error import RateLimitError from openai import OpenAIError
from semantic_router.encoders import OpenAIEncoder from semantic_router.encoders import OpenAIEncoder
@pytest.fixture @pytest.fixture
def openai_encoder(mocker): def openai_encoder(mocker):
mocker.patch("openai.Embedding.create") mocker.patch("openai.Client")
return OpenAIEncoder(name="test-engine", openai_api_key="test_api_key") return OpenAIEncoder(openai_api_key="test_api_key")
class TestOpenAIEncoder: class TestOpenAIEncoder:
def test_initialization_with_api_key(self, openai_encoder): def test_openai_encoder_init_success(self, mocker):
assert openai.api_key == "test_api_key", "API key should be set correctly" mocker.patch("os.getenv", return_value="fake-api-key")
assert openai_encoder.name == "test-engine", "Engine name not set correctly" encoder = OpenAIEncoder()
assert encoder.client is not None
def test_initialization_without_api_key(self, mocker, monkeypatch):
monkeypatch.delenv("OPENAI_API_KEY", raising=False) def test_openai_encoder_init_no_api_key(self, mocker):
mocker.patch("openai.Embedding.create") mocker.patch("os.getenv", return_value=None)
with pytest.raises(ValueError): with pytest.raises(ValueError) as e:
OpenAIEncoder(name="test-engine") OpenAIEncoder()
assert "OpenAI API key cannot be 'None'." in str(e.value)
def test_call_method_success(self, openai_encoder, mocker):
mocker.patch( def test_openai_encoder_call_uninitialized_client(self, openai_encoder):
"openai.Embedding.create", # Set the client to None to simulate an uninitialized client
return_value={"data": [{"embedding": [0.1, 0.2, 0.3]}]}, openai_encoder.client = None
with pytest.raises(ValueError) as e:
openai_encoder(["test document"])
assert "OpenAI client is not initialized." in str(e.value)
def test_openai_encoder_init_exception(self, mocker):
mocker.patch("os.getenv", return_value="fake-api-key")
mocker.patch("openai.Client", side_effect=Exception("Initialization error"))
with pytest.raises(ValueError) as e:
OpenAIEncoder()
assert (
"OpenAI API client failed to initialize. Error: Initialization error"
in str(e.value)
) )
result = openai_encoder(["test"]) def test_openai_encoder_call_success(self, openai_encoder, mocker):
assert isinstance(result, list), "Result should be a list" mocker.patch("os.getenv", return_value="fake-api-key")
assert len(result) == 1 and len(result[0]) == 3, "Result list size is incorrect" mocker.patch.object(
openai_encoder.client.embeddings,
def test_call_method_rate_limit_error__raises_value_error_after_max_retries( "create",
self, openai_encoder, mocker return_value={"data": [{"embedding": [0.1, 0.2]}]},
):
mocker.patch("semantic_router.encoders.openai.sleep")
mocker.patch(
"openai.Embedding.create",
side_effect=RateLimitError(message="rate limit exceeded", http_status=429),
) )
embeddings = openai_encoder(["test document"])
with pytest.raises(ValueError): assert embeddings == [[0.1, 0.2]]
openai_encoder(["test"])
def test_openai_encoder_call_with_retries(self, openai_encoder, mocker):
def test_call_method_failure(self, openai_encoder, mocker): mocker.patch("os.getenv", return_value="fake-api-key")
mocker.patch("openai.Embedding.create", return_value={}) mocker.patch("time.sleep", return_value=None) # To speed up the test
mocker.patch.object(
with pytest.raises(ValueError): openai_encoder.client.embeddings,
openai_encoder(["test"]) "create",
side_effect=OpenAIError("Test error"),
def test_call_method_rate_limit_error__exponential_backoff_single_retry(
self, openai_encoder, mocker
):
mock_sleep = mocker.patch("semantic_router.encoders.openai.sleep")
mocker.patch(
"openai.Embedding.create",
side_effect=[
RateLimitError("rate limit exceeded"),
{"data": [{"embedding": [1, 2, 3]}]},
],
) )
with pytest.raises(ValueError) as e:
openai_encoder(["sample text"]) openai_encoder(["test document"])
assert "No embeddings returned. Error: Test error" in str(e.value)
mock_sleep.assert_called_once_with(1) # 2**0
def test_openai_encoder_call_failure_non_openai_error(self, openai_encoder, mocker):
def test_call_method_rate_limit_error__exponential_backoff_multiple_retries( mocker.patch("os.getenv", return_value="fake-api-key")
self, openai_encoder, mocker mocker.patch("time.sleep", return_value=None) # To speed up the test
): mocker.patch.object(
mock_sleep = mocker.patch("semantic_router.encoders.openai.sleep") openai_encoder.client.embeddings,
mocker.patch( "create",
"openai.Embedding.create", side_effect=Exception("Non-OpenAIError"),
side_effect=[
RateLimitError("rate limit exceeded"),
RateLimitError("rate limit exceeded"),
{"data": [{"embedding": [1, 2, 3]}]},
],
) )
with pytest.raises(ValueError) as e:
openai_encoder(["sample text"]) openai_encoder(["test document"])
assert "OpenAI API call failed. Error: Non-OpenAIError" in str(e.value)
assert mock_sleep.call_count == 2
mock_sleep.assert_any_call(1) # 2**0 def test_openai_encoder_call_successful_retry(self, openai_encoder, mocker):
mock_sleep.assert_any_call(2) # 2**1 mocker.patch("os.getenv", return_value="fake-api-key")
mocker.patch("time.sleep", return_value=None) # To speed up the test
def test_call_method_rate_limit_error__exponential_backoff_max_retries_exceeded( responses = [OpenAIError("Test error"), {"data": [{"embedding": [0.1, 0.2]}]}]
self, openai_encoder, mocker mocker.patch.object(
): openai_encoder.client.embeddings, "create", side_effect=responses
mock_sleep = mocker.patch("semantic_router.encoders.openai.sleep")
mocker.patch(
"openai.Embedding.create", side_effect=RateLimitError("rate limit exceeded")
) )
embeddings = openai_encoder(["test document"])
with pytest.raises(ValueError): assert embeddings == [[0.1, 0.2]]
openai_encoder(["sample text"])
assert mock_sleep.call_count == 5 # Assuming 5 retries
mock_sleep.assert_any_call(1) # 2**0
mock_sleep.assert_any_call(2) # 2**1
mock_sleep.assert_any_call(4) # 2**2
mock_sleep.assert_any_call(8) # 2**3
mock_sleep.assert_any_call(16) # 2**4
def test_call_method_rate_limit_error__exponential_backoff_successful(
self, openai_encoder, mocker
):
mock_sleep = mocker.patch("semantic_router.encoders.openai.sleep")
mocker.patch(
"openai.Embedding.create",
side_effect=[
RateLimitError("rate limit exceeded"),
RateLimitError("rate limit exceeded"),
{"data": [{"embedding": [1, 2, 3]}]},
],
)
embeddings = openai_encoder(["sample text"])
assert mock_sleep.call_count == 2
assert embeddings == [[1, 2, 3]]
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# Semantic Router Walkthrough # Semantic Router Walkthrough
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The Semantic Router library can be used as a super fast route making layer on top of LLMs. That means rather than waiting on a slow agent to decide what to do, we can use the magic of semantic vector space to make routes. Cutting route making time down from seconds to milliseconds. The Semantic Router library can be used as a super fast route making layer on top of LLMs. That means rather than waiting on a slow agent to decide what to do, we can use the magic of semantic vector space to make routes. Cutting route making time down from seconds to milliseconds.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Getting Started ## Getting Started
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
We start by installing the library: We start by installing the library:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
!pip install -qU semantic-router==0.0.6 !pip install -qU semantic-router==0.0.6
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
We start by defining a dictionary mapping routes to example phrases that should trigger those routes. We start by defining a dictionary mapping routes to example phrases that should trigger those routes.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
from semantic_router.schema import Route from semantic_router.schema import Route
politics = Route( politics = Route(
name="politics", name="politics",
utterances=[ utterances=[
"isn't politics the best thing ever", "isn't politics the best thing ever",
"why don't you tell me about your political opinions", "why don't you tell me about your political opinions",
"don't you just love the president" "don't you just hate the president", "don't you just love the president" "don't you just hate the president",
"they're going to destroy this country!", "they're going to destroy this country!",
"they will save the country!", "they will save the country!",
], ],
) )
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Let's define another for good measure: Let's define another for good measure:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
chitchat = Route( chitchat = Route(
name="chitchat", name="chitchat",
utterances=[ utterances=[
"how's the weather today?", "how's the weather today?",
"how are things going?", "how are things going?",
"lovely weather today", "lovely weather today",
"the weather is horrendous", "the weather is horrendous",
"let's go to the chippy", "let's go to the chippy",
], ],
) )
routes = [politics, chitchat] routes = [politics, chitchat]
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Now we initialize our embedding model: Now we initialize our embedding model:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
import os import os
from getpass import getpass from getpass import getpass
from semantic_router.encoders import CohereEncoder from semantic_router.encoders import CohereEncoder
os.environ["COHERE_API_KEY"] = os.getenv("COHERE_API_KEY") or getpass( os.environ["COHERE_API_KEY"] = os.getenv("COHERE_API_KEY") or getpass(
"Enter Cohere API Key: " "Enter Cohere API Key: "
) )
encoder = CohereEncoder() encoder = CohereEncoder()
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Now we define the `RouteLayer`. When called, the route layer will consume text (a query) and output the category (`Route`) it belongs to — to initialize a `RouteLayer` we need our `encoder` model and a list of `routes`. Now we define the `RouteLayer`. When called, the route layer will consume text (a query) and output the category (`Route`) it belongs to — to initialize a `RouteLayer` we need our `encoder` model and a list of `routes`.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
from semantic_router.router import RouteLayer from semantic_router.layer import RouteLayer
dl = RouteLayer(encoder=encoder, routes=routes) dl = RouteLayer(encoder=encoder, routes=routes)
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Now we can test it: Now we can test it:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
dl("don't you love politics?") dl("don't you love politics?")
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
dl("how's the weather today?") dl("how's the weather today?")
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Both are classified accurately, what if we send a query that is unrelated to our existing `Route` objects? Both are classified accurately, what if we send a query that is unrelated to our existing `Route` objects?
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
dl("I'm interested in learning about llama 2") dl("I'm interested in learning about llama 2")
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
In this case, we return `None` because no matches were identified. In this case, we return `None` because no matches were identified.
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment