diff --git a/litellm/images/main.py b/litellm/images/main.py index 79bb07fc394..dd96eeb86dc 100644 --- a/litellm/images/main.py +++ b/litellm/images/main.py @@ -80,6 +80,9 @@ _PROVIDERS_RESOLVING_THEIR_OWN_IMAGE_BASE: Final = ( # Cache for ImageEditRequestUtils to avoid repeated __getattr__ calls _ImageEditRequestUtils_cache: type["ImageEditRequestUtils"] | None = None +# Kept out of all_litellm_params because that list is stripped before drop_params is applied. +_IMAGE_EDIT_CONTROL_KWARGS: Final = frozenset(("drop_params", "additional_drop_params")) + def _get_ImageEditRequestUtils() -> type["ImageEditRequestUtils"]: """Get ImageEditRequestUtils, loading it lazily if needed.""" @@ -886,7 +889,11 @@ def image_edit( "style", "async_call", ] - non_default_params: Final = filter_out_litellm_params(kwargs, excluding=openai_params) + non_default_params: Final = { + key: value + for key, value in filter_out_litellm_params(kwargs, excluding=openai_params).items() + if key not in _IMAGE_EDIT_CONTROL_KWARGS + } litellm_logging_obj: Final[LiteLLMLoggingObj] = kwargs.get("litellm_logging_obj") litellm_call_id: Final[str | None] = kwargs.get("litellm_call_id", None) model_info: Final = kwargs.get("model_info", None) diff --git a/tests/unit/images/test_image_edit_extra_params.py b/tests/unit/images/test_image_edit_extra_params.py index c3b0a5d2828..f94b72dfeda 100644 --- a/tests/unit/images/test_image_edit_extra_params.py +++ b/tests/unit/images/test_image_edit_extra_params.py @@ -141,6 +141,35 @@ def test_image_edit_forwards_scalar_array_as_repeated_fields(): assert b"style_a" in body and b"style_b" in body and b"style_c" in body +@pytest.mark.parametrize("drop_params", [True, False]) +def test_image_edit_strips_per_model_drop_params_from_multipart(drop_params: bool): + """Per-model drop_params / additional_drop_params are LiteLLM control + flags. If they survive into non_default_params they are flattened onto the + OpenAI images/edits multipart body and the provider rejects the call with + Unknown parameter: 'drop_params'. Issue #40153.""" + captured = {} + client = HTTPHandler(client=httpx.Client(transport=httpx.MockTransport(_capture_image_edit_request(captured)))) + + litellm.image_edit( + model="openai/gpt-image-1", + image=PNG_BYTES, + prompt="Make the background blue", + api_key="sk-test", + api_base="https://edit.example/v1", + client=client, + seed=42, + drop_params=drop_params, + additional_drop_params=["quality_level"], + ) + + fields = _multipart_text_fields(captured["content_type"], captured["body"]) + assert "drop_params" not in fields + assert "additional_drop_params" not in fields + assert fields["seed"] == "42" + assert fields["prompt"] == "Make the background blue" + assert fields["model"] == "gpt-image-1" + + @pytest.mark.asyncio async def test_aimage_edit_forwards_extra_body(): """aimage_edit used to drop extra_headers/extra_query/extra_body when