fix(image): correct Gemini image requests

This commit is contained in:
silveroxides
2026-09-01 09:34:26 +02:00
parent 9f4f51aaef
commit f180509263
4 changed files with 123 additions and 8 deletions
+2 -1
View File
@@ -190,4 +190,5 @@ cython_debug/
.ruff_cache/
# PyPI configuration file
.pypirc
.pypirc
AGENTS-LOCAL.md
+34
View File
@@ -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.
+12 -5
View File
@@ -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:
+75 -2
View File
@@ -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"),