From 7115a558bc1d71641c98de6d75ab4309510a7a7c Mon Sep 17 00:00:00 2001 From: Krista Pratico Date: Wed, 29 May 2024 12:45:10 -0700 Subject: [PATCH 1/3] [azure] only add deployment in url path for deployment-based APIs --- src/openai/lib/azure.py | 12 ++- tests/lib/test_azure.py | 170 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 166 insertions(+), 16 deletions(-) diff --git a/src/openai/lib/azure.py b/src/openai/lib/azure.py index b76b83c61c..2ad8b705b3 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..8b664a4c41 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,156 @@ 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", + 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", + 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" + ), + ( + 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", + 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", + 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" + ), + ], +) +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 From dba3f7eff767b08485ef31773c61ff60a408b981 Mon Sep 17 00:00:00 2001 From: Krista Pratico Date: Thu, 30 May 2024 16:08:09 -0700 Subject: [PATCH 2/3] add additional test --- tests/lib/test_azure.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/lib/test_azure.py b/tests/lib/test_azure.py index 8b664a4c41..21972e5bcc 100644 --- a/tests/lib/test_azure.py +++ b/tests/lib/test_azure.py @@ -113,6 +113,17 @@ def test_client_copying_override_options(client: Client) -> None: {"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/", + ), + "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", @@ -181,6 +192,17 @@ def test_client_copying_override_options(client: Client) -> None: {"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/", + ), + "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", From 291fdcad5c5b5d27bd99f706cb02bc205b50e040 Mon Sep 17 00:00:00 2001 From: Krista Pratico Date: Wed, 5 Jun 2024 11:57:25 -0700 Subject: [PATCH 3/3] add more test --- src/openai/lib/azure.py | 6 +- tests/lib/test_azure.py | 195 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 190 insertions(+), 11 deletions(-) diff --git a/src/openai/lib/azure.py b/src/openai/lib/azure.py index 2ad8b705b3..a1a473bc09 100644 --- a/src/openai/lib/azure.py +++ b/src/openai/lib/azure.py @@ -55,17 +55,17 @@ 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 "/deployments" not in str(self.base_url.path): + 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: + 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('/')}" + 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) diff --git a/tests/lib/test_azure.py b/tests/lib/test_azure.py index 21972e5bcc..6aab036c5f 100644 --- a/tests/lib/test_azure.py +++ b/tests/lib/test_azure.py @@ -78,6 +78,17 @@ def test_client_copying_override_options(client: Client) -> None: {}, "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", @@ -106,7 +117,19 @@ def test_client_copying_override_options(client: Client) -> None: AzureOpenAI( api_version="2024-02-01", api_key="example API key", - base_url="https://example.azure-api.net/PTU/deployments/my-deployment/", + 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", @@ -117,7 +140,29 @@ def test_client_copying_override_options(client: Client) -> None: AzureOpenAI( api_version="2024-02-01", api_key="example API key", - base_url="https://example.azure-api.net/PTU/", + 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", @@ -128,13 +173,35 @@ def test_client_copying_override_options(client: Client) -> None: AzureOpenAI( api_version="2024-02-01", api_key="example API key", - base_url="https://example.azure-api.net/PTU/deployments/my-deployment/", + base_url="https://example.azure-api.net/PTU", ), - "https://example.azure-api.net/PTU/deployments/my-deployment/", + "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", @@ -157,6 +224,17 @@ def test_client_copying_override_options(client: Client) -> None: {}, "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", @@ -185,7 +263,19 @@ def test_client_copying_override_options(client: Client) -> None: AsyncAzureOpenAI( api_version="2024-02-01", api_key="example API key", - base_url="https://example.azure-api.net/PTU/deployments/my-deployment/", + 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", @@ -196,7 +286,29 @@ def test_client_copying_override_options(client: Client) -> None: AsyncAzureOpenAI( api_version="2024-02-01", api_key="example API key", - base_url="https://example.azure-api.net/PTU/", + 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", @@ -207,13 +319,35 @@ def test_client_copying_override_options(client: Client) -> None: AsyncAzureOpenAI( api_version="2024-02-01", api_key="example API key", - base_url="https://example.azure-api.net/PTU/deployments/my-deployment/", + base_url="https://example.azure-api.net/PTU", ), - "https://example.azure-api.net/PTU/deployments/my-deployment/", + "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: @@ -226,3 +360,48 @@ def test_client_prepare_url(client: Client, base_url: str, api_path: str, json_d ) 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