Refine workflow model reference detection
- Accept explicit model URNs without requiring model widget metadata - Restrict model scanning to widgets with category/model hints or model-like filenames - Remove over-broad single-widget-value detection path - Add tests for cached model text, custom selectors, and constant/model slot edge cases
This commit is contained in:
+18
-13
@@ -1012,17 +1012,11 @@ def get_model_widget_category_hints(
|
||||
dynamic_category_hints = get_dynamic_widget_category_hints(node, widget_index)
|
||||
|
||||
output_category_hint = get_node_output_category_hint(node)
|
||||
widgets_values = node.get("widgets_values", [])
|
||||
has_single_widget_value = len(widgets_values) == 1
|
||||
output_widget_category_hint = (
|
||||
output_category_hint
|
||||
if (
|
||||
not dynamic_category_hints
|
||||
and (
|
||||
indexed_category_hint
|
||||
or widget_category_hint
|
||||
or has_single_widget_value
|
||||
)
|
||||
and (indexed_category_hint or widget_category_hint)
|
||||
)
|
||||
else None
|
||||
)
|
||||
@@ -1068,13 +1062,16 @@ def is_placeholder_model_value(value: Any) -> bool:
|
||||
|
||||
|
||||
def should_scan_as_model_reference(value: Any, declared_model_widget: bool) -> bool:
|
||||
"""Detect model references, including extensionless known model widgets."""
|
||||
if is_model_filename(value):
|
||||
"""Detect references only when the value or its widget provides model evidence."""
|
||||
if isinstance(value, str) and URN_REGEX.match(value.strip()):
|
||||
return True
|
||||
|
||||
if not declared_model_widget or not isinstance(value, str):
|
||||
return False
|
||||
|
||||
if is_model_filename(value):
|
||||
return True
|
||||
|
||||
return bool(value.strip()) and not is_placeholder_model_value(value)
|
||||
|
||||
|
||||
@@ -1268,8 +1265,8 @@ def get_node_model_info(
|
||||
"""
|
||||
Extract model references from a single node.
|
||||
|
||||
This scans all widgets_values entries and tries to identify which ones
|
||||
are model file references by attempting to resolve them.
|
||||
This scans widget values backed by model metadata or a model-like widget name.
|
||||
Explicit model URNs are also accepted without widget metadata.
|
||||
|
||||
Args:
|
||||
node: Node dictionary from workflow JSON
|
||||
@@ -1454,11 +1451,19 @@ def get_node_model_info(
|
||||
if model_widget_category_hints
|
||||
else ([effective_category_hint] if effective_category_hint else None)
|
||||
)
|
||||
category_backed_model_widget = bool(
|
||||
model_widget_category_hint
|
||||
or schema_output_category_hint
|
||||
)
|
||||
named_model_file_widget = bool(
|
||||
is_workflow_model_widget_candidate(node, idx)
|
||||
and is_model_filename(value)
|
||||
)
|
||||
|
||||
if not should_scan_as_model_reference(
|
||||
value,
|
||||
declared_model_widget=bool(
|
||||
model_widget_category_hint or schema_output_category_hint
|
||||
declared_model_widget=(
|
||||
category_backed_model_widget or named_model_file_widget
|
||||
),
|
||||
):
|
||||
# Check for dict-type widget values containing model references (e.g. Power Lora Loader)
|
||||
|
||||
@@ -80,6 +80,135 @@ class WorkflowAnalyzerCaseSensitivityTests(unittest.TestCase):
|
||||
|
||||
|
||||
class WorkflowAnalyzerCategoryHintTests(unittest.TestCase):
|
||||
def test_show_anything_cached_model_text_is_not_a_model_reference(self):
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
model_dir = os.path.join(tmpdir, "KREA2")
|
||||
os.makedirs(model_dir)
|
||||
model_path = os.path.join(
|
||||
model_dir, "krea2_turbo_fp8_scaled.safetensors"
|
||||
)
|
||||
with open(model_path, "wb"):
|
||||
pass
|
||||
|
||||
workflow = {
|
||||
"nodes": [
|
||||
{
|
||||
"id": 141,
|
||||
"type": "easy showAnything",
|
||||
"title": "Diffusion model",
|
||||
"inputs": [{"name": "anything", "type": "*", "link": 700}],
|
||||
"outputs": [{"name": "output", "type": "*", "links": []}],
|
||||
"widgets": [{"name": "text"}],
|
||||
"widgets_values": [
|
||||
r"KREA2\krea2_turbo_fp8_scaled.safetensors"
|
||||
],
|
||||
}
|
||||
]
|
||||
}
|
||||
available_models = [
|
||||
{
|
||||
"filename": "krea2_turbo_fp8_scaled.safetensors",
|
||||
"path": model_path,
|
||||
"relative_path": r"KREA2\krea2_turbo_fp8_scaled.safetensors",
|
||||
"category": "diffusion_models",
|
||||
"base_directory": tmpdir,
|
||||
}
|
||||
]
|
||||
|
||||
refs = analyze_workflow_models(
|
||||
workflow, available_models=available_models
|
||||
)
|
||||
|
||||
self.assertEqual([], refs)
|
||||
|
||||
def test_model_like_widget_name_keeps_custom_model_reference_detection(self):
|
||||
workflow = {
|
||||
"nodes": [
|
||||
{
|
||||
"id": 199,
|
||||
"type": "CustomModelSelector",
|
||||
"widgets": [{"name": "filename"}],
|
||||
"widgets_values": ["custom_model.safetensors"],
|
||||
"outputs": [],
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
refs = analyze_workflow_models(workflow, available_models=[])
|
||||
|
||||
self.assertEqual(1, len(refs))
|
||||
self.assertEqual("custom_model.safetensors", refs[0]["original_path"])
|
||||
|
||||
def test_set_node_model_slot_name_does_not_turn_constant_into_model(self):
|
||||
workflow = {
|
||||
"nodes": [
|
||||
{
|
||||
"id": 3,
|
||||
"type": "SetNode",
|
||||
"title": "Set_MODEL_BASE",
|
||||
"inputs": [{"name": "MODEL", "type": "MODEL", "link": 701}],
|
||||
"outputs": [{"name": "MODEL", "type": "MODEL", "links": []}],
|
||||
"widgets": [{"name": "Constant"}],
|
||||
"widgets_values": ["MODEL_BASE"],
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"type": "SetNode",
|
||||
"title": "Set_MODEL_LORA",
|
||||
"inputs": [{"name": "MODEL", "type": "MODEL", "link": 702}],
|
||||
"outputs": [{"name": "MODEL", "type": "MODEL", "links": []}],
|
||||
"widgets": [{"name": "Constant"}],
|
||||
"widgets_values": ["MODEL_LORA"],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
refs = analyze_workflow_models(workflow, available_models=[])
|
||||
|
||||
self.assertEqual([], refs)
|
||||
|
||||
def test_model_slot_does_not_turn_device_widget_into_model(self):
|
||||
workflow = {
|
||||
"nodes": [
|
||||
{
|
||||
"id": 104,
|
||||
"type": "CustomVideoUpscaler",
|
||||
"inputs": [
|
||||
{"name": "model", "type": "MODEL", "link": 703},
|
||||
{
|
||||
"name": "offload_device",
|
||||
"type": "COMBO",
|
||||
"widget": {"name": "offload_device"},
|
||||
},
|
||||
],
|
||||
"outputs": [{"name": "IMAGE", "type": "IMAGE", "links": []}],
|
||||
"widgets": [{"name": "offload_device"}],
|
||||
"widgets_values": ["cuda:0"],
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
refs = analyze_workflow_models(workflow, available_models=[])
|
||||
|
||||
self.assertEqual([], refs)
|
||||
|
||||
def test_filename_widget_template_without_model_extension_is_ignored(self):
|
||||
workflow = {
|
||||
"nodes": [
|
||||
{
|
||||
"id": 155,
|
||||
"type": "Image Saver",
|
||||
"widgets": [{"name": "filename"}],
|
||||
"widgets_values": ["Krea2_%time_%seed"],
|
||||
"outputs": [],
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
refs = analyze_workflow_models(workflow, available_models=[])
|
||||
|
||||
self.assertEqual([], refs)
|
||||
|
||||
def test_upscale_model_loader_widget_named_model_name_stays_upscale(self):
|
||||
workflow = {
|
||||
"nodes": [
|
||||
|
||||
Reference in New Issue
Block a user