From ef445cc2466e060de3cafc30ad922cebd64f502f Mon Sep 17 00:00:00 2001 From: stupidloud Date: Thu, 2 Jul 2026 23:23:39 +0800 Subject: [PATCH] fix(image): narrow Gemini Flash aspect-ratio and image-size scoping Address review feedback that the capability checks were broader than the documented per-model matrix: - Drop the extreme aspect ratios (1:4, 4:1, 1:8, 8:1) from the Flash allow-list. They are only documented for 3.1 Flash / Flash Lite, so the global set could send an unsupported ratio to 2.5 Flash Image or 3.1 Pro Image. Keep the ratios common to every Flash image model. - Identify imageSize support positively via "gemini-3" instead of excluding "2.5". The old predicate also matched gemini-2.0-flash-preview-image- generation, which (with the default 1K size) altered that model's request shape even though only Gemini 3+ image models accept a configurable size. Add tests for the gemini-2.0 image-size drop and the extreme-ratio drop. Co-Authored-By: Claude Opus 4.8 (1M context) --- nanobot/providers/image_generation.py | 20 ++++++++++++++------ tests/providers/test_image_generation.py | 22 ++++++++++++++++++++-- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/nanobot/providers/image_generation.py b/nanobot/providers/image_generation.py index 0d27f04e..f9d76e28 100644 --- a/nanobot/providers/image_generation.py +++ b/nanobot/providers/image_generation.py @@ -33,12 +33,15 @@ _AIHUBMIX_ASPECT_RATIO_SIZES = { } _GEMINI_DEFAULT_TIMEOUT_S = 120.0 _GEMINI_IMAGEN_ASPECT_RATIOS = {"1:1", "9:16", "16:9", "3:4", "4:3"} -# Aspect ratios accepted by the Gemini Flash image (generateContent) models. +# Aspect ratios documented for every Gemini Flash image (generateContent) model. +# The extreme ratios (1:4, 4:1, 1:8, 8:1) are only listed for the 3.1 Flash / +# Flash Lite tables, so they are left out to avoid sending an unsupported value +# to 2.5 Flash Image or 3.1 Pro Image. _GEMINI_FLASH_ASPECT_RATIOS = { - "1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", - "9:16", "16:9", "21:9", "1:4", "4:1", "1:8", "8:1", + "1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9", } -# Image-size tokens accepted by Gemini 3+ image models (2.5 Flash Image ignores it). +# Image-size tokens accepted by Gemini 3+ image models (earlier Flash image +# models expose only a single fixed resolution). _GEMINI_FLASH_IMAGE_SIZES = {"512", "1K", "2K", "4K"} _OLLAMA_DEFAULT_SIDE = 1024 _OLLAMA_SIZE_PRESETS = { @@ -787,8 +790,13 @@ def _gemini_flash_image_config( def _gemini_flash_supports_image_size(model: str) -> bool: - """Return whether the model honors ``imageSize`` (Gemini 3+ image models).""" - return "2.5" not in model.lower() + """Return whether the model honors ``imageSize``. + + Only Gemini 3+ image models expose a configurable image size; earlier Flash + image models (2.0, 2.5) generate at a single fixed resolution, so ``imageSize`` + is identified positively rather than by excluding a single version. + """ + return "gemini-3" in model.lower() async def _aihubmix_images_from_payload( diff --git a/tests/providers/test_image_generation.py b/tests/providers/test_image_generation.py index 8e311049..19f5eb4d 100644 --- a/tests/providers/test_image_generation.py +++ b/tests/providers/test_image_generation.py @@ -465,14 +465,32 @@ async def test_gemini_flash_2_5_drops_image_size() -> None: @pytest.mark.asyncio -async def test_gemini_flash_ignores_unsupported_hints() -> None: +async def test_gemini_flash_2_0_drops_image_size() -> None: fake = FakeClient(_gemini_flash_image_response()) client = GeminiImageGenerationClient(api_key="AIza-test", client=fake) # type: ignore[arg-type] + await client.generate( + prompt="draw a cat", + model="gemini-2.0-flash-preview-image-generation", + aspect_ratio="16:9", + image_size="1K", + ) + + image_config = fake.calls[0]["json"]["generationConfig"]["responseFormat"]["image"] + assert image_config == {"aspectRatio": "16:9"} + + +@pytest.mark.asyncio +async def test_gemini_flash_ignores_unsupported_hints() -> None: + fake = FakeClient(_gemini_flash_image_response()) + client = GeminiImageGenerationClient(api_key="AIza-test", client=fake) # type: ignore[arg-type] + + # 7:5 is not a documented ratio; 1:8 is only valid for 3.1 Flash, not Pro; + # 1024x1024 is not a valid Gemini image-size token. All are dropped. await client.generate( prompt="draw a cat", model="gemini-3-pro-image", - aspect_ratio="7:5", + aspect_ratio="1:8", image_size="1024x1024", )