mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-26 01:12:21 +00:00
fix(chatgpt): limit image generation params
This commit is contained in:
parent
f3a65bd26b
commit
f97780a636
2 changed files with 50 additions and 108 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue