Add legacy AILab node aliases

This commit is contained in:
Your Name
2026-05-16 10:55:35 +02:00
parent 9532eebb74
commit 2250058c4b
5 changed files with 111 additions and 61 deletions
+4
View File
@@ -1847,9 +1847,13 @@ class ThinkingLLM_QwenVL_Advanced(QwenVLBase):
NODE_CLASS_MAPPINGS = {
"ThinkingLLM_QwenVL": ThinkingLLM_QwenVL,
"ThinkingLLM_QwenVL_Advanced": ThinkingLLM_QwenVL_Advanced,
"AILab_QwenVL": ThinkingLLM_QwenVL,
"AILab_QwenVL_Advanced": ThinkingLLM_QwenVL_Advanced,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"ThinkingLLM_QwenVL": "ThinkingLLM",
"ThinkingLLM_QwenVL_Advanced": "ThinkingLLM Advanced",
"AILab_QwenVL": "ThinkingLLM",
"AILab_QwenVL_Advanced": "ThinkingLLM Advanced",
}
+4
View File
@@ -1528,9 +1528,13 @@ class ThinkingLLM_QwenVL_GGUF_Advanced(QwenVLGGUFBase):
NODE_CLASS_MAPPINGS = {
"ThinkingLLM_QwenVL_GGUF": ThinkingLLM_QwenVL_GGUF,
"ThinkingLLM_QwenVL_GGUF_Advanced": ThinkingLLM_QwenVL_GGUF_Advanced,
"AILab_QwenVL_GGUF": ThinkingLLM_QwenVL_GGUF,
"AILab_QwenVL_GGUF_Advanced": ThinkingLLM_QwenVL_GGUF_Advanced,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"ThinkingLLM_QwenVL_GGUF": "ThinkingLLM (GGUF)",
"ThinkingLLM_QwenVL_GGUF_Advanced": "ThinkingLLM Advanced (GGUF)",
"AILab_QwenVL_GGUF": "ThinkingLLM (GGUF)",
"AILab_QwenVL_GGUF_Advanced": "ThinkingLLM Advanced (GGUF)",
}
+2
View File
@@ -1060,8 +1060,10 @@ class ThinkingLLM_QwenVL_GGUF_PromptEnhancer:
NODE_CLASS_MAPPINGS = {
"ThinkingLLM_QwenVL_GGUF_PromptEnhancer": ThinkingLLM_QwenVL_GGUF_PromptEnhancer,
"AILab_QwenVL_GGUF_PromptEnhancer": ThinkingLLM_QwenVL_GGUF_PromptEnhancer,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"ThinkingLLM_QwenVL_GGUF_PromptEnhancer": "ThinkingLLM Prompt Enhancer (GGUF)",
"AILab_QwenVL_GGUF_PromptEnhancer": "ThinkingLLM Prompt Enhancer (GGUF)",
}
+2
View File
@@ -595,8 +595,10 @@ class ThinkingLLM_QwenVL_PromptEnhancer(QwenVLBase):
NODE_CLASS_MAPPINGS = {
"ThinkingLLM_QwenVL_PromptEnhancer": ThinkingLLM_QwenVL_PromptEnhancer,
"AILab_QwenVL_PromptEnhancer": ThinkingLLM_QwenVL_PromptEnhancer,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"ThinkingLLM_QwenVL_PromptEnhancer": "ThinkingLLM Prompt Enhancer",
"AILab_QwenVL_PromptEnhancer": "ThinkingLLM Prompt Enhancer",
}
+99 -61
View File
@@ -8,6 +8,7 @@ plain shell without ML dependencies.
"""
import ast
import json
import re
import unittest
from pathlib import Path
@@ -15,6 +16,7 @@ from pathlib import Path
HERE = Path(__file__).resolve().parent
PKG = HERE.parent
WORKFLOW_DIR = PKG / "example_workflows"
STREAMING_FILES = {
"AILab_QwenVL_GGUF_PromptEnhancer.py": {
@@ -44,6 +46,49 @@ def parse_source(filename: str) -> ast.AST:
return ast.parse(read_source(filename), filename=filename)
def extract_dict_node(filename: str, name: str) -> ast.Dict:
tree = parse_source(filename)
for node in tree.body:
if not isinstance(node, ast.Assign):
continue
for target in node.targets:
if isinstance(target, ast.Name) and target.id == name:
if isinstance(node.value, ast.Dict):
return node.value
raise AssertionError(f"{name} in {filename} is not a dict literal")
raise AssertionError(f"{name} not found in {filename}")
def extract_mapping_target_names(filename: str, name: str) -> dict[str, str]:
mapping = {}
dict_node = extract_dict_node(filename, name)
for key_node, value_node in zip(dict_node.keys, dict_node.values):
key = ast.literal_eval(key_node)
if isinstance(value_node, ast.Name):
mapping[key] = value_node.id
else:
raise AssertionError(f"{name}[{key}] in {filename} is not a direct name reference")
return mapping
def extract_literal_dict(filename: str, name: str) -> dict[str, str]:
mapping = {}
dict_node = extract_dict_node(filename, name)
for key_node, value_node in zip(dict_node.keys, dict_node.values):
key = ast.literal_eval(key_node)
mapping[key] = ast.literal_eval(value_node)
return mapping
def iter_workflow_types(path: Path) -> list[str]:
payload = json.loads(path.read_text(encoding="utf-8"))
return [
node.get("type")
for node in payload.get("nodes", [])
if isinstance(node, dict) and isinstance(node.get("type"), str)
]
class TestTerminalDisplayHelper(unittest.TestCase):
def test_helper_file_exists(self):
self.assertTrue((PKG / "AILab_StreamDisplay.py").exists())
@@ -163,70 +208,63 @@ class TestNoResidualGlobals(unittest.TestCase):
self.assertFalse(visitor.found)
class TestClassKeyUniqueness(unittest.TestCase):
"""ThinkingLLM class keys must NOT collide with Qwen3 AILab_* keys."""
def test_no_ailab_class_keys(self):
for filename in [
"AILab_QwenVL.py",
"AILab_QwenVL_GGUF.py",
"AILab_QwenVL_PromptEnhancer.py",
"AILab_QwenVL_GGUF_PromptEnhancer.py",
]:
source = read_source(filename)
mappings_block = re.search(
r"NODE_CLASS_MAPPINGS\s*=\s*\{(.*?)\}", source, re.DOTALL
)
if mappings_block:
keys = re.findall(r'"([^"]*)"', mappings_block.group(1))
with self.subTest(filename=filename):
self.assertTrue(
all(k.startswith("ThinkingLLM_") for k in keys),
f"{filename} contains non-ThinkingLLM_ class keys: {keys}",
)
def test_thinkingllm_prefix_on_all_class_names(self):
for filename in [
"AILab_QwenVL.py",
"AILab_QwenVL_GGUF.py",
"AILab_QwenVL_PromptEnhancer.py",
"AILab_QwenVL_GGUF_PromptEnhancer.py",
]:
tree = parse_source(filename)
class_names = [
node.name
for node in ast.walk(tree)
if isinstance(node, ast.ClassDef)
and node.name.startswith("ThinkingLLM_")
]
with self.subTest(filename=filename):
self.assertTrue(
len(class_names) >= 1,
f"{filename} has no ThinkingLLM_* class definitions",
)
class TestThinkingAndStreamToggles(unittest.TestCase):
"""All 6 node classes must expose enable_thinking and stream_tokens_to_terminal."""
NODE_SOURCES = {
"AILab_QwenVL.py",
"AILab_QwenVL_GGUF.py",
"AILab_QwenVL_PromptEnhancer.py",
"AILab_QwenVL_GGUF_PromptEnhancer.py",
class TestLegacyNodeNameCompatibility(unittest.TestCase):
CURRENT_TO_LEGACY = {
"ThinkingLLM_QwenVL": "AILab_QwenVL",
"ThinkingLLM_QwenVL_Advanced": "AILab_QwenVL_Advanced",
"ThinkingLLM_QwenVL_GGUF": "AILab_QwenVL_GGUF",
"ThinkingLLM_QwenVL_GGUF_Advanced": "AILab_QwenVL_GGUF_Advanced",
"ThinkingLLM_QwenVL_PromptEnhancer": "AILab_QwenVL_PromptEnhancer",
"ThinkingLLM_QwenVL_GGUF_PromptEnhancer": "AILab_QwenVL_GGUF_PromptEnhancer",
}
def test_thinking_toggle_present_on_all_nodes(self):
for filename in self.NODE_SOURCES:
source = read_source(filename)
with self.subTest(filename=filename):
self.assertIn('"enable_thinking": ("BOOLEAN"', source)
MAPPING_FILES = {
"AILab_QwenVL.py": [
"ThinkingLLM_QwenVL",
"ThinkingLLM_QwenVL_Advanced",
],
"AILab_QwenVL_GGUF.py": [
"ThinkingLLM_QwenVL_GGUF",
"ThinkingLLM_QwenVL_GGUF_Advanced",
],
"AILab_QwenVL_PromptEnhancer.py": [
"ThinkingLLM_QwenVL_PromptEnhancer",
],
"AILab_QwenVL_GGUF_PromptEnhancer.py": [
"ThinkingLLM_QwenVL_GGUF_PromptEnhancer",
],
}
def test_stream_toggle_present_on_all_nodes(self):
for filename in self.NODE_SOURCES:
source = read_source(filename)
with self.subTest(filename=filename):
self.assertIn('"stream_tokens_to_terminal": ("BOOLEAN"', source)
def test_current_names_remain_canonical(self):
for filename, canonical_names in self.MAPPING_FILES.items():
mappings = extract_mapping_target_names(filename, "NODE_CLASS_MAPPINGS")
for canonical_name in canonical_names:
with self.subTest(filename=filename, canonical_name=canonical_name):
self.assertIn(canonical_name, mappings)
def test_legacy_aliases_map_to_same_runtime_class_name(self):
for filename, canonical_names in self.MAPPING_FILES.items():
mappings = extract_mapping_target_names(filename, "NODE_CLASS_MAPPINGS")
displays = extract_literal_dict(filename, "NODE_DISPLAY_NAME_MAPPINGS")
for canonical_name in canonical_names:
legacy_name = self.CURRENT_TO_LEGACY[canonical_name]
with self.subTest(filename=filename, legacy_name=legacy_name):
self.assertEqual(mappings[canonical_name], mappings[legacy_name])
self.assertEqual(displays[canonical_name], displays[legacy_name])
def test_example_workflow_legacy_types_are_covered(self):
resolved_types = set()
for filename in self.MAPPING_FILES:
mappings = extract_mapping_target_names(filename, "NODE_CLASS_MAPPINGS")
resolved_types.update(mappings.keys())
workflow_files = sorted(WORKFLOW_DIR.glob("*.json"))
self.assertTrue(workflow_files, "expected shipped example workflows")
for workflow_file in workflow_files:
for node_type in iter_workflow_types(workflow_file):
if node_type.startswith("AILab_QwenVL"):
with self.subTest(workflow=workflow_file.name, node_type=node_type):
self.assertIn(node_type, resolved_types)
class _GlobalCheck(ast.NodeVisitor):