diff --git a/litellm/llms/azure/responses/transformation.py b/litellm/llms/azure/responses/transformation.py index cf9f970995c..169e0d5d79f 100644 --- a/litellm/llms/azure/responses/transformation.py +++ b/litellm/llms/azure/responses/transformation.py @@ -41,12 +41,10 @@ class AzureOpenAIResponsesAPIConfig(OpenAIResponsesAPIConfig): def _handle_reasoning_item(self, item: Dict[str, Any]) -> Dict[str, Any]: """ - Handle reasoning items specifically to filter out status=None using OpenAI's model. + Handle reasoning items to filter out the status field. Issue: https://github.com/BerriAI/litellm/issues/13484 - OpenAI API does not accept ReasoningItem(status=None), so we need to: - 1. Check if the item is a reasoning type - 2. Create a ResponseReasoningItem object with the item data - 3. Convert it back to dict with exclude_none=True to filter None values + + Azure OpenAI API does not accept 'status' field in reasoning input items. """ if item.get("type") == "reasoning": try: @@ -82,6 +80,30 @@ class AzureOpenAIResponsesAPIConfig(OpenAIResponsesAPIConfig): } return filtered_item return item + + def _validate_input_param( + self, input: Union[str, ResponseInputParam] + ) -> Union[str, ResponseInputParam]: + """ + Override parent method to also filter out 'status' field from message items. + Azure OpenAI API does not accept 'status' field in input messages. + """ + # First call parent's validation + validated_input = super()._validate_input_param(input) + + # Then filter out status from message items + if isinstance(validated_input, list): + filtered_input = [] + for item in validated_input: + if isinstance(item, dict) and item.get("type") == "message": + # Filter out status field from message items + filtered_item = {k: v for k, v in item.items() if k != "status"} + filtered_input.append(filtered_item) + else: + filtered_input.append(item) + return filtered_input # type: ignore + + return validated_input def transform_responses_api_request( self, diff --git a/tests/llm_responses_api_testing/test_azure_responses_api.py b/tests/llm_responses_api_testing/test_azure_responses_api.py index 7217b23db08..312fec15522 100644 --- a/tests/llm_responses_api_testing/test_azure_responses_api.py +++ b/tests/llm_responses_api_testing/test_azure_responses_api.py @@ -50,9 +50,11 @@ async def test_azure_responses_api_preview_api_version(): @pytest.mark.asyncio async def test_azure_responses_api_status_error(): """ - Ensure new azure preview api version is working + Test that 'status' field is not sent in the final request body to Azure API. + The status field should be filtered out from input messages before making the API call. """ - litellm._turn_on_debug() + from unittest.mock import AsyncMock, MagicMock + import json request_data = { "model": "computer-use-preview", @@ -88,11 +90,95 @@ async def test_azure_responses_api_status_error(): "stream": False, "tools": [], } - response = await litellm.aresponses( - model="azure/computer-use-preview", - truncation="auto", - api_version="preview", - api_base=os.getenv("AZURE_RESPONSES_OPENAI_ENDPOINT"), - api_key=os.getenv("AZURE_RESPONSES_OPENAI_API_KEY"), - input=request_data["input"], + + # Mock response + mock_response_data = { + "id": "resp_123", + "object": "response", + "created_at": 1234567890, + "model": "computer-use-preview", + "status": "completed", + "output": [ + { + "id": "msg_123", + "role": "assistant", + "type": "message", + "status": "completed", + "content": [{"type": "output_text", "text": "Here's an interesting fact."}], + } + ], + } + + captured_request_body = {} + + async def mock_post(*args, **kwargs): + # Capture the request body + nonlocal captured_request_body + if "json" in kwargs: + captured_request_body = kwargs["json"] + elif "data" in kwargs: + captured_request_body = json.loads(kwargs["data"]) + + import httpx + + # Create a proper httpx Response object + response_content = json.dumps(mock_response_data).encode("utf-8") + response = httpx.Response( + status_code=200, + headers={"content-type": "application/json"}, + content=response_content, + request=httpx.Request(method="POST", url="https://test.openai.azure.com"), + ) + return response + + from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler + from unittest.mock import patch + + with patch.object(AsyncHTTPHandler, "post", new=mock_post): + response = await litellm.aresponses( + model="azure/computer-use-preview", + truncation="auto", + api_version="preview", + api_base="https://test.openai.azure.com", + api_key="test-key", + input=request_data["input"], + ) + + # Verify that 'status' field is not present in any of the input messages + print("Final request body:", json.dumps(captured_request_body, indent=4, default=str)) + assert "input" in captured_request_body, "Request body should contain 'input' field" + + expected_input = [ + { + "content": "tell me an interesting fact", + "role": "user" + }, + { + "id": "rs_0ab687487834d9df0068e462a1b2d88197aabbc832c9ba5316", + "summary": [], + "type": "reasoning" + }, + { + "id": "msg_0ab687487834d9df0068e462a1df188197b74b1eef05102c18", + "content": [ + { + "annotations": [], + "text": "very good morning", + "type": "output_text", + "logprobs": [] + } + ], + "role": "assistant", + "type": "message" + }, + { + "role": "user", + "content": "tell me another" + } + ] + + assert captured_request_body["input"] == expected_input, ( + f"Request body input should match expected format without 'status' field.\n" + f"Expected: {json.dumps(expected_input, indent=2)}\n" + f"Got: {json.dumps(captured_request_body['input'], indent=2)}" )