Files
goodguy1963-ComfyUI-Thinkin…/__init__.py
T
Marcel Hofer da97ff6544 release: prepare v3.0.1
Decision Router memory and explanation controls

Add keep_model_loaded to the image and text routers. It defaults to releasing the llama.cpp model and MTMD handler after routing, including errors and empty batches; users can retain the model for faster queue reuse.

Add an opt-in include_reason pass that records a bounded post-hoc explanation in decision_json. The native decision head remains authoritative for route selection and probabilities. Explanation errors are reported without failing routing.

Document both controls and bump package, node registry, and runtime metadata to 3.0.1.

Validation: 227 tests passed in the bundled ComfyUI Python runtime (17.62 seconds), including 29 Decision Router tests. Version metadata parsing and git diff --check passed.

Limitation: explanation generation has not been smoke-tested with downloaded model weights; model-specific chat compatibility and added latency may vary.
2026-10-29 02:17:57 +01:00

118 lines
4.1 KiB
Python

"""
{
"name": "ComfyUI-ThinkingLLM",
"description": "A multimodal ComfyUI AI node pack with Qwen, Gemma 4, GGUF, Whisper, image and text decision routing, and live thinking output.",
"author": "goodguy1963",
"version": "3.0.1",
"url": "https://github.com/goodguy1963/ComfyUI-ThinkingLLM",
"category": "image"
}
"""
import importlib.util
import os
import sys
# Get the directory of the current script
current_dir = os.path.dirname(__file__)
sys.path.insert(0, current_dir)
NODE_CLASS_MAPPINGS = {}
NODE_DISPLAY_NAME_MAPPINGS = {}
COMMERCIAL_RELEASE = os.environ.get("THINKINGLLM_COMMERCIAL_RELEASE") == "1"
WEB_DIRECTORY = "./web_commercial" if COMMERCIAL_RELEASE else "./web"
COMMERCIAL_MODULES = {
"AILab_OutputCleaner",
"AILab_QwenVL",
"AILab_QwenVL_PromptEnhancer",
"AILab_StreamDisplay",
}
COMMERCIAL_NODE_MODULES = {
"decision_router",
"story_split_node",
"system_prompt_preset",
"vram_cleanup",
}
LEGACY_NODE_REPLACEMENTS = {
"AILab_QwenVL": "ThinkingLLM_QwenVL",
"AILab_QwenVL_Advanced": "ThinkingLLM_QwenVL_Advanced",
"AILab_QwenVL_GGUF": "ThinkingLLM_QwenVL_GGUF",
"AILab_QwenVL_GGUF_Advanced": "ThinkingLLM_QwenVL_GGUF_Advanced",
"AILab_QwenVL_PromptEnhancer": "ThinkingLLM_QwenVL_PromptEnhancer",
"AILab_QwenVL_GGUF_PromptEnhancer": "ThinkingLLM_QwenVL_GGUF_PromptEnhancer",
}
if COMMERCIAL_RELEASE:
LEGACY_NODE_REPLACEMENTS = {
old: new for old, new in LEGACY_NODE_REPLACEMENTS.items() if "GGUF" not in old
}
def load_modules_from_directory(directory):
for file in os.listdir(directory):
if file.endswith(".py"):
file_path = os.path.join(directory, file)
module_name = os.path.basename(file)[:-3]
if module_name == os.path.basename(__file__)[:-3]:
continue
if COMMERCIAL_RELEASE:
allowed = (
COMMERCIAL_MODULES
if os.path.abspath(directory) == os.path.abspath(current_dir)
else COMMERCIAL_NODE_MODULES
)
if module_name not in allowed:
continue
try:
spec = importlib.util.spec_from_file_location(module_name, file_path)
if spec is None or spec.loader is None:
raise ImportError(f"Unable to create import spec for {module_name}")
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)
if hasattr(module, "NODE_CLASS_MAPPINGS"):
NODE_CLASS_MAPPINGS.update(module.NODE_CLASS_MAPPINGS)
if hasattr(module, "NODE_DISPLAY_NAME_MAPPINGS"):
NODE_DISPLAY_NAME_MAPPINGS.update(module.NODE_DISPLAY_NAME_MAPPINGS)
except Exception as e:
print(f"Error loading module {module_name}: {e}")
load_modules_from_directory(current_dir)
# Also load from nodes subdirectory
nodes_dir = os.path.join(current_dir, "nodes")
if os.path.exists(nodes_dir):
load_modules_from_directory(nodes_dir)
for legacy_node_name in LEGACY_NODE_REPLACEMENTS:
NODE_CLASS_MAPPINGS.pop(legacy_node_name, None)
NODE_DISPLAY_NAME_MAPPINGS.pop(legacy_node_name, None)
def _register_legacy_node_replacements():
try:
from comfy_api.latest._io import NodeReplace
from server import PromptServer
except Exception:
return
manager = getattr(getattr(PromptServer, "instance", None), "node_replace_manager", None)
if manager is None:
return
for old_node_id, new_node_id in LEGACY_NODE_REPLACEMENTS.items():
manager.register(NodeReplace(new_node_id=new_node_id, old_node_id=old_node_id))
_register_legacy_node_replacements()
NODE_CLASS_MAPPINGS = dict(sorted(NODE_CLASS_MAPPINGS.items(), key=lambda x: NODE_DISPLAY_NAME_MAPPINGS.get(x[0], x[0])))
NODE_DISPLAY_NAME_MAPPINGS = dict(sorted(NODE_DISPLAY_NAME_MAPPINGS.items(), key=lambda x: x[1]))
__all__ = [
"NODE_CLASS_MAPPINGS",
"NODE_DISPLAY_NAME_MAPPINGS",
"LEGACY_NODE_REPLACEMENTS",
]