From 967fc0ff33e5916fc60754ea74eb34b127f963c4 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Tue, 23 Jun 2026 19:43:21 +0000 Subject: [PATCH] test(handler): cover responses-path model override; scope fix to body-model surfaces Add sync and async response_api_handler regression tests proving extra_body cannot override the model on the responses surface, mirroring the existing handler test harness. Both fail on the prior data.update behavior. Revert the two generate_content call sites back to their original merge. Google GenAI pins the model in the request URL (models/{model}:generateContent), so extra_body cannot redirect which model runs there and the guard was unnecessary. The fix now applies only to the chat completion and responses API paths, where the model lives in the request body. --- litellm/llms/custom_httpx/llm_http_handler.py | 4 +- .../custom_httpx/test_llm_http_handler.py | 75 +++++++++++++++++++ 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/litellm/llms/custom_httpx/llm_http_handler.py b/litellm/llms/custom_httpx/llm_http_handler.py index a21d2d81284..c2df5613e8e 100644 --- a/litellm/llms/custom_httpx/llm_http_handler.py +++ b/litellm/llms/custom_httpx/llm_http_handler.py @@ -11199,7 +11199,7 @@ class BaseLLMHTTPHandler: ) if extra_body: - data = _merge_extra_body_preserving_model(data, extra_body) + data.update(extra_body) ## LOGGING logging_obj.pre_call( @@ -11314,7 +11314,7 @@ class BaseLLMHTTPHandler: ) if extra_body: - data = _merge_extra_body_preserving_model(data, extra_body) + data.update(extra_body) ## LOGGING logging_obj.pre_call( diff --git a/tests/test_litellm/llms/custom_httpx/test_llm_http_handler.py b/tests/test_litellm/llms/custom_httpx/test_llm_http_handler.py index 64cc5860958..d88e0b173c1 100644 --- a/tests/test_litellm/llms/custom_httpx/test_llm_http_handler.py +++ b/tests/test_litellm/llms/custom_httpx/test_llm_http_handler.py @@ -272,6 +272,81 @@ async def test_async_response_api_handler_streams_when_provider_transform_adds_s assert client.post.call_args.kwargs["json"]["stream"] is True +def test_response_api_handler_extra_body_cannot_override_model(): + handler = BaseLLMHTTPHandler() + config = Mock() + config.validate_environment.return_value = {} + config.get_complete_url.return_value = "https://chatgpt.example.com/responses" + config.transform_responses_api_request.return_value = { + "model": "gpt-5.3-codex", + "input": "hi", + "stream": True, + } + config.sign_request.return_value = ({}, None) + client = HTTPHandler(client=httpx.Client()) + client.post = Mock( + return_value=httpx.Response( + 200, + request=httpx.Request("POST", "https://chatgpt.example.com/responses"), + ) + ) + logging_obj = Mock() + + handler.response_api_handler( + model="gpt-5.3-codex", + input="hi", + responses_api_provider_config=config, + response_api_optional_request_params={}, + custom_llm_provider="chatgpt", + litellm_params=GenericLiteLLMParams(), + logging_obj=logging_obj, + extra_body={"model": "attacker-model", "passthrough": "kept"}, + client=client, + ) + + sent_body = client.post.call_args.kwargs["json"] + assert sent_body["model"] == "gpt-5.3-codex" + assert sent_body["passthrough"] == "kept" + + +@pytest.mark.asyncio +async def test_async_response_api_handler_extra_body_cannot_override_model(): + handler = BaseLLMHTTPHandler() + config = Mock() + config.validate_environment.return_value = {} + config.get_complete_url.return_value = "https://chatgpt.example.com/responses" + config.transform_responses_api_request.return_value = { + "model": "gpt-5.3-codex", + "input": "hi", + "stream": True, + } + config.sign_request.return_value = ({}, None) + client = AsyncHTTPHandler() + client.post = AsyncMock( + return_value=httpx.Response( + 200, + request=httpx.Request("POST", "https://chatgpt.example.com/responses"), + ) + ) + logging_obj = Mock() + + await handler.async_response_api_handler( + model="gpt-5.3-codex", + input="hi", + responses_api_provider_config=config, + response_api_optional_request_params={}, + custom_llm_provider="chatgpt", + litellm_params=GenericLiteLLMParams(), + logging_obj=logging_obj, + extra_body={"model": "attacker-model", "passthrough": "kept"}, + client=client, + ) + + sent_body = client.post.call_args.kwargs["json"] + assert sent_body["model"] == "gpt-5.3-codex" + assert sent_body["passthrough"] == "kept" + + def test_merge_extra_body_preserving_model_blocks_model_override(): merged = _merge_extra_body_preserving_model( {"model": "authorized", "messages": []},