diff --git a/litellm/llms/openai/realtime/handler.py b/litellm/llms/openai/realtime/handler.py index 0343f22e7d1..3406414a29d 100644 --- a/litellm/llms/openai/realtime/handler.py +++ b/litellm/llms/openai/realtime/handler.py @@ -84,17 +84,20 @@ class OpenAIRealtime(OpenAIChatCompletion): """ Construct the backend websocket URL with all query parameters (including 'model'). """ - from httpx import URL + from httpx import URL, QueryParams - api_base = api_base.replace("https://", "wss://") - api_base = api_base.replace("http://", "ws://") - url = URL(api_base) - # Set the correct path - url = url.copy_with(path="/v1/realtime") - # Include all query parameters including 'model' - if query_params: - url = url.copy_with(params=query_params) - return str(url) + api_base_url: Final = URL(api_base) + websocket_scheme: Final = {"https": "wss", "http": "ws"}.get(api_base_url.scheme, api_base_url.scheme) + base_path: Final = api_base_url.path.rstrip("/") + realtime_path: Final = ( + base_path + if base_path.endswith("/v1/realtime") + else f"{base_path}/realtime" + if base_path.endswith("/v1") + else f"{base_path}/v1/realtime" + ) + merged_params: Final = QueryParams(query_params).merge(api_base_url.params) + return str(api_base_url.copy_with(scheme=websocket_scheme, path=realtime_path, params=merged_params)) def _make_event_normalizer(self) -> RealtimeEventNormalizer | None: """Return a per-session GA event normalizer, or None for passthrough. diff --git a/tests/test_litellm/llms/openai/realtime/test_openai_realtime_handler.py b/tests/test_litellm/llms/openai/realtime/test_openai_realtime_handler.py index 4221954d787..b5ef864e686 100644 --- a/tests/test_litellm/llms/openai/realtime/test_openai_realtime_handler.py +++ b/tests/test_litellm/llms/openai/realtime/test_openai_realtime_handler.py @@ -9,9 +9,16 @@ from litellm.llms.custom_httpx.http_handler import get_shared_realtime_ssl_conte @pytest.mark.parametrize( - "api_base", ["https://api.openai.com/v1", "https://api.openai.com"] + ("api_base", "expected_scheme", "expected_path"), + [ + ("https://api.openai.com", "wss", "/v1/realtime"), + ("https://api.openai.com/v1", "wss", "/v1/realtime"), + ("https://api.openai.com/v1/realtime", "wss", "/v1/realtime"), + ("http://localhost:8000/gateway/openai", "ws", "/gateway/openai/v1/realtime"), + ("https://gateway.example.com/tenant/openai/v1/", "wss", "/tenant/openai/v1/realtime"), + ], ) -def test_openai_realtime_handler_url_construction(api_base): +def test_openai_realtime_handler_url_construction(api_base, expected_scheme, expected_path): from litellm.llms.openai.realtime.handler import OpenAIRealtime handler = OpenAIRealtime() @@ -21,9 +28,10 @@ def test_openai_realtime_handler_url_construction(api_base): "model": "gpt-4o-realtime-preview-2024-10-01", }, ) - # Model parameter should be included in the URL - assert url.startswith("wss://api.openai.com/v1/realtime?") - assert "model=gpt-4o-realtime-preview-2024-10-01" in url + parsed_url = httpx.URL(url) + assert parsed_url.scheme == expected_scheme + assert parsed_url.path == expected_path + assert parsed_url.params["model"] == "gpt-4o-realtime-preview-2024-10-01" def test_openai_realtime_handler_url_with_extra_params(): @@ -31,16 +39,21 @@ def test_openai_realtime_handler_url_with_extra_params(): from litellm.types.realtime import RealtimeQueryParams handler = OpenAIRealtime() - api_base = "https://api.openai.com/v1" + api_base = ( + "https://gateway.example.com/tenant/openai/v1" + "?gateway_route=europe&gateway_route=backup" + "&model=gateway-model&intent=gateway-intent" + ) query_params: RealtimeQueryParams = { "model": "gpt-4o-realtime-preview-2024-10-01", "intent": "chat", } url = handler._construct_url(api_base=api_base, query_params=query_params) - # Both 'model' and other params should be included in the query string - assert url.startswith("wss://api.openai.com/v1/realtime?") - assert "model=gpt-4o-realtime-preview-2024-10-01" in url - assert "intent=chat" in url + parsed_url = httpx.URL(url) + assert parsed_url.path == "/tenant/openai/v1/realtime" + assert parsed_url.params.get_list("gateway_route") == ["europe", "backup"] + assert parsed_url.params["model"] == "gateway-model" + assert parsed_url.params["intent"] == "gateway-intent" def test_openai_realtime_handler_model_parameter_inclusion():