mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-11 03:38:38 +00:00
fix(openrouter): let aspect_ratio or resolution win over a pixel size on /images
On the chat path, an image_config from litellm_params replaced the one built
from size, so a caller that also sent size "1024x1024" still got the
configured aspect ratio or image size. /images answers a pixel size next to a
mismatched aspect_ratio or resolution with a 400 ('size "1024x1024" conflicts
with resolution "2K".', seen on 2026-10-03), so the same config started failing
When the body carries aspect_ratio or resolution, an explicit pixel size is now
left out. A tier size such as "2K" still goes with aspect_ratio
This commit is contained in:
parent
f545af5b7d
commit
89bae53233
2 changed files with 54 additions and 1 deletions
|
|
@ -16,6 +16,7 @@ Response shape:
|
|||
}
|
||||
"""
|
||||
|
||||
import re
|
||||
from types import MappingProxyType
|
||||
from typing import TYPE_CHECKING, Any, Final
|
||||
|
||||
|
|
@ -53,6 +54,8 @@ LEGACY_IMAGE_CONFIG_FIELDS: Final = MappingProxyType({"aspect_ratio": "aspect_ra
|
|||
NON_BODY_PARAMS: Final = frozenset(
|
||||
{"model", "prompt", "messages", "modalities", "stream", "image_config", "extra_headers"}
|
||||
)
|
||||
PIXEL_SIZE: Final = re.compile(r"\d+x\d+")
|
||||
SIZE_OVERRIDING_FIELDS: Final = frozenset({"aspect_ratio", "resolution"})
|
||||
|
||||
|
||||
class OpenRouterImageGenerationConfig(BaseImageGenerationConfig):
|
||||
|
|
@ -180,9 +183,12 @@ class OpenRouterImageGenerationConfig(BaseImageGenerationConfig):
|
|||
"""
|
||||
image_config is the request shape of the older chat-based path. Its fields map onto the
|
||||
/images names so existing configs keep working, and explicit top-level values win
|
||||
|
||||
A configured aspect_ratio or resolution wins over an OpenAI pixel size, the way image_config
|
||||
won over size on the chat path, because /images answers that pair with a 400
|
||||
"""
|
||||
legacy_image_config: Final = optional_params.get("image_config") or {}
|
||||
return {
|
||||
body: Final[dict[str, object]] = {
|
||||
"model": model,
|
||||
"prompt": prompt,
|
||||
**{
|
||||
|
|
@ -192,6 +198,10 @@ class OpenRouterImageGenerationConfig(BaseImageGenerationConfig):
|
|||
},
|
||||
**{key: value for key, value in optional_params.items() if key not in NON_BODY_PARAMS},
|
||||
}
|
||||
drop_pixel_size: Final = not SIZE_OVERRIDING_FIELDS.isdisjoint(body) and (
|
||||
PIXEL_SIZE.fullmatch(str(body.get("size", ""))) is not None
|
||||
)
|
||||
return {key: value for key, value in body.items() if not (drop_pixel_size and key == "size")}
|
||||
|
||||
def transform_image_generation_response(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -236,6 +236,33 @@ def test_transform_request_maps_legacy_image_config_and_explicit_fields_win(
|
|||
assert body == {"model": HYBRID_MODEL, "prompt": PROMPT, **expected_fields}
|
||||
|
||||
|
||||
# On 2026-10-03 POST https://openrouter.ai/api/v1/images returned 400 for size "1024x1024" with aspect_ratio
|
||||
# "3:2" (openai/gpt-image-1-mini) and with resolution "2K" (google/gemini-2.5-flash-image). The size field in
|
||||
# https://openrouter.ai/openapi.json says a tier size such as "2K" combines with aspect_ratio
|
||||
@pytest.mark.parametrize(
|
||||
("optional_params", "expected_fields"),
|
||||
[
|
||||
({"size": "1024x1024", "image_config": {"aspect_ratio": "16:9"}}, {"aspect_ratio": "16:9"}),
|
||||
({"size": "1024x1024", "resolution": "4K"}, {"resolution": "4K"}),
|
||||
({"size": "1024x1024", "aspect_ratio": "1:1"}, {"aspect_ratio": "1:1"}),
|
||||
({"size": "2K", "aspect_ratio": "16:9"}, {"size": "2K", "aspect_ratio": "16:9"}),
|
||||
({"size": "1024x1024"}, {"size": "1024x1024"}),
|
||||
],
|
||||
)
|
||||
def test_transform_request_lets_aspect_ratio_or_resolution_win_over_a_pixel_size(
|
||||
optional_params: dict[str, object], expected_fields: dict[str, object]
|
||||
):
|
||||
body = CONFIG.transform_image_generation_request(
|
||||
model=HYBRID_MODEL,
|
||||
prompt=PROMPT,
|
||||
optional_params=optional_params,
|
||||
litellm_params={},
|
||||
headers={},
|
||||
)
|
||||
|
||||
assert body == {"model": HYBRID_MODEL, "prompt": PROMPT, **expected_fields}
|
||||
|
||||
|
||||
def test_transform_response_returns_every_image_in_order():
|
||||
response = _transform_response(httpx.Response(200, json=_images_response("aW1hZ2Ux", "aW1hZ2Uy")))
|
||||
|
||||
|
|
@ -332,6 +359,22 @@ def test_hybrid_image_text_model_uses_the_same_images_endpoint():
|
|||
assert json.loads(request.content) == {"model": HYBRID_MODEL, "prompt": PROMPT}
|
||||
|
||||
|
||||
def test_legacy_image_config_with_an_openai_pixel_size_sends_only_the_aspect_ratio():
|
||||
recorder = RequestRecorder(_images_response("aW1hZ2Ux"))
|
||||
|
||||
litellm.image_generation(
|
||||
model=f"openrouter/{HYBRID_MODEL}",
|
||||
prompt=PROMPT,
|
||||
size="1024x1024",
|
||||
image_config={"aspect_ratio": "16:9"},
|
||||
api_key="sk-test",
|
||||
client=_client(recorder),
|
||||
)
|
||||
|
||||
(request,) = recorder.requests
|
||||
assert json.loads(request.content) == {"model": HYBRID_MODEL, "prompt": PROMPT, "aspect_ratio": "16:9"}
|
||||
|
||||
|
||||
def test_legacy_chat_completions_api_base_still_reaches_the_images_endpoint():
|
||||
recorder = RequestRecorder(_images_response("aW1hZ2Ux"))
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue