diff --git a/litellm/llms/custom_httpx/llm_http_handler.py b/litellm/llms/custom_httpx/llm_http_handler.py index dd39c29203..3a290e7ce8 100644 --- a/litellm/llms/custom_httpx/llm_http_handler.py +++ b/litellm/llms/custom_httpx/llm_http_handler.py @@ -2009,6 +2009,9 @@ class BaseLLMHTTPHandler: headers=headers, ) + if extra_body: + data.update(extra_body) + ## LOGGING logging_obj.pre_call( input=input, @@ -2135,6 +2138,9 @@ class BaseLLMHTTPHandler: headers=headers, ) + if extra_body: + data.update(extra_body) + ## LOGGING logging_obj.pre_call( input=input, diff --git a/tests/llm_responses_api_testing/test_openai_responses_api.py b/tests/llm_responses_api_testing/test_openai_responses_api.py index f93720620b..173860a0aa 100644 --- a/tests/llm_responses_api_testing/test_openai_responses_api.py +++ b/tests/llm_responses_api_testing/test_openai_responses_api.py @@ -1676,3 +1676,133 @@ async def test_openai_streaming_logging(): await asyncio.sleep(2) assert tcl.validate_usage, "Usage should be validated" + + +# Tests for extra_body parameter passing +class MockResponse: + def __init__(self, json_data, status_code): + self._json_data = json_data + self.status_code = status_code + self.text = str(json_data) + self.headers = httpx.Headers({}) + + def json(self): + return self._json_data + + +@pytest.fixture +def extra_body_mock_response_data(): + return { + "id": "resp_test123", + "object": "response", + "created_at": 1234567890, + "status": "completed", + "model": "gpt-4o", + "output": [ + { + "type": "message", + "id": "msg_123", + "status": "completed", + "role": "assistant", + "content": [ + {"type": "output_text", "text": "Hello!", "annotations": []} + ], + } + ], + "usage": {"input_tokens": 10, "output_tokens": 5, "total_tokens": 15}, + "parallel_tool_calls": True, + "text": {"format": {"type": "text"}}, + "error": None, + "metadata": {}, + "temperature": 1.0, + "reasoning": {"effort": None, "summary": None}, + } + + +@pytest.mark.asyncio +async def test_aresponses_extra_body_params_passed(extra_body_mock_response_data): + """Test that extra_body parameters are passed in async mode.""" + with patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", + new_callable=AsyncMock, + ) as mock_post: + mock_post.return_value = MockResponse(extra_body_mock_response_data, 200) + + response = await litellm.aresponses( + model="gpt-4o", + input="Test input", + max_output_tokens=20, + extra_body={ + "custom_param_1": "value1", + "custom_param_2": {"nested": "value2"}, + "experimental_feature": True, + }, + ) + + assert response is not None + assert response.id is not None + + request_body = mock_post.call_args.kwargs["json"] + + assert "custom_param_1" in request_body + assert request_body["custom_param_1"] == "value1" + assert "custom_param_2" in request_body + assert request_body["custom_param_2"]["nested"] == "value2" + assert "experimental_feature" in request_body + assert request_body["experimental_feature"] is True + assert request_body["model"] == "gpt-4o" + assert request_body["input"] == "Test input" + + +def test_responses_extra_body_params_passed_sync(extra_body_mock_response_data): + """Test that extra_body parameters are passed in sync mode.""" + with patch( + "litellm.llms.custom_httpx.http_handler.HTTPHandler.post", + return_value=MockResponse(extra_body_mock_response_data, 200), + ) as mock_post: + response = litellm.responses( + model="gpt-4o", + input="Sync test", + max_output_tokens=20, + extra_body={ + "sync_custom_param": "sync_value", + "another_param": 42, + }, + ) + + assert response is not None + assert response.id is not None + + request_body = mock_post.call_args.kwargs["json"] + + assert "sync_custom_param" in request_body + assert request_body["sync_custom_param"] == "sync_value" + assert "another_param" in request_body + assert request_body["another_param"] == 42 + assert request_body["model"] == "gpt-4o" + + +@pytest.mark.asyncio +async def test_extra_body_merges_with_request_data(extra_body_mock_response_data): + """Test that extra_body is merged into the request data.""" + with patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", + new_callable=AsyncMock, + ) as mock_post: + mock_post.return_value = MockResponse(extra_body_mock_response_data, 200) + + await litellm.aresponses( + model="gpt-4o", + input="Test", + temperature=0.7, + max_output_tokens=20, + extra_body={ + "custom_field": "custom_value", + }, + ) + + request_body = mock_post.call_args.kwargs["json"] + + assert "temperature" in request_body + assert "custom_field" in request_body + assert request_body["custom_field"] == "custom_value"