From aec0ab777bde6f59cd7fc4be153caad247e973de Mon Sep 17 00:00:00 2001 From: abi_jey Date: Tue, 25 Nov 2025 19:18:41 +0000 Subject: [PATCH] feat: add GA protocol as litellm_params for realtime api on azure provider --- litellm/llms/azure/realtime/handler.py | 54 ++++++++++++++++-- .../realtime/test_azure_realtime_handler.py | 56 +++++++++++++++++++ 2 files changed, 105 insertions(+), 5 deletions(-) diff --git a/litellm/llms/azure/realtime/handler.py b/litellm/llms/azure/realtime/handler.py index 8e5581206d..14f772ac07 100644 --- a/litellm/llms/azure/realtime/handler.py +++ b/litellm/llms/azure/realtime/handler.py @@ -28,16 +28,60 @@ async def forward_messages(client_ws: Any, backend_ws: Any): class AzureOpenAIRealtime(AzureChatCompletion): - def _construct_url(self, api_base: str, model: str, api_version: str) -> str: + def _get_realtime_protocol(self) -> str: + """Return the configured realtime protocol. + + Supported values (case-insensitive): + - "beta" -> use legacy `/openai/realtime` (current default) + - "v1" -> use `/openai/v1/realtime` + - "ga" -> alias for "v1" (GA path is v1) + + If the parameter is missing or invalid, we fall back to the current + behavior for full backwards compatibility. """ - Example output: + + # `litellm_params` is the standard place to configure provider-specific + # behavior. We keep this defensive in case the attribute isn't set. + params: Any = getattr(self, "litellm_params", None) + if not isinstance(params, dict): + return "beta" + + value = params.get("realtime_protocol") + if not isinstance(value, str): + return "beta" + + value_normalized = value.lower() + if value_normalized in {"v1", "ga"}: + return "v1" + + # Treat anything else (including explicit "beta") as current default + return "beta" + + def _construct_url( + self, + api_base: str, + model: str, + api_version: str, + ) -> str: + """Construct the websocket URL for Azure OpenAI realtime. + + Example default output (beta / legacy behavior): "wss://my-endpoint-sweden-berri992.openai.azure.com/openai/realtime?api-version=2024-10-01-preview&deployment=gpt-4o-realtime-preview"; + When `realtime_protocol` is set to "v1" or "GA" via `litellm_params`, + this switches to `/openai/v1/realtime`. """ + api_base = api_base.replace("https://", "wss://") - return ( - f"{api_base}/openai/realtime?api-version={api_version}&deployment={model}" - ) + + protocol = self._get_realtime_protocol() + if protocol == "v1": + path = "/openai/v1/realtime" + else: + # default / beta behavior + path = "/openai/realtime" + + return f"{api_base}{path}?api-version={api_version}&deployment={model}" async def async_realtime( self, diff --git a/tests/test_litellm/llms/azure/realtime/test_azure_realtime_handler.py b/tests/test_litellm/llms/azure/realtime/test_azure_realtime_handler.py index 7bcbe37156..1446bc3df7 100644 --- a/tests/test_litellm/llms/azure/realtime/test_azure_realtime_handler.py +++ b/tests/test_litellm/llms/azure/realtime/test_azure_realtime_handler.py @@ -71,3 +71,59 @@ async def test_async_realtime_uses_max_size_parameter(): mock_realtime_streaming.assert_called_once() mock_streaming_instance.bidirectional_forward.assert_awaited_once() + +@pytest.mark.asyncio +async def test_construct_url_uses_legacy_realtime_by_default(): + """By default we should keep using `/openai/realtime` (beta behavior).""" + + from litellm.llms.azure.realtime.handler import AzureOpenAIRealtime + + handler = AzureOpenAIRealtime() + api_base = "https://my-endpoint.openai.azure.com" + api_version = "2024-10-01-preview" + model = "gpt-4o-realtime-preview" + + url = handler._construct_url(api_base=api_base, model=model, api_version=api_version) + + assert url.startswith("wss://my-endpoint.openai.azure.com") + assert "/openai/realtime" in url + assert "/openai/v1/realtime" not in url + + +@pytest.mark.asyncio +async def test_construct_url_uses_v1_when_realtime_protocol_v1_or_ga(): + """Setting `realtime_protocol` to v1/GA should switch to `/openai/v1/realtime`.""" + + from litellm.llms.azure.realtime.handler import AzureOpenAIRealtime + + api_base = "https://my-endpoint.openai.azure.com" + api_version = "2024-10-01-preview" + model = "gpt-4o-realtime-preview" + + # Helper to construct handler URL with a specific realtime_protocol. + # We avoid mutating handler attributes directly since type checkers don't + # know about `litellm_params` on this class. Instead, we patch the + # `_get_realtime_protocol` helper which is what `_construct_url` uses. + + # v1 -> /openai/v1/realtime + handler_v1 = AzureOpenAIRealtime() + with patch.object(handler_v1, "_get_realtime_protocol", return_value="v1"): + url_v1 = handler_v1._construct_url(api_base=api_base, model=model, api_version=api_version) + assert "/openai/v1/realtime" in url_v1 + assert "/openai/realtime" not in url_v1 + + # GA (case-insensitive) -> /openai/v1/realtime + handler_ga = AzureOpenAIRealtime() + with patch.object(handler_ga, "_get_realtime_protocol", return_value="v1"): + url_ga = handler_ga._construct_url(api_base=api_base, model=model, api_version=api_version) + assert "/openai/v1/realtime" in url_ga + assert "/openai/realtime" not in url_ga + + # beta or any other value keeps legacy path + handler_beta = AzureOpenAIRealtime() + with patch.object(handler_beta, "_get_realtime_protocol", return_value="beta"): + url_beta = handler_beta._construct_url(api_base=api_base, model=model, api_version=api_version) + assert "/openai/realtime" in url_beta + assert "/openai/v1/realtime" not in url_beta + +