Commit c98d7400
Changed files (3)
src
openai
lib
resources
beta
realtime
tests
src/openai/lib/azure.py
@@ -49,6 +49,9 @@ class MutuallyExclusiveAuthError(OpenAIError):
class BaseAzureClient(BaseClient[_HttpxClientT, _DefaultStreamT]):
+ _azure_endpoint: httpx.URL | None
+ _azure_deployment: str | None
+
@override
def _build_request(
self,
@@ -58,11 +61,29 @@ class BaseAzureClient(BaseClient[_HttpxClientT, _DefaultStreamT]):
) -> 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, retries_taken=retries_taken)
+ @override
+ def _prepare_url(self, url: str) -> httpx.URL:
+ """Adjust the URL if the client was configured with an Azure endpoint + deployment
+ and the API feature being called is **not** a deployments-based endpoint
+ (i.e. requires /deployments/deployment-name in the URL path).
+ """
+ if self._azure_deployment and self._azure_endpoint and url not in _deployments_endpoints:
+ merge_url = httpx.URL(url)
+ if merge_url.is_relative_url:
+ merge_raw_path = (
+ self._azure_endpoint.raw_path.rstrip(b"/") + b"/openai/" + merge_url.raw_path.lstrip(b"/")
+ )
+ return self._azure_endpoint.copy_with(raw_path=merge_raw_path)
+
+ return merge_url
+
+ return super()._prepare_url(url)
+
class AzureOpenAI(BaseAzureClient[httpx.Client, Stream[Any]], OpenAI):
@overload
@@ -160,8 +181,8 @@ class AzureOpenAI(BaseAzureClient[httpx.Client, Stream[Any]], OpenAI):
azure_ad_token_provider: A function that returns an Azure Active Directory token, will be invoked on every request.
- azure_deployment: A model deployment, if given sets the base client URL to include `/deployments/{azure_deployment}`.
- Note: this means you won't be able to use non-deployment endpoints. Not supported with Assistants APIs.
+ azure_deployment: A model deployment, if given with `azure_endpoint`, sets the base client URL to include `/deployments/{azure_deployment}`.
+ Not supported with Assistants APIs.
"""
if api_key is None:
api_key = os.environ.get("AZURE_OPENAI_API_KEY")
@@ -224,6 +245,8 @@ class AzureOpenAI(BaseAzureClient[httpx.Client, Stream[Any]], OpenAI):
self._api_version = api_version
self._azure_ad_token = azure_ad_token
self._azure_ad_token_provider = azure_ad_token_provider
+ self._azure_deployment = azure_deployment if azure_endpoint else None
+ self._azure_endpoint = httpx.URL(azure_endpoint) if azure_endpoint else None
@override
def copy(
@@ -307,12 +330,12 @@ class AzureOpenAI(BaseAzureClient[httpx.Client, Stream[Any]], OpenAI):
return options
- def _configure_realtime(self, model: str, extra_query: Query) -> tuple[Query, dict[str, str]]:
+ def _configure_realtime(self, model: str, extra_query: Query) -> tuple[httpx.URL, dict[str, str]]:
auth_headers = {}
query = {
**extra_query,
"api-version": self._api_version,
- "deployment": model,
+ "deployment": self._azure_deployment or model,
}
if self.api_key != "<missing API key>":
auth_headers = {"api-key": self.api_key}
@@ -320,7 +343,17 @@ class AzureOpenAI(BaseAzureClient[httpx.Client, Stream[Any]], OpenAI):
token = self._get_azure_ad_token()
if token:
auth_headers = {"Authorization": f"Bearer {token}"}
- return query, auth_headers
+
+ if self.websocket_base_url is not None:
+ base_url = httpx.URL(self.websocket_base_url)
+ merge_raw_path = base_url.raw_path.rstrip(b"/") + b"/realtime"
+ realtime_url = base_url.copy_with(raw_path=merge_raw_path)
+ else:
+ base_url = self._prepare_url("/realtime")
+ realtime_url = base_url.copy_with(scheme="wss")
+
+ url = realtime_url.copy_with(params={**query})
+ return url, auth_headers
class AsyncAzureOpenAI(BaseAzureClient[httpx.AsyncClient, AsyncStream[Any]], AsyncOpenAI):
@@ -422,8 +455,8 @@ class AsyncAzureOpenAI(BaseAzureClient[httpx.AsyncClient, AsyncStream[Any]], Asy
azure_ad_token_provider: A function that returns an Azure Active Directory token, will be invoked on every request.
- azure_deployment: A model deployment, if given sets the base client URL to include `/deployments/{azure_deployment}`.
- Note: this means you won't be able to use non-deployment endpoints. Not supported with Assistants APIs.
+ azure_deployment: A model deployment, if given with `azure_endpoint`, sets the base client URL to include `/deployments/{azure_deployment}`.
+ Not supported with Assistants APIs.
"""
if api_key is None:
api_key = os.environ.get("AZURE_OPENAI_API_KEY")
@@ -486,6 +519,8 @@ class AsyncAzureOpenAI(BaseAzureClient[httpx.AsyncClient, AsyncStream[Any]], Asy
self._api_version = api_version
self._azure_ad_token = azure_ad_token
self._azure_ad_token_provider = azure_ad_token_provider
+ self._azure_deployment = azure_deployment if azure_endpoint else None
+ self._azure_endpoint = httpx.URL(azure_endpoint) if azure_endpoint else None
@override
def copy(
@@ -571,12 +606,12 @@ class AsyncAzureOpenAI(BaseAzureClient[httpx.AsyncClient, AsyncStream[Any]], Asy
return options
- async def _configure_realtime(self, model: str, extra_query: Query) -> tuple[Query, dict[str, str]]:
+ async def _configure_realtime(self, model: str, extra_query: Query) -> tuple[httpx.URL, dict[str, str]]:
auth_headers = {}
query = {
**extra_query,
"api-version": self._api_version,
- "deployment": model,
+ "deployment": self._azure_deployment or model,
}
if self.api_key != "<missing API key>":
auth_headers = {"api-key": self.api_key}
@@ -584,4 +619,14 @@ class AsyncAzureOpenAI(BaseAzureClient[httpx.AsyncClient, AsyncStream[Any]], Asy
token = await self._get_azure_ad_token()
if token:
auth_headers = {"Authorization": f"Bearer {token}"}
- return query, auth_headers
+
+ if self.websocket_base_url is not None:
+ base_url = httpx.URL(self.websocket_base_url)
+ merge_raw_path = base_url.raw_path.rstrip(b"/") + b"/realtime"
+ realtime_url = base_url.copy_with(raw_path=merge_raw_path)
+ else:
+ base_url = self._prepare_url("/realtime")
+ realtime_url = base_url.copy_with(scheme="wss")
+
+ url = realtime_url.copy_with(params={**query})
+ return url, auth_headers
src/openai/resources/beta/realtime/realtime.py
@@ -324,15 +324,15 @@ class AsyncRealtimeConnectionManager:
extra_query = self.__extra_query
auth_headers = self.__client.auth_headers
if is_async_azure_client(self.__client):
- extra_query, auth_headers = await self.__client._configure_realtime(self.__model, extra_query)
-
- url = self._prepare_url().copy_with(
- params={
- **self.__client.base_url.params,
- "model": self.__model,
- **extra_query,
- },
- )
+ url, auth_headers = await self.__client._configure_realtime(self.__model, extra_query)
+ else:
+ url = self._prepare_url().copy_with(
+ params={
+ **self.__client.base_url.params,
+ "model": self.__model,
+ **extra_query,
+ },
+ )
log.debug("Connecting to %s", url)
if self.__websocket_connection_options:
log.debug("Connection options: %s", self.__websocket_connection_options)
@@ -506,15 +506,15 @@ class RealtimeConnectionManager:
extra_query = self.__extra_query
auth_headers = self.__client.auth_headers
if is_azure_client(self.__client):
- extra_query, auth_headers = self.__client._configure_realtime(self.__model, extra_query)
-
- url = self._prepare_url().copy_with(
- params={
- **self.__client.base_url.params,
- "model": self.__model,
- **extra_query,
- },
- )
+ url, auth_headers = self.__client._configure_realtime(self.__model, extra_query)
+ else:
+ url = self._prepare_url().copy_with(
+ params={
+ **self.__client.base_url.params,
+ "model": self.__model,
+ **extra_query,
+ },
+ )
log.debug("Connecting to %s", url)
if self.__websocket_connection_options:
log.debug("Connection options: %s", self.__websocket_connection_options)
tests/lib/test_azure.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import logging
from typing import Union, cast
from typing_extensions import Literal, Protocol
@@ -239,3 +241,564 @@ class TestAzureLogging:
for record in caplog.records:
if is_dict(record.args) and record.args.get("headers") and is_dict(record.args["headers"]):
assert record.args["headers"]["Authorization"] == "<redacted>"
+
+
+@pytest.mark.parametrize(
+ "client,base_url,api,json_data,expected",
+ [
+ # Deployment-based endpoints
+ # AzureOpenAI: No deployment specified
+ (
+ 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": "deployment-body"},
+ "https://example-resource.azure.openai.com/openai/deployments/deployment-body/chat/completions?api-version=2024-02-01",
+ ),
+ # AzureOpenAI: Deployment specified
+ (
+ AzureOpenAI(
+ api_version="2024-02-01",
+ api_key="example API key",
+ azure_endpoint="https://example-resource.azure.openai.com",
+ azure_deployment="deployment-client",
+ ),
+ "https://example-resource.azure.openai.com/openai/deployments/deployment-client/",
+ "/chat/completions",
+ {"model": "deployment-body"},
+ "https://example-resource.azure.openai.com/openai/deployments/deployment-client/chat/completions?api-version=2024-02-01",
+ ),
+ # AzureOpenAI: "deployments" in the DNS name
+ (
+ AzureOpenAI(
+ api_version="2024-02-01",
+ api_key="example API key",
+ azure_endpoint="https://deployments.example-resource.azure.openai.com",
+ ),
+ "https://deployments.example-resource.azure.openai.com/openai/",
+ "/chat/completions",
+ {"model": "deployment-body"},
+ "https://deployments.example-resource.azure.openai.com/openai/deployments/deployment-body/chat/completions?api-version=2024-02-01",
+ ),
+ # AzureOpenAI: Deployment called deployments
+ (
+ AzureOpenAI(
+ api_version="2024-02-01",
+ api_key="example API key",
+ azure_endpoint="https://example-resource.azure.openai.com",
+ azure_deployment="deployments",
+ ),
+ "https://example-resource.azure.openai.com/openai/deployments/deployments/",
+ "/chat/completions",
+ {"model": "deployment-body"},
+ "https://example-resource.azure.openai.com/openai/deployments/deployments/chat/completions?api-version=2024-02-01",
+ ),
+ # AzureOpenAI: base_url and azure_deployment specified; ignored b/c not supported
+ (
+ AzureOpenAI( # type: ignore
+ api_version="2024-02-01",
+ api_key="example API key",
+ base_url="https://example.azure-api.net/PTU/",
+ azure_deployment="deployment-client",
+ ),
+ "https://example.azure-api.net/PTU/",
+ "/chat/completions",
+ {"model": "deployment-body"},
+ "https://example.azure-api.net/PTU/deployments/deployment-body/chat/completions?api-version=2024-02-01",
+ ),
+ # AsyncAzureOpenAI: No deployment specified
+ (
+ 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": "deployment-body"},
+ "https://example-resource.azure.openai.com/openai/deployments/deployment-body/chat/completions?api-version=2024-02-01",
+ ),
+ # AsyncAzureOpenAI: Deployment specified
+ (
+ AsyncAzureOpenAI(
+ api_version="2024-02-01",
+ api_key="example API key",
+ azure_endpoint="https://example-resource.azure.openai.com",
+ azure_deployment="deployment-client",
+ ),
+ "https://example-resource.azure.openai.com/openai/deployments/deployment-client/",
+ "/chat/completions",
+ {"model": "deployment-body"},
+ "https://example-resource.azure.openai.com/openai/deployments/deployment-client/chat/completions?api-version=2024-02-01",
+ ),
+ # AsyncAzureOpenAI: "deployments" in the DNS name
+ (
+ AsyncAzureOpenAI(
+ api_version="2024-02-01",
+ api_key="example API key",
+ azure_endpoint="https://deployments.example-resource.azure.openai.com",
+ ),
+ "https://deployments.example-resource.azure.openai.com/openai/",
+ "/chat/completions",
+ {"model": "deployment-body"},
+ "https://deployments.example-resource.azure.openai.com/openai/deployments/deployment-body/chat/completions?api-version=2024-02-01",
+ ),
+ # AsyncAzureOpenAI: Deployment called deployments
+ (
+ AsyncAzureOpenAI(
+ api_version="2024-02-01",
+ api_key="example API key",
+ azure_endpoint="https://example-resource.azure.openai.com",
+ azure_deployment="deployments",
+ ),
+ "https://example-resource.azure.openai.com/openai/deployments/deployments/",
+ "/chat/completions",
+ {"model": "deployment-body"},
+ "https://example-resource.azure.openai.com/openai/deployments/deployments/chat/completions?api-version=2024-02-01",
+ ),
+ # AsyncAzureOpenAI: base_url and azure_deployment specified; azure_deployment ignored b/c not supported
+ (
+ AsyncAzureOpenAI( # type: ignore
+ api_version="2024-02-01",
+ api_key="example API key",
+ base_url="https://example.azure-api.net/PTU/",
+ azure_deployment="deployment-client",
+ ),
+ "https://example.azure-api.net/PTU/",
+ "/chat/completions",
+ {"model": "deployment-body"},
+ "https://example.azure-api.net/PTU/deployments/deployment-body/chat/completions?api-version=2024-02-01",
+ ),
+ ],
+)
+def test_prepare_url_deployment_endpoint(
+ client: Client, base_url: str, api: str, json_data: dict[str, str], expected: str
+) -> None:
+ req = client._build_request(
+ FinalRequestOptions.construct(
+ method="post",
+ url=api,
+ json_data=json_data,
+ )
+ )
+ assert req.url == expected
+ assert client.base_url == base_url
+
+
+@pytest.mark.parametrize(
+ "client,base_url,api,json_data,expected",
+ [
+ # Non-deployment endpoints
+ # AzureOpenAI: No deployment specified
+ (
+ 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: No deployment specified
+ (
+ 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": "deployment-body"},
+ "https://example-resource.azure.openai.com/openai/assistants?api-version=2024-02-01",
+ ),
+ # AzureOpenAI: Deployment specified
+ (
+ AzureOpenAI(
+ api_version="2024-02-01",
+ api_key="example API key",
+ azure_endpoint="https://example-resource.azure.openai.com",
+ azure_deployment="deployment-client",
+ ),
+ "https://example-resource.azure.openai.com/openai/deployments/deployment-client/",
+ "/models",
+ {},
+ "https://example-resource.azure.openai.com/openai/models?api-version=2024-02-01",
+ ),
+ # AzureOpenAI: Deployment specified
+ (
+ AzureOpenAI(
+ api_version="2024-02-01",
+ api_key="example API key",
+ azure_endpoint="https://example-resource.azure.openai.com",
+ azure_deployment="deployment-client",
+ ),
+ "https://example-resource.azure.openai.com/openai/deployments/deployment-client/",
+ "/assistants",
+ {"model": "deployment-body"},
+ "https://example-resource.azure.openai.com/openai/assistants?api-version=2024-02-01",
+ ),
+ # AzureOpenAI: "deployments" in the DNS name
+ (
+ AzureOpenAI(
+ api_version="2024-02-01",
+ api_key="example API key",
+ azure_endpoint="https://deployments.example-resource.azure.openai.com",
+ ),
+ "https://deployments.example-resource.azure.openai.com/openai/",
+ "/models",
+ {},
+ "https://deployments.example-resource.azure.openai.com/openai/models?api-version=2024-02-01",
+ ),
+ # AzureOpenAI: Deployment called "deployments"
+ (
+ AzureOpenAI(
+ api_version="2024-02-01",
+ api_key="example API key",
+ azure_endpoint="https://example-resource.azure.openai.com",
+ azure_deployment="deployments",
+ ),
+ "https://example-resource.azure.openai.com/openai/deployments/deployments/",
+ "/models",
+ {},
+ "https://example-resource.azure.openai.com/openai/models?api-version=2024-02-01",
+ ),
+ # AzureOpenAI: base_url and azure_deployment specified; azure_deployment ignored b/c not supported
+ (
+ AzureOpenAI( # type: ignore
+ api_version="2024-02-01",
+ api_key="example API key",
+ base_url="https://example.azure-api.net/PTU/",
+ azure_deployment="deployment-client",
+ ),
+ "https://example.azure-api.net/PTU/",
+ "/models",
+ {},
+ "https://example.azure-api.net/PTU/models?api-version=2024-02-01",
+ ),
+ # AsyncAzureOpenAI: No deployment specified
+ (
+ 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: No deployment specified
+ (
+ 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": "deployment-body"},
+ "https://example-resource.azure.openai.com/openai/assistants?api-version=2024-02-01",
+ ),
+ # AsyncAzureOpenAI: Deployment specified
+ (
+ AsyncAzureOpenAI(
+ api_version="2024-02-01",
+ api_key="example API key",
+ azure_endpoint="https://example-resource.azure.openai.com",
+ azure_deployment="deployment-client",
+ ),
+ "https://example-resource.azure.openai.com/openai/deployments/deployment-client/",
+ "/models",
+ {},
+ "https://example-resource.azure.openai.com/openai/models?api-version=2024-02-01",
+ ),
+ # AsyncAzureOpenAI: Deployment specified
+ (
+ AsyncAzureOpenAI(
+ api_version="2024-02-01",
+ api_key="example API key",
+ azure_endpoint="https://example-resource.azure.openai.com",
+ azure_deployment="deployment-client",
+ ),
+ "https://example-resource.azure.openai.com/openai/deployments/deployment-client/",
+ "/assistants",
+ {"model": "deployment-body"},
+ "https://example-resource.azure.openai.com/openai/assistants?api-version=2024-02-01",
+ ),
+ # AsyncAzureOpenAI: "deployments" in the DNS name
+ (
+ AsyncAzureOpenAI(
+ api_version="2024-02-01",
+ api_key="example API key",
+ azure_endpoint="https://deployments.example-resource.azure.openai.com",
+ ),
+ "https://deployments.example-resource.azure.openai.com/openai/",
+ "/models",
+ {},
+ "https://deployments.example-resource.azure.openai.com/openai/models?api-version=2024-02-01",
+ ),
+ # AsyncAzureOpenAI: Deployment called "deployments"
+ (
+ AsyncAzureOpenAI(
+ api_version="2024-02-01",
+ api_key="example API key",
+ azure_endpoint="https://example-resource.azure.openai.com",
+ azure_deployment="deployments",
+ ),
+ "https://example-resource.azure.openai.com/openai/deployments/deployments/",
+ "/models",
+ {},
+ "https://example-resource.azure.openai.com/openai/models?api-version=2024-02-01",
+ ),
+ # AsyncAzureOpenAI: base_url and azure_deployment specified; azure_deployment ignored b/c not supported
+ (
+ AsyncAzureOpenAI( # type: ignore
+ api_version="2024-02-01",
+ api_key="example API key",
+ base_url="https://example.azure-api.net/PTU/",
+ azure_deployment="deployment-client",
+ ),
+ "https://example.azure-api.net/PTU/",
+ "/models",
+ {},
+ "https://example.azure-api.net/PTU/models?api-version=2024-02-01",
+ ),
+ ],
+)
+def test_prepare_url_nondeployment_endpoint(
+ client: Client, base_url: str, api: str, json_data: dict[str, str], expected: str
+) -> None:
+ req = client._build_request(
+ FinalRequestOptions.construct(
+ method="post",
+ url=api,
+ json_data=json_data,
+ )
+ )
+ assert req.url == expected
+ assert client.base_url == base_url
+
+
+@pytest.mark.parametrize(
+ "client,base_url,json_data,expected",
+ [
+ # Realtime endpoint
+ # AzureOpenAI: No deployment specified
+ (
+ 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/",
+ {"model": "deployment-body"},
+ "wss://example-resource.azure.openai.com/openai/realtime?api-version=2024-02-01&deployment=deployment-body",
+ ),
+ # AzureOpenAI: Deployment specified
+ (
+ AzureOpenAI(
+ api_version="2024-02-01",
+ api_key="example API key",
+ azure_endpoint="https://example-resource.azure.openai.com",
+ azure_deployment="deployment-client",
+ ),
+ "https://example-resource.azure.openai.com/openai/deployments/deployment-client/",
+ {"model": "deployment-body"},
+ "wss://example-resource.azure.openai.com/openai/realtime?api-version=2024-02-01&deployment=deployment-client",
+ ),
+ # AzureOpenAI: "deployments" in the DNS name
+ (
+ AzureOpenAI(
+ api_version="2024-02-01",
+ api_key="example API key",
+ azure_endpoint="https://deployments.azure.openai.com",
+ ),
+ "https://deployments.azure.openai.com/openai/",
+ {"model": "deployment-body"},
+ "wss://deployments.azure.openai.com/openai/realtime?api-version=2024-02-01&deployment=deployment-body",
+ ),
+ # AzureOpenAI: Deployment called "deployments"
+ (
+ AzureOpenAI(
+ api_version="2024-02-01",
+ api_key="example API key",
+ azure_endpoint="https://example-resource.azure.openai.com",
+ azure_deployment="deployments",
+ ),
+ "https://example-resource.azure.openai.com/openai/deployments/deployments/",
+ {"model": "deployment-body"},
+ "wss://example-resource.azure.openai.com/openai/realtime?api-version=2024-02-01&deployment=deployments",
+ ),
+ # AzureOpenAI: base_url and azure_deployment specified; azure_deployment ignored b/c not supported
+ (
+ AzureOpenAI( # type: ignore
+ api_version="2024-02-01",
+ api_key="example API key",
+ base_url="https://example.azure-api.net/PTU/",
+ azure_deployment="my-deployment",
+ ),
+ "https://example.azure-api.net/PTU/",
+ {"model": "deployment-body"},
+ "wss://example.azure-api.net/PTU/realtime?api-version=2024-02-01&deployment=deployment-body",
+ ),
+ # AzureOpenAI: websocket_base_url specified
+ (
+ AzureOpenAI(
+ api_version="2024-02-01",
+ api_key="example API key",
+ azure_endpoint="https://example-resource.azure.openai.com",
+ websocket_base_url="wss://example-resource.azure.openai.com/base",
+ ),
+ "https://example-resource.azure.openai.com/openai/",
+ {"model": "deployment-body"},
+ "wss://example-resource.azure.openai.com/base/realtime?api-version=2024-02-01&deployment=deployment-body",
+ ),
+ ],
+)
+def test_prepare_url_realtime(client: AzureOpenAI, base_url: str, json_data: dict[str, str], expected: str) -> None:
+ url, _ = client._configure_realtime(json_data["model"], {})
+ assert str(url) == expected
+ assert client.base_url == base_url
+
+
+@pytest.mark.parametrize(
+ "client,base_url,json_data,expected",
+ [
+ # AsyncAzureOpenAI: No deployment specified
+ (
+ 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/",
+ {"model": "deployment-body"},
+ "wss://example-resource.azure.openai.com/openai/realtime?api-version=2024-02-01&deployment=deployment-body",
+ ),
+ # AsyncAzureOpenAI: Deployment specified
+ (
+ AsyncAzureOpenAI(
+ api_version="2024-02-01",
+ api_key="example API key",
+ azure_endpoint="https://example-resource.azure.openai.com",
+ azure_deployment="deployment-client",
+ ),
+ "https://example-resource.azure.openai.com/openai/deployments/deployment-client/",
+ {"model": "deployment-body"},
+ "wss://example-resource.azure.openai.com/openai/realtime?api-version=2024-02-01&deployment=deployment-client",
+ ),
+ # AsyncAzureOpenAI: "deployments" in the DNS name
+ (
+ AsyncAzureOpenAI(
+ api_version="2024-02-01",
+ api_key="example API key",
+ azure_endpoint="https://deployments.azure.openai.com",
+ ),
+ "https://deployments.azure.openai.com/openai/",
+ {"model": "deployment-body"},
+ "wss://deployments.azure.openai.com/openai/realtime?api-version=2024-02-01&deployment=deployment-body",
+ ),
+ # AsyncAzureOpenAI: Deployment called "deployments"
+ (
+ AsyncAzureOpenAI(
+ api_version="2024-02-01",
+ api_key="example API key",
+ azure_endpoint="https://example-resource.azure.openai.com",
+ azure_deployment="deployments",
+ ),
+ "https://example-resource.azure.openai.com/openai/deployments/deployments/",
+ {"model": "deployment-body"},
+ "wss://example-resource.azure.openai.com/openai/realtime?api-version=2024-02-01&deployment=deployments",
+ ),
+ # AsyncAzureOpenAI: base_url and azure_deployment specified; azure_deployment ignored b/c not supported
+ (
+ AsyncAzureOpenAI( # type: ignore
+ api_version="2024-02-01",
+ api_key="example API key",
+ base_url="https://example.azure-api.net/PTU/",
+ azure_deployment="deployment-client",
+ ),
+ "https://example.azure-api.net/PTU/",
+ {"model": "deployment-body"},
+ "wss://example.azure-api.net/PTU/realtime?api-version=2024-02-01&deployment=deployment-body",
+ ),
+ # AsyncAzureOpenAI: websocket_base_url specified
+ (
+ AsyncAzureOpenAI(
+ api_version="2024-02-01",
+ api_key="example API key",
+ azure_endpoint="https://example-resource.azure.openai.com",
+ websocket_base_url="wss://example-resource.azure.openai.com/base",
+ ),
+ "https://example-resource.azure.openai.com/openai/",
+ {"model": "deployment-body"},
+ "wss://example-resource.azure.openai.com/base/realtime?api-version=2024-02-01&deployment=deployment-body",
+ ),
+ ],
+)
+async def test_prepare_url_realtime_async(
+ client: AsyncAzureOpenAI, base_url: str, json_data: dict[str, str], expected: str
+) -> None:
+ url, _ = await client._configure_realtime(json_data["model"], {})
+ assert str(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/"
+
+ # (not recommended) user sets base_url to target different deployment
+ 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"
+
+ # (not recommended) user sets base_url to remove deployment
+ client.base_url = "https://example-resource.azure.openai.com/openai/"
+ req = client._build_request(
+ FinalRequestOptions.construct(
+ method="post",
+ url="/chat/completions",
+ json_data={"model": "deployment"},
+ )
+ )
+ assert (
+ req.url
+ == "https://example-resource.azure.openai.com/openai/deployments/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"