From f1805092637a044481b5feaaf6866e100a26cd45 Mon Sep 17 00:00:00 2001 From: silveroxides Date: Tue, 1 Sep 2026 09:34:26 +0200 Subject: [PATCH] fix(image): correct Gemini image requests --- .gitignore | 3 +- AGENTS.md | 34 +++++++++++++++++ gemini_nodes.py | 17 ++++++--- tests/test_gemini_nodes.py | 77 +++++++++++++++++++++++++++++++++++++- 4 files changed, 123 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 049104d..ccdcea2 100644 --- a/.gitignore +++ b/.gitignore @@ -190,4 +190,5 @@ cython_debug/ .ruff_cache/ # PyPI configuration file -.pypirc \ No newline at end of file +.pypirc +AGENTS-LOCAL.md diff --git a/AGENTS.md b/AGENTS.md index 29a5243..d1ad374 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,5 +1,8 @@ # Agent Instructions +Read `AGENTS-LOCAL.md` when present. It contains repository-machine-specific +operating rules and must remain local only; do not stage or commit it. + Use `$comfyui-custom-node-development` for all development, testing, review, and packaging work in this repository. If the skill is unavailable, stop and tell the user that it must be installed before continuing. @@ -16,6 +19,37 @@ python_executable: C:\Users\ishim\Tools\ComfyUI\.venv\Scripts\python.exe ## Repository-Specific Instructions +### Evidence and claim discipline + +- Do not present assumptions, plausible explanations, remembered behavior, or + pattern-based guesses as established facts. +- Before making a causal, behavioral, compatibility, or architectural claim, + verify it against direct evidence covering the relevant path. Inspect the + actual source, configuration, inputs, consumers, connections, and runtime + assembly needed to support the claim. +- Distinguish verified facts, supported inferences, and unknowns explicitly. + When required evidence is unavailable, state that the conclusion cannot yet + be determined instead of filling the gap with a likely explanation. +- Do not infer an end-to-end result from an isolated helper, preset, schema, + unit test, string-construction test, or passing test suite. Verify the actual + integration and consumer behavior before generalizing beyond what was tested. +- Do not invent workflow topology, data flow, precedence, model behavior, or + user intent. Inspect the relevant artifact or ask for the specific missing + evidence when it cannot be discovered locally. +- Do not generalize a model or prompt protocol from a shared tokenizer, helper, + socket type, or similarly named node. Verify the target model, node contract, + template assembly, and consuming path. A node-specific prefix or label does + not establish generic encoder behavior. +- When correcting an unsupported claim, identify exactly which parts were + verified and which parts were assumed. Do not replace one unsupported + explanation with another. +- Accept failure and request guidance or advice on the approach when continued + attempts are unlikely to work and are being made only to force the task toward + apparent completion; do not continue speculative trial-and-error to avoid + acknowledging that the current approach has failed. + +### Test selection + - Use `tests/run_tests.py` for all Python test selection. - During iteration, run an exact test or one explicit group. - Before handoff, run `tests/run_tests.py --changed` once for accumulated changes. diff --git a/gemini_nodes.py b/gemini_nodes.py index 9cac497..bc57844 100644 --- a/gemini_nodes.py +++ b/gemini_nodes.py @@ -202,9 +202,9 @@ class SSL_GeminiTextPrompt(IO.ComfyNode): "gemini-3-flash-preview", "gemini-3.5-flash", "gemini-3.5-flash-lite", "gemini-3.6-flash", GEMINI_3_7_FLASH, GEMINI_4_FLASH_PREVIEW ] IMAGE_MODELS = [ - "gemini-2.5-flash-image-preview", "gemini-2.5-flash-image", + "gemini-2.5-flash-image", "gemini-3.1-flash-image", "gemini-3.1-flash-lite-image", - "gemini-3-pro-image-preview", "gemini-3-pro-image", "nano-banana-pro-preview", + "gemini-3-pro-image-preview", "gemini-3-pro-image", ] MEDIA_RES_MODELS = [ "gemini-3.1-flash-lite", "gemini-3-flash-preview", "gemini-3.1-pro-preview", @@ -227,7 +227,7 @@ class SSL_GeminiTextPrompt(IO.ComfyNode): cls.GemConfig.Input("config"), IO.String.Input("prompt", multiline=True), IO.String.Input("system_instruction", default="You are a helpful AI assistant.", multiline=True), - IO.Combo.Input("model", options=["gemini-1.5-pro-002", "gemini-2.0-flash", "gemini-2.0-flash-lite", "gemini-2.5-flash-preview-04-17", "gemini-2.5-pro-exp-03-25", "gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.5-flash-lite", "gemini-3-flash-preview", "gemini-3.1-flash-lite", "gemini-3.1-pro-preview", "gemini-3.5-flash", "gemini-3.5-flash-lite", "gemini-3.6-flash", cls.GEMINI_3_7_FLASH, "gemini-2.5-flash-image-preview", "gemini-3.1-flash-image", "gemini-3.1-flash-lite-image", "gemini-3-pro-image", "nano-banana-pro-preview", "gemini-pro-latest", "gemini-flash-latest", "gemini-flash-lite-latest"], default="gemini-2.5-flash"), + IO.Combo.Input("model", options=["gemini-1.5-pro-002", "gemini-2.0-flash", "gemini-2.0-flash-lite", "gemini-2.5-flash-preview-04-17", "gemini-2.5-pro-exp-03-25", "gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.5-flash-lite", "gemini-3-flash-preview", "gemini-3.1-flash-lite", "gemini-3.1-pro-preview", "gemini-3.5-flash", "gemini-3.5-flash-lite", "gemini-3.6-flash", cls.GEMINI_3_7_FLASH, "gemini-3.1-flash-image", "gemini-3.1-flash-lite-image", "gemini-3-pro-image", "gemini-pro-latest", "gemini-flash-latest", "gemini-flash-lite-latest"], default="gemini-2.5-flash"), IO.Float.Input("temperature", default=1.0, min=0.0, max=1.0, step=0.01), IO.Float.Input("top_p", default=0.95, min=0.0, max=1.0, step=0.01), IO.Int.Input("top_k", default=40, min=1, max=100, step=1), @@ -697,7 +697,8 @@ class SSL_GeminiTextPrompt(IO.ComfyNode): @classmethod def _build_generate_content_config(cls, model, temperature, top_p, top_k, max_output_tokens, seed, include_images, response_modalities, aspect_ratio, padded_system_instruction, - thinking_level, thinking_budget, include_thoughts, media_resolution): + thinking_level, thinking_budget, include_thoughts, media_resolution, + allow_all_people=False): # Centralized builder for GenerateContentConfig used by different model/feature branches safety = [ types.SafetySetting(category="HARM_CATEGORY_HARASSMENT", threshold="BLOCK_NONE"), @@ -709,6 +710,11 @@ class SSL_GeminiTextPrompt(IO.ComfyNode): # Modified: Only trigger image config if include_images is True AND the model is actually an image model if include_images and model in cls.IMAGE_MODELS: + image_config = {} + if aspect_ratio not in (None, "None"): + image_config["aspect_ratio"] = aspect_ratio + if allow_all_people: + image_config["person_generation"] = "ALLOW_ALL" return types.GenerateContentConfig( temperature=temperature, top_p=top_p, @@ -717,7 +723,7 @@ class SSL_GeminiTextPrompt(IO.ComfyNode): max_output_tokens=max_output_tokens, safety_settings=safety, response_modalities=response_modalities, - image_config=types.ImageConfig(aspect_ratio=aspect_ratio), + image_config=types.ImageConfig(**image_config) if image_config else None, system_instruction=[types.Part.from_text(text=padded_system_instruction)], ) @@ -1071,6 +1077,7 @@ class SSL_GeminiTextPrompt(IO.ComfyNode): thinking_budget=thinking_budget, include_thoughts=include_thoughts, media_resolution=media_resolution, + allow_all_people=bool(config.get("use_vertexai_env", False) or config.get("vertexai_express", False)), ) if use_seed and actual_seed is not None: diff --git a/tests/test_gemini_nodes.py b/tests/test_gemini_nodes.py index ab21eed..1c94862 100644 --- a/tests/test_gemini_nodes.py +++ b/tests/test_gemini_nodes.py @@ -883,9 +883,11 @@ def test_proxy_uses_sdk_http_options_without_mutating_environment(monkeypatch): assert gemini_nodes.os.environ["HTTP_PROXY"] == "http://existing-proxy:9000" -def test_confirmed_current_models_are_visible_without_removing_legacy_ids(): +def test_confirmed_current_models_are_visible_without_deprecated_image_aliases(): schema = gemini_nodes.SSL_GeminiTextPrompt.define_schema() model_input = next(input_ for input_ in schema.inputs if input_.id == "model") + input_ids = [input_.id for input_ in schema.inputs] + aspect_ratio = next(input_ for input_ in schema.inputs if input_.id == "aspect_ratio") for model in ( "gemini-3.7-flash", @@ -893,10 +895,81 @@ def test_confirmed_current_models_are_visible_without_removing_legacy_ids(): "gemini-3.1-flash-image", "gemini-3.1-flash-lite-image", "gemini-3-pro-image", - "gemini-2.5-flash-image-preview", ): assert model in model_input.options + assert "gemini-2.5-flash-image-preview" not in model_input.options + assert "nano-banana-pro-preview" not in model_input.options + assert input_ids[input_ids.index("include_images") + 1] == "aspect_ratio" + assert aspect_ratio.options == ["None", "1:1", "9:16", "16:9", "3:4", "4:3", "3:2", "2:3", "5:4", "4:5", "21:9"] + assert aspect_ratio.default == "None" + + +def test_image_config_omits_none_aspect_ratio(): + config = gemini_nodes.SSL_GeminiTextPrompt._build_generate_content_config( + model="gemini-3.1-flash-image", + temperature=1.0, + top_p=0.95, + top_k=40, + max_output_tokens=8192, + seed=7, + include_images=True, + response_modalities=["TEXT", "IMAGE"], + aspect_ratio="None", + padded_system_instruction="system", + thinking_level="None", + thinking_budget=0, + include_thoughts=False, + media_resolution="unspecified", + ) + + assert config.response_modalities == ["TEXT", "IMAGE"] + assert config.image_config is None + + +def test_image_config_preserves_concrete_aspect_ratio(): + config = gemini_nodes.SSL_GeminiTextPrompt._build_generate_content_config( + model="gemini-3-pro-image", + temperature=1.0, + top_p=0.95, + top_k=40, + max_output_tokens=8192, + seed=7, + include_images=True, + response_modalities=["IMAGE", "TEXT"], + aspect_ratio="16:9", + padded_system_instruction="system", + thinking_level="None", + thinking_budget=0, + include_thoughts=False, + media_resolution="unspecified", + ) + + assert config.image_config.aspect_ratio == "16:9" + + +def test_enterprise_image_config_allows_all_people(): + config = gemini_nodes.SSL_GeminiTextPrompt._build_generate_content_config( + model="gemini-3.1-flash-image", + temperature=1.0, + top_p=0.95, + top_k=40, + max_output_tokens=8192, + seed=7, + include_images=True, + response_modalities=["IMAGE", "TEXT"], + aspect_ratio="None", + padded_system_instruction="system", + thinking_level="None", + thinking_budget=0, + include_thoughts=False, + media_resolution="unspecified", + allow_all_people=True, + ) + + assert config.image_config.aspect_ratio is None + assert config.image_config.person_generation == "ALLOW_ALL" + @pytest.mark.parametrize( ("selected_level", "expected_level"),