feat(image_gen): config-only resolution control for nano-banana (default 4K) - #62
Conversation
…fault 4K) Add an output-resolution knob for the nano-banana Gemini image backend, config-only — no per-call parameter on the image_generate tool schema. image_gen.nano-banana.resolution (config.yaml) selects the size sent as image_config.image_size (1K/2K/4K, uppercase), defaulting to 4K. Verified against the live proxy that image_config.image_size is the field that changes output dimensions on the text-to-image path and composes with aspect_ratio (4K+16:9 -> 5504x3072, 4K+1:1 -> 4096x4096, 2K+16:9 -> 2752x1536, 1K+16:9 -> 1376x768). The proxy rejects image_config.resolution and ignores response_format.image_size, so only image_config.image_size is sent. Resolution precedence (first hit wins): NANO_BANANA_IMAGE_RESOLUTION env -> image_gen.nano-banana.resolution -> default 4K. Values are normalized to the uppercase ladder; an out-of-ladder value falls back to the default rather than 400ing the proxy. A per-model cap degrades gracefully (Lite = 1K clamps down and logs, instead of erroring), and if the proxy rejects image_size the request is retried once without it (current no-resolution behavior) so the generation still lands. An unrelated 400 (e.g. a safety block) is not masked by the fallback. Edits tend to preserve the source image's dimensions regardless, so this primarily affects text-to-image; documented in the backend docstring.
cwest
left a comment
There was a problem hiding this comment.
No changes needed.
The resolution knob is config-only and the core image_generate tool schema is untouched, which is the constraint that mattered here. The resolver reads image_gen.nano-banana.resolution with a sane precedence (env override, then config, then the 4K default), normalizes casing, and falls back to the default on an out-of-ladder value instead of forwarding something the proxy would reject. It sends image_config.image_size, which is the field that actually moves the pixels, and clamps to each model's documented ceiling so a lower-cap model degrades rather than erroring.
The degradation retry is scoped correctly: it only strips image_size and retries when the proxy's own error names that field, so an unrelated 400 (a safety block, say) still surfaces as an error instead of being swallowed by a blind retry. The config key nests under image_gen next to cache and is additive, so no schema version bump.
Tests are behavior contracts, not snapshots, and cover the whole surface: precedence, normalization, the cap clamp, payload shape, the success response carrying resolution, and both degradation branches. Ran the file locally (46 passed) plus the wider image_gen and config suites (345 passed, 0 failed). Checks are green and there are no open threads.
…fault 4K) (#62) Add an output-resolution knob for the nano-banana Gemini image backend, config-only — no per-call parameter on the image_generate tool schema. image_gen.nano-banana.resolution (config.yaml) selects the size sent as image_config.image_size (1K/2K/4K, uppercase), defaulting to 4K. Verified against the live proxy that image_config.image_size is the field that changes output dimensions on the text-to-image path and composes with aspect_ratio (4K+16:9 -> 5504x3072, 4K+1:1 -> 4096x4096, 2K+16:9 -> 2752x1536, 1K+16:9 -> 1376x768). The proxy rejects image_config.resolution and ignores response_format.image_size, so only image_config.image_size is sent. Resolution precedence (first hit wins): NANO_BANANA_IMAGE_RESOLUTION env -> image_gen.nano-banana.resolution -> default 4K. Values are normalized to the uppercase ladder; an out-of-ladder value falls back to the default rather than 400ing the proxy. A per-model cap degrades gracefully (Lite = 1K clamps down and logs, instead of erroring), and if the proxy rejects image_size the request is retried once without it (current no-resolution behavior) so the generation still lands. An unrelated 400 (e.g. a safety block) is not masked by the fallback. Edits tend to preserve the source image's dimensions regardless, so this primarily affects text-to-image; documented in the backend docstring. (cherry picked from commit f03cf1c)
What
Adds an output-resolution knob to the nano-banana Gemini image backend, config-only — no per-call parameter on the
image_generatetool schema.image_gen.nano-banana.resolutioninconfig.yamlselects the size sent to the proxy asimage_config.image_size(1K/2K/4K, uppercase K), defaulting to4K.Verified proxy contract
Probed the live proxy directly to pin the field (the research left this as an implement-time verification):
image_config.image_size∈{1K,2K,4K}changes the decoded PNG dimensions on the text-to-image path and composes withaspect_ratio.image_config.resolution→ HTTP 400 (Unknown name "resolution").response_format.image_size→ ignored (returns default geometry).So only
image_config.image_sizeis sent.E2E evidence (real generation, decoded PNGs, live proxy)
Behavior
NANO_BANANA_IMAGE_RESOLUTIONenv →image_gen.nano-banana.resolution→ default4K.k→ uppercase (proxy rejects lowercase); an out-of-ladder value falls back to the default rather than 400ing the proxy.image_size, the request is retried once without it (current no-resolution behavior) so the generation still lands. An unrelated 400 (e.g. a safety block) is not masked by the fallback.Scope guarantee
No change to the core
image_generatetool schema — its parameter list (prompt,aspect_ratio,image_url,reference_image_urls) is unchanged;tools/image_generation_tool.pyis untouched. Resolution is purely config-driven; every call uses the configured size.Tests
TDD (behavior contracts, no change-detector snapshots).
tests/plugins/image_gen/test_nano_banana_provider.pygrew 31 → 46 tests covering precedence, normalization, per-model cap clamp, payload shape, success-responseresolution, and both graceful-degradation branches.test_config+ drift/validation/set-value/clears-custom): green