From 4ad5de10cb68d9e19fef6069495dbea4c158a181 Mon Sep 17 00:00:00 2001 From: Cesar Garcia <128240629+Chesars@users.noreply.github.com> Date: Mon, 19 Jan 2026 15:37:41 -0300 Subject: [PATCH] fix(realtime): disable SSL for ws:// WebSocket connections (#19345) When using http:// api_base (converted to ws://), the websockets library throws "ssl argument is incompatible with a ws:// URI". Only pass SSL context for secure wss:// connections. Co-authored-by: Krish Dholakia --- litellm/llms/openai/realtime/handler.py | 4 +- .../realtime/test_openai_realtime_handler.py | 58 +++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/litellm/llms/openai/realtime/handler.py b/litellm/llms/openai/realtime/handler.py index 6ab43ab31e4..fd04ac4d458 100644 --- a/litellm/llms/openai/realtime/handler.py +++ b/litellm/llms/openai/realtime/handler.py @@ -56,7 +56,9 @@ class OpenAIRealtime(OpenAIChatCompletion): url = self._construct_url(api_base, query_params) try: - ssl_context = get_shared_realtime_ssl_context() + # Only use SSL context for secure websocket connections (wss://) + # websockets library doesn't accept ssl argument for ws:// URIs + ssl_context = None if url.startswith("ws://") else get_shared_realtime_ssl_context() # Log a masked request preview consistent with other endpoints. logging_obj.pre_call( input=None, 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 eb9f8027761..87923a8093b 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 @@ -265,3 +265,61 @@ 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_async_realtime_ws_url_has_no_ssl(): + """ + Test that when using http:// api_base (converted to ws://), the ssl argument + is set to None. The websockets library doesn't accept ssl argument for ws:// URIs. + + This verifies the fix for: https://github.com/BerriAI/litellm/issues/19222 + """ + from litellm.llms.openai.realtime.handler import OpenAIRealtime + from litellm.types.realtime import RealtimeQueryParams + + handler = OpenAIRealtime() + api_base = "http://localhost:8113" # Non-SSL local server + api_key = "test-key" + model = "test-model" + query_params: RealtimeQueryParams = {"model": model} + + dummy_websocket = AsyncMock() + dummy_logging_obj = MagicMock() + mock_backend_ws = AsyncMock() + + class DummyAsyncContextManager: + def __init__(self, value): + self.value = value + async def __aenter__(self): + return self.value + async def __aexit__(self, exc_type, exc, tb): + return None + + with patch("websockets.connect", return_value=DummyAsyncContextManager(mock_backend_ws)) as mock_ws_connect, \ + patch("litellm.llms.openai.realtime.handler.RealTimeStreaming") as mock_realtime_streaming: + + mock_streaming_instance = MagicMock() + mock_realtime_streaming.return_value = mock_streaming_instance + mock_streaming_instance.bidirectional_forward = AsyncMock() + + await handler.async_realtime( + model=model, + websocket=dummy_websocket, + logging_obj=dummy_logging_obj, + api_base=api_base, + api_key=api_key, + query_params=query_params, + ) + + # Verify websockets.connect was called + mock_ws_connect.assert_called_once() + called_url = mock_ws_connect.call_args[0][0] + called_kwargs = mock_ws_connect.call_args[1] + + # Verify URL was converted from http:// to ws:// + assert called_url.startswith("ws://localhost:8113/v1/realtime?") + assert f"model={model}" in called_url + + # Verify ssl is None for ws:// URLs (the fix for issue #19222) + assert called_kwargs["ssl"] is None