diff --git a/semantic_router/llms/openai.py b/semantic_router/llms/openai.py index 3465eec9a32765d1c68ad0f53ccdbb59cef2235b..7dc565e8877a46d74e5fd86d4e3d05e04c52175c 100644 --- a/semantic_router/llms/openai.py +++ b/semantic_router/llms/openai.py @@ -1,5 +1,6 @@ import os from typing import List, Optional, Any, Callable, Dict, Union +from pydantic import PrivateAttr import openai from openai._types import NotGiven, NOT_GIVEN @@ -21,8 +22,8 @@ from openai.types.chat.chat_completion_message_tool_call import ( class OpenAILLM(BaseLLM): - client: Optional[openai.OpenAI] - async_client: Optional[openai.AsyncOpenAI] + _client: Optional[openai.OpenAI] = PrivateAttr(default=None) + _async_client: Optional[openai.AsyncOpenAI] = PrivateAttr(default=None) def __init__( self, @@ -38,8 +39,8 @@ class OpenAILLM(BaseLLM): if api_key is None: raise ValueError("OpenAI API key cannot be 'None'.") try: - self.async_client = openai.AsyncOpenAI(api_key=api_key) - self.client = openai.OpenAI(api_key=api_key) + self._async_client = openai.AsyncOpenAI(api_key=api_key) + self._client = openai.OpenAI(api_key=api_key) except Exception as e: raise ValueError( f"OpenAI API client failed to initialize. Error: {e}" @@ -86,14 +87,14 @@ class OpenAILLM(BaseLLM): messages: List[Message], function_schemas: Optional[List[Dict[str, Any]]] = None, ) -> str: - if self.client is None: + if self._client is None: raise ValueError("OpenAI client is not initialized.") try: tools: Union[List[Dict[str, Any]], NotGiven] = ( function_schemas if function_schemas else NOT_GIVEN ) - completion = self.client.chat.completions.create( + completion = self._client.chat.completions.create( model=self.name, messages=[m.to_openai() for m in messages], temperature=self.temperature, @@ -130,14 +131,14 @@ class OpenAILLM(BaseLLM): messages: List[Message], function_schemas: Optional[List[Dict[str, Any]]] = None, ) -> str: - if self.async_client is None: + if self._async_client is None: raise ValueError("OpenAI async_client is not initialized.") try: tools: Union[List[Dict[str, Any]], NotGiven] = ( function_schemas if function_schemas is not None else NOT_GIVEN ) - completion = await self.async_client.chat.completions.create( + completion = await self._async_client.chat.completions.create( model=self.name, messages=[m.to_openai() for m in messages], temperature=self.temperature, diff --git a/tests/unit/encoders/test_openai.py b/tests/unit/encoders/test_openai.py index 538e969250051c3da305702a42944903d1cb71cf..96171cce5b462f76dfbd88d7795bfc684ca100c8 100644 --- a/tests/unit/encoders/test_openai.py +++ b/tests/unit/encoders/test_openai.py @@ -30,7 +30,7 @@ class TestOpenAIEncoder: side_effect = ["fake-model-name", "fake-api-key", "fake-org-id"] mocker.patch("os.getenv", side_effect=side_effect) encoder = OpenAIEncoder() - assert encoder.client is not None + assert encoder._client is not None def test_openai_encoder_init_no_api_key(self, mocker): mocker.patch("os.getenv", return_value=None) @@ -39,7 +39,7 @@ class TestOpenAIEncoder: def test_openai_encoder_call_uninitialized_client(self, openai_encoder): # Set the client to None to simulate an uninitialized client - openai_encoder.client = None + openai_encoder._client = None with pytest.raises(ValueError) as e: openai_encoder(["test document"]) assert "OpenAI client is not initialized." in str(e.value) @@ -74,7 +74,7 @@ class TestOpenAIEncoder: responses = [OpenAIError("OpenAI error"), mock_response] mocker.patch.object( - openai_encoder.client.embeddings, "create", side_effect=responses + openai_encoder._client.embeddings, "create", side_effect=responses ) with patch("semantic_router.encoders.openai.sleep", return_value=None): embeddings = openai_encoder(["test document"]) @@ -84,7 +84,7 @@ class TestOpenAIEncoder: mocker.patch("os.getenv", return_value="fake-api-key") mocker.patch("time.sleep", return_value=None) # To speed up the test mocker.patch.object( - openai_encoder.client.embeddings, + openai_encoder._client.embeddings, "create", side_effect=Exception("Non-OpenAIError"), ) @@ -114,7 +114,7 @@ class TestOpenAIEncoder: responses = [OpenAIError("OpenAI error"), mock_response] mocker.patch.object( - openai_encoder.client.embeddings, "create", side_effect=responses + openai_encoder._client.embeddings, "create", side_effect=responses ) with patch("semantic_router.encoders.openai.sleep", return_value=None): embeddings = openai_encoder(["test document"]) diff --git a/tests/unit/llms/test_llm_azure_openai.py b/tests/unit/llms/test_llm_azure_openai.py index 793091957df7fdd89792aebec43a6fe87763e02b..def792b372790a912b68956bd83e5aa5023a5b7e 100644 --- a/tests/unit/llms/test_llm_azure_openai.py +++ b/tests/unit/llms/test_llm_azure_openai.py @@ -12,13 +12,13 @@ def azure_openai_llm(mocker): class TestOpenAILLM: def test_azure_openai_llm_init_with_api_key(self, azure_openai_llm): - assert azure_openai_llm.client is not None, "Client should be initialized" + assert azure_openai_llm._client is not None, "Client should be initialized" assert azure_openai_llm.name == "gpt-4o", "Default name not set correctly" def test_azure_openai_llm_init_success(self, mocker): mocker.patch("os.getenv", return_value="fake-api-key") llm = AzureOpenAILLM() - assert llm.client is not None + assert llm._client is not None def test_azure_openai_llm_init_without_api_key(self, mocker): mocker.patch("os.getenv", return_value=None) @@ -44,7 +44,7 @@ class TestOpenAILLM: def test_azure_openai_llm_call_uninitialized_client(self, azure_openai_llm): # Set the client to None to simulate an uninitialized client - azure_openai_llm.client = None + azure_openai_llm._client = None with pytest.raises(ValueError) as e: llm_input = [Message(role="user", content="test")] azure_openai_llm(llm_input) @@ -83,7 +83,7 @@ class TestOpenAILLM: mocker.patch("os.getenv", return_value="fake-api-key") mocker.patch.object( - azure_openai_llm.client.chat.completions, + azure_openai_llm._client.chat.completions, "create", return_value=mock_completion, ) diff --git a/tests/unit/llms/test_llm_openai.py b/tests/unit/llms/test_llm_openai.py index 4287fc882214c8524c5ce611c11af136eea935c5..ee7e82a9d741d31cc7303756a87e5dd007d32f72 100644 --- a/tests/unit/llms/test_llm_openai.py +++ b/tests/unit/llms/test_llm_openai.py @@ -42,13 +42,13 @@ example_function_schema = { class TestOpenAILLM: def test_openai_llm_init_with_api_key(self, openai_llm): - assert openai_llm.client is not None, "Client should be initialized" + assert openai_llm._client is not None, "Client should be initialized" assert openai_llm.name == "gpt-4o", "Default name not set correctly" def test_openai_llm_init_success(self, mocker): mocker.patch("os.getenv", return_value="fake-api-key") llm = OpenAILLM() - assert llm.client is not None + assert llm._client is not None def test_openai_llm_init_without_api_key(self, mocker): mocker.patch("os.getenv", return_value=None) @@ -57,7 +57,7 @@ class TestOpenAILLM: def test_openai_llm_call_uninitialized_client(self, openai_llm): # Set the client to None to simulate an uninitialized client - openai_llm.client = None + openai_llm._client = None with pytest.raises(ValueError) as e: llm_input = [Message(role="user", content="test")] openai_llm(llm_input) @@ -79,7 +79,7 @@ class TestOpenAILLM: mocker.patch("os.getenv", return_value="fake-api-key") mocker.patch.object( - openai_llm.client.chat.completions, "create", return_value=mock_completion + openai_llm._client.chat.completions, "create", return_value=mock_completion ) llm_input = [Message(role="user", content="test")] output = openai_llm(llm_input) @@ -127,7 +127,7 @@ class TestOpenAILLM: # mocker.MagicMock(function=mocker.MagicMock(arguments="result")) # ] # mocker.patch.object( - # openai_llm.client.chat.completions, "create", return_value=mock_completion + # openai_llm._client.chat.completions, "create", return_value=mock_completion # ) # llm_input = [Message(role="user", content="test")] # function_schemas = [{"type": "function", "name": "sample_function"}] @@ -145,7 +145,7 @@ class TestOpenAILLM: mock_completion.choices[0].message.tool_calls = [mock_tool_call] mocker.patch.object( - openai_llm.client.chat.completions, "create", return_value=mock_completion + openai_llm._client.chat.completions, "create", return_value=mock_completion ) llm_input = [Message(role="user", content="test")] @@ -160,7 +160,7 @@ class TestOpenAILLM: mock_completion = mocker.MagicMock() mock_completion.choices[0].message.tool_calls = None mocker.patch.object( - openai_llm.client.chat.completions, "create", return_value=mock_completion + openai_llm._client.chat.completions, "create", return_value=mock_completion ) llm_input = [Message(role="user", content="test")] function_schemas = [{"type": "function", "name": "sample_function"}] @@ -180,7 +180,7 @@ class TestOpenAILLM: mocker.MagicMock(function=mocker.MagicMock(arguments=None)) ] mocker.patch.object( - openai_llm.client.chat.completions, "create", return_value=mock_completion + openai_llm._client.chat.completions, "create", return_value=mock_completion ) llm_input = [Message(role="user", content="test")] function_schemas = [{"type": "function", "name": "sample_function"}] @@ -230,7 +230,7 @@ class TestOpenAILLM: # Patching the completions.create method to return the mocked completion mocker.patch.object( - openai_llm.client.chat.completions, "create", return_value=mock_completion + openai_llm._client.chat.completions, "create", return_value=mock_completion ) # Input message list