diff --git a/litellm/llms/chatgpt/image_generation/transformation.py b/litellm/llms/chatgpt/image_generation/transformation.py index 02f180b3759..bd78f36e997 100644 --- a/litellm/llms/chatgpt/image_generation/transformation.py +++ b/litellm/llms/chatgpt/image_generation/transformation.py @@ -40,10 +40,8 @@ GPT_IMAGE_2_MAX_PIXELS = 8_294_400 GPT_IMAGE_2_MAX_EDGE = 3840 GPT_IMAGE_2_MAX_RATIO = 3.0 -ALLOWED_BACKGROUNDS = {"transparent", "opaque", "auto"} -ALLOWED_MODERATION_VALUES = {"low", "auto"} ALLOWED_OUTPUT_FORMATS = {"png", "jpeg", "webp"} -ALLOWED_QUALITIES = {"low", "medium", "high", "auto"} +INTERNAL_OPTIONAL_PARAMS = {"chatgpt_responses_model"} if TYPE_CHECKING: from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj @@ -61,17 +59,8 @@ class ChatGPTImageGenerationConfig(BaseImageGenerationConfig): self, model: str ) -> List[OpenAIImageGenerationOptionalParams]: return [ - "background", - "moderation", - "n", - "output_compression", "output_format", - "partial_images", - "quality", - "response_format", "size", - "stream", - "user", ] def map_openai_params( @@ -178,19 +167,12 @@ class ChatGPTImageGenerationConfig(BaseImageGenerationConfig): image_tool = request["tools"][0] for key in ( - "background", "output_format", - "quality", "size", ): if optional_params.get(key) is not None: image_tool[key] = optional_params[key] - if optional_params.get("partial_images") is not None: - request["partial_images"] = optional_params["partial_images"] - if optional_params.get("user") is not None: - request["user"] = optional_params["user"] - return request def _validate_openai_image_generation_params( @@ -202,51 +184,22 @@ class ChatGPTImageGenerationConfig(BaseImageGenerationConfig): "(for example gpt-image-1.5 or gpt-image-2)." ) - if optional_params.get("response_format") == "url": + supported_params = set(self.get_supported_openai_params(model)) + unsupported_params = [ + key + for key in optional_params + if key not in supported_params and key not in INTERNAL_OPTIONAL_PARAMS + ] + if unsupported_params: raise ValueError( - "response_format='url' is not supported for GPT Image models. " - "GPT Image models always return base64-encoded images." + f"Parameters {unsupported_params} are not supported for model {model}. " + f"Supported parameters are {sorted(supported_params)}." ) - n = optional_params.get("n") - if n is not None and not (1 <= int(n) <= 10): - raise ValueError("n must be between 1 and 10") - if n is not None and int(n) > 1: - raise ValueError( - "n > 1 is not supported for ChatGPT image generation. " - "Call image_generation multiple times to generate multiple images." - ) - - quality = optional_params.get("quality") - if quality is not None and quality not in ALLOWED_QUALITIES: - raise ValueError("quality must be one of low, medium, high, or auto") - output_format = optional_params.get("output_format") if output_format is not None and output_format not in ALLOWED_OUTPUT_FORMATS: raise ValueError("output_format must be one of png, jpeg, or webp") - output_compression = optional_params.get("output_compression") - if output_compression is not None and not (0 <= int(output_compression) <= 100): - raise ValueError("output_compression must be between 0 and 100") - - background = optional_params.get("background") - if background is not None and background not in ALLOWED_BACKGROUNDS: - raise ValueError("background must be one of transparent, opaque, or auto") - if model.startswith(GPT_IMAGE_2_MODEL_PREFIX) and background == "transparent": - raise ValueError("transparent backgrounds are not supported in gpt-image-2") - if background == "transparent" and output_format not in (None, "png", "webp"): - raise ValueError( - "transparent background requires output_format png or webp" - ) - - moderation = optional_params.get("moderation") - if moderation is not None and moderation not in ALLOWED_MODERATION_VALUES: - raise ValueError("moderation must be one of low or auto") - - partial_images = optional_params.get("partial_images") - if partial_images is not None and not (0 <= int(partial_images) <= 3): - raise ValueError("partial_images must be between 0 and 3") - size = optional_params.get("size") if size is not None and model.startswith(GPT_IMAGE_2_MODEL_PREFIX): self._validate_gpt_image_2_size(size) @@ -324,10 +277,7 @@ class ChatGPTImageGenerationConfig(BaseImageGenerationConfig): if image_usage is not None: response.usage = image_usage response.size = optional_params.get("size") - response.quality = optional_params.get("quality") - response.output_format = optional_params.get( - "output_format", optional_params.get("response_format") - ) + response.output_format = optional_params.get("output_format") response._hidden_params["model"] = model return response diff --git a/tests/image_gen_tests/test_chatgpt_image_generation.py b/tests/image_gen_tests/test_chatgpt_image_generation.py index 495a62a3ea8..f980764c117 100644 --- a/tests/image_gen_tests/test_chatgpt_image_generation.py +++ b/tests/image_gen_tests/test_chatgpt_image_generation.py @@ -30,7 +30,7 @@ def test_chatgpt_image_generation_transforms_request(monkeypatch, tmp_path): request = config.transform_image_generation_request( model="gpt-image-2", prompt="draw a quiet harbor at sunrise", - optional_params={"size": "1024x1024", "quality": "high"}, + optional_params={"size": "1024x1024", "output_format": "png"}, litellm_params={"chatgpt_responses_model": "gpt-5.5"}, headers={}, ) @@ -54,7 +54,7 @@ def test_chatgpt_image_generation_transforms_request(monkeypatch, tmp_path): "type": "image_generation", "model": "gpt-image-2", "size": "1024x1024", - "quality": "high", + "output_format": "png", } ] assert request["tool_choice"] == {"type": "image_generation"} @@ -75,7 +75,7 @@ def test_chatgpt_image_generation_does_not_add_openai_defaults(monkeypatch, tmp_ assert request["tools"] == [{"type": "image_generation", "model": "gpt-image-2"}] -def test_chatgpt_image_generation_forwards_official_generate_params( +def test_chatgpt_image_generation_forwards_supported_generate_params( monkeypatch, tmp_path ): monkeypatch.setenv("CHATGPT_TOKEN_DIR", str(tmp_path)) @@ -85,17 +85,8 @@ def test_chatgpt_image_generation_forwards_official_generate_params( model="gpt-image-2", prompt="draw a quiet harbor at sunrise", optional_params={ - "background": "opaque", - "moderation": "low", - "n": 1, - "output_compression": 75, "output_format": "webp", - "partial_images": 2, - "quality": "medium", - "response_format": "b64_json", "size": "1536x1024", - "stream": True, - "user": "user-123", }, litellm_params={"chatgpt_responses_model": "gpt-5.5"}, headers={}, @@ -105,35 +96,16 @@ def test_chatgpt_image_generation_forwards_official_generate_params( { "type": "image_generation", "model": "gpt-image-2", - "background": "opaque", "output_format": "webp", - "quality": "medium", "size": "1536x1024", } ] - assert request["partial_images"] == 2 - assert request["user"] == "user-123" - assert "response_format" not in request["tools"][0] - assert "stream" not in request["tools"][0] - assert "user" not in request["tools"][0] @pytest.mark.parametrize( "optional_params, error", [ - ({"n": 0}, "n must be between 1 and 10"), - ({"n": 2}, "n > 1 is not supported for ChatGPT image generation"), - ({"quality": "hd"}, "quality must be one of low, medium, high, or auto"), ({"output_format": "jpg"}, "output_format must be one of png, jpeg, or webp"), - ({"output_compression": 101}, "output_compression must be between 0 and 100"), - ({"background": "transparent"}, "transparent backgrounds are not supported"), - ( - {"background": "transparent", "output_format": "jpeg"}, - "transparent backgrounds are not supported", - ), - ({"moderation": "strict"}, "moderation must be one of low or auto"), - ({"partial_images": 4}, "partial_images must be between 0 and 3"), - ({"response_format": "url"}, "response_format='url' is not supported"), ({"size": "1535x1024"}, "multiples of 16px"), ({"size": "4096x1024"}, "maximum edge length"), ({"size": "1024x256"}, "ratio must not exceed 3:1"), @@ -166,11 +138,41 @@ def test_chatgpt_image_generation_config_registered(monkeypatch, tmp_path): assert isinstance(config, ChatGPTImageGenerationConfig) +def test_chatgpt_image_generation_only_supports_prompt_output_format_and_size( + monkeypatch, tmp_path +): + monkeypatch.setenv("CHATGPT_TOKEN_DIR", str(tmp_path)) + config = ChatGPTImageGenerationConfig() + + assert config.get_supported_openai_params("gpt-image-2") == [ + "output_format", + "size", + ] + + +def test_chatgpt_image_generation_rejects_unsupported_optional_params( + monkeypatch, tmp_path +): + monkeypatch.setenv("CHATGPT_TOKEN_DIR", str(tmp_path)) + config = ChatGPTImageGenerationConfig() + + with pytest.raises( + ValueError, match="Parameters \\['quality'\\] are not supported" + ): + config.transform_image_generation_request( + model="gpt-image-2", + prompt="draw a cat", + optional_params={"quality": "high"}, + litellm_params={}, + headers={}, + ) + + def test_chatgpt_image_generation_maps_supported_openai_params(monkeypatch, tmp_path): monkeypatch.setenv("CHATGPT_TOKEN_DIR", str(tmp_path)) config = ChatGPTImageGenerationConfig() - optional_params = {"quality": "high"} + optional_params = {"output_format": "png"} result = config.map_openai_params( non_default_params={ "quality": "low", @@ -183,7 +185,7 @@ def test_chatgpt_image_generation_maps_supported_openai_params(monkeypatch, tmp_ ) assert result is optional_params - assert result == {"quality": "high", "size": "1024x1024"} + assert result == {"output_format": "png", "size": "1024x1024"} def test_chatgpt_image_generation_rejects_unsupported_openai_param( @@ -328,7 +330,7 @@ def test_chatgpt_image_generation_extracts_b64_from_sse_completed_response( model_response=ImageResponse(), logging_obj=mock_logging(), request_data={"input": "draw a cat"}, - optional_params={"size": "1024x1024", "quality": "high"}, + optional_params={"size": "1024x1024"}, litellm_params={}, encoding=None, ) @@ -336,7 +338,7 @@ def test_chatgpt_image_generation_extracts_b64_from_sse_completed_response( assert response.data is not None assert response.data[0].b64_json == "b64-image-data" assert response.size == "1024x1024" - assert response.quality == "high" + assert response.quality is None assert response.output_format is None assert response.usage is None assert response._hidden_params is not None @@ -379,16 +381,6 @@ def test_chatgpt_image_generation_uses_optional_responses_model_and_env( ("gpt-image-1.5", {"size": "auto"}, None), ("gpt-image-2", {"size": "auto"}, None), ("gpt-image-2", {"size": "bad-size"}, "size must be auto or WIDTHxHEIGHT"), - ( - "gpt-image-1.5", - {"background": "transparent", "output_format": "jpeg"}, - "transparent background requires output_format png or webp", - ), - ( - "gpt-image-1.5", - {"background": "not-real"}, - "background must be one of transparent, opaque, or auto", - ), ], ) def test_chatgpt_image_generation_validates_additional_param_paths( @@ -460,14 +452,14 @@ def test_chatgpt_image_generation_extracts_json_response(monkeypatch, tmp_path): model_response=ImageResponse(), logging_obj=mock_logging(), request_data={}, - optional_params={"response_format": "b64_json"}, + optional_params={"output_format": "png"}, litellm_params={}, encoding=None, ) assert response.data is not None assert [item.b64_json for item in response.data] == ["b64-image-data"] - assert response.output_format == "b64_json" + assert response.output_format == "png" assert response.usage is not None assert response.usage.input_tokens == 11 assert response.usage.input_tokens_details.image_tokens == 1