diff --git a/src/openai/lib/azure.py b/src/openai/lib/azure.py index b76b83c61c..a1a473bc09 100644 --- a/src/openai/lib/azure.py +++ b/src/openai/lib/azure.py @@ -55,11 +55,21 @@ def _build_request( ) -> httpx.Request: if options.url in _deployments_endpoints and is_mapping(options.json_data): model = options.json_data.get("model") - if model is not None and not "/deployments" in str(self.base_url): + if model is not None and "/deployments/" not in str(self.base_url.path): options.url = f"/deployments/{model}{options.url}" return super()._build_request(options) + @override + def _prepare_url(self, url: str) -> httpx.URL: + if "/deployments/" in str(self.base_url.path) and url not in _deployments_endpoints: + merge_url = httpx.URL(url) + if merge_url.is_relative_url: + merge_path = f"{self.base_url.path.rsplit('/deployments/', maxsplit=1)[0]}/{merge_url.path.lstrip('/')}" + return self.base_url.copy_with(path=merge_path) + + return super()._prepare_url(url) + class AzureOpenAI(BaseAzureClient[httpx.Client, Stream[Any]], OpenAI): @overload diff --git a/tests/lib/test_azure.py b/tests/lib/test_azure.py index 9360b2925a..6aab036c5f 100644 --- a/tests/lib/test_azure.py +++ b/tests/lib/test_azure.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Union from typing_extensions import Literal @@ -22,21 +24,6 @@ ) -@pytest.mark.parametrize("client", [sync_client, async_client]) -def test_implicit_deployment_path(client: Client) -> None: - req = client._build_request( - FinalRequestOptions.construct( - method="post", - url="/chat/completions", - json_data={"model": "my-deployment-model"}, - ) - ) - assert ( - req.url - == "https://example-resource.azure.openai.com/openai/deployments/my-deployment-model/chat/completions?api-version=2023-07-01" - ) - - @pytest.mark.parametrize( "client,method", [ @@ -64,3 +51,357 @@ def test_client_copying_override_options(client: Client) -> None: api_version="2022-05-01", ) assert copied._custom_query == {"api-version": "2022-05-01"} + + +@pytest.mark.parametrize( + "client,base_url,api_path,json_data,expected", + [ + ( + AzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + ), + "https://example-resource.azure.openai.com/openai/", + "/chat/completions", + {"model": "my-deployment"}, + "https://example-resource.azure.openai.com/openai/deployments/my-deployment/chat/completions?api-version=2024-02-01" + ), + ( + AzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + ), + "https://example-resource.azure.openai.com/openai/", + "/models", + {}, + "https://example-resource.azure.openai.com/openai/models?api-version=2024-02-01" + ), + ( + AzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + ), + "https://example-resource.azure.openai.com/openai/", + "/assistants", + {"model": "gpt-4"}, + "https://example-resource.azure.openai.com/openai/assistants?api-version=2024-02-01" + ), + ( + AzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + azure_deployment="my-deployment" + ), + "https://example-resource.azure.openai.com/openai/deployments/my-deployment/", + "/chat/completions", + {"model": "placeholder"}, + "https://example-resource.azure.openai.com/openai/deployments/my-deployment/chat/completions?api-version=2024-02-01" + ), + ( + AzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + azure_deployment="my-deployment" + ), + "https://example-resource.azure.openai.com/openai/deployments/my-deployment/", + "/models", + {}, + "https://example-resource.azure.openai.com/openai/models?api-version=2024-02-01" + ), + ( + AzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + azure_deployment="my-deployment" + ), + "https://example-resource.azure.openai.com/openai/deployments/my-deployment/", + "/assistants", + {"model": "gpt-4"}, + "https://example-resource.azure.openai.com/openai/assistants?api-version=2024-02-01" + ), + ( + AzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + base_url="https://example.azure-api.net/PTU/deployments/my-deployment", + ), + "https://example.azure-api.net/PTU/deployments/my-deployment/", + "/chat/completions", + {"model": "placeholder"}, + "https://example.azure-api.net/PTU/deployments/my-deployment/chat/completions?api-version=2024-02-01" + ), + ( + AzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + base_url="https://example.azure-api.net/PTU/deployments/my-deployment", + ), + "https://example.azure-api.net/PTU/deployments/my-deployment/", + "/models", + {}, + "https://example.azure-api.net/PTU/models?api-version=2024-02-01" + ), + ( + AzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + base_url="https://example.azure-api.net/PTU/deployments/my-deployment", + ), + "https://example.azure-api.net/PTU/deployments/my-deployment/", + "/assistants", + {"model": "gpt-4"}, + "https://example.azure-api.net/PTU/assistants?api-version=2024-02-01" + ), + ( + AzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + base_url="https://example.azure-api.net/PTU", + ), + "https://example.azure-api.net/PTU/", + "/chat/completions", + {"model": "my-deployment"}, + "https://example.azure-api.net/PTU/deployments/my-deployment/chat/completions?api-version=2024-02-01" + ), + ( + AzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + base_url="https://example.azure-api.net/PTU", + ), + "https://example.azure-api.net/PTU/", + "/models", + {}, + "https://example.azure-api.net/PTU/models?api-version=2024-02-01" + ), + ( + AzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + base_url="https://example.azure-api.net/PTU", + ), + "https://example.azure-api.net/PTU/", + "/assistants", + {"model": "gpt-4"}, + "https://example.azure-api.net/PTU/assistants?api-version=2024-02-01" + ), + ( # The below test case fails -- even before the change in _prepare_url + AzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + base_url="https://example.azure-api.net/deployments", + ), + "https://example.azure-api.net/deployments/", + "/chat/completions", + {"model": "my-deployment"}, + "https://example.azure-api.net/deployments/deployments/my-deployment/chat/completions?api-version=2024-02-01" + ), + ( + AsyncAzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + ), + "https://example-resource.azure.openai.com/openai/", + "/chat/completions", + {"model": "my-deployment"}, + "https://example-resource.azure.openai.com/openai/deployments/my-deployment/chat/completions?api-version=2024-02-01" + ), + ( + AsyncAzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + ), + "https://example-resource.azure.openai.com/openai/", + "/models", + {}, + "https://example-resource.azure.openai.com/openai/models?api-version=2024-02-01" + ), + ( + AsyncAzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + ), + "https://example-resource.azure.openai.com/openai/", + "/assistants", + {"model": "gpt-4"}, + "https://example-resource.azure.openai.com/openai/assistants?api-version=2024-02-01" + ), + ( + AsyncAzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + azure_deployment="my-deployment" + ), + "https://example-resource.azure.openai.com/openai/deployments/my-deployment/", + "/chat/completions", + {"model": "placeholder"}, + "https://example-resource.azure.openai.com/openai/deployments/my-deployment/chat/completions?api-version=2024-02-01" + ), + ( + AsyncAzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + azure_deployment="my-deployment" + ), + "https://example-resource.azure.openai.com/openai/deployments/my-deployment/", + "/models", + {}, + "https://example-resource.azure.openai.com/openai/models?api-version=2024-02-01" + ), + ( + AsyncAzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + azure_deployment="my-deployment" + ), + "https://example-resource.azure.openai.com/openai/deployments/my-deployment/", + "/assistants", + {"model": "gpt-4"}, + "https://example-resource.azure.openai.com/openai/assistants?api-version=2024-02-01" + ), + ( + AsyncAzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + base_url="https://example.azure-api.net/PTU/deployments/my-deployment", + ), + "https://example.azure-api.net/PTU/deployments/my-deployment/", + "/chat/completions", + {"model": "placeholder"}, + "https://example.azure-api.net/PTU/deployments/my-deployment/chat/completions?api-version=2024-02-01" + ), + ( + AsyncAzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + base_url="https://example.azure-api.net/PTU/deployments/my-deployment", + ), + "https://example.azure-api.net/PTU/deployments/my-deployment/", + "/models", + {}, + "https://example.azure-api.net/PTU/models?api-version=2024-02-01" + ), + ( + AsyncAzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + base_url="https://example.azure-api.net/PTU/deployments/my-deployment", + ), + "https://example.azure-api.net/PTU/deployments/my-deployment/", + "/assistants", + {"model": "gpt-4"}, + "https://example.azure-api.net/PTU/assistants?api-version=2024-02-01" + ), + ( + AsyncAzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + base_url="https://example.azure-api.net/PTU", + ), + "https://example.azure-api.net/PTU/", + "/chat/completions", + {"model": "my-deployment"}, + "https://example.azure-api.net/PTU/deployments/my-deployment/chat/completions?api-version=2024-02-01" + ), + ( + AsyncAzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + base_url="https://example.azure-api.net/PTU", + ), + "https://example.azure-api.net/PTU/", + "/models", + {}, + "https://example.azure-api.net/PTU/models?api-version=2024-02-01" + ), + ( + AsyncAzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + base_url="https://example.azure-api.net/PTU", + ), + "https://example.azure-api.net/PTU/", + "/assistants", + {"model": "gpt-4"}, + "https://example.azure-api.net/PTU/assistants?api-version=2024-02-01" + ), + ( # The below test case fails -- even before the change in _prepare_url + AsyncAzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + base_url="https://example.azure-api.net/deployments", + ), + "https://example.azure-api.net/deployments/", + "/chat/completions", + {"model": "my-deployment"}, + "https://example.azure-api.net/deployments/deployments/my-deployment/chat/completions?api-version=2024-02-01" + ) + ], +) +def test_client_prepare_url(client: Client, base_url: str, api_path: str, json_data: dict[str, str], expected: str) -> None: + req = client._build_request( + FinalRequestOptions.construct( + method="post", + url=api_path, + json_data=json_data, + ) + ) + assert req.url == expected + assert client.base_url == base_url + + +def test_client_sets_base_url(client: Client) -> None: + client = AzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + azure_deployment="my-deployment" + ) + assert client.base_url == "https://example-resource.azure.openai.com/openai/deployments/my-deployment/" + req = client._build_request( + FinalRequestOptions.construct( + method="post", + url="/chat/completions", + json_data= {"model": "placeholder"}, + ) + ) + assert req.url == "https://example-resource.azure.openai.com/openai/deployments/my-deployment/chat/completions?api-version=2024-02-01" + req = client._build_request( + FinalRequestOptions.construct( + method="post", + url="/models", + json_data= {}, + ) + ) + assert req.url == "https://example-resource.azure.openai.com/openai/models?api-version=2024-02-01" + + # user sets base_url to target different endpoint + client.base_url = "https://example-resource.azure.openai.com/openai/deployments/different-deployment/" + req = client._build_request( + FinalRequestOptions.construct( + method="post", + url="/chat/completions", + json_data= {"model": "placeholder"}, + ) + ) + assert req.url == "https://example-resource.azure.openai.com/openai/deployments/different-deployment/chat/completions?api-version=2024-02-01" + req = client._build_request( + FinalRequestOptions.construct( + method="post", + url="/models", + json_data= {}, + ) + ) + assert req.url == "https://example-resource.azure.openai.com/openai/models?api-version=2024-02-01" \ No newline at end of file