From da3f58aeed75655b762d67b64e7ac2a2c3fcd849 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Tue, 23 Jun 2026 16:02:03 -0700 Subject: [PATCH] test(responses): guard Azure /responses native routing (LIT-2986) Azure OpenAI native /responses must dispatch to the native responses handler, never the chat-completions bridge. Routing it through the bridge historically collapsed streaming to delta-only events (dropping response.created, output_item.added, content_part.added and the matching .done events) and broke clients like Codex CLI that depend on the full response lifecycle. The existing bridge-flag tests all mock get_provider_responses_api_config, so none covered the real Azure resolution that was the original trigger (config is None -> bridge). These exercise the real ProviderConfigManager and assert azure/* (gpt and o-series) resolves a native responses config and dispatches to the native handler for both streaming and non-streaming, failing if Azure ever falls back to the bridge again. --- .../test_responses_api_bridge_flag.py | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/tests/test_litellm/responses/test_responses_api_bridge_flag.py b/tests/test_litellm/responses/test_responses_api_bridge_flag.py index 463af6562f1..4fce07056b4 100644 --- a/tests/test_litellm/responses/test_responses_api_bridge_flag.py +++ b/tests/test_litellm/responses/test_responses_api_bridge_flag.py @@ -10,12 +10,16 @@ import os import sys from unittest.mock import MagicMock, patch +import pytest + sys.path.insert( 0, os.path.abspath("../../..") ) # Adds the parent directory to the system path import litellm from litellm.types.llms.openai import ResponseAPIUsage, ResponsesAPIResponse +from litellm.types.utils import LlmProviders +from litellm.utils import ProviderConfigManager class TestUseResponsesApiBridgeFlag: @@ -280,3 +284,83 @@ class TestUseResponsesApiBridgeFlag: mock_native_handler.assert_called_once() assert result is not None + + +class TestAzureResponsesNativeRoutingRegression: + """Regression coverage for LIT-2986. + + Azure OpenAI native /responses must route to the native responses handler, + never the chat-completions bridge. When Azure was (incorrectly) sent through + the bridge, streaming collapsed to delta-only events and broke clients such as + Codex CLI that depend on the full lifecycle (response.created, output_item.added, + content_part.added, the matching .done events, response.completed). + + The trigger was the dispatch branch `responses_api_provider_config is None or + use_chat_completions_api is True`. Unlike the tests above, these exercise the + *real* provider-config resolution instead of mocking it, so they fail if Azure + ever stops resolving a native responses config (the original root cause). + """ + + @pytest.mark.parametrize( + "model, expected_config", + [ + ("gpt-4o", litellm.AzureOpenAIResponsesAPIConfig), + ("gpt-5", litellm.AzureOpenAIResponsesAPIConfig), + ("o4-mini", litellm.AzureOpenAIOSeriesResponsesAPIConfig), + ], + ) + def test_azure_resolves_native_responses_config(self, model, expected_config): + config = ProviderConfigManager.get_provider_responses_api_config( + model=model, provider=LlmProviders.AZURE + ) + assert config is not None + assert isinstance(config, expected_config) + + @patch( + "litellm.responses.main.litellm_completion_transformation_handler.response_api_handler" + ) + @patch("litellm.responses.main.base_llm_http_handler.response_api_handler") + def test_azure_responses_routes_native_not_bridge( + self, mock_native_handler, mock_bridge_handler + ): + mock_native_handler.return_value = MagicMock() + mock_bridge_handler.return_value = MagicMock() + + litellm.responses( + model="azure/gpt-4o", + input="Hello", + api_base="https://example.openai.azure.com", + api_key="test-key", + api_version="2025-04-01-preview", + litellm_logging_obj=MagicMock(), + ) + + mock_native_handler.assert_called_once() + mock_bridge_handler.assert_not_called() + assert ( + mock_native_handler.call_args.kwargs["responses_api_provider_config"] + is not None + ) + + @patch( + "litellm.responses.main.litellm_completion_transformation_handler.response_api_handler" + ) + @patch("litellm.responses.main.base_llm_http_handler.response_api_handler") + def test_azure_streaming_responses_routes_native_not_bridge( + self, mock_native_handler, mock_bridge_handler + ): + mock_native_handler.return_value = MagicMock() + mock_bridge_handler.return_value = MagicMock() + + litellm.responses( + model="azure/gpt-4o", + input="Hello", + stream=True, + api_base="https://example.openai.azure.com", + api_key="test-key", + api_version="2025-04-01-preview", + litellm_logging_obj=MagicMock(), + ) + + mock_native_handler.assert_called_once() + mock_bridge_handler.assert_not_called()