Compare commits

...
4 Commits
Author SHA1 Message Date
Vito 9a55c23282 Merge pull request #70 from ComfyAssets/release/1.1.1
Release 1.1.1
2026-10-13 18:36:23 -07:00
Vito Sansevero e90bde5849 chore: release 1.1.1
Gemini Prompt node migrated from the deprecated google-generativeai SDK to google-genai.
2026-10-13 18:29:24 -07:00
Vito Sansevero 49523e10de chore(gemini_prompt): migrate to google-genai SDK
- google-generativeai is deprecated and emits a FutureWarning at node
  load; google-genai is Google's supported replacement
- Image analysis now uses genai.Client().models.generate_content
  instead of the module-level configure/GenerativeModel API
- Model listing reads supported_actions, the new SDK's name for
  supported_generation_methods
- Install instructions, requirements.txt, and README updated to the
  new package name
2026-10-13 18:27:52 -07:00
Vito 8e6cf4233a Merge pull request #69 from ComfyAssets/release/1.1.0
Release 1.1.0
2026-10-13 08:12:57 -07:00
9 changed files with 53 additions and 55 deletions
+1 -1
View File
@@ -1067,7 +1067,7 @@ MIT License - see [LICENSE](LICENSE) file for details.
- **Test Coverage**: 100% (470+ comprehensive tests)
- **Python Version**: 3.8+
- **ComfyUI Compatibility**: Latest
- **Dependencies**: Minimal (PyTorch, NumPy, Pillow, google-generativeai for Gemini)
- **Dependencies**: Minimal (PyTorch, NumPy, Pillow, google-genai for Gemini)
## 🙏 Attribution
+3 -3
View File
@@ -21,7 +21,7 @@ Get your free Gemini API key from [Google AI Studio](https://makersuite.google.c
### 2. Install Dependencies
```bash
pip install google-generativeai
pip install google-genai
```
### 3. Configure API Key
@@ -204,6 +204,6 @@ Errors are displayed in the prompt output for easy debugging.
- Verify API key is valid
- Image might be too large (resize if needed)
**Import error for google-generativeai**:
- Run `pip install google-generativeai` in your ComfyUI environment
**Import error for google-genai**:
- Run `pip install google-genai` in your ComfyUI environment
- Restart ComfyUI after installation
+7 -13
View File
@@ -115,30 +115,24 @@ def analyze_image_with_gemini(
else:
system_prompt = PROMPT_TEMPLATES.get(prompt_type, PROMPT_TEMPLATES["flux"])
# Here we would normally make the API call to Gemini
# For now, we'll import the google-generativeai library
try:
import google.generativeai as genai
from google import genai
except ImportError:
return (
"",
"google-generativeai library not installed. Please run: pip install google-generativeai",
"google-genai library not installed. Please run: pip install google-genai",
)
try:
# Configure Gemini
genai.configure(api_key=api_key)
client = genai.Client(api_key=api_key)
# Create model
model = genai.GenerativeModel(model_name)
# Generate content
response = model.generate_content(
[
response = client.models.generate_content(
model=model_name,
contents=[
system_prompt,
pil_image,
"Analyze this image and generate an appropriate prompt according to the instructions.",
]
],
)
# Extract text from response
+5 -5
View File
@@ -59,10 +59,10 @@ def _fetch_models_from_api(
Tuple of (model_names_list, model_descriptions_dict)
"""
try:
import google.generativeai as genai
from google import genai
except ImportError:
if not silent:
logger.error("google-generativeai not installed")
logger.error("google-genai not installed")
return [], {}
# Get API key
@@ -77,15 +77,15 @@ def _fetch_models_from_api(
return [], {}
try:
genai.configure(api_key=api_key)
client = genai.Client(api_key=api_key)
models = []
descriptions = {}
# Fetch all models
for model in genai.list_models():
for model in client.models.list():
# Only include models that support generateContent
if "generateContent" in model.supported_generation_methods:
if "generateContent" in (model.supported_actions or []):
# Remove "models/" prefix from name
model_name = model.name.replace("models/", "")
models.append(model_name)
+1 -1
View File
@@ -63,7 +63,7 @@ Supports multiple prompt formats:
- Video: Motion and temporal descriptions for video generation
Requires Gemini API key (set GEMINI_API_KEY env var or provide in node).
Install: pip install google-generativeai
Install: pip install google-genai
"""
def generate_prompt(
+1 -1
View File
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "kikotools"
description = "Simple tools for ComfyUI"
version = "1.1.0"
version = "1.1.1"
license = {text = "MIT"}
dependencies = []
+1 -1
View File
@@ -1,4 +1,4 @@
# Runtime dependencies for ComfyUI-KikoTools
# Gemini API integration (optional - only needed for Gemini Prompt node)
google-generativeai
google-genai
+4 -4
View File
@@ -187,10 +187,10 @@ class TestGeminiLogic:
assert validate_prompt_type("") is False
assert validate_prompt_type(None) is False
@pytest.mark.skip(reason="Requires google-generativeai library")
@pytest.mark.skip(reason="Requires google-genai library")
def test_analyze_image_with_gemini_success(self):
"""Test successful image analysis with Gemini."""
pass # Skipped as it requires google-generativeai
pass # Skipped as it requires google-genai
def test_analyze_image_no_api_key(self):
"""Test analysis without API key."""
@@ -204,10 +204,10 @@ class TestGeminiLogic:
assert result == ""
assert "API key not found" in error
@pytest.mark.skip(reason="Requires google-generativeai library")
@pytest.mark.skip(reason="Requires google-genai library")
def test_analyze_image_with_custom_prompt(self):
"""Test analysis with custom prompt."""
pass # Skipped as it requires google-generativeai
pass # Skipped as it requires google-genai
class TestPromptTemplates:
+30 -26
View File
@@ -24,17 +24,22 @@ def cache_file(tmp_path, monkeypatch):
def fake_genai(model_specs):
genai = MagicMock()
genai.list_models.return_value = [
genai.Client.return_value.models.list.return_value = [
SimpleNamespace(
name=f"models/{name}",
display_name=display,
supported_generation_methods=methods,
supported_actions=methods,
)
for name, display, methods in model_specs
]
return genai
def sdk_modules(genai):
"""sys.modules patch that makes `from google import genai` return genai."""
return {"google": MagicMock(genai=genai), "google.genai": genai}
class TestSortModels:
def test_newer_versions_come_first(self):
result = models._sort_models(
@@ -111,27 +116,25 @@ class TestCache:
class TestFetchModelsFromApi:
def test_missing_sdk_returns_empty(self):
with patch.dict(sys.modules, {"google.generativeai": None, "google": None}):
with patch.dict(sys.modules, {"google.genai": None, "google": None}):
assert models._fetch_models_from_api("k") == ([], {})
def test_no_api_key_returns_empty(self):
genai = fake_genai([])
google = MagicMock(generativeai=genai)
with (
patch.dict(sys.modules, {"google": google, "google.generativeai": genai}),
patch.dict(sys.modules, sdk_modules(genai)),
patch.object(logic, "get_api_key", return_value=None),
):
assert models._fetch_models_from_api(None, silent=True) == ([], {})
def test_api_key_taken_from_logic_when_not_given(self):
genai = fake_genai([("gemini-2.5-pro", "Pro", ["generateContent"])])
google = MagicMock(generativeai=genai)
with (
patch.dict(sys.modules, {"google": google, "google.generativeai": genai}),
patch.dict(sys.modules, sdk_modules(genai)),
patch.object(logic, "get_api_key", return_value="envkey"),
):
names, _ = models._fetch_models_from_api(None)
genai.configure.assert_called_once_with(api_key="envkey")
genai.Client.assert_called_once_with(api_key="envkey")
assert names == ["gemini-2.5-pro"]
def test_filters_to_generate_content_and_strips_prefix(self):
@@ -142,17 +145,15 @@ class TestFetchModelsFromApi:
("gemini-2.5-flash", "Flash", ["generateContent", "countTokens"]),
]
)
google = MagicMock(generativeai=genai)
with patch.dict(sys.modules, {"google": google, "google.generativeai": genai}):
with patch.dict(sys.modules, sdk_modules(genai)):
names, desc = models._fetch_models_from_api("k")
assert names == ["gemini-2.5-flash", "gemini-1.5-pro"]
assert desc == {"gemini-1.5-pro": "Pro 1.5", "gemini-2.5-flash": "Flash"}
def test_api_error_returns_empty(self):
genai = MagicMock()
genai.list_models.side_effect = RuntimeError("boom")
google = MagicMock(generativeai=genai)
with patch.dict(sys.modules, {"google": google, "google.generativeai": genai}):
genai.Client.return_value.models.list.side_effect = RuntimeError("boom")
with patch.dict(sys.modules, sdk_modules(genai)):
assert models._fetch_models_from_api("k") == ([], {})
@@ -205,20 +206,19 @@ class TestLogicExtra:
def test_missing_sdk_reported(self):
img = np.zeros((1, 4, 4, 3), dtype=np.float32)
with patch.dict(sys.modules, {"google.generativeai": None, "google": None}):
with patch.dict(sys.modules, {"google.genai": None, "google": None}):
out, err = logic.analyze_image_with_gemini(img, "flux", api_key="k")
assert out == "" and "not installed" in err
def _run(self, response=None, error=None, **kwargs):
genai = MagicMock()
model = genai.GenerativeModel.return_value
client_models = genai.Client.return_value.models
if error:
model.generate_content.side_effect = error
client_models.generate_content.side_effect = error
else:
model.generate_content.return_value = response
google = MagicMock(generativeai=genai)
client_models.generate_content.return_value = response
img = np.zeros((1, 4, 4, 3), dtype=np.float32)
with patch.dict(sys.modules, {"google": google, "google.generativeai": genai}):
with patch.dict(sys.modules, sdk_modules(genai)):
result = logic.analyze_image_with_gemini(img, "flux", api_key="k", **kwargs)
return result, genai
@@ -227,24 +227,28 @@ class TestLogicExtra:
SimpleNamespace(text=" hello \n"), model_name="gemini-x"
)
assert (out, err) == ("hello", None)
genai.configure.assert_called_once_with(api_key="k")
genai.GenerativeModel.assert_called_once_with("gemini-x")
genai.Client.assert_called_once_with(api_key="k")
call = genai.Client.return_value.models.generate_content.call_args
assert call.kwargs["model"] == "gemini-x"
def test_custom_prompt_is_sent_as_system_prompt(self):
(_, _), genai = self._run(SimpleNamespace(text="x"), custom_prompt="BE TERSE")
sent = genai.GenerativeModel.return_value.generate_content.call_args[0][0]
sent = genai.Client.return_value.models.generate_content.call_args.kwargs[
"contents"
]
assert sent[0] == "BE TERSE"
def test_unknown_prompt_type_falls_back_to_flux_template(self):
genai = MagicMock()
genai.GenerativeModel.return_value.generate_content.return_value = (
genai.Client.return_value.models.generate_content.return_value = (
SimpleNamespace(text="x")
)
google = MagicMock(generativeai=genai)
img = np.zeros((1, 4, 4, 3), dtype=np.float32)
with patch.dict(sys.modules, {"google": google, "google.generativeai": genai}):
with patch.dict(sys.modules, sdk_modules(genai)):
logic.analyze_image_with_gemini(img, "nonsense", api_key="k")
sent = genai.GenerativeModel.return_value.generate_content.call_args[0][0]
sent = genai.Client.return_value.models.generate_content.call_args.kwargs[
"contents"
]
assert sent[0] == logic.PROMPT_TEMPLATES["flux"]
def test_empty_response(self):