From 7dd09daa988223ee7b8046da761e8943b971ca5f Mon Sep 17 00:00:00 2001 From: joereyna Date: Fri, 10 Apr 2026 10:59:27 -0700 Subject: [PATCH] route OpenAI responses WebSocket through managed HTTP path --- .../llms/openai/responses/transformation.py | 11 ++++-- .../test_responses_websocket_all_providers.py | 34 ++++++++++++------- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/litellm/llms/openai/responses/transformation.py b/litellm/llms/openai/responses/transformation.py index cafb745862d..3631244495e 100644 --- a/litellm/llms/openai/responses/transformation.py +++ b/litellm/llms/openai/responses/transformation.py @@ -348,8 +348,15 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig): return False def supports_native_websocket(self) -> bool: - """OpenAI supports native WebSocket for Responses API""" - return True + """Use managed HTTP-streaming path for Responses API WebSocket mode. + + OpenAI's wss://api.openai.com/v1/responses endpoint exists but does not + reliably stream events back in response to response.create messages in all + deployment contexts. The ManagedResponsesWebSocketHandler makes a normal + HTTP streaming call to /v1/responses and forwards the events over the + WebSocket, which is proven to work. + """ + return False ######################################################### ########## DELETE RESPONSE API TRANSFORMATION ############## diff --git a/tests/test_litellm/responses/test_responses_websocket_all_providers.py b/tests/test_litellm/responses/test_responses_websocket_all_providers.py index efec841cc4d..1f98f43266d 100644 --- a/tests/test_litellm/responses/test_responses_websocket_all_providers.py +++ b/tests/test_litellm/responses/test_responses_websocket_all_providers.py @@ -38,19 +38,19 @@ from litellm.llms.xai.responses.transformation import XAIResponsesAPIConfig class TestResponsesAPIWebSocketSupport: """Test that all providers have websocket support configured correctly""" - def test_openai_supports_native_websocket(self): - """OpenAI should support native websocket""" + def test_openai_uses_managed_websocket(self): + """OpenAI uses managed WebSocket handler (HTTP streaming path)""" config = OpenAIResponsesAPIConfig() assert ( - config.supports_native_websocket() is True - ), "OpenAI should support native websocket" + config.supports_native_websocket() is False + ), "OpenAI should use managed websocket handler" - def test_azure_supports_native_websocket(self): - """Azure should support native websocket (inherits from OpenAI)""" + def test_azure_uses_managed_websocket(self): + """Azure uses managed WebSocket handler (inherits from OpenAI)""" config = AzureOpenAIResponsesAPIConfig() assert ( - config.supports_native_websocket() is True - ), "Azure should support native websocket" + config.supports_native_websocket() is False + ), "Azure should use managed websocket handler" def test_xai_uses_managed_websocket(self): """XAI should use managed websocket handler""" @@ -1000,7 +1000,9 @@ class TestNativeWebSocketUrlConstruction: mock_config = MagicMock(spec=OpenAIResponsesAPIConfig) mock_config.supports_native_websocket.return_value = True - mock_config.get_complete_url.return_value = "https://api.openai.com/v1/responses" + mock_config.get_complete_url.return_value = ( + "https://api.openai.com/v1/responses" + ) mock_config.validate_environment.return_value = {} mock_logging = MagicMock() @@ -1024,8 +1026,11 @@ class TestNativeWebSocketUrlConstruction: assert len(captured_urls) == 1 from urllib.parse import parse_qs, urlparse + qs = parse_qs(urlparse(captured_urls[0]).query) - assert qs.get("model") == ["gpt-4o-mini"], f"Expected model in URL, got: {captured_urls[0]}" + assert qs.get("model") == [ + "gpt-4o-mini" + ], f"Expected model in URL, got: {captured_urls[0]}" @pytest.mark.asyncio async def test_ws_url_preserves_existing_params_and_adds_model(self): @@ -1071,6 +1076,11 @@ class TestNativeWebSocketUrlConstruction: assert len(captured_urls) == 1 from urllib.parse import parse_qs, urlparse + qs = parse_qs(urlparse(captured_urls[0]).query) - assert qs.get("model") == ["gpt-4o"], f"model missing from URL: {captured_urls[0]}" - assert qs.get("api-version") == ["2024-05-01"], f"existing param lost: {captured_urls[0]}" + assert qs.get("model") == [ + "gpt-4o" + ], f"model missing from URL: {captured_urls[0]}" + assert qs.get("api-version") == [ + "2024-05-01" + ], f"existing param lost: {captured_urls[0]}"