Add Gemini 4 Carbon and Hydrogen support
This commit is contained in:
+50
-17
@@ -249,7 +249,16 @@ class SSL_GeminiTextPrompt(IO.ComfyNode):
|
||||
GEMINI_3_8_FLASH = "gemini-3.8-flash"
|
||||
GEMINI_4_ARGON = "gemini-4-argon"
|
||||
GEMINI_4_ARGON_CYBER_PERMISSIVE = "gemini-4-argon-cyber-permissive"
|
||||
GEMINI_4_CARBON = "gemini-4-carbon"
|
||||
GEMINI_4_CARBON_CYBER_PERMISSIVE = "gemini-4-carbon-cyber-permissive"
|
||||
GEMINI_4_HYDROGEN = "gemini-4-hydrogen"
|
||||
ARGON_MODELS = (GEMINI_4_ARGON, GEMINI_4_ARGON_CYBER_PERMISSIVE)
|
||||
GEMINI_4_MODELS = (
|
||||
*ARGON_MODELS,
|
||||
GEMINI_4_CARBON,
|
||||
GEMINI_4_CARBON_CYBER_PERMISSIVE,
|
||||
GEMINI_4_HYDROGEN,
|
||||
)
|
||||
VIDEO_MIME_TYPE = "video/mp4"
|
||||
VIDEO_INLINE_LIMIT_BYTES = 100 * 1024 * 1024
|
||||
VERTEX_CACHE_INLINE_LIMIT_BYTES = 10 * 1024 * 1024
|
||||
@@ -263,11 +272,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_3_7_FLASH, GEMINI_3_8_FLASH, GEMINI_4_ARGON, GEMINI_4_ARGON_CYBER_PERMISSIVE, 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_3_8_FLASH, GEMINI_4_ARGON, GEMINI_4_ARGON_CYBER_PERMISSIVE, GEMINI_4_CARBON, GEMINI_4_CARBON_CYBER_PERMISSIVE, GEMINI_4_HYDROGEN, 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_3_7_FLASH, GEMINI_3_8_FLASH, GEMINI_4_ARGON, GEMINI_4_ARGON_CYBER_PERMISSIVE, 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_3_8_FLASH, GEMINI_4_ARGON, GEMINI_4_ARGON_CYBER_PERMISSIVE, GEMINI_4_CARBON, GEMINI_4_CARBON_CYBER_PERMISSIVE, GEMINI_4_HYDROGEN, GEMINI_4_FLASH_PREVIEW
|
||||
]
|
||||
IMAGE_MODELS = [
|
||||
"gemini-2.5-flash-image",
|
||||
@@ -281,7 +290,7 @@ class SSL_GeminiTextPrompt(IO.ComfyNode):
|
||||
}
|
||||
MEDIA_RES_MODELS = [
|
||||
"gemini-3.1-flash-lite", "gemini-3-flash-preview", "gemini-3.1-pro-preview",
|
||||
"gemini-3.5-flash", "gemini-pro-latest", "gemini-flash-latest", "gemini-flash-lite-latest"
|
||||
"gemini-3.5-flash", GEMINI_4_CARBON, GEMINI_4_CARBON_CYBER_PERMISSIVE, GEMINI_4_HYDROGEN, "gemini-pro-latest", "gemini-flash-latest", "gemini-flash-lite-latest"
|
||||
]
|
||||
|
||||
@classmethod
|
||||
@@ -301,11 +310,11 @@ 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, cls.GEMINI_3_8_FLASH, cls.GEMINI_4_ARGON, cls.GEMINI_4_ARGON_CYBER_PERMISSIVE, "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.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, cls.GEMINI_3_8_FLASH, cls.GEMINI_4_ARGON, cls.GEMINI_4_ARGON_CYBER_PERMISSIVE, cls.GEMINI_4_CARBON, cls.GEMINI_4_CARBON_CYBER_PERMISSIVE, cls.GEMINI_4_HYDROGEN, "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),
|
||||
IO.Int.Input("max_output_tokens", default=8192, min=1, max=65536, step=1),
|
||||
IO.Int.Input("max_output_tokens", default=8192, min=1, max=1048576, step=1),
|
||||
IO.Boolean.Input("include_images", default=False),
|
||||
IO.Combo.Input("aspect_ratio", options=["None", "1:1", "9:16", "16:9", "3:4", "4:3", "3:2", "2:3", "5:4", "4:5", "21:9"], default="None"),
|
||||
IO.Combo.Input("bypass_mode", options=["None", "system_instruction", "prompt", "both"], default="None"),
|
||||
@@ -315,9 +324,9 @@ class SSL_GeminiTextPrompt(IO.ComfyNode):
|
||||
IO.Int.Input("proxy_port", default=7890, min=1, max=65535),
|
||||
IO.Boolean.Input("use_seed", default=True),
|
||||
IO.Int.Input("seed", default=0, min=0, max=2147483647),
|
||||
IO.Int.Input("timeout", default=30, min=15, max=300, step=15),
|
||||
IO.Int.Input("timeout", default=30, min=15, max=1200, 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. Gemini 3.7/3.8 Flash always uses this control, defaults to medium, and ignores thinking_budget."),
|
||||
IO.Combo.Input("thinking_level", options=["None", "low", "medium", "high", "xhigh"], 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/3.8 Flash and Gemini 4 models always use this control and ignore thinking_budget. 'xhigh' is supported only by Gemini 4 Hydrogen."),
|
||||
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."),
|
||||
@@ -549,19 +558,34 @@ class SSL_GeminiTextPrompt(IO.ComfyNode):
|
||||
@classmethod
|
||||
def _resolve_gemini_3_7_thinking_level(cls, thinking_level, model_name=None):
|
||||
if thinking_level is None or thinking_level == "None":
|
||||
if model_name == cls.GEMINI_4_ARGON_CYBER_PERMISSIVE:
|
||||
if model_name in (
|
||||
cls.GEMINI_4_ARGON_CYBER_PERMISSIVE,
|
||||
cls.GEMINI_4_CARBON_CYBER_PERMISSIVE,
|
||||
cls.GEMINI_4_HYDROGEN,
|
||||
):
|
||||
return "high"
|
||||
return "medium"
|
||||
if thinking_level == "minimal":
|
||||
if model_name in (cls.GEMINI_3_8_FLASH, cls.GEMINI_4_ARGON, cls.GEMINI_4_ARGON_CYBER_PERMISSIVE):
|
||||
if model_name in (cls.GEMINI_3_8_FLASH, *cls.GEMINI_4_MODELS):
|
||||
allowed = (
|
||||
"low, medium, high, or xhigh"
|
||||
if model_name == cls.GEMINI_4_HYDROGEN
|
||||
else "low, medium, or high"
|
||||
)
|
||||
raise ValueError(
|
||||
f"{model_name} thinking_level must be low, medium, or high."
|
||||
f"{model_name} thinking_level must be {allowed}."
|
||||
)
|
||||
print(
|
||||
f"[WARNING] {model_name or 'Gemini 3.7 Flash'} does not support minimal "
|
||||
"thinking; using low instead."
|
||||
)
|
||||
return "low"
|
||||
if thinking_level == "xhigh":
|
||||
if model_name != cls.GEMINI_4_HYDROGEN:
|
||||
raise ValueError(
|
||||
f"{model_name or 'This model'} does not support xhigh thinking_level."
|
||||
)
|
||||
return "xhigh"
|
||||
if thinking_level not in {"low", "medium", "high"}:
|
||||
raise ValueError(
|
||||
f"{model_name or 'Gemini 3.7 Flash'} thinking_level must be low, medium, or high."
|
||||
@@ -660,9 +684,9 @@ class SSL_GeminiTextPrompt(IO.ComfyNode):
|
||||
eff_thinking_level = "None"
|
||||
eff_thinking_budget = -1
|
||||
eff_include_thoughts = False
|
||||
eff_temperature = None if model in (cls.GEMINI_3_7_FLASH, cls.GEMINI_3_8_FLASH, cls.GEMINI_4_ARGON, cls.GEMINI_4_ARGON_CYBER_PERMISSIVE) else float(temperature)
|
||||
eff_top_p = None if model in (cls.GEMINI_3_7_FLASH, cls.GEMINI_3_8_FLASH, cls.GEMINI_4_ARGON, cls.GEMINI_4_ARGON_CYBER_PERMISSIVE) else float(top_p)
|
||||
eff_top_k = None if model in (cls.GEMINI_3_7_FLASH, cls.GEMINI_3_8_FLASH, cls.GEMINI_4_ARGON, cls.GEMINI_4_ARGON_CYBER_PERMISSIVE) else int(top_k)
|
||||
eff_temperature = None if model in (cls.GEMINI_3_7_FLASH, cls.GEMINI_3_8_FLASH, *cls.GEMINI_4_MODELS) else float(temperature)
|
||||
eff_top_p = None if model in (cls.GEMINI_3_7_FLASH, cls.GEMINI_3_8_FLASH, *cls.GEMINI_4_MODELS) else float(top_p)
|
||||
eff_top_k = None if model in (cls.GEMINI_3_7_FLASH, cls.GEMINI_3_8_FLASH, *cls.GEMINI_4_MODELS) else int(top_k)
|
||||
|
||||
# Logic mirroring _build_generate_content_config exactly
|
||||
if include_images and model in cls.IMAGE_MODELS:
|
||||
@@ -671,11 +695,13 @@ class SSL_GeminiTextPrompt(IO.ComfyNode):
|
||||
eff_image_size = "None" if image_size in (None, "None") else str(image_size)
|
||||
# When generating images, thinking params are ignored
|
||||
|
||||
elif model in (cls.GEMINI_3_7_FLASH, cls.GEMINI_3_8_FLASH, cls.GEMINI_4_ARGON, cls.GEMINI_4_ARGON_CYBER_PERMISSIVE):
|
||||
elif model in (cls.GEMINI_3_7_FLASH, cls.GEMINI_3_8_FLASH, *cls.GEMINI_4_MODELS):
|
||||
eff_thinking_level = cls._resolve_gemini_3_7_thinking_level(thinking_level, model)
|
||||
eff_include_thoughts = include_thoughts
|
||||
|
||||
elif model in cls.GEN3_THINKING_MODELS and thinking_level is not None and thinking_level != "None":
|
||||
if thinking_level == "xhigh":
|
||||
raise ValueError(f"{model} does not support xhigh thinking_level.")
|
||||
eff_thinking_level = thinking_level
|
||||
eff_include_thoughts = include_thoughts
|
||||
# Budget is ignored in this specific branch
|
||||
@@ -922,7 +948,7 @@ class SSL_GeminiTextPrompt(IO.ComfyNode):
|
||||
system_instruction=[types.Part.from_text(text=padded_system_instruction)],
|
||||
)
|
||||
|
||||
if model in (cls.GEMINI_3_7_FLASH, cls.GEMINI_3_8_FLASH, cls.GEMINI_4_ARGON, cls.GEMINI_4_ARGON_CYBER_PERMISSIVE):
|
||||
if model in (cls.GEMINI_3_7_FLASH, cls.GEMINI_3_8_FLASH, *cls.GEMINI_4_MODELS):
|
||||
return types.GenerateContentConfig(
|
||||
max_output_tokens=max_output_tokens,
|
||||
safety_settings=safety,
|
||||
@@ -937,6 +963,8 @@ class SSL_GeminiTextPrompt(IO.ComfyNode):
|
||||
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":
|
||||
if thinking_level == "xhigh":
|
||||
raise ValueError(f"{model} does not support xhigh thinking_level.")
|
||||
return types.GenerateContentConfig(
|
||||
temperature=temperature,
|
||||
top_p=top_p,
|
||||
@@ -1335,10 +1363,10 @@ class SSL_GeminiTextPrompt(IO.ComfyNode):
|
||||
and (config.get("use_vertexai_env", False) or config.get("vertexai_express", False))
|
||||
and context_inline_bytes > cls.VERTEX_CACHE_INLINE_LIMIT_BYTES
|
||||
)
|
||||
or (use_cache and model in cls.ARGON_MODELS)
|
||||
or (use_cache and model in cls.GEMINI_4_MODELS)
|
||||
)
|
||||
if skip_context_cache:
|
||||
if use_cache and model in cls.ARGON_MODELS:
|
||||
if use_cache and model in cls.GEMINI_4_MODELS:
|
||||
print(f"[WARNING] Explicit context cache skipped for {model} (implicit caching only); using full request")
|
||||
else:
|
||||
print("[WARNING] Vertex context cache skipped because inline cached media exceeds 10 MB; using full request")
|
||||
@@ -1365,6 +1393,11 @@ class SSL_GeminiTextPrompt(IO.ComfyNode):
|
||||
safety_settings=resolved_safety_settings,
|
||||
)
|
||||
|
||||
if generate_content_config.http_options is None:
|
||||
generate_content_config.http_options = types.HttpOptions(
|
||||
timeout=max(1, int(float(timeout) * 1000))
|
||||
)
|
||||
|
||||
if schema_snapshot is not None:
|
||||
generate_content_config.response_mime_type = "application/json"
|
||||
generate_content_config.response_json_schema = schema_snapshot
|
||||
|
||||
+44
-11
@@ -265,7 +265,16 @@ class SSL_GeminiInteractionsTextPrompt(IO.ComfyNode):
|
||||
GEMINI_3_8_FLASH = "gemini-3.8-flash"
|
||||
GEMINI_4_ARGON = "gemini-4-argon"
|
||||
GEMINI_4_ARGON_CYBER_PERMISSIVE = "gemini-4-argon-cyber-permissive"
|
||||
GEMINI_4_CARBON = "gemini-4-carbon"
|
||||
GEMINI_4_CARBON_CYBER_PERMISSIVE = "gemini-4-carbon-cyber-permissive"
|
||||
GEMINI_4_HYDROGEN = "gemini-4-hydrogen"
|
||||
ARGON_MODELS = (GEMINI_4_ARGON, GEMINI_4_ARGON_CYBER_PERMISSIVE)
|
||||
GEMINI_4_MODELS = (
|
||||
*ARGON_MODELS,
|
||||
GEMINI_4_CARBON,
|
||||
GEMINI_4_CARBON_CYBER_PERMISSIVE,
|
||||
GEMINI_4_HYDROGEN,
|
||||
)
|
||||
VIDEO_MIME_TYPE = "video/mp4"
|
||||
VIDEO_INLINE_LIMIT_BYTES = 100 * 1024 * 1024
|
||||
|
||||
@@ -287,11 +296,11 @@ class SSL_GeminiInteractionsTextPrompt(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_3_7_FLASH, GEMINI_3_8_FLASH, GEMINI_4_ARGON, GEMINI_4_ARGON_CYBER_PERMISSIVE, 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_3_8_FLASH, GEMINI_4_ARGON, GEMINI_4_ARGON_CYBER_PERMISSIVE, GEMINI_4_CARBON, GEMINI_4_CARBON_CYBER_PERMISSIVE, GEMINI_4_HYDROGEN, 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_3_7_FLASH, GEMINI_3_8_FLASH, GEMINI_4_ARGON, GEMINI_4_ARGON_CYBER_PERMISSIVE, 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_3_8_FLASH, GEMINI_4_ARGON, GEMINI_4_ARGON_CYBER_PERMISSIVE, GEMINI_4_CARBON, GEMINI_4_CARBON_CYBER_PERMISSIVE, GEMINI_4_HYDROGEN, GEMINI_4_FLASH_PREVIEW
|
||||
]
|
||||
IMAGE_MODELS = [
|
||||
"gemini-2.5-flash-image",
|
||||
@@ -325,11 +334,11 @@ class SSL_GeminiInteractionsTextPrompt(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, cls.GEMINI_3_8_FLASH, cls.GEMINI_4_ARGON, cls.GEMINI_4_ARGON_CYBER_PERMISSIVE, "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.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, cls.GEMINI_3_8_FLASH, cls.GEMINI_4_ARGON, cls.GEMINI_4_ARGON_CYBER_PERMISSIVE, cls.GEMINI_4_CARBON, cls.GEMINI_4_CARBON_CYBER_PERMISSIVE, cls.GEMINI_4_HYDROGEN, "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, tooltip="Ignored in Interactions API"),
|
||||
IO.Float.Input("top_p", default=0.95, min=0.0, max=1.0, step=0.01, tooltip="Ignored in Interactions API"),
|
||||
IO.Int.Input("top_k", default=40, min=1, max=100, step=1, tooltip="Ignored in Interactions API"),
|
||||
IO.Int.Input("max_output_tokens", default=8192, min=1, max=65536, step=1),
|
||||
IO.Int.Input("max_output_tokens", default=8192, min=1, max=1048576, step=1),
|
||||
IO.Boolean.Input("include_images", default=False),
|
||||
IO.Combo.Input("aspect_ratio", options=["None", "1:1", "9:16", "16:9", "3:4", "4:3", "3:2", "2:3", "5:4", "4:5", "21:9", "1:8", "8:1", "1:4", "4:1"], default="None"),
|
||||
IO.Combo.Input("bypass_mode", options=["None", "system_instruction", "prompt", "both"], default="None"),
|
||||
@@ -339,9 +348,9 @@ class SSL_GeminiInteractionsTextPrompt(IO.ComfyNode):
|
||||
IO.Int.Input("proxy_port", default=7890, min=1, max=65535),
|
||||
IO.Boolean.Input("use_seed", default=True),
|
||||
IO.Int.Input("seed", default=0, min=0, max=2147483647),
|
||||
IO.Int.Input("timeout", default=30, min=15, max=300, step=15),
|
||||
IO.Int.Input("timeout", default=30, min=15, max=1200, step=15),
|
||||
IO.Boolean.Input("include_thoughts", default=False),
|
||||
IO.Combo.Input("thinking_level", options=["None", "minimal", "low", "medium", "high"], default="low", tooltip="Thinking level for reasoning models."),
|
||||
IO.Combo.Input("thinking_level", options=["None", "minimal", "low", "medium", "high", "xhigh"], default="low", tooltip="Thinking level for reasoning models. 'xhigh' is supported only by Gemini 4 Hydrogen."),
|
||||
IO.Combo.Input("media_resolution", options=["unspecified", "low", "medium", "high", "ultra_high"], default="unspecified", tooltip="Set input media resolution for image, video and pdf."),
|
||||
IO.String.Input("retry_pattern", default="(PROHIBITED_CONTENT|UNAVAILABLE|request/processing|RESOURCE_EXHAUSTED)", 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."),
|
||||
@@ -569,19 +578,34 @@ class SSL_GeminiInteractionsTextPrompt(IO.ComfyNode):
|
||||
@classmethod
|
||||
def _resolve_gemini_3_7_thinking_level(cls, thinking_level, model_name=None):
|
||||
if thinking_level is None or thinking_level == "None":
|
||||
if model_name == cls.GEMINI_4_ARGON_CYBER_PERMISSIVE:
|
||||
if model_name in (
|
||||
cls.GEMINI_4_ARGON_CYBER_PERMISSIVE,
|
||||
cls.GEMINI_4_CARBON_CYBER_PERMISSIVE,
|
||||
cls.GEMINI_4_HYDROGEN,
|
||||
):
|
||||
return "high"
|
||||
return "medium"
|
||||
if thinking_level == "minimal":
|
||||
if model_name in (cls.GEMINI_3_8_FLASH, cls.GEMINI_4_ARGON, cls.GEMINI_4_ARGON_CYBER_PERMISSIVE):
|
||||
if model_name in (cls.GEMINI_3_8_FLASH, *cls.GEMINI_4_MODELS):
|
||||
allowed = (
|
||||
"low, medium, high, or xhigh"
|
||||
if model_name == cls.GEMINI_4_HYDROGEN
|
||||
else "low, medium, or high"
|
||||
)
|
||||
raise ValueError(
|
||||
f"{model_name} thinking_level must be low, medium, or high."
|
||||
f"{model_name} thinking_level must be {allowed}."
|
||||
)
|
||||
print(
|
||||
f"[WARNING] {model_name or 'Gemini 3.7 Flash'} does not support minimal "
|
||||
"thinking; using low instead."
|
||||
)
|
||||
return "low"
|
||||
if thinking_level == "xhigh":
|
||||
if model_name != cls.GEMINI_4_HYDROGEN:
|
||||
raise ValueError(
|
||||
f"{model_name or 'This model'} does not support xhigh thinking_level."
|
||||
)
|
||||
return "xhigh"
|
||||
if thinking_level not in {"low", "medium", "high"}:
|
||||
raise ValueError(
|
||||
f"{model_name or 'Gemini 3.7 Flash'} thinking_level must be low, medium, or high."
|
||||
@@ -672,15 +696,19 @@ class SSL_GeminiInteractionsTextPrompt(IO.ComfyNode):
|
||||
eff_aspect_ratio = str(aspect_ratio)
|
||||
eff_image_size = "None" if image_size in (None, "None") else str(image_size)
|
||||
|
||||
elif model in (cls.GEMINI_3_7_FLASH, cls.GEMINI_3_8_FLASH, cls.GEMINI_4_ARGON, cls.GEMINI_4_ARGON_CYBER_PERMISSIVE):
|
||||
elif model in (cls.GEMINI_3_7_FLASH, cls.GEMINI_3_8_FLASH, *cls.GEMINI_4_MODELS):
|
||||
eff_thinking_level = cls._resolve_gemini_3_7_thinking_level(thinking_level, model)
|
||||
eff_include_thoughts = include_thoughts
|
||||
|
||||
elif model in cls.GEN3_THINKING_MODELS and thinking_level not in (None, "None"):
|
||||
if thinking_level == "xhigh":
|
||||
raise ValueError(f"{model} does not support xhigh thinking_level.")
|
||||
eff_thinking_level = thinking_level
|
||||
eff_include_thoughts = include_thoughts
|
||||
|
||||
elif thinking_level not in (None, "None"):
|
||||
if thinking_level == "xhigh":
|
||||
raise ValueError(f"{model} does not support xhigh thinking_level.")
|
||||
eff_thinking_level = thinking_level
|
||||
eff_include_thoughts = include_thoughts
|
||||
|
||||
@@ -1219,11 +1247,15 @@ class SSL_GeminiInteractionsTextPrompt(IO.ComfyNode):
|
||||
|
||||
# Assemble generation_config
|
||||
effective_thinking_level = None
|
||||
if model in (cls.GEMINI_3_7_FLASH, cls.GEMINI_3_8_FLASH, cls.GEMINI_4_ARGON, cls.GEMINI_4_ARGON_CYBER_PERMISSIVE):
|
||||
if model in (cls.GEMINI_3_7_FLASH, cls.GEMINI_3_8_FLASH, *cls.GEMINI_4_MODELS):
|
||||
effective_thinking_level = cls._resolve_gemini_3_7_thinking_level(thinking_level, model)
|
||||
elif model in cls.GEN3_THINKING_MODELS and thinking_level not in (None, "None"):
|
||||
if thinking_level == "xhigh":
|
||||
raise ValueError(f"{model} does not support xhigh thinking_level.")
|
||||
effective_thinking_level = thinking_level
|
||||
elif thinking_level not in (None, "None"):
|
||||
if thinking_level == "xhigh":
|
||||
raise ValueError(f"{model} does not support xhigh thinking_level.")
|
||||
effective_thinking_level = thinking_level
|
||||
|
||||
generation_config: dict[str, Any] = {}
|
||||
@@ -1252,6 +1284,7 @@ class SSL_GeminiInteractionsTextPrompt(IO.ComfyNode):
|
||||
kwargs: dict[str, Any] = {
|
||||
"model": model,
|
||||
"input": input_contents,
|
||||
"timeout": float(timeout),
|
||||
}
|
||||
if resolved_safety_settings is not None:
|
||||
kwargs["safety_settings"] = list(resolved_safety_settings)
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ name = "ComfyUI_Gemini_Expanded_API"
|
||||
description = "Node for usage of Gemini API"
|
||||
version = "1.6.2"
|
||||
license = {file = "LICENSE"}
|
||||
dependencies = ["google-genai>=2.17.0", "numpy", "torch"]
|
||||
dependencies = ["google-genai>=2.34.0", "numpy", "torch"]
|
||||
|
||||
[project.urls]
|
||||
Repository = "https://github.com/silveroxides/ComfyUI_Gemini_Expanded_API"
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
google-genai>=2.17.0
|
||||
google-genai>=2.34.0
|
||||
numpy
|
||||
torch
|
||||
Pillow
|
||||
|
||||
@@ -58,6 +58,9 @@ def structured_client(monkeypatch):
|
||||
(gemini_nodes.SSL_GeminiTextPrompt.GEMINI_3_8_FLASH, {"thinking_level": "medium"}),
|
||||
(gemini_nodes.SSL_GeminiTextPrompt.GEMINI_4_ARGON, {"thinking_level": "medium"}),
|
||||
(gemini_nodes.SSL_GeminiTextPrompt.GEMINI_4_ARGON_CYBER_PERMISSIVE, {"thinking_level": "high"}),
|
||||
(gemini_nodes.SSL_GeminiTextPrompt.GEMINI_4_CARBON, {"thinking_level": "medium"}),
|
||||
(gemini_nodes.SSL_GeminiTextPrompt.GEMINI_4_CARBON_CYBER_PERMISSIVE, {"thinking_level": "high"}),
|
||||
(gemini_nodes.SSL_GeminiTextPrompt.GEMINI_4_HYDROGEN, {"thinking_level": "high"}),
|
||||
])
|
||||
@pytest.mark.parametrize("client_config", [{}, {"vertexai_express": True}, {"use_vertexai_env": True, "vertexai_project": "test", "vertexai_location": "global"}])
|
||||
def test_builder_schema_reaches_generate_content(structured_client, monkeypatch, model, settings, client_config):
|
||||
@@ -1809,3 +1812,144 @@ def test_gemini_batch_images_list(structured_client):
|
||||
assert isinstance(output[0], list) and len(output[0]) == 2
|
||||
assert output[0] == ["Img Resp 1", "Img Resp 2"]
|
||||
assert len(structured_client.calls) == 2
|
||||
|
||||
|
||||
def test_gemini_4_carbon_and_hydrogen_schema_and_limits():
|
||||
schema = gemini_nodes.SSL_GeminiTextPrompt.define_schema()
|
||||
inputs = {inp.id: inp for inp in schema.inputs}
|
||||
|
||||
for model_id in (
|
||||
"gemini-4-carbon",
|
||||
"gemini-4-carbon-cyber-permissive",
|
||||
"gemini-4-hydrogen",
|
||||
):
|
||||
assert model_id in inputs["model"].options
|
||||
assert model_id in gemini_nodes.SSL_GeminiTextPrompt.THINKING_MODELS
|
||||
assert model_id in gemini_nodes.SSL_GeminiTextPrompt.GEN3_THINKING_MODELS
|
||||
assert model_id in gemini_nodes.SSL_GeminiTextPrompt.MEDIA_RES_MODELS
|
||||
|
||||
assert "xhigh" in inputs["thinking_level"].options
|
||||
assert inputs["max_output_tokens"].max == 1048576
|
||||
assert inputs["timeout"].max == 1200
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("model_id", "selected", "expected"),
|
||||
[
|
||||
("gemini-4-carbon", "None", "MEDIUM"),
|
||||
("gemini-4-carbon-cyber-permissive", "None", "HIGH"),
|
||||
("gemini-4-hydrogen", "None", "HIGH"),
|
||||
("gemini-4-hydrogen", "xhigh", "xhigh"),
|
||||
("gemini-4-hydrogen", "low", "LOW"),
|
||||
],
|
||||
)
|
||||
def test_gemini_4_carbon_and_hydrogen_thinking_config(model_id, selected, expected):
|
||||
config = gemini_nodes.SSL_GeminiTextPrompt._build_generate_content_config(
|
||||
model=model_id,
|
||||
temperature=0.4,
|
||||
top_p=0.8,
|
||||
top_k=20,
|
||||
max_output_tokens=131072,
|
||||
seed=7,
|
||||
include_images=False,
|
||||
response_modalities=["TEXT"],
|
||||
aspect_ratio="None",
|
||||
padded_system_instruction="system",
|
||||
thinking_level=selected,
|
||||
thinking_budget=4096,
|
||||
include_thoughts=True,
|
||||
media_resolution="unspecified",
|
||||
)
|
||||
|
||||
assert config.thinking_config.thinking_level.value == expected
|
||||
assert config.thinking_config.thinking_budget is None
|
||||
assert config.max_output_tokens == 131072
|
||||
assert config.temperature is None
|
||||
assert config.top_p is None
|
||||
assert config.top_k is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("model_id", "bad_level"),
|
||||
[
|
||||
("gemini-4-carbon", "minimal"),
|
||||
("gemini-4-carbon-cyber-permissive", "minimal"),
|
||||
("gemini-4-hydrogen", "minimal"),
|
||||
("gemini-4-carbon", "xhigh"),
|
||||
("gemini-4-carbon-cyber-permissive", "xhigh"),
|
||||
("gemini-4-argon", "xhigh"),
|
||||
("gemini-3.8-flash", "xhigh"),
|
||||
("gemini-3.1-pro-preview", "xhigh"),
|
||||
],
|
||||
)
|
||||
def test_gemini_4_invalid_thinking_levels_rejected(model_id, bad_level):
|
||||
with pytest.raises(ValueError):
|
||||
gemini_nodes.SSL_GeminiTextPrompt._build_generate_content_config(
|
||||
model=model_id,
|
||||
temperature=1.0,
|
||||
top_p=0.95,
|
||||
top_k=40,
|
||||
max_output_tokens=8192,
|
||||
seed=7,
|
||||
include_images=False,
|
||||
response_modalities=["TEXT"],
|
||||
aspect_ratio="None",
|
||||
padded_system_instruction="system",
|
||||
thinking_level=bad_level,
|
||||
thinking_budget=0,
|
||||
include_thoughts=False,
|
||||
media_resolution="unspecified",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("model_id", ["gemini-4-carbon", "gemini-4-carbon-cyber-permissive", "gemini-4-hydrogen"])
|
||||
def test_gemini_4_carbon_and_hydrogen_skip_explicit_context_cache(structured_client, model_id):
|
||||
structured_client.responses = [[("final", "gemini4 response")]]
|
||||
args = _execute_kwargs(_config(use_cache=True, cache_seed=42))
|
||||
args["model"] = model_id
|
||||
args["max_output_tokens"] = 131072
|
||||
args["timeout"] = 1200
|
||||
|
||||
output = gemini_nodes.SSL_GeminiTextPrompt.execute(**args)
|
||||
|
||||
assert output[0] == "gemini4 response"
|
||||
assert len(structured_client.calls) == 1
|
||||
sent_config = structured_client.calls[0]["config"]
|
||||
assert sent_config.cached_content is None
|
||||
assert sent_config.max_output_tokens == 131072
|
||||
assert sent_config.http_options.timeout == 1200000
|
||||
|
||||
|
||||
@pytest.mark.parametrize("model_id", ["gemini-4-carbon", "gemini-4-hydrogen"])
|
||||
def test_gemini_4_carbon_and_hydrogen_fingerprint_omits_sampling_params(model_id):
|
||||
common = {
|
||||
"config": {"api_key": "key", "api_version": "v1"},
|
||||
"prompt": "prompt",
|
||||
"system_instruction": "system",
|
||||
"model": model_id,
|
||||
"max_output_tokens": 131072,
|
||||
"include_images": False,
|
||||
"aspect_ratio": "None",
|
||||
"bypass_mode": "None",
|
||||
"use_seed": False,
|
||||
"seed": 0,
|
||||
}
|
||||
|
||||
fp1, _ = gemini_nodes.SSL_GeminiTextPrompt._compute_fingerprint_and_check_cache(
|
||||
**common,
|
||||
temperature=0.1,
|
||||
top_p=0.5,
|
||||
top_k=10,
|
||||
thinking_budget=0,
|
||||
thinking_level="None",
|
||||
)
|
||||
fp2, _ = gemini_nodes.SSL_GeminiTextPrompt._compute_fingerprint_and_check_cache(
|
||||
**common,
|
||||
temperature=0.9,
|
||||
top_p=0.99,
|
||||
top_k=90,
|
||||
thinking_budget=1000,
|
||||
thinking_level="high" if model_id == "gemini-4-hydrogen" else "medium",
|
||||
)
|
||||
assert fp1 == fp2
|
||||
|
||||
|
||||
@@ -828,3 +828,72 @@ def test_interactions_batch_videos(mock_interactions_client):
|
||||
assert len(mock_interactions_client.calls) == 2
|
||||
|
||||
|
||||
def test_interactions_gemini_4_carbon_and_hydrogen_schema_and_limits():
|
||||
schema = interactions_nodes.SSL_GeminiInteractionsTextPrompt.define_schema()
|
||||
inputs = {inp.id: inp for inp in schema.inputs}
|
||||
|
||||
for model_id in (
|
||||
"gemini-4-carbon",
|
||||
"gemini-4-carbon-cyber-permissive",
|
||||
"gemini-4-hydrogen",
|
||||
):
|
||||
assert model_id in inputs["model"].options
|
||||
assert model_id in interactions_nodes.SSL_GeminiInteractionsTextPrompt.THINKING_MODELS
|
||||
assert model_id in interactions_nodes.SSL_GeminiInteractionsTextPrompt.GEN3_THINKING_MODELS
|
||||
|
||||
assert "xhigh" in inputs["thinking_level"].options
|
||||
assert inputs["max_output_tokens"].max == 1048576
|
||||
assert inputs["timeout"].max == 1200
|
||||
|
||||
|
||||
def test_interactions_gemini_4_carbon_and_hydrogen_thinking(mock_interactions_client):
|
||||
mock_interactions_client.responses = [
|
||||
{"text": "carbon", "thoughts": "t1", "id": "int_c1"},
|
||||
{"text": "carbon cyber", "thoughts": "t2", "id": "int_c2"},
|
||||
{"text": "hydrogen", "thoughts": "t3", "id": "int_h1"},
|
||||
{"text": "hydrogen xhigh", "thoughts": "t4", "id": "int_h2"},
|
||||
]
|
||||
|
||||
for idx, (model_id, selected, expected) in enumerate([
|
||||
("gemini-4-carbon", "None", "medium"),
|
||||
("gemini-4-carbon-cyber-permissive", "None", "high"),
|
||||
("gemini-4-hydrogen", "None", "high"),
|
||||
("gemini-4-hydrogen", "xhigh", "xhigh"),
|
||||
]):
|
||||
args = _execute_kwargs(
|
||||
model=model_id,
|
||||
thinking_level=selected,
|
||||
include_thoughts=True,
|
||||
max_output_tokens=131072,
|
||||
timeout=1200,
|
||||
)
|
||||
interactions_nodes.SSL_GeminiInteractionsTextPrompt.execute(**args)
|
||||
call = mock_interactions_client.calls[idx]
|
||||
assert call["model"] == model_id
|
||||
assert call["generation_config"]["thinking_level"] == expected
|
||||
assert call["generation_config"]["thinking_summaries"] == "auto"
|
||||
assert call["generation_config"]["max_output_tokens"] == 131072
|
||||
assert call["timeout"] == 1200.0
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("model_id", "bad_level"),
|
||||
[
|
||||
("gemini-4-carbon", "minimal"),
|
||||
("gemini-4-carbon-cyber-permissive", "minimal"),
|
||||
("gemini-4-hydrogen", "minimal"),
|
||||
("gemini-4-carbon", "xhigh"),
|
||||
("gemini-4-carbon-cyber-permissive", "xhigh"),
|
||||
("gemini-4-argon", "xhigh"),
|
||||
("gemini-3.8-flash", "xhigh"),
|
||||
("gemini-3.1-pro-preview", "xhigh"),
|
||||
],
|
||||
)
|
||||
def test_interactions_gemini_4_rejects_invalid_thinking_levels(mock_interactions_client, model_id, bad_level):
|
||||
args = _execute_kwargs(model=model_id, thinking_level=bad_level)
|
||||
with pytest.raises(ValueError):
|
||||
interactions_nodes.SSL_GeminiInteractionsTextPrompt.execute(**args)
|
||||
assert len(mock_interactions_client.calls) == 0
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user