feat(gemini): add 3.7 Flash
This commit is contained in:
+43
-7
@@ -143,6 +143,7 @@ class SSL_GeminiTextPrompt(IO.ComfyNode):
|
||||
_cache: dict = {}
|
||||
_seed_map_cache: dict = {} # Maps (input_seed, fingerprint_without_seed) -> successful_gemini_seed
|
||||
_client_cache: dict = {} # Maps client_key tuple -> genai.Client instance
|
||||
GEMINI_3_7_FLASH = "gemini-3.7-flash"
|
||||
|
||||
# Placeholder only. Keep this out of the model combo until Google's hosted-model
|
||||
# documentation confirms the final ID and gemini-3.6-flash-equivalent capabilities
|
||||
@@ -153,11 +154,11 @@ class SSL_GeminiTextPrompt(IO.ComfyNode):
|
||||
THINKING_MODELS = [
|
||||
"gemini-1.5-pro-002", "gemini-2.0-flash-thinking-exp", "gemini-2.0-flash-thinking-exp-01-21", "gemini-2.0-flash-thinking-exp-1219",
|
||||
"gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.5-flash-preview-04-17", "gemini-2.5-pro-exp-03-25",
|
||||
"gemini-3-flash-preview", "gemini-3.1-pro-preview", "gemini-3.5-flash", "gemini-3.5-flash-lite", "gemini-3.6-flash", GEMINI_4_FLASH_PREVIEW, "gemini-pro-latest", "gemini-flash-latest", "gemini-flash-lite-latest"
|
||||
"gemini-3-flash-preview", "gemini-3.1-pro-preview", "gemini-3.5-flash", "gemini-3.5-flash-lite", "gemini-3.6-flash", GEMINI_3_7_FLASH, GEMINI_4_FLASH_PREVIEW, "gemini-pro-latest", "gemini-flash-latest", "gemini-flash-lite-latest"
|
||||
]
|
||||
GEN3_THINKING_MODELS = [
|
||||
"gemini-pro-latest", "gemini-flash-latest", "gemini-3.1-pro-preview",
|
||||
"gemini-3-flash-preview", "gemini-3.5-flash", "gemini-3.5-flash-lite", "gemini-3.6-flash", GEMINI_4_FLASH_PREVIEW
|
||||
"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",
|
||||
@@ -185,7 +186,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", "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-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.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),
|
||||
@@ -211,7 +212,7 @@ class SSL_GeminiTextPrompt(IO.ComfyNode):
|
||||
IO.Int.Input("seed", default=0, min=0, max=2147483647),
|
||||
IO.Int.Input("timeout", default=30, min=15, max=300, step=15),
|
||||
IO.Boolean.Input("include_thoughts", default=False),
|
||||
IO.Combo.Input("thinking_level", options=["None", "low", "medium", "high"], default="None", tooltip="Does not work at the same time as 'thinking_budget'. if this is set, then thinking budget is ignored."),
|
||||
IO.Combo.Input("thinking_level", options=["None", "low", "medium", "high"], default="None", tooltip="Does not work at the same time as 'thinking_budget'. If this is set, then thinking budget is ignored. Gemini 3.7 Flash always uses this control, defaults to medium, and ignores thinking_budget."),
|
||||
IO.Combo.Input("media_resolution", options=["unspecified", "low", "medium", "high"], default="unspecified", tooltip="Set input media resolution for image, video and pdf. This changes tokens consumed."),
|
||||
IO.String.Input("retry_pattern", default="", optional=True, multiline=False, tooltip="Regex pattern to match in response text. If matched, retry with new seed. Leave empty to disable."),
|
||||
IO.Int.Input("max_retries", default=3, min=0, max=10, step=1, tooltip="Maximum number of retry attempts when pattern matches. 0 disables retry."),
|
||||
@@ -310,6 +311,22 @@ class SSL_GeminiTextPrompt(IO.ComfyNode):
|
||||
)
|
||||
return frames, batch_counts
|
||||
|
||||
@classmethod
|
||||
def _resolve_gemini_3_7_thinking_level(cls, thinking_level):
|
||||
if thinking_level is None or thinking_level == "None":
|
||||
return "medium"
|
||||
if thinking_level == "minimal":
|
||||
print(
|
||||
"[WARNING] Gemini 3.7 Flash does not support minimal "
|
||||
"thinking; using low instead."
|
||||
)
|
||||
return "low"
|
||||
if thinking_level not in {"low", "medium", "high"}:
|
||||
raise ValueError(
|
||||
"Gemini 3.7 Flash thinking_level must be low, medium, or high."
|
||||
)
|
||||
return thinking_level
|
||||
|
||||
@classmethod
|
||||
def _compute_fingerprint_and_check_cache(cls, config, prompt, system_instruction, model, temperature, top_p, top_k, max_output_tokens,
|
||||
include_images, aspect_ratio, bypass_mode, thinking_budget, use_seed, seed,
|
||||
@@ -340,6 +357,9 @@ class SSL_GeminiTextPrompt(IO.ComfyNode):
|
||||
eff_thinking_level = "None"
|
||||
eff_thinking_budget = -1
|
||||
eff_include_thoughts = False
|
||||
eff_temperature = None if model == cls.GEMINI_3_7_FLASH else float(temperature)
|
||||
eff_top_p = None if model == cls.GEMINI_3_7_FLASH else float(top_p)
|
||||
eff_top_k = None if model == cls.GEMINI_3_7_FLASH else int(top_k)
|
||||
|
||||
# Logic mirroring _build_generate_content_config exactly
|
||||
if include_images and model in cls.IMAGE_MODELS:
|
||||
@@ -347,6 +367,10 @@ class SSL_GeminiTextPrompt(IO.ComfyNode):
|
||||
eff_aspect_ratio = str(aspect_ratio)
|
||||
# When generating images, thinking params are ignored
|
||||
|
||||
elif model == cls.GEMINI_3_7_FLASH:
|
||||
eff_thinking_level = cls._resolve_gemini_3_7_thinking_level(thinking_level)
|
||||
eff_include_thoughts = include_thoughts
|
||||
|
||||
elif model in cls.GEN3_THINKING_MODELS and thinking_level is not None and thinking_level != "None":
|
||||
eff_thinking_level = thinking_level
|
||||
eff_include_thoughts = include_thoughts
|
||||
@@ -369,9 +393,9 @@ class SSL_GeminiTextPrompt(IO.ComfyNode):
|
||||
prompt,
|
||||
system_instruction,
|
||||
model,
|
||||
float(temperature),
|
||||
float(top_p),
|
||||
int(top_k),
|
||||
eff_temperature,
|
||||
eff_top_p,
|
||||
eff_top_k,
|
||||
int(max_output_tokens),
|
||||
eff_include_images, # EFFECTIVE include_images
|
||||
eff_aspect_ratio, # EFFECTIVE aspect_ratio
|
||||
@@ -491,6 +515,18 @@ class SSL_GeminiTextPrompt(IO.ComfyNode):
|
||||
system_instruction=[types.Part.from_text(text=padded_system_instruction)],
|
||||
)
|
||||
|
||||
if model == cls.GEMINI_3_7_FLASH:
|
||||
return types.GenerateContentConfig(
|
||||
max_output_tokens=max_output_tokens,
|
||||
safety_settings=safety,
|
||||
thinking_config=types.ThinkingConfig(
|
||||
include_thoughts=include_thoughts,
|
||||
thinking_level=cls._resolve_gemini_3_7_thinking_level(thinking_level),
|
||||
),
|
||||
response_modalities=response_modalities,
|
||||
system_instruction=[types.Part.from_text(text=padded_system_instruction)],
|
||||
)
|
||||
|
||||
G3Pro = ["gemini-3.1-pro-preview"]
|
||||
# Modified: Added check for thinking_level != "None"
|
||||
if model in G3Pro and thinking_level is not None and thinking_level != "None":
|
||||
|
||||
@@ -443,6 +443,7 @@ def test_confirmed_current_models_are_visible_without_removing_legacy_ids():
|
||||
model_input = next(input_ for input_ in schema.inputs if input_.id == "model")
|
||||
|
||||
for model in (
|
||||
"gemini-3.7-flash",
|
||||
"gemini-3.5-flash",
|
||||
"gemini-3.1-flash-image",
|
||||
"gemini-3.1-flash-lite-image",
|
||||
@@ -452,6 +453,105 @@ def test_confirmed_current_models_are_visible_without_removing_legacy_ids():
|
||||
assert model in model_input.options
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("selected_level", "expected_level"),
|
||||
[("None", "medium"), ("low", "low"), ("medium", "medium"), ("high", "high")],
|
||||
)
|
||||
def test_gemini_3_7_uses_supported_thinking_levels(selected_level, expected_level):
|
||||
config = gemini_nodes.SSL_GeminiTextPrompt._build_generate_content_config(
|
||||
model="gemini-3.7-flash",
|
||||
temperature=0.4,
|
||||
top_p=0.8,
|
||||
top_k=20,
|
||||
max_output_tokens=1024,
|
||||
seed=7,
|
||||
include_images=False,
|
||||
response_modalities=["TEXT"],
|
||||
aspect_ratio="None",
|
||||
padded_system_instruction="system",
|
||||
thinking_level=selected_level,
|
||||
thinking_budget=4096,
|
||||
include_thoughts=True,
|
||||
media_resolution="unspecified",
|
||||
)
|
||||
|
||||
assert config.thinking_config.thinking_level.value == expected_level.upper()
|
||||
assert config.thinking_config.thinking_budget is None
|
||||
assert config.temperature is None
|
||||
assert config.top_p is None
|
||||
assert config.top_k is None
|
||||
|
||||
|
||||
def test_gemini_3_7_fingerprint_matches_effective_request_controls():
|
||||
common = {
|
||||
"config": {"api_key": "key", "api_version": "v1"},
|
||||
"prompt": "prompt",
|
||||
"system_instruction": "system",
|
||||
"model": "gemini-3.7-flash",
|
||||
"max_output_tokens": 1024,
|
||||
"include_images": False,
|
||||
"aspect_ratio": "None",
|
||||
"bypass_mode": "None",
|
||||
"use_seed": False,
|
||||
"seed": 0,
|
||||
"include_thoughts": True,
|
||||
}
|
||||
|
||||
default_fingerprint, _ = gemini_nodes.SSL_GeminiTextPrompt._compute_fingerprint_and_check_cache(
|
||||
**common,
|
||||
temperature=0.4,
|
||||
top_p=0.8,
|
||||
top_k=20,
|
||||
thinking_budget=0,
|
||||
thinking_level="None",
|
||||
)
|
||||
explicit_medium_fingerprint, _ = gemini_nodes.SSL_GeminiTextPrompt._compute_fingerprint_and_check_cache(
|
||||
**common,
|
||||
temperature=1.0,
|
||||
top_p=0.95,
|
||||
top_k=40,
|
||||
thinking_budget=8192,
|
||||
thinking_level="medium",
|
||||
)
|
||||
high_fingerprint, _ = gemini_nodes.SSL_GeminiTextPrompt._compute_fingerprint_and_check_cache(
|
||||
**common,
|
||||
temperature=1.0,
|
||||
top_p=0.95,
|
||||
top_k=40,
|
||||
thinking_budget=8192,
|
||||
thinking_level="high",
|
||||
)
|
||||
minimal_fingerprint, _ = gemini_nodes.SSL_GeminiTextPrompt._compute_fingerprint_and_check_cache(
|
||||
**common,
|
||||
temperature=1.0,
|
||||
top_p=0.95,
|
||||
top_k=40,
|
||||
thinking_budget=8192,
|
||||
thinking_level="minimal",
|
||||
)
|
||||
low_fingerprint, _ = gemini_nodes.SSL_GeminiTextPrompt._compute_fingerprint_and_check_cache(
|
||||
**common,
|
||||
temperature=0.2,
|
||||
top_p=0.7,
|
||||
top_k=10,
|
||||
thinking_budget=0,
|
||||
thinking_level="low",
|
||||
)
|
||||
|
||||
assert default_fingerprint == explicit_medium_fingerprint
|
||||
assert high_fingerprint != default_fingerprint
|
||||
assert minimal_fingerprint == low_fingerprint
|
||||
|
||||
|
||||
def test_gemini_3_7_coerces_minimal_to_low_with_warning(capsys):
|
||||
resolved = gemini_nodes.SSL_GeminiTextPrompt._resolve_gemini_3_7_thinking_level(
|
||||
"minimal"
|
||||
)
|
||||
|
||||
assert resolved == "low"
|
||||
assert "does not support minimal thinking; using low instead" in capsys.readouterr().out
|
||||
|
||||
|
||||
def test_gemini_4_placeholder_matches_3_6_without_ui_exposure():
|
||||
placeholder = gemini_nodes.SSL_GeminiTextPrompt.GEMINI_4_FLASH_PREVIEW
|
||||
reference = "gemini-3.6-flash"
|
||||
|
||||
Reference in New Issue
Block a user