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 <krrishdholakia@gmail.com>
This commit is contained in:
Cesar Garcia 2026-01-19 15:37:41 -03:00 committed by GitHub
parent 1678f621db
commit 4ad5de10cb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 61 additions and 1 deletions

View file

@ -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,

View file

@ -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