From 194b81d2adf8c4d8b74aa77c42b211f3101eab8d Mon Sep 17 00:00:00 2001 From: xodn348 Date: Wed, 20 May 2026 08:01:13 +0000 Subject: [PATCH] fix(images): cast n to int from multipart form data in image edit Multipart/form-data delivers all fields as strings, so n='1' instead of the integer 1. Azure's backend validates n with integer comparisons (e.g. 1 <= n <= 10) and raises: TypeError: '<=' not supported between instances of 'str' and 'int' which propagates as APIError: AzureException APIError - '<=' not supported between instances of 'str' and 'int'. Fix: coerce n from str to int (or drop it on invalid input) inside get_requested_image_edit_optional_param() before the params are forwarded to the provider. Fixes #27978 --- litellm/images/utils.py | 8 ++++ tests/image_gen_tests/test_image_edits.py | 47 +++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/litellm/images/utils.py b/litellm/images/utils.py index 8d3e96f1433..2df38f412ac 100644 --- a/litellm/images/utils.py +++ b/litellm/images/utils.py @@ -80,6 +80,14 @@ class ImageEditRequestUtils: filtered_params = { k: v for k, v in params.items() if k in valid_keys and v is not None } + # Multipart form data delivers all fields as strings; coerce n to int so + # Azure's backend validation (which does integer comparisons) doesn't fail + # with "TypeError: '<=' not supported between instances of 'str' and 'int'". + if "n" in filtered_params and isinstance(filtered_params["n"], str): + try: + filtered_params["n"] = int(filtered_params["n"]) + except (ValueError, TypeError): + filtered_params.pop("n", None) return cast(ImageEditOptionalRequestParams, filtered_params) @staticmethod diff --git a/tests/image_gen_tests/test_image_edits.py b/tests/image_gen_tests/test_image_edits.py index ca8ec3bbe32..862d635cc5f 100644 --- a/tests/image_gen_tests/test_image_edits.py +++ b/tests/image_gen_tests/test_image_edits.py @@ -773,3 +773,50 @@ async def test_image_edit_array_handling(): # Verify that both calls were made to the API assert mock_post.call_count == 2 + + +def test_image_edit_n_coercion_from_string(): + """ + Regression test for https://github.com/BerriAI/litellm/issues/27978. + + When the proxy receives a multipart/form-data request, all form fields + arrive as strings (e.g. n='1' instead of n=1). Azure's backend validates + n as an integer and raises: + TypeError: '<=' not supported between instances of 'str' and 'int' + unless litellm coerces the value before forwarding the request. + """ + from litellm.images.utils import ImageEditRequestUtils + + # Simulate what _read_request_body returns for a multipart form upload + params_from_form = { + "n": "1", # string, as delivered by multipart form data + "model": "azure/gpt-image-2", + "prompt": "make it ghibli", + "size": "1024x1024", + } + + result = ImageEditRequestUtils.get_requested_image_edit_optional_param( + params_from_form + ) + + assert result["n"] == 1, f"Expected n=1 (int), got n={result['n']!r}" + assert isinstance(result["n"], int), f"Expected int, got {type(result['n'])}" + + +def test_image_edit_n_coercion_invalid_string(): + """n cannot be parsed → it is dropped silently rather than crashing.""" + from litellm.images.utils import ImageEditRequestUtils + + params = {"n": "not_a_number", "model": "azure/gpt-image-2", "prompt": "test"} + result = ImageEditRequestUtils.get_requested_image_edit_optional_param(params) + assert "n" not in result, "Invalid n string should be dropped, not passed through" + + +def test_image_edit_n_already_int(): + """When n is already an int (direct SDK call), it passes through unchanged.""" + from litellm.images.utils import ImageEditRequestUtils + + params = {"n": 2, "model": "azure/gpt-image-2", "prompt": "test"} + result = ImageEditRequestUtils.get_requested_image_edit_optional_param(params) + assert result["n"] == 2 + assert isinstance(result["n"], int)