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.
This commit is contained in:
mateo-berri 2026-06-23 19:43:21 +00:00
parent da7509d2c5
commit 967fc0ff33
No known key found for this signature in database
2 changed files with 77 additions and 2 deletions

View file

@ -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(

View file

@ -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": []},