From 08fe9f7b3ae895c87044d1b3c10ca35f0d58fbdf Mon Sep 17 00:00:00 2001 From: arcdrake22 Date: Sun, 2 Aug 2026 22:25:59 +0200 Subject: [PATCH] fix(image): send Gemini Flash hints via generationConfig.imageConfig The live v1beta API rejects the legacy responseFormat.image block (enum-based aspectRatio/imageSize fields) for gemini-3.1-flash-lite-image with INVALID_ARGUMENT, even for documented plain-string values. Gemini Flash image models accept plain-string hints under generationConfig.imageConfig instead (e.g. aspectRatio 16:9, imageSize 1K), which the API accepts. Switch the flash path to imageConfig and update the provider tests accordingly. Other providers (aihubmix, ollama, imagen) are untouched. --- nanobot/providers/image_generation.py | 17 ++++++++++++----- tests/providers/test_image_generation.py | 16 ++++++++-------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/nanobot/providers/image_generation.py b/nanobot/providers/image_generation.py index 2a28a0fc3..996e63b9b 100644 --- a/nanobot/providers/image_generation.py +++ b/nanobot/providers/image_generation.py @@ -808,7 +808,12 @@ class GeminiImageGenerationClient(ImageGenerationProvider): generation_config: dict[str, Any] = {"responseModalities": ["TEXT", "IMAGE"]} image_config = _gemini_flash_image_config(model, aspect_ratio, image_size) if image_config: - generation_config["responseFormat"] = {"image": image_config} + # Gemini Flash image models accept plain-string values under + # ``generationConfig.imageConfig``. The legacy + # ``responseFormat.image`` block is rejected with INVALID_ARGUMENT + # by gemini-3.1-flash-lite-image (enum-based fields), so it is not + # used here. + generation_config["imageConfig"] = image_config body: dict[str, Any] = { "contents": [{"role": "user", "parts": parts}], @@ -864,11 +869,13 @@ def _gemini_flash_image_config( aspect_ratio: str | None, image_size: str | None, ) -> dict[str, str]: - """Build the ``responseFormat.image`` config for Gemini Flash image models. + """Build the ``generationConfig.imageConfig`` config for Gemini Flash image models. - Capabilities are model-specific: Gemini 3.1 Flash variants support four - additional extreme ratios, while configurable image sizes are limited to - the documented Gemini 3 image model families. + Values are the documented plain strings (e.g. ``16:9``, ``1K``) that the + live v1beta API accepts under ``imageConfig``. Capabilities are + model-specific: Gemini 3.1 Flash variants support four additional extreme + ratios, while configurable image sizes are limited to the documented + Gemini 3 image model families. """ config: dict[str, str] = {} if aspect_ratio and aspect_ratio in _gemini_flash_supported_aspect_ratios(model): diff --git a/tests/providers/test_image_generation.py b/tests/providers/test_image_generation.py index 8181ab1d5..a116f4dd8 100644 --- a/tests/providers/test_image_generation.py +++ b/tests/providers/test_image_generation.py @@ -464,7 +464,7 @@ async def test_gemini_flash_forwards_aspect_ratio_and_image_size() -> None: image_size="2K", ) - image_config = fake.calls[0]["json"]["generationConfig"]["responseFormat"]["image"] + image_config = fake.calls[0]["json"]["generationConfig"]["imageConfig"] assert image_config == {"aspectRatio": "16:9", "imageSize": "2K"} @@ -480,7 +480,7 @@ async def test_gemini_flash_2_5_drops_image_size() -> None: image_size="1K", ) - image_config = fake.calls[0]["json"]["generationConfig"]["responseFormat"]["image"] + image_config = fake.calls[0]["json"]["generationConfig"]["imageConfig"] assert image_config == {"aspectRatio": "4:3"} @@ -496,7 +496,7 @@ async def test_gemini_flash_2_0_drops_image_size() -> None: image_size="1K", ) - image_config = fake.calls[0]["json"]["generationConfig"]["responseFormat"]["image"] + image_config = fake.calls[0]["json"]["generationConfig"]["imageConfig"] assert image_config == {"aspectRatio": "16:9"} @@ -524,8 +524,8 @@ async def test_gemini_flash_scopes_extreme_aspect_ratios_by_model( aspect_ratio=aspect_ratio, ) - response_format = fake.calls[0]["json"]["generationConfig"].get("responseFormat") - assert response_format == ({"image": expected} if expected else None) + image_config = fake.calls[0]["json"]["generationConfig"].get("imageConfig") + assert image_config == expected @pytest.mark.parametrize( @@ -553,8 +553,8 @@ async def test_gemini_flash_scopes_image_size_by_model( image_size=image_size, ) - response_format = fake.calls[0]["json"]["generationConfig"].get("responseFormat") - assert response_format == ({"image": expected} if expected else None) + image_config = fake.calls[0]["json"]["generationConfig"].get("imageConfig") + assert image_config == expected @pytest.mark.asyncio @@ -571,7 +571,7 @@ async def test_gemini_flash_ignores_unsupported_hints() -> None: image_size="1024x1024", ) - assert "responseFormat" not in fake.calls[0]["json"]["generationConfig"] + assert "imageConfig" not in fake.calls[0]["json"]["generationConfig"] @pytest.mark.asyncio