Files
goodguy1963-ComfyUI-Thinkin…/tests/test_new_features.py
T

2761 lines
119 KiB
Python

"""Lightweight regression tests for the local ThinkingLLM fork.
Run from the ComfyUI-ThinkingLLM folder:
python tests/test_new_features.py
These tests intentionally avoid importing the model modules so they can run in a
plain shell without ML dependencies.
"""
import ast
import base64
import contextlib
import importlib.util
import inspect
import io
import json
import os
import re
import shutil
import subprocess
import sys
import tempfile
import types
import unittest
import wave
from pathlib import Path
from unittest import mock
HERE = Path(__file__).resolve().parent
PKG = HERE.parent
WORKFLOW_DIR = PKG / "example_workflows"
STREAMING_FILES = {
"AILab_QwenVL_GGUF_PromptEnhancer.py": {
"stage": "INITIAL GENERATION",
"label": "QwenVL GGUF",
},
"AILab_QwenVL_PromptEnhancer.py": {
"stage": "HF TEXT GENERATION",
"label": "QwenVL HF",
},
"AILab_QwenVL.py": {
"stage": None,
"label": "QwenVL HF",
},
"AILab_QwenVL_GGUF.py": {
"stage": "STREAMING",
"label": "QwenVL GGUF",
},
}
def read_source(filename: str) -> str:
return (PKG / filename).read_text(encoding="utf-8")
def parse_source(filename: str) -> ast.AST:
return ast.parse(read_source(filename), filename=filename)
def build_stub_module(name: str, **attrs):
module = types.ModuleType(name)
for key, value in attrs.items():
setattr(module, key, value)
return module
class _StubQuantization:
FP16 = types.SimpleNamespace(value="fp16")
@classmethod
def get_values(cls):
return [cls.FP16.value]
def build_loader_test_stubs() -> dict[str, types.ModuleType]:
pil_image = build_stub_module("PIL.Image", Image=object)
pil_package = build_stub_module("PIL", Image=pil_image)
comfy_model_management = build_stub_module(
"comfy.model_management",
get_torch_device=lambda: types.SimpleNamespace(type="cuda", index=0),
throw_exception_if_processing_interrupted=lambda: None,
)
comfy_package = build_stub_module("comfy", model_management=comfy_model_management)
output_clean_config = type("OutputCleanConfig", (), {"__init__": lambda self, *args, **kwargs: None})
terminal_stream_display = type("TerminalStreamDisplay", (), {})
stream_degeneration_error = type("StreamDegenerationError", (RuntimeError,), {})
qwen_base = type("QwenVLBase", (), {"__init__": lambda self, *args, **kwargs: None})
return {
"numpy": build_stub_module("numpy", ndarray=object),
"torch": build_stub_module(
"torch",
Tensor=object,
dtype=object,
float16=object,
float32=object,
device=type("device", (), {}),
cuda=types.SimpleNamespace(device_count=lambda: 0, is_available=lambda: False),
backends=types.SimpleNamespace(mps=types.SimpleNamespace(is_available=lambda: False)),
no_grad=lambda func=None, *args, **kwargs: (lambda inner: inner) if func is None else func,
inference_mode=lambda func=None, *args, **kwargs: (lambda inner: inner) if func is None else func,
),
"huggingface_hub": build_stub_module(
"huggingface_hub",
hf_hub_download=lambda *args, **kwargs: "",
snapshot_download=lambda *args, **kwargs: "",
),
"PIL": pil_package,
"PIL.Image": pil_image,
"folder_paths": build_stub_module(
"folder_paths",
models_dir=str(PKG),
folder_names_and_paths={},
get_folder_paths=lambda *args, **kwargs: [],
),
"AILab_StreamDisplay": build_stub_module(
"AILab_StreamDisplay",
StreamDegenerationError=stream_degeneration_error,
StreamDegenerationGuard=type("StreamDegenerationGuard", (), {"__init__": lambda self, *args, **kwargs: None, "push": lambda self, text: None}),
TerminalStreamDisplay=terminal_stream_display,
extract_stream_token=lambda chunk: {"reasoning": "", "content": (chunk.get("choices") or [{}])[0].get("delta", {}).get("content", "")},
strip_degenerate_repetition=lambda text, *args, **kwargs: text,
),
"AILab_LlamaCppInstaller": build_stub_module(
"AILab_LlamaCppInstaller",
ensure_llama_cpp_backend=lambda *args, **kwargs: object(),
format_llama_cpp_backend_info=lambda info=None: "stub backend",
get_last_llama_cpp_backend_info=lambda: {"gpu_offload": None, "vision_handlers": ["Qwen3VLChatHandler"]},
relax_windows_dll_directory_for_long_paths=contextlib.nullcontext,
),
"AILab_OutputCleaner": build_stub_module(
"AILab_OutputCleaner",
OutputCleanConfig=output_clean_config,
clean_model_output=lambda text, *args, **kwargs: text,
prompt_output_guard=lambda text, *args, **kwargs: text,
),
"transformers": build_stub_module(
"transformers",
AutoModelForCausalLM=object,
AutoModelForVision2Seq=object,
AutoModelForImageTextToText=object,
AutoProcessor=object,
AutoTokenizer=object,
BitsAndBytesConfig=type("BitsAndBytesConfig", (), {}),
),
"comfy": comfy_package,
"comfy.model_management": comfy_model_management,
"AILab_QwenVL": build_stub_module(
"AILab_QwenVL",
ATTENTION_MODES=["auto"],
HF_ALL_MODELS={"stub-model": {}},
HF_TEXT_MODELS={},
HF_VL_MODELS={},
NODE_PROMPT_STATE={},
PROMPT_CACHE={},
_make_node_state_key=lambda *args, **kwargs: "node-key",
_build_workflow_fingerprint=lambda *args, **kwargs: "workflow-fingerprint",
_default_model_from_config=lambda models, fallback: next(
(
name
for name, info in models.items()
if isinstance(info, dict) and info.get("default")
),
next(iter(models.keys()), fallback),
),
apply_qwen_soft_thinking_directive=lambda *args, **kwargs: None,
build_node_input_signature=lambda *args, **kwargs: {},
download_hf_file_to_path=lambda *args, **kwargs: None,
ensure_model=lambda *args, **kwargs: None,
ensure_cuda_vram_headroom=lambda *args, **kwargs: None,
estimate_qwen_text_tokens=lambda *args, **kwargs: 0,
get_cache_key=lambda *args, **kwargs: "cache-key",
get_alternative_cache_key=lambda *args, **kwargs: "alt-cache-key",
get_image_hash=lambda *args, **kwargs: "image-hash",
get_video_hash=lambda *args, **kwargs: "video-hash",
save_prompt_cache=lambda *args, **kwargs: None,
get_node_saved_prompt=lambda *args, **kwargs: None,
get_node_saved_prompt_with_seed=lambda *args, **kwargs: None,
resolve_qwen_thinking_mode=lambda *args, **kwargs: False,
resolve_qwen_context_window=lambda *args, **kwargs: 0,
set_node_saved_prompt=lambda *args, **kwargs: None,
load_node_prompt_state=lambda *args, **kwargs: None,
QwenVLBase=qwen_base,
Quantization=_StubQuantization,
TOOLTIPS={},
),
}
def load_thinkingllm_loader_subset(module_filenames: list[str]):
package_name = "thinkingllm_loader_smoke"
tracked_names = [package_name, *[Path(filename).stem for filename in module_filenames]]
previous_modules = {
name: sys.modules[name]
for name in tracked_names
if name in sys.modules
}
real_listdir = os.listdir
pkg_path = PKG.resolve()
nodes_path = (PKG / "nodes").resolve()
def fake_listdir(path):
resolved = Path(path).resolve()
if resolved == pkg_path:
return ["__init__.py", *module_filenames]
if resolved == nodes_path:
return []
return real_listdir(path)
try:
with mock.patch.dict(sys.modules, build_loader_test_stubs(), clear=False):
with mock.patch("os.listdir", side_effect=fake_listdir):
spec = importlib.util.spec_from_file_location(package_name, PKG / "__init__.py")
if spec is None or spec.loader is None:
raise AssertionError("failed to create loader spec for ThinkingLLM package")
module = importlib.util.module_from_spec(spec)
sys.modules[package_name] = module
spec.loader.exec_module(module)
return module
finally:
for name in tracked_names:
if name in previous_modules:
sys.modules[name] = previous_modules[name]
else:
sys.modules.pop(name, None)
def load_module_from_file(filename: str, module_name: str):
previous_module = sys.modules.get(module_name)
try:
spec = importlib.util.spec_from_file_location(module_name, PKG / filename)
if spec is None or spec.loader is None:
raise AssertionError(f"failed to create loader spec for {filename}")
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)
return module
finally:
if previous_module is None:
sys.modules.pop(module_name, None)
else:
sys.modules[module_name] = previous_module
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)
]
def load_model_recommendations() -> dict:
return json.loads((PKG / "web" / "model_recommendations.json").read_text(encoding="utf-8"))
def matches_recommendation_rule(model_name: str, rules: list[dict]) -> bool:
lowered = model_name.lower()
ordered = sorted(rules, key=lambda rule: rule.get("priority", 0), reverse=True)
for rule in ordered:
include = [token.lower() for token in rule.get("match_any", [])]
exclude = [token.lower() for token in rule.get("exclude_any", [])]
if any(token in lowered for token in include) and not any(token in lowered for token in exclude):
return True
return False
def iter_catalog_display_names() -> list[str]:
names: list[str] = []
hf_payload = json.loads((PKG / "hf_models.json").read_text(encoding="utf-8"))
for section in ("hf_vl_models", "hf_text_models"):
names.extend(list((hf_payload.get(section) or {}).keys()))
gguf_payload = json.loads((PKG / "gguf_models.json").read_text(encoding="utf-8"))
for section in ("Qwen_model", "qwenVL_model"):
entries = gguf_payload.get(section) or {}
for repo_key, repo in entries.items():
if not isinstance(repo, dict):
continue
for model_file in repo.get("model_files") or []:
names.append(str(Path(model_file).name))
return names
class TestTerminalDisplayHelper(unittest.TestCase):
def test_helper_file_exists(self):
self.assertTrue((PKG / "AILab_StreamDisplay.py").exists())
def test_helper_class_and_methods_exist(self):
tree = parse_source("AILab_StreamDisplay.py")
helper_class = None
for node in tree.body:
if isinstance(node, ast.ClassDef) and node.name == "TerminalStreamDisplay":
helper_class = node
break
self.assertIsNotNone(helper_class, "TerminalStreamDisplay class missing")
method_names = {
node.name for node in helper_class.body if isinstance(node, ast.FunctionDef)
}
for method in ["start_stage", "push", "flush", "end_stage", "push_compact", "end_compact"]:
self.assertIn(method, method_names)
class TestBufferedStreamingIntegration(unittest.TestCase):
def test_old_rolling_tail_patterns_removed(self):
old_patterns = [
r'print\(f"\\r\{tail\}"',
r"tail\s*=\s*full_streamed\[-200:\]",
r"tail\s*=\s*full_text\[-200:\]",
]
for filename in STREAMING_FILES:
source = read_source(filename)
for pattern in old_patterns:
with self.subTest(filename=filename, pattern=pattern):
self.assertIsNone(re.search(pattern, source))
def test_all_streaming_files_use_helper(self):
for filename, expectations in STREAMING_FILES.items():
source = read_source(filename)
with self.subTest(filename=filename):
self.assertIn("TerminalStreamDisplay", source)
self.assertIn(expectations["label"], source)
if expectations["stage"]:
self.assertIn(expectations["stage"], source)
self.assertIn(".push_compact(", source, f"{filename} should use compact streaming display")
self.assertIn(".end_compact(", source, f"{filename} should finish compact streaming cleanly")
def test_gguf_prompt_enhancer_has_no_off_toggle_text_stream_fallback(self):
source = read_source("AILab_QwenVL_GGUF_PromptEnhancer.py")
self.assertNotIn("_maybe_emit_prompt_compact_progress", source)
class TestModelRecommendations(unittest.TestCase):
def test_recommendation_rules_have_sources(self):
payload = load_model_recommendations()
rules = payload.get("rules") or []
self.assertTrue(rules, "model_recommendations.json should define at least one rule")
for rule in rules:
with self.subTest(rule=rule.get("id")):
self.assertTrue(rule.get("sources"), "each recommendation rule should record its research source")
def test_recommendation_notes_are_creative_work_focused(self):
payload = load_model_recommendations()
banned_terms = ("coding", "programming", "benchmark", "webdev", "math competition")
for rule in payload.get("rules") or []:
for note in rule.get("notes") or []:
lowered = note.lower()
with self.subTest(rule=rule.get("id"), note=note):
self.assertFalse(
any(term in lowered for term in banned_terms),
"recommendation notes should focus on creative prompt generation rather than coding/benchmarking",
)
def test_recommendations_cover_all_catalog_models(self):
payload = load_model_recommendations()
rules = payload.get("rules") or []
uncovered = [name for name in iter_catalog_display_names() if not matches_recommendation_rule(name, rules)]
self.assertEqual([], uncovered)
def test_qwen36_hauhau_exists_in_text_and_vision_catalogs(self):
payload = json.loads((PKG / "gguf_models.json").read_text(encoding="utf-8"))
text_catalog = payload.get("Qwen_model") or {}
vision_catalog = payload.get("qwenVL_model") or {}
text_key = next((key for key in text_catalog if "Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive" in key), None)
vision_key = next((key for key in vision_catalog if "Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive" in key), None)
self.assertIsNotNone(text_key)
self.assertIsNotNone(vision_key)
self.assertIn("mmproj-Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive-f16.gguf", vision_catalog[vision_key]["mmproj_file"])
self.assertGreaterEqual(len(text_catalog[text_key].get("model_files") or []), 11)
self.assertGreaterEqual(len(vision_catalog[vision_key].get("model_files") or []), 11)
def test_gemma4_12b_is_available_for_hf_and_gguf_vision_and_text(self):
hf_payload = json.loads((PKG / "hf_models.json").read_text(encoding="utf-8"))
gguf_payload = json.loads((PKG / "gguf_models.json").read_text(encoding="utf-8"))
self.assertTrue(any("Gemma-4-12B" in key for key in hf_payload.get("hf_vl_models", {})))
self.assertTrue(any("Gemma-4-12B" in key for key in hf_payload.get("hf_text_models", {})))
gguf_text = gguf_payload.get("Qwen_model") or {}
gguf_vision = gguf_payload.get("qwenVL_model") or {}
text_key = next((key for key in gguf_text if "Gemma-4-12B" in key), None)
vision_key = next((key for key in gguf_vision if "Gemma-4-12B" in key), None)
self.assertIsNotNone(text_key)
self.assertIsNotNone(vision_key)
self.assertIn("mmproj-F16.gguf", gguf_vision[vision_key]["mmproj_file"])
self.assertGreaterEqual(len(gguf_text[text_key].get("model_files") or []), 8)
self.assertGreaterEqual(len(gguf_vision[vision_key].get("model_files") or []), 8)
def test_qwen3_asr_gguf_catalog_is_removed(self):
gguf_payload = json.loads((PKG / "gguf_models.json").read_text(encoding="utf-8"))
vision_catalog = gguf_payload.get("qwenVL_model") or {}
asr_entries = {key: value for key, value in vision_catalog.items() if "Qwen3-ASR" in key}
self.assertEqual({}, asr_entries)
def test_legacy_gguf_model_file_entries_expand_to_runnable_models(self):
with mock.patch.dict(sys.modules, build_loader_test_stubs(), clear=False):
module = load_module_from_file(
"AILab_QwenVL_GGUF.py",
"thinkingllm_legacy_gguf_model_files_expand_test",
)
models = module.GGUF_VL_CATALOG.get("models") or {}
self.assertIn("gemma-4-12b-it-Q4_K_M.gguf [~7.5GB]", models)
resolved = module._resolve_model_entry("gemma-4-12b-it-Q4_K_M.gguf [~7.5GB]")
self.assertEqual(resolved.model_filename, "gemma-4-12b-it-Q4_K_M.gguf")
self.assertEqual(resolved.mmproj_filename, "mmproj-F16.gguf")
def test_ltx_presets_are_available_for_vision_and_prompt_enhancers(self):
data = json.loads(Path("AILab_System_Prompts.json").read_text(encoding="utf-8"))
self.assertIn("🎦 LTX 2.3 NSFW I2V Scene", data["_preset_prompts"])
self.assertIn("🎦 LTX 2.3 NSFW I2V Scene", data["qwenvl"])
self.assertIn("📖 LTX 2.3 NSFW T2V Scene", data["qwen_text"]["styles"])
vision_prompt = data["qwenvl"]["🎦 LTX 2.3 NSFW I2V Scene"]
text_prompt = data["qwen_text"]["styles"]["📖 LTX 2.3 NSFW T2V Scene"]["system_prompt"]
self.assertIn("LTX 2.3", vision_prompt)
self.assertIn("image first", vision_prompt)
self.assertIn("LTX 2.3", text_prompt)
self.assertIn("Do not change the user input intent", text_prompt)
self.assertNotIn("Example:", vision_prompt)
self.assertNotIn("Examples:", text_prompt)
def test_custom_prompt_image_no_preset_alias_is_available_for_saved_workflows(self):
label = "💬 Custom prompt + image (no preset)"
data = json.loads(Path("AILab_System_Prompts.json").read_text(encoding="utf-8"))
self.assertIn(label, data["_preset_prompts"])
self.assertEqual(data["qwenvl"][label], "")
with mock.patch.dict(sys.modules, build_loader_test_stubs(), clear=False):
package = load_thinkingllm_loader_subset(["AILab_QwenVL_GGUF.py"])
values = package.NODE_CLASS_MAPPINGS["ThinkingLLM_QwenVL_GGUF"].INPUT_TYPES()["required"]["preset_prompt"][0]
self.assertIn("🚫 No preset (image-only)", values)
self.assertIn(label, values)
def test_ltx_presets_are_exposed_by_relevant_nodes(self):
with mock.patch.dict(sys.modules, build_loader_test_stubs(), clear=False):
package = load_thinkingllm_loader_subset(
[
"AILab_QwenVL.py",
"AILab_QwenVL_GGUF.py",
"AILab_QwenVL_PromptEnhancer.py",
"AILab_QwenVL_GGUF_PromptEnhancer.py",
]
)
expectations = {
"ThinkingLLM_QwenVL": "preset_prompt",
"ThinkingLLM_QwenVL_Advanced": "preset_prompt",
"ThinkingLLM_QwenVL_GGUF": "preset_prompt",
"ThinkingLLM_QwenVL_GGUF_Advanced": "preset_prompt",
"ThinkingLLM_QwenVL_PromptEnhancer": "enhancement_style",
"ThinkingLLM_QwenVL_GGUF_PromptEnhancer": "preset_system_prompt",
}
for node_name, widget_name in expectations.items():
with self.subTest(node_name=node_name):
values = package.NODE_CLASS_MAPPINGS[node_name].INPUT_TYPES()["required"][widget_name][0]
self.assertTrue(any("LTX 2.3" in value for value in values))
def test_preset_tooltip_payload_matches_system_prompts(self):
system_prompts = json.loads(Path("AILab_System_Prompts.json").read_text(encoding="utf-8"))
tooltip_payload = json.loads(Path("web/preset_tooltips.json").read_text(encoding="utf-8"))
expected = {
"preset_prompt": system_prompts["qwenvl"],
"enhancement_style": {
name: entry["system_prompt"]
for name, entry in system_prompts["qwen_text"]["styles"].items()
},
"preset_system_prompt": {
name: entry["system_prompt"]
for name, entry in system_prompts["qwen_text"]["styles"].items()
},
}
self.assertEqual(tooltip_payload, expected)
def test_appearance_js_hooks_preset_dropdown_tooltips(self):
source = read_source("web/js/appearance.js")
self.assertIn("preset_tooltips.json", source)
self.assertIn("hookPresetTooltipPreviews(node)", source)
for widget_name in ["preset_prompt", "enhancement_style", "preset_system_prompt"]:
with self.subTest(widget_name=widget_name):
self.assertIn(f'"{widget_name}"', source)
def test_hf_default_flag_controls_default_model(self):
with mock.patch.dict(sys.modules, build_loader_test_stubs(), clear=False):
package = load_thinkingllm_loader_subset(["AILab_QwenVL.py"])
required = package.NODE_CLASS_MAPPINGS["ThinkingLLM_QwenVL"].INPUT_TYPES()["required"]
_, meta = required["model_name"]
self.assertEqual(meta["default"], "Qwen3-VL-4B-Instruct-Abliterated [DL: 7.5GB, VRAM: 6.0GB]")
def test_appearance_js_loads_recommendation_widget(self):
source = read_source("web/js/appearance.js")
self.assertIn("model_recommendations.json", source)
self.assertIn("ComfyWidgets", source)
self.assertIn("recommended_settings", source)
self.assertIn("findWidgets(node, RECOMMENDATION_WIDGET_NAME)", source)
self.assertIn("removeDuplicateRecommendationWidgets(node, widget)", source)
self.assertIn("node.widgets.splice(index, 1)", source)
self.assertIn("support_notes?.audio", source)
self.assertIn("AUDIO_UNSUPPORTED_MESSAGE", source)
self.assertIn("else if (hasAudioInput(node))", source)
self.assertIn("ThinkingLLM_Gemma4_Audio_GGUF", source)
self.assertIn("ThinkingLLM_Whisper_ASR", source)
self.assertIn("Whisper ASR transcription", source)
self.assertNotIn("Prompt enhancer compact terminal progress is active", source)
self.assertIn("queueRecommendationRefresh", source)
def test_appearance_js_styles_all_visible_plugin_nodes(self):
source = read_source("web/js/appearance.js")
self.assertIn("if (!node.color && theme.nodeColor)", source)
self.assertIn("if (!node.bgcolor && theme.nodeBgColor)", source)
expected_node_names = [
"AILab_QwenVL",
"AILab_QwenVL_Advanced",
"AILab_QwenVL_PromptEnhancer",
"AILab_QwenVL_GGUF",
"AILab_QwenVL_GGUF_Advanced",
"AILab_QwenVL_GGUF_PromptEnhancer",
"ThinkingLLM_QwenVL",
"ThinkingLLM_QwenVL_Advanced",
"ThinkingLLM_QwenVL_PromptEnhancer",
"ThinkingLLM_QwenVL_GGUF",
"ThinkingLLM_QwenVL_GGUF_Advanced",
"ThinkingLLM_Gemma4_Audio_GGUF",
"ThinkingLLM_Whisper_ASR",
"ThinkingLLM_QwenVL_GGUF_PromptEnhancer",
"VRAMCleanup",
"StorySplitNode",
]
for node_name in expected_node_names:
with self.subTest(node_name=node_name):
self.assertIn(f'"{node_name}"', source)
def test_gguf_prompt_enhancer_caps_context_and_safe_constructs_llama(self):
source = read_source("AILab_QwenVL_GGUF_PromptEnhancer.py")
self.assertIn("_PROMPT_ENHANCER_MAX_CONTEXT_LENGTH = 32768", source)
self.assertIn("_resolve_prompt_enhancer_context_length", source)
self.assertIn("construct_llama_safely(Llama, kwargs", source)
def test_gguf_nodes_guard_failed_llama_construction(self):
source = read_source("AILab_QwenVL_GGUF.py")
self.assertIn("def construct_llama_safely", source)
self.assertIn("_thinkingllm_llama_init_complete", source)
self.assertIn("construct_llama_safely(Llama, llm_kwargs_filtered", source)
def test_gguf_streaming_files_use_shared_extractor(self):
for filename in ("AILab_QwenVL_GGUF.py", "AILab_QwenVL_GGUF_PromptEnhancer.py"):
source = read_source(filename)
with self.subTest(filename=filename):
self.assertIn("extract_stream_token", source, f"{filename} should use shared chunk extraction")
self.assertIn("display_token = reasoning_token + content_token", source)
self.assertNotIn("display_token = reasoning_token or content_token", source)
self.assertNotIn("stream_display.push(display_token)", source)
def test_stream_token_extractor_handles_reasoning_content(self):
from AILab_StreamDisplay import extract_stream_token
# Reasoning chunk
chunk = {"choices": [{"delta": {"reasoning_content": "Let me think...", "content": ""}}]}
self.assertEqual(extract_stream_token(chunk), {"reasoning": "Let me think...", "content": ""})
# Content-only chunk
chunk2 = {"choices": [{"delta": {"reasoning_content": "", "content": "The answer is 42."}}]}
self.assertEqual(extract_stream_token(chunk2), {"reasoning": "", "content": "The answer is 42."})
# Mixed chunk
chunk3 = {"choices": [{"delta": {"reasoning_content": "Hmm...", "content": "OK"}}]}
self.assertEqual(extract_stream_token(chunk3), {"reasoning": "Hmm...", "content": "OK"})
# Empty chunk
self.assertEqual(extract_stream_token({}), {"reasoning": "", "content": ""})
# llama.cpp/OpenAI-compatible variants seen across releases
top_level_reasoning = {"reasoning_content": "thinking..."}
self.assertEqual(extract_stream_token(top_level_reasoning), {"reasoning": "thinking...", "content": ""})
message_chunk = {"choices": [{"message": {"content": "final answer"}}]}
self.assertEqual(extract_stream_token(message_chunk), {"reasoning": "", "content": "final answer"})
object_chunk = types.SimpleNamespace(
choices=[types.SimpleNamespace(delta=types.SimpleNamespace(reasoning_content="why", content=" then"))]
)
self.assertEqual(extract_stream_token(object_chunk), {"reasoning": "why", "content": " then"})
def test_compact_stream_display_does_not_flush_unstyled_tail(self):
from AILab_StreamDisplay import TerminalStreamDisplay
import io
display = TerminalStreamDisplay("UnitTest", flush_interval=999, min_chunk_chars=999, compact=True, line_width=24)
stream = io.StringIO()
with contextlib.redirect_stdout(stream):
display.start_stage("STREAMING")
display.push_compact("partial compact token output with visible words")
display.end_stage()
output = stream.getvalue()
self.assertNotIn("[UnitTest]", output)
self.assertNotIn("\r", output)
self.assertNotIn("…", output)
self.assertIn("partial compact token", output)
self.assertIn("output with visible", output)
self.assertIn("words", output)
def test_stream_degeneracy_guard_catches_repeated_word_loop(self):
from AILab_StreamDisplay import StreamDegenerationError, StreamDegenerationGuard, strip_degenerate_repetition
guard = StreamDegenerationGuard(repeated_word_limit=5)
with self.assertRaises(StreamDegenerationError):
guard.push("mightiest, mightiest, mightiest, mightiest, mightiest, ")
self.assertEqual(
strip_degenerate_repetition(
"A strong start, mightiest, mightiest, mightiest, mightiest, mightiest",
repeated_word_limit=5,
),
"A strong start",
)
def test_visible_streaming_has_no_reasoning_heartbeat_pollution(self):
for filename in STREAMING_FILES:
source = read_source(filename)
with self.subTest(filename=filename):
if "GGUF" in filename or filename in {"AILab_QwenVL.py", "AILab_QwenVL_PromptEnhancer.py"}:
stream_pushes = [line for line in source.splitlines() if "push_compact" in line]
self.assertTrue(stream_pushes)
self.assertNotIn("last_status_at = _maybe_emit_answer_stream_heartbeat", source)
self.assertNotIn("last_status_at = _maybe_emit_hf_stream_heartbeat", source)
self.assertNotIn("last_status_at = _maybe_emit_hf_prompt_stream_heartbeat", source)
self.assertNotIn("last_status_at = _maybe_emit_prompt_stream_heartbeat", source)
self.assertNotIn("terminal stream shows readable progress", source)
class TestPromptEnhancerMetadata(unittest.TestCase):
def test_hf_prompt_enhancer_metadata_matches_runtime(self):
source = read_source("AILab_QwenVL_PromptEnhancer.py")
self.assertIn('RETURN_TYPES = ("STRING", "STRING")', source)
self.assertIn('RETURN_NAMES = ("ENHANCED_OUTPUT", "RAW_TRACE")', source)
self.assertIn('"stream_tokens_to_terminal": ("BOOLEAN"', source)
self.assertIn('"unique_id": "UNIQUE_ID"', source)
self.assertIn('"extra_pnginfo": "EXTRA_PNGINFO"', source)
def test_raw_trace_still_exposed_across_nodes(self):
expectations = {
"AILab_QwenVL_PromptEnhancer.py": 'RETURN_NAMES = ("ENHANCED_OUTPUT", "RAW_TRACE")',
"AILab_QwenVL_GGUF_PromptEnhancer.py": 'RETURN_NAMES = ("ENHANCED_OUTPUT", "RAW_TRACE")',
"AILab_QwenVL.py": 'RETURN_NAMES = ("RESPONSE", "RAW_TRACE")',
"AILab_QwenVL_GGUF.py": 'RETURN_NAMES = ("RESPONSE", "RAW_TRACE")',
}
for filename, marker in expectations.items():
with self.subTest(filename=filename):
self.assertIn(marker, read_source(filename))
class TestHuggingFaceTokenSupport(unittest.TestCase):
TOKEN_NODE_FILES = [
"AILab_QwenVL.py",
"AILab_QwenVL_GGUF.py",
"AILab_QwenVL_PromptEnhancer.py",
"AILab_QwenVL_GGUF_PromptEnhancer.py",
]
def _load_qwenvl_with_stubs(self):
stub_modules = build_loader_test_stubs()
stub_modules.pop("AILab_QwenVL", None)
with mock.patch.dict(sys.modules, stub_modules, clear=False):
return load_module_from_file("AILab_QwenVL.py", "thinkingllm_qwenvl_hf_token_test")
def test_hf_token_widget_exists_in_download_nodes(self):
for filename in self.TOKEN_NODE_FILES:
source = read_source(filename)
with self.subTest(filename=filename):
self.assertIn('"hf_token": ("STRING"', source)
self.assertRegex(source, r'"hf_token": \("STRING", \{[^\n]+"tooltip"')
def test_hf_token_is_not_part_of_prompt_or_cache_signatures(self):
for filename in self.TOKEN_NODE_FILES:
tree = parse_source(filename)
with self.subTest(filename=filename):
for node in ast.walk(tree):
if not isinstance(node, ast.Call):
continue
if isinstance(node.func, ast.Name):
function_name = node.func.id
elif isinstance(node.func, ast.Attribute):
function_name = node.func.attr
else:
continue
if function_name not in {"build_node_input_signature", "get_cache_key"}:
continue
keyword_names = {keyword.arg for keyword in node.keywords if keyword.arg}
self.assertNotIn("hf_token", keyword_names)
for arg in node.args:
self.assertNotEqual(getattr(arg, "id", None), "hf_token")
def test_hf_download_prefers_exact_file_and_passes_token(self):
module = self._load_qwenvl_with_stubs()
captured_kwargs = []
def fake_hf_hub_download(**kwargs):
captured_kwargs.append(dict(kwargs))
local_dir = Path(kwargs["local_dir"])
local_dir.mkdir(parents=True, exist_ok=True)
downloaded_path = local_dir / kwargs["filename"]
downloaded_path.parent.mkdir(parents=True, exist_ok=True)
downloaded_path.write_bytes(b"GGUF")
return str(downloaded_path)
def fail_snapshot_download(**kwargs):
raise AssertionError("snapshot_download should not run when exact file download succeeds")
with tempfile.TemporaryDirectory() as temp_dir:
target_path = Path(temp_dir) / "model.gguf"
with mock.patch.object(module, "hf_hub_download", side_effect=fake_hf_hub_download):
with mock.patch.object(module, "snapshot_download", side_effect=fail_snapshot_download):
module.download_hf_file_to_path(
["private/repo"],
"model.gguf",
target_path,
hf_token="hf_secret_test_token",
)
self.assertEqual(captured_kwargs[0]["token"], "hf_secret_test_token")
self.assertEqual(captured_kwargs[0]["filename"], "model.gguf")
def test_hf_download_errors_redact_token_and_add_access_hint(self):
module = self._load_qwenvl_with_stubs()
secret = "hf_secret_test_token"
message = module._hf_download_error_message(
RuntimeError(f"401 Unauthorized: {secret}"),
repo_id="private/repo",
hf_token=secret,
)
self.assertNotIn(secret, message)
self.assertIn("<redacted HF token>", message)
self.assertIn("access was rejected", message)
def test_hf_token_can_fall_back_to_environment(self):
module = self._load_qwenvl_with_stubs()
with mock.patch.dict(module.os.environ, {"HF_TOKEN": "hf_env_secret"}, clear=True):
self.assertEqual(module._clean_hf_token(""), "hf_env_secret")
message = module._hf_download_error_message(
RuntimeError("401 Unauthorized: hf_env_secret"),
repo_id="private/repo",
hf_token="",
)
self.assertNotIn("hf_env_secret", message)
self.assertIn("<redacted HF token>", message)
self.assertIn("access was rejected", message)
def test_hf_file_download_failure_raises_redacted_message(self):
module = self._load_qwenvl_with_stubs()
secret = "hf_secret_test_token"
def fake_snapshot_download(**kwargs):
raise RuntimeError(f"403 Forbidden: {secret}")
with tempfile.TemporaryDirectory() as temp_dir:
target_path = Path(temp_dir) / "missing.gguf"
with mock.patch.object(module, "hf_hub_download", side_effect=fake_snapshot_download):
with mock.patch.object(module, "snapshot_download", side_effect=fake_snapshot_download):
with self.assertRaises(FileNotFoundError) as raised:
module.download_hf_file_to_path(
["private/repo"],
"missing.gguf",
target_path,
hf_token=secret,
)
message = str(raised.exception)
self.assertNotIn(secret, message)
self.assertIn("<redacted HF token>", message)
self.assertIn("access was rejected", message)
def test_frontend_hides_only_legacy_gguf_advanced_shims(self):
source = read_source("web/js/appearance.js")
for widget_name in [
"legacy_seed_mode",
"legacy_unload_after_run",
]:
with self.subTest(widget_name=widget_name):
self.assertIn(f'"{widget_name}"', source)
internal_widgets_block = source[
source.index("const GGUF_ADVANCED_INTERNAL_WIDGETS = new Set(["):
source.index("]);", source.index("const GGUF_ADVANCED_INTERNAL_WIDGETS = new Set([")):
]
for widget_name in [
"n_ubatch",
"n_threads",
"n_threads_batch",
"flash_attn",
"offload_kqv",
"ctx_checkpoints",
]:
with self.subTest(widget_name=widget_name):
self.assertNotIn(f'"{widget_name}"', internal_widgets_block)
self.assertNotIn('GGUF_ADVANCED_INTERNAL_WIDGETS = new Set([\n "hf_token"', source)
self.assertIn("hideStableWidget", source)
self.assertIn("clearHfTokenAfterExecution", source)
class TestInterruptSupport(unittest.TestCase):
INTERRUPT_FILES = [
"AILab_QwenVL.py",
"AILab_QwenVL_GGUF.py",
"AILab_QwenVL_PromptEnhancer.py",
"AILab_QwenVL_GGUF_PromptEnhancer.py",
]
def test_interrupt_import_present(self):
for filename in self.INTERRUPT_FILES:
source = read_source(filename)
with self.subTest(filename=filename):
self.assertIn("from comfy.model_management import throw_exception_if_processing_interrupted", source)
def test_interrupt_check_in_loops(self):
for filename in self.INTERRUPT_FILES:
source = read_source(filename)
with self.subTest(filename=filename):
self.assertIn("throw_exception_if_processing_interrupted()", source)
def test_interrupt_after_blocking_calls(self):
"""Verify interrupt checks exist after non-streaming generate/create_chat_completion calls."""
for filename in self.INTERRUPT_FILES:
source = read_source(filename)
with self.subTest(filename=filename):
# Every file should have at least one interrupt check outside streaming loops
# (after blocking calls + in stream loops = at least 2 checks per file,
# but simple nodes may have only 1 non-streaming path)
count = source.count("throw_exception_if_processing_interrupted()")
self.assertGreaterEqual(
count, 1,
f"{filename} has only {count} interrupt check(s); expected >= 1"
)
class TestNoResidualGlobals(unittest.TestCase):
def test_no_global_last_saved_prompt_statement(self):
for filename in [
"AILab_QwenVL.py",
"AILab_QwenVL_GGUF.py",
"AILab_QwenVL_PromptEnhancer.py",
"AILab_QwenVL_GGUF_PromptEnhancer.py",
]:
tree = parse_source(filename)
visitor = _GlobalCheck()
visitor.visit(tree)
with self.subTest(filename=filename):
self.assertFalse(visitor.found)
class TestLoaderModuleRegistration(unittest.TestCase):
def test_loader_registers_qwenvl_modules_with_stubbed_dependencies(self):
package = load_thinkingllm_loader_subset(
[
"AILab_QwenVL_GGUF.py",
"AILab_WhisperASR.py",
"AILab_QwenVL_PromptEnhancer.py",
"AILab_QwenVL_GGUF_PromptEnhancer.py",
]
)
for node_name in [
"ThinkingLLM_QwenVL_GGUF",
"ThinkingLLM_QwenVL_GGUF_Advanced",
"ThinkingLLM_Gemma4_Audio_GGUF",
"ThinkingLLM_Whisper_ASR",
"ThinkingLLM_QwenVL_PromptEnhancer",
"ThinkingLLM_QwenVL_GGUF_PromptEnhancer",
]:
with self.subTest(node_name=node_name):
self.assertIn(node_name, package.NODE_CLASS_MAPPINGS)
self.assertNotIn("ThinkingLLM_Qwen3_ASR_GGUF", package.NODE_CLASS_MAPPINGS)
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",
}
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_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_package_export_search_surface_has_no_duplicate_thinkingllm_rows(self):
package = load_thinkingllm_loader_subset([
"AILab_QwenVL.py",
"AILab_QwenVL_GGUF.py",
"AILab_QwenVL_PromptEnhancer.py",
"AILab_QwenVL_GGUF_PromptEnhancer.py",
"AILab_WhisperASR.py",
])
exported_names = set(package.NODE_CLASS_MAPPINGS)
exported_displays = package.NODE_DISPLAY_NAME_MAPPINGS
self.assertFalse(
[name for name in exported_names if name.startswith("AILab_QwenVL")],
"legacy AILab aliases should not be exported as visible search rows",
)
seen = {}
duplicates = {}
for node_name, display_name in exported_displays.items():
if not str(display_name).startswith("ThinkingLLM"):
continue
if display_name in seen:
duplicates.setdefault(display_name, [seen[display_name]]).append(node_name)
else:
seen[display_name] = node_name
self.assertEqual(duplicates, {})
def test_package_exposes_legacy_replacements_for_old_workflows(self):
package = load_thinkingllm_loader_subset([
"AILab_QwenVL.py",
"AILab_QwenVL_GGUF.py",
"AILab_QwenVL_PromptEnhancer.py",
"AILab_QwenVL_GGUF_PromptEnhancer.py",
])
replacements = getattr(package, "LEGACY_NODE_REPLACEMENTS", {})
expected = {legacy: current for current, legacy in self.CURRENT_TO_LEGACY.items()}
self.assertEqual(replacements, expected)
def test_package_registers_legacy_replacements_when_comfy_manager_is_available(self):
class StubNodeReplace:
def __init__(self, new_node_id, old_node_id):
self.new_node_id = new_node_id
self.old_node_id = old_node_id
class StubManager:
def __init__(self):
self.registered = []
def register(self, node_replace):
self.registered.append((node_replace.old_node_id, node_replace.new_node_id))
manager = StubManager()
prompt_server = type("PromptServer", (), {"instance": types.SimpleNamespace(node_replace_manager=manager)})
stubs = build_loader_test_stubs()
stubs.update({
"server": build_stub_module("server", PromptServer=prompt_server),
"comfy_api": build_stub_module("comfy_api"),
"comfy_api.latest": build_stub_module("comfy_api.latest"),
"comfy_api.latest._io": build_stub_module("comfy_api.latest._io", NodeReplace=StubNodeReplace),
})
with mock.patch.dict(sys.modules, stubs, clear=False):
package = load_thinkingllm_loader_subset([
"AILab_QwenVL.py",
"AILab_QwenVL_GGUF.py",
"AILab_QwenVL_PromptEnhancer.py",
"AILab_QwenVL_GGUF_PromptEnhancer.py",
])
self.assertEqual(dict(manager.registered), package.LEGACY_NODE_REPLACEMENTS)
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 TestGGUFAdvancedWorkflowCompatibility(unittest.TestCase):
LEGACY_REQUIRED_PREFIX = [
"model_name",
"device",
"preset_prompt",
"custom_prompt",
"max_tokens",
"temperature",
"top_p",
"repetition_penalty",
"frame_count",
"ctx",
"n_batch",
"gpu_layers",
"image_max_tokens",
"top_k",
"pool_size",
"keep_model_loaded",
"seed",
]
def _load_gguf_advanced_class(self):
package = load_thinkingllm_loader_subset(["AILab_QwenVL_GGUF.py"])
return package.NODE_CLASS_MAPPINGS["ThinkingLLM_QwenVL_GGUF_Advanced"]
def test_gguf_advanced_required_widgets_preserve_legacy_order(self):
node_cls = self._load_gguf_advanced_class()
required_keys = list(node_cls.INPUT_TYPES()["required"].keys())
self.assertEqual(
required_keys[: len(self.LEGACY_REQUIRED_PREFIX)],
self.LEGACY_REQUIRED_PREFIX,
)
self.assertEqual(
required_keys[len(self.LEGACY_REQUIRED_PREFIX): len(self.LEGACY_REQUIRED_PREFIX) + 2],
["legacy_seed_mode", "legacy_unload_after_run"],
)
self.assertGreater(required_keys.index("n_ubatch"), required_keys.index("legacy_unload_after_run"))
def test_gguf_advanced_process_signature_matches_required_widget_order(self):
node_cls = self._load_gguf_advanced_class()
signature = inspect.signature(node_cls.process)
parameter_names = [
name
for name, parameter in signature.parameters.items()
if name != "self" and parameter.kind in (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.KEYWORD_ONLY)
]
expected_prefix = self.LEGACY_REQUIRED_PREFIX + [
"legacy_seed_mode",
"legacy_unload_after_run",
"n_ubatch",
"n_threads",
"n_threads_batch",
"flash_attn",
"offload_kqv",
"ctx_checkpoints",
]
self.assertEqual(parameter_names[: len(expected_prefix)], expected_prefix)
def test_gguf_advanced_accepts_legacy_serialized_values(self):
node_cls = self._load_gguf_advanced_class()
required = node_cls.INPUT_TYPES()["required"]
legacy_seed_mode_options, legacy_seed_mode_meta = required["legacy_seed_mode"]
n_ubatch_type, n_ubatch_meta = required["n_ubatch"]
self.assertIn(False, legacy_seed_mode_options)
self.assertEqual(legacy_seed_mode_meta["default"], "fixed")
self.assertEqual(n_ubatch_type, "INT")
self.assertEqual(n_ubatch_meta["min"], 0)
def test_gguf_runtime_normalizes_legacy_zero_ubatch(self):
source = read_source("AILab_QwenVL_GGUF.py")
self.assertIn("n_ubatch_val = int(n_ubatch) if n_ubatch is not None else 0", source)
self.assertIn("if n_ubatch_val <= 0:", source)
self.assertIn("n_ubatch_val = min(n_batch_val, 512)", source)
def test_gguf_vision_nodes_accept_optional_audio_without_legacy_order_change(self):
node_cls = self._load_gguf_advanced_class()
input_types = node_cls.INPUT_TYPES()
self.assertEqual(input_types["optional"]["audio"], ("AUDIO",))
simple_cls = load_thinkingllm_loader_subset(["AILab_QwenVL_GGUF.py"]).NODE_CLASS_MAPPINGS["ThinkingLLM_QwenVL_GGUF"]
self.assertEqual(simple_cls.INPUT_TYPES()["optional"]["audio"], ("AUDIO",))
def test_thinkingllm_node_input_capabilities_are_intentional(self):
package = load_thinkingllm_loader_subset([
"AILab_QwenVL.py",
"AILab_QwenVL_GGUF.py",
"AILab_QwenVL_PromptEnhancer.py",
"AILab_QwenVL_GGUF_PromptEnhancer.py",
"AILab_WhisperASR.py",
])
expected_optional_inputs = {
"ThinkingLLM_QwenVL": {"image", "video"},
"ThinkingLLM_QwenVL_Advanced": {"image", "video"},
"ThinkingLLM_QwenVL_PromptEnhancer": set(),
"ThinkingLLM_QwenVL_GGUF": {"image", "video", "audio", "audio_file_path"},
"ThinkingLLM_QwenVL_GGUF_Advanced": {"image", "video", "audio", "audio_file_path"},
"ThinkingLLM_Gemma4_Audio_GGUF": {"audio", "audio_file_path"},
"ThinkingLLM_Whisper_ASR": {"audio", "audio_file_path"},
"ThinkingLLM_QwenVL_GGUF_PromptEnhancer": set(),
}
for node_name, expected in expected_optional_inputs.items():
with self.subTest(node_name=node_name):
input_types = package.NODE_CLASS_MAPPINGS[node_name].INPUT_TYPES()
self.assertEqual(set(input_types.get("optional", {})), expected)
def test_gguf_vision_selector_includes_google_gemma4_e4b_image_model(self):
package = load_thinkingllm_loader_subset(["AILab_QwenVL_GGUF.py"])
expected_model_file = "google_gemma-4-E4B-it-Q4_K_M.gguf"
for node_name in ("ThinkingLLM_QwenVL_GGUF", "ThinkingLLM_QwenVL_GGUF_Advanced"):
with self.subTest(node_name=node_name):
model_keys = package.NODE_CLASS_MAPPINGS[node_name].INPUT_TYPES()["required"]["model_name"][0]
self.assertTrue(
any(expected_model_file in key for key in model_keys),
f"{expected_model_file} should be available in the image-capable GGUF selector",
)
def test_gguf_vision_advanced_allows_larger_reasoning_output_budget(self):
node_cls = self._load_gguf_advanced_class()
max_tokens_type, max_tokens_meta = node_cls.INPUT_TYPES()["required"]["max_tokens"]
self.assertEqual(max_tokens_type, "INT")
self.assertGreaterEqual(max_tokens_meta["max"], 32768)
def test_simple_gguf_node_exposes_raw_trace(self):
node_cls = load_thinkingllm_loader_subset(["AILab_QwenVL_GGUF.py"]).NODE_CLASS_MAPPINGS["ThinkingLLM_QwenVL_GGUF"]
self.assertEqual(node_cls.RETURN_TYPES, ("STRING", "STRING"))
self.assertEqual(node_cls.RETURN_NAMES, ("RESPONSE", "RAW_TRACE"))
def test_gemma4_audio_node_is_audio_only_and_model_filtered(self):
node_cls = load_thinkingllm_loader_subset(["AILab_QwenVL_GGUF.py"]).NODE_CLASS_MAPPINGS["ThinkingLLM_Gemma4_Audio_GGUF"]
input_types = node_cls.INPUT_TYPES()
optional = input_types["optional"]
self.assertEqual(optional["audio"], ("AUDIO",))
self.assertIn("audio_file_path", optional)
self.assertNotIn("image", optional)
self.assertNotIn("video", optional)
model_keys, model_meta = input_types["required"]["model_name"]
self.assertTrue(model_keys, "audio node should expose at least one Gemma 4 audio model")
self.assertTrue(all("gemma-4" in key.lower() for key in model_keys))
self.assertTrue(all("26b" not in key.lower() and "31b" not in key.lower() for key in model_keys))
self.assertIn("gemma-4-12b", model_meta["default"].lower())
self.assertIn("Audio", input_types["required"]["custom_prompt"][1]["default"])
def test_gguf_nodes_disable_auto_finalization_retry_by_default(self):
package = load_thinkingllm_loader_subset([
"AILab_QwenVL_GGUF.py",
"AILab_QwenVL_GGUF_PromptEnhancer.py",
])
for node_name in (
"ThinkingLLM_QwenVL_GGUF",
"ThinkingLLM_QwenVL_GGUF_Advanced",
"ThinkingLLM_Gemma4_Audio_GGUF",
"ThinkingLLM_QwenVL_GGUF_PromptEnhancer",
):
with self.subTest(node_name=node_name):
required = package.NODE_CLASS_MAPPINGS[node_name].INPUT_TYPES()["required"]
self.assertIn("auto_finalization_retry", required)
self.assertEqual(required["auto_finalization_retry"][0], "BOOLEAN")
self.assertEqual(required["auto_finalization_retry"][1]["default"], False)
def test_qwen3_asr_node_is_not_registered(self):
package = load_thinkingllm_loader_subset(["AILab_QwenVL_GGUF.py", "AILab_WhisperASR.py"])
self.assertNotIn("ThinkingLLM_Qwen3_ASR_GGUF", package.NODE_CLASS_MAPPINGS)
def test_whisper_asr_node_is_audio_only(self):
node_cls = load_thinkingllm_loader_subset(["AILab_WhisperASR.py"]).NODE_CLASS_MAPPINGS["ThinkingLLM_Whisper_ASR"]
input_types = node_cls.INPUT_TYPES()
self.assertEqual(input_types["optional"]["audio"], ("AUDIO",))
self.assertIn("audio_file_path", input_types["optional"])
model_keys, model_meta = input_types["required"]["model_size"]
self.assertIn("base", model_keys)
self.assertIn("large-v3", model_keys)
self.assertEqual(model_meta["default"], "small")
self.assertEqual(input_types["required"]["device"][1]["default"], "cpu")
self.assertEqual(input_types["required"]["compute_type"][1]["default"], "int8")
self.assertNotIn("image", input_types["optional"])
self.assertNotIn("video", input_types["optional"])
def test_whisper_asr_returns_install_message_when_backend_missing(self):
node_cls = load_thinkingllm_loader_subset(["AILab_WhisperASR.py"]).NODE_CLASS_MAPPINGS["ThinkingLLM_Whisper_ASR"]
with mock.patch.dict(sys.modules, {"faster_whisper": None}, clear=False):
transcript, segments_json, raw_trace = node_cls().process(
model_size="base",
language="auto",
task="transcribe",
device="auto",
compute_type="auto",
beam_size=5,
vad_filter=True,
audio_file_path="dummy.wav",
)
self.assertIn("faster-whisper is not installed", transcript)
self.assertEqual(segments_json, "[]")
self.assertIn("pip install faster-whisper", raw_trace)
def test_whisper_asr_transcribes_with_mock_backend(self):
module = load_module_from_file(
"AILab_WhisperASR.py",
"thinkingllm_whisper_mock_backend_test",
)
class FakeSegment:
def __init__(self, start, end, text):
self.start = start
self.end = end
self.text = text
fake_info = types.SimpleNamespace(language="de", language_probability=0.99, duration=1.0)
captured = {}
class FakeWhisperModel:
def __init__(self, model_size, device, compute_type, local_files_only=False):
captured["init"] = (model_size, device, compute_type)
def transcribe(self, path, language, task, beam_size, vad_filter):
captured["transcribe"] = (Path(path).suffix, language, task, beam_size, vad_filter)
return [FakeSegment(0.0, 1.0, " Hallo Welt. ")], fake_info
fake_backend = build_stub_module("faster_whisper", WhisperModel=FakeWhisperModel)
with tempfile.TemporaryDirectory() as temp_dir:
source = Path(temp_dir) / "input.wav"
with wave.open(str(source), "wb") as wav:
wav.setnchannels(1)
wav.setsampwidth(2)
wav.setframerate(16000)
wav.writeframes(b"\0\0" * 1600)
with mock.patch.dict(sys.modules, {"faster_whisper": fake_backend}, clear=False):
transcript, segments_json, raw_trace = module.ThinkingLLM_Whisper_ASR().process(
model_size="base",
language="de",
task="transcribe",
device="cpu",
compute_type="int8",
beam_size=3,
vad_filter=True,
audio_file_path=str(source),
)
self.assertEqual(transcript, "Hallo Welt.")
self.assertEqual(json.loads(segments_json)[0]["text"], "Hallo Welt.")
self.assertEqual(captured["init"], ("base", "cpu", "int8"))
self.assertEqual(captured["transcribe"], (".wav", "de", "transcribe", 3, True))
self.assertIn("language=de", raw_trace)
def test_whisper_asr_prefers_cached_model_without_hf_ping(self):
module = load_module_from_file(
"AILab_WhisperASR.py",
"thinkingllm_whisper_cached_model_test",
)
class FakeSegment:
start = 0.0
end = 1.0
text = " cached transcript "
fake_info = types.SimpleNamespace(language="en", language_probability=0.99, duration=1.0)
captured = {}
class FakeWhisperModel:
def __init__(self, model_size, device, compute_type, local_files_only=False):
captured["init"] = (model_size, device, compute_type, local_files_only)
def transcribe(self, path, language, task, beam_size, vad_filter):
return [FakeSegment()], fake_info
fake_backend = build_stub_module("faster_whisper", WhisperModel=FakeWhisperModel)
with tempfile.TemporaryDirectory() as temp_dir:
source = Path(temp_dir) / "input.wav"
with wave.open(str(source), "wb") as wav:
wav.setnchannels(1)
wav.setsampwidth(2)
wav.setframerate(16000)
wav.writeframes(b"\0\0" * 1600)
with mock.patch.dict(sys.modules, {"faster_whisper": fake_backend}, clear=False):
transcript, _, raw_trace = module.ThinkingLLM_Whisper_ASR().process(
model_size="small",
language="auto",
task="transcribe",
device="cpu",
compute_type="int8",
beam_size=5,
vad_filter=True,
audio_file_path=str(source),
)
self.assertEqual(transcript, "cached transcript")
self.assertEqual(captured["init"], ("small", "cpu", "int8", True))
self.assertIn("local_files_only=True", raw_trace)
def test_whisper_asr_downloads_once_when_cache_is_missing(self):
module = load_module_from_file(
"AILab_WhisperASR.py",
"thinkingllm_whisper_cache_miss_test",
)
class FakeSegment:
start = 0.0
end = 1.0
text = " downloaded transcript "
fake_info = types.SimpleNamespace(language="en", language_probability=0.99, duration=1.0)
attempts = []
class FakeWhisperModel:
def __init__(self, model_size, device, compute_type, local_files_only=False):
attempts.append(local_files_only)
if local_files_only:
raise RuntimeError("local_files_only=True and no cached snapshot was found")
def transcribe(self, path, language, task, beam_size, vad_filter):
return [FakeSegment()], fake_info
fake_backend = build_stub_module("faster_whisper", WhisperModel=FakeWhisperModel)
with tempfile.TemporaryDirectory() as temp_dir:
source = Path(temp_dir) / "input.wav"
with wave.open(str(source), "wb") as wav:
wav.setnchannels(1)
wav.setsampwidth(2)
wav.setframerate(16000)
wav.writeframes(b"\0\0" * 1600)
with mock.patch.dict(sys.modules, {"faster_whisper": fake_backend}, clear=False):
transcript, _, raw_trace = module.ThinkingLLM_Whisper_ASR().process(
model_size="small",
language="auto",
task="transcribe",
device="cpu",
compute_type="int8",
beam_size=5,
vad_filter=True,
audio_file_path=str(source),
)
self.assertEqual(transcript, "downloaded transcript")
self.assertEqual(attempts, [True, False])
self.assertIn("local_files_only=False", raw_trace)
self.assertIn("Local Whisper cache miss", raw_trace)
def test_audio_to_wav_base64_resamples_to_16khz_mono(self):
import numpy as real_numpy
with mock.patch.dict(sys.modules, build_loader_test_stubs(), clear=False):
module = load_module_from_file(
"AILab_QwenVL_GGUF.py",
"thinkingllm_audio_resample_test",
)
module.np = real_numpy
waveform = real_numpy.stack([
real_numpy.linspace(-0.5, 0.5, 800, dtype=real_numpy.float32),
real_numpy.linspace(0.5, -0.5, 800, dtype=real_numpy.float32),
])
encoded = module._audio_to_wav_base64({"waveform": waveform, "sample_rate": 8000})
self.assertEqual(len(encoded), 1)
with wave.open(io.BytesIO(base64.b64decode(encoded[0])), "rb") as wav:
self.assertEqual(wav.getframerate(), 16000)
self.assertEqual(wav.getnchannels(), 1)
self.assertEqual(wav.getsampwidth(), 2)
self.assertEqual(wav.getnframes(), 1600)
def test_audio_to_wav_base64_rejects_invalid_audio_metadata(self):
import numpy as real_numpy
with mock.patch.dict(sys.modules, build_loader_test_stubs(), clear=False):
module = load_module_from_file(
"AILab_QwenVL_GGUF.py",
"thinkingllm_audio_validation_test",
)
module.np = real_numpy
self.assertEqual(module._audio_to_wav_base64({"waveform": real_numpy.zeros((1, 10)), "sample_rate": 0}), [])
self.assertEqual(module._audio_to_wav_base64({"sample_rate": 16000}), [])
def test_audio_file_path_to_wav_base64_decodes_m4a_with_ffmpeg(self):
if shutil.which("ffmpeg") is None:
self.skipTest("ffmpeg is required for audio file path decoding")
with mock.patch.dict(sys.modules, build_loader_test_stubs(), clear=False):
module = load_module_from_file(
"AILab_QwenVL_GGUF.py",
"thinkingllm_audio_file_path_test",
)
with tempfile.TemporaryDirectory(prefix="thinkingllm audio ") as temp_dir:
m4a_path = Path(temp_dir) / "sample voice memo.m4a"
subprocess.run(
[
"ffmpeg",
"-hide_banner",
"-v",
"error",
"-f",
"lavfi",
"-i",
"sine=frequency=440:duration=0.25",
"-c:a",
"aac",
str(m4a_path),
],
check=True,
capture_output=True,
)
encoded = module._audio_file_path_to_wav_base64(str(m4a_path))
self.assertEqual(len(encoded), 1)
with wave.open(io.BytesIO(base64.b64decode(encoded[0])), "rb") as wav:
self.assertEqual(wav.getframerate(), 16000)
self.assertEqual(wav.getnchannels(), 1)
self.assertEqual(wav.getsampwidth(), 2)
self.assertGreater(wav.getnframes(), 3000)
self.assertLess(wav.getnframes(), 5000)
def test_audio_nodes_skip_saved_prompt_cache(self):
with mock.patch.dict(sys.modules, build_loader_test_stubs(), clear=False):
module = load_module_from_file(
"AILab_QwenVL_GGUF.py",
"thinkingllm_audio_nodes_skip_saved_prompt_cache_test",
)
class DummyLoader(module.QwenVLGGUFBase):
def _load_model(self, *args, **kwargs):
self.chat_handler = object()
self.last_backend_trace = ""
def _encode_media(self, image, video, frame_count):
return []
def _invoke(self, *args, **kwargs):
return "real transcript", "raw transcript"
with mock.patch.object(
module,
"get_node_saved_prompt_with_seed",
side_effect=AssertionError("audio nodes must not reuse saved prompts"),
), mock.patch.object(
module,
"set_node_saved_prompt",
side_effect=AssertionError("audio nodes must not store saved prompts"),
), mock.patch.object(
module,
"_audio_inputs_to_wav_base64",
return_value=(["BASE64WAV"], []),
), mock.patch.object(
module,
"_resolve_model_entry",
return_value=types.SimpleNamespace(context_length=4096),
):
text, raw_trace = DummyLoader().run(
model_name="Gemma-4-12B-it-Q4_K_M.gguf [~7.5GB]",
preset_prompt="🚫 No preset (image-only)",
custom_prompt="Analyze the audio.",
image=None,
video=None,
frame_count=1,
max_tokens=64,
temperature=0.0,
top_p=1.0,
repetition_penalty=1.0,
seed=1,
keep_model_loaded=False,
device="auto",
node_class="ThinkingLLM_Gemma4_Audio_GGUF",
)
self.assertEqual(text, "real transcript")
self.assertEqual(raw_trace, "raw transcript")
class TestLlamaCppInstaller(unittest.TestCase):
def _resolve_linux_install_spec(self, installer, *, py_minor, cuda_version, machine="x86_64"):
with mock.patch.dict(installer.os.environ, {}, clear=True):
with mock.patch.object(
installer.sys,
"version_info",
types.SimpleNamespace(major=3, minor=py_minor),
):
with mock.patch.object(installer.platform, "machine", return_value=machine):
cuda_versions = {
"toolkit": "",
"torch": cuda_version,
"nvidia_smi": "",
"selected": cuda_version,
"selected_source": "torch" if cuda_version else "unknown",
}
with mock.patch.object(installer, "_detect_cuda_versions", return_value=cuda_versions):
return installer._resolve_linux_install_spec()
def test_linux_install_spec_prefers_known_cuda_128_wheels_for_verified_python_tags(self):
installer = load_module_from_file(
"AILab_LlamaCppInstaller.py",
"thinkingllm_llama_installer_linux_spec_test",
)
for py_tag in ("cp310", "cp311", "cp312", "cp313", "cp314"):
with self.subTest(py_tag=py_tag):
install_spec, install_reason = self._resolve_linux_install_spec(
installer,
py_minor=int(py_tag[-2:]),
cuda_version="12.8",
)
expected_spec = (
"https://github.com/JamePeng/llama-cpp-python/releases/download/"
"v0.3.35-cu128-Basic-linux-20260406/"
f"llama_cpp_python-0.3.35+cu128.basic-{py_tag}-{py_tag}-linux_x86_64.whl"
)
self.assertEqual(install_spec, expected_spec)
self.assertEqual(install_reason, "known Linux wheel (torch CUDA 12.8)")
self.assertNotEqual(install_spec, installer.DEFAULT_JAMEPENG_GIT_SPEC)
def test_linux_install_spec_keeps_existing_known_cuda_124_wheel(self):
installer = load_module_from_file(
"AILab_LlamaCppInstaller.py",
"thinkingllm_llama_installer_linux_spec_124_test",
)
install_spec, install_reason = self._resolve_linux_install_spec(
installer,
py_minor=12,
cuda_version="12.4",
)
self.assertEqual(
install_spec,
"https://github.com/JamePeng/llama-cpp-python/releases/download/"
"v0.3.34-cu124-Basic-linux-20260331/"
"llama_cpp_python-0.3.34+cu124.basic-cp312-cp312-linux_x86_64.whl",
)
self.assertEqual(install_reason, "known Linux wheel (torch CUDA 12.4)")
self.assertNotEqual(install_spec, installer.DEFAULT_JAMEPENG_GIT_SPEC)
def test_linux_install_spec_blocks_unverified_combinations_by_default(self):
installer = load_module_from_file(
"AILab_LlamaCppInstaller.py",
"thinkingllm_llama_installer_linux_spec_fallback_test",
)
install_spec, install_reason = self._resolve_linux_install_spec(
installer,
py_minor=11,
cuda_version="12.4",
)
self.assertEqual(install_spec, "")
self.assertEqual(install_reason, "no verified Linux wheel match")
def test_linux_install_spec_source_build_requires_explicit_opt_in(self):
installer = load_module_from_file(
"AILab_LlamaCppInstaller.py",
"thinkingllm_llama_installer_linux_spec_source_opt_in_test",
)
with mock.patch.dict(installer.os.environ, {"THINKINGLLM_LLAMA_CPP_ALLOW_SOURCE_BUILD": "1"}, clear=True):
with mock.patch.object(installer.sys, "version_info", types.SimpleNamespace(major=3, minor=11)):
with mock.patch.object(installer.platform, "machine", return_value="x86_64"):
with mock.patch.object(
installer,
"_detect_cuda_versions",
return_value={"toolkit": "", "torch": "12.4", "nvidia_smi": "", "selected": "12.4", "selected_source": "torch"},
):
install_spec, install_reason = installer._resolve_linux_install_spec()
self.assertEqual(install_spec, installer.DEFAULT_JAMEPENG_GIT_SPEC)
self.assertEqual(install_reason, "source-build fallback (explicit opt-in)")
def test_source_build_env_enables_cuda_when_toolkit_exists(self):
installer = load_module_from_file(
"AILab_LlamaCppInstaller.py",
"thinkingllm_llama_installer_source_env_test",
)
with mock.patch.dict(installer.os.environ, {"CMAKE_ARGS": "-DOTHER=1"}, clear=True):
with mock.patch.object(installer, "_detect_cuda_home_version", return_value="12.8"):
install_env = installer._build_source_install_env()
self.assertIn("-DOTHER=1", install_env["CMAKE_ARGS"])
self.assertIn("-DGGML_CUDA=on", install_env["CMAKE_ARGS"])
self.assertEqual(install_env["FORCE_CMAKE"], "1")
def test_windows_runtime_dll_dirs_include_torch_lib(self):
installer = load_module_from_file(
"AILab_LlamaCppInstaller.py",
"thinkingllm_llama_installer_dirs_test",
)
torch_module = types.SimpleNamespace(__file__=r"C:\stub\torch\__init__.py")
with mock.patch.dict(sys.modules, {"torch": torch_module}, clear=False):
with mock.patch.object(installer.os.path, "isdir", return_value=True):
self.assertEqual(
installer._get_windows_runtime_dll_dirs(),
[r"C:\stub\torch\lib"],
)
def test_windows_long_path_dll_fallback_uses_path(self):
installer = load_module_from_file(
"AILab_LlamaCppInstaller.py",
"thinkingllm_llama_installer_test",
)
with tempfile.TemporaryDirectory() as temp_dir:
original_path = os.environ.get("PATH", "")
def raise_long_path_error(path):
error = FileNotFoundError(f"[WinError 206] path too long: {path}")
error.winerror = 206
raise error
with mock.patch.object(installer.platform, "system", return_value="Windows"):
with mock.patch.object(installer.os, "add_dll_directory", side_effect=raise_long_path_error):
with mock.patch.dict(installer.os.environ, {"PATH": original_path}, clear=False):
with installer._relax_windows_dll_directory_for_long_paths():
handle = installer.os.add_dll_directory(temp_dir)
self.assertTrue(installer.os.environ["PATH"].startswith(temp_dir))
self.assertFalse(installer.os.environ["PATH"].startswith(temp_dir + ";"))
self.assertTrue(hasattr(handle, "close"))
class TestGGUFWindllRuntimeFallback(unittest.TestCase):
def test_gguf_prompt_enhancer_reuses_catalog_model_from_extra_llm_path_without_download(self):
calls = []
class DummyLlama:
def __init__(self, **kwargs):
calls.append(kwargs.get("model_path"))
installer_module = build_stub_module(
"AILab_LlamaCppInstaller",
ensure_llama_cpp_backend=lambda *args, **kwargs: DummyLlama,
format_llama_cpp_backend_info=lambda info=None: "stub backend",
get_last_llama_cpp_backend_info=lambda: {"gpu_offload": True, "vision_handlers": []},
relax_windows_dll_directory_for_long_paths=contextlib.nullcontext,
)
with tempfile.TemporaryDirectory() as temp_dir:
temp_root = Path(temp_dir)
dev_models = temp_root / "dev" / "models"
shared_llm = temp_root / "shared" / "models" / "LLM"
shared_model_dir = shared_llm / "GGUF" / "Qwen" / "Qwen3-4B-GGUF"
shared_model_dir.mkdir(parents=True)
model_filename = "Qwen3-4B-Q4_K_M.gguf"
(shared_model_dir / model_filename).write_bytes(b"GGUF")
stubs = build_loader_test_stubs()
stubs.update(
{
"folder_paths": build_stub_module(
"folder_paths",
models_dir=str(dev_models),
folder_names_and_paths={"LLM": ([str(shared_llm)], set())},
get_folder_paths=lambda name: [str(shared_llm)] if name == "LLM" else [],
),
"AILab_LlamaCppInstaller": installer_module,
}
)
with mock.patch.dict(sys.modules, stubs, clear=False):
module = load_module_from_file(
"AILab_QwenVL_GGUF_PromptEnhancer.py",
"thinkingllm_gguf_prompt_extra_llm_reuse_test",
)
model_name = next(
name
for name in module.ThinkingLLM_QwenVL_GGUF_PromptEnhancer.load_gguf_models()["models"]
if name.startswith(model_filename)
)
loader = module.ThinkingLLM_QwenVL_GGUF_PromptEnhancer()
with mock.patch.dict(sys.modules, stubs, clear=False), mock.patch.object(
module,
"download_hf_file_to_path",
side_effect=AssertionError("download should not be called for shared local GGUF"),
), mock.patch.object(
module,
"read_gguf_architecture",
return_value="qwen3",
):
loader._load_model(
model_name=model_name,
device="cpu",
enable_thinking=True,
unique_id=None,
context_length_override=128,
quiet=True,
)
self.assertEqual(len(calls), 1)
self.assertTrue(str(calls[0]).endswith(model_filename))
def test_gguf_loader_reuses_catalog_model_from_extra_llm_path_without_download(self):
calls = []
class DummyChatHandler:
def __init__(self, **kwargs):
calls.append(("chat", kwargs.get("clip_model_path")))
class DummyLlama:
def __init__(self, **kwargs):
calls.append(("llama", kwargs.get("model_path")))
chat_format_module = build_stub_module(
"llama_cpp.llama_chat_format",
Qwen35VLChatHandler=DummyChatHandler,
Qwen3VLChatHandler=DummyChatHandler,
)
installer_module = build_stub_module(
"AILab_LlamaCppInstaller",
ensure_llama_cpp_backend=lambda *args, **kwargs: DummyLlama,
format_llama_cpp_backend_info=lambda info=None: "stub backend",
get_last_llama_cpp_backend_info=lambda: {
"gpu_offload": True,
"vision_handlers": ["Qwen35VLChatHandler"],
},
relax_windows_dll_directory_for_long_paths=contextlib.nullcontext,
)
with tempfile.TemporaryDirectory() as temp_dir:
temp_root = Path(temp_dir)
dev_models = temp_root / "dev" / "models"
shared_llm = temp_root / "shared" / "models" / "LLM"
shared_model_dir = shared_llm / "GGUF" / "HauhauCS" / "Qwen3.5-4B-Uncensored-HauhauCS-Aggressive"
shared_model_dir.mkdir(parents=True)
model_filename = "Qwen3.5-4B-Uncensored-HauhauCS-Aggressive-Q8_0.gguf"
mmproj_filename = "mmproj-Qwen3.5-4B-Uncensored-HauhauCS-Aggressive-BF16.gguf"
(shared_model_dir / model_filename).write_bytes(b"GGUF")
(shared_model_dir / mmproj_filename).write_bytes(b"mmproj")
stubs = build_loader_test_stubs()
stubs.update(
{
"folder_paths": build_stub_module(
"folder_paths",
models_dir=str(dev_models),
folder_names_and_paths={"LLM": ([str(shared_llm)], set())},
get_folder_paths=lambda name: [str(shared_llm)] if name == "LLM" else [],
),
"AILab_LlamaCppInstaller": installer_module,
"llama_cpp": build_stub_module("llama_cpp", llama_chat_format=chat_format_module),
"llama_cpp.llama_chat_format": chat_format_module,
}
)
with mock.patch.dict(sys.modules, stubs, clear=False):
module = load_module_from_file(
"AILab_QwenVL_GGUF.py",
"thinkingllm_gguf_extra_llm_reuse_test",
)
model_name = next(
name
for name in module.GGUF_VL_CATALOG["models"]
if name.startswith(model_filename)
)
loader = module.QwenVLGGUFBase()
with mock.patch.dict(sys.modules, stubs, clear=False), mock.patch.object(
module,
"_download_single_file",
side_effect=AssertionError("download should not be called for shared local GGUF"),
), mock.patch.object(
module,
"_pick_device",
return_value="cpu",
), mock.patch.object(
module,
"read_gguf_architecture",
return_value="qwen35",
):
loader._load_model(
model_name=model_name,
device="cpu",
ctx=128,
n_batch=16,
n_ubatch=None,
gpu_layers=0,
image_max_tokens=64,
top_k=0,
pool_size=1024,
n_threads=None,
n_threads_batch=None,
flash_attn=False,
offload_kqv=False,
ctx_checkpoints=None,
enable_thinking=True,
unique_id=None,
)
self.assertEqual(calls[0][0], "chat")
self.assertTrue(str(calls[0][1]).endswith(mmproj_filename))
self.assertEqual(calls[1][0], "llama")
self.assertTrue(str(calls[1][1]).endswith(model_filename))
def test_gguf_loader_reuses_google_gemma_alias_from_extra_llm_path_without_download(self):
calls = []
class DummyChatHandler:
def __init__(self, **kwargs):
calls.append(("chat", kwargs.get("clip_model_path")))
class DummyLlama:
def __init__(self, **kwargs):
calls.append(("llama", kwargs.get("model_path")))
chat_format_module = build_stub_module(
"llama_cpp.llama_chat_format",
Gemma4ChatHandler=DummyChatHandler,
)
installer_module = build_stub_module(
"AILab_LlamaCppInstaller",
ensure_llama_cpp_backend=lambda *args, **kwargs: DummyLlama,
format_llama_cpp_backend_info=lambda info=None: "stub backend",
get_last_llama_cpp_backend_info=lambda: {
"gpu_offload": True,
"vision_handlers": ["Gemma4ChatHandler"],
},
relax_windows_dll_directory_for_long_paths=contextlib.nullcontext,
)
with tempfile.TemporaryDirectory() as temp_dir:
temp_root = Path(temp_dir)
dev_models = temp_root / "dev" / "models"
shared_llm = temp_root / "shared" / "models" / "LLM"
shared_model_dir = shared_llm / "GGUF" / "unsloth" / "gemma-4-E4B-it-GGUF"
shared_model_dir.mkdir(parents=True)
local_model_filename = "gemma-4-E4B-it-Q8_0.gguf"
local_mmproj_filename = "mmproj-F16.gguf"
(shared_model_dir / local_model_filename).write_bytes(b"GGUF")
(shared_model_dir / local_mmproj_filename).write_bytes(b"mmproj")
stubs = build_loader_test_stubs()
stubs.update(
{
"folder_paths": build_stub_module(
"folder_paths",
models_dir=str(dev_models),
folder_names_and_paths={"LLM": ([str(shared_llm)], set())},
get_folder_paths=lambda name: [str(shared_llm)] if name == "LLM" else [],
),
"AILab_LlamaCppInstaller": installer_module,
"llama_cpp": build_stub_module("llama_cpp", llama_chat_format=chat_format_module),
"llama_cpp.llama_chat_format": chat_format_module,
}
)
with mock.patch.dict(sys.modules, stubs, clear=False):
module = load_module_from_file(
"AILab_QwenVL_GGUF.py",
"thinkingllm_gguf_google_gemma_alias_reuse_test",
)
model_name = "google_gemma-4-E4B-it-Q8_0.gguf [~8GB]"
loader = module.QwenVLGGUFBase()
with mock.patch.dict(sys.modules, stubs, clear=False), mock.patch.object(
module,
"_download_single_file",
side_effect=AssertionError("download should not be called for shared local GGUF alias"),
), mock.patch.object(
module,
"_pick_device",
return_value="cpu",
), mock.patch.object(
module,
"read_gguf_architecture",
return_value="gemma4",
):
loader._load_model(
model_name=model_name,
device="cpu",
ctx=128,
n_batch=16,
n_ubatch=None,
gpu_layers=0,
image_max_tokens=64,
top_k=0,
pool_size=1024,
n_threads=None,
n_threads_batch=None,
flash_attn=False,
offload_kqv=False,
ctx_checkpoints=None,
enable_thinking=True,
unique_id=None,
)
self.assertEqual(calls[0][0], "chat")
self.assertTrue(str(calls[0][1]).endswith((local_mmproj_filename, "mmproj-google_gemma-4-E4B-it-f16.gguf")))
self.assertEqual(calls[1][0], "llama")
self.assertTrue(str(calls[1][1]).endswith((local_model_filename, "google_gemma-4-E4B-it-Q8_0.gguf")))
def test_gguf_prompt_enhancer_reuses_google_gemma_alias_from_extra_llm_path_without_download(self):
calls = []
class DummyLlama:
def __init__(self, **kwargs):
calls.append(kwargs.get("model_path"))
installer_module = build_stub_module(
"AILab_LlamaCppInstaller",
ensure_llama_cpp_backend=lambda *args, **kwargs: DummyLlama,
format_llama_cpp_backend_info=lambda info=None: "stub backend",
get_last_llama_cpp_backend_info=lambda: {"gpu_offload": True, "vision_handlers": []},
relax_windows_dll_directory_for_long_paths=contextlib.nullcontext,
)
with tempfile.TemporaryDirectory() as temp_dir:
temp_root = Path(temp_dir)
dev_models = temp_root / "dev" / "models"
shared_llm = temp_root / "shared" / "models" / "LLM"
shared_model_dir = shared_llm / "GGUF" / "unsloth" / "gemma-4-E4B-it-GGUF"
shared_model_dir.mkdir(parents=True)
local_model_filename = "gemma-4-E4B-it-Q8_0.gguf"
(shared_model_dir / local_model_filename).write_bytes(b"GGUF")
stubs = build_loader_test_stubs()
stubs.update(
{
"folder_paths": build_stub_module(
"folder_paths",
models_dir=str(dev_models),
folder_names_and_paths={"LLM": ([str(shared_llm)], set())},
get_folder_paths=lambda name: [str(shared_llm)] if name == "LLM" else [],
),
"AILab_LlamaCppInstaller": installer_module,
}
)
with mock.patch.dict(sys.modules, stubs, clear=False):
module = load_module_from_file(
"AILab_QwenVL_GGUF_PromptEnhancer.py",
"thinkingllm_gguf_prompt_google_gemma_alias_reuse_test",
)
model_name = "google_gemma-4-E4B-it-Q8_0.gguf [~8GB]"
loader = module.ThinkingLLM_QwenVL_GGUF_PromptEnhancer()
with mock.patch.dict(sys.modules, stubs, clear=False), mock.patch.object(
module,
"download_hf_file_to_path",
side_effect=AssertionError("download should not be called for shared local GGUF alias"),
), mock.patch.object(
module,
"read_gguf_architecture",
return_value="gemma4",
):
loader._load_model(
model_name=model_name,
device="cpu",
enable_thinking=True,
unique_id=None,
context_length_override=128,
quiet=True,
)
self.assertEqual(len(calls), 1)
self.assertTrue(str(calls[0]).endswith(local_model_filename))
def test_gguf_runtime_reuses_windows_dll_relaxation_for_handler_and_llama(self):
enter_events = []
@contextlib.contextmanager
def tracking_relaxation():
enter_events.append("enter")
try:
yield
finally:
enter_events.append("exit")
class Qwen3VLChatHandler:
def __init__(self, **kwargs):
enter_events.append(("chat", sorted(kwargs)))
class DummyLlama:
def __init__(self, **kwargs):
enter_events.append(("llama", sorted(kwargs)))
chat_format_module = build_stub_module(
"llama_cpp.llama_chat_format",
Qwen3VLChatHandler=Qwen3VLChatHandler,
)
installer_module = build_stub_module(
"AILab_LlamaCppInstaller",
ensure_llama_cpp_backend=lambda *args, **kwargs: DummyLlama,
format_llama_cpp_backend_info=lambda info=None: "stub backend",
get_last_llama_cpp_backend_info=lambda: {"gpu_offload": True, "vision_handlers": ["Qwen3VLChatHandler"]},
relax_windows_dll_directory_for_long_paths=tracking_relaxation,
)
stub_modules = build_loader_test_stubs()
stub_modules.update(
{
"AILab_LlamaCppInstaller": installer_module,
"llama_cpp": build_stub_module("llama_cpp", llama_chat_format=chat_format_module),
"llama_cpp.llama_chat_format": chat_format_module,
}
)
with tempfile.TemporaryDirectory() as temp_dir:
model_path = Path(temp_dir) / "model.gguf"
mmproj_path = Path(temp_dir) / "mmproj.gguf"
model_path.write_bytes(b"GGUF")
mmproj_path.write_bytes(b"mmproj")
with mock.patch.dict(sys.modules, stub_modules, clear=False):
module = load_module_from_file(
"AILab_QwenVL_GGUF.py",
"thinkingllm_gguf_runtime_dll_test",
)
loader = module.QwenVLGGUFBase()
resolved = module.GGUFVLResolved(
display_name="stub",
repo_id=None,
alt_repo_ids=[],
author=None,
repo_dirname="stub",
model_filename=str(model_path),
mmproj_filename=str(mmproj_path),
context_length=4096,
image_max_tokens=1024,
n_batch=64,
gpu_layers=0,
top_k=20,
pool_size=1024,
)
with mock.patch.object(module, "_resolve_model_entry", return_value=resolved):
with mock.patch.object(module, "_pick_device", return_value="cpu"):
with mock.patch.object(module, "read_gguf_architecture", return_value="qwen3"):
loader._load_model(
model_name="stub",
device="cpu",
ctx=None,
n_batch=None,
n_ubatch=None,
gpu_layers=None,
image_max_tokens=None,
top_k=None,
pool_size=None,
n_threads=None,
n_threads_batch=None,
flash_attn=False,
offload_kqv=False,
ctx_checkpoints=None,
enable_thinking=True,
unique_id=None,
)
self.assertEqual(enter_events[0:3], [
"enter",
("chat", ["clip_model_path", "force_reasoning", "image_max_tokens", "verbose"]),
"exit",
])
self.assertEqual(enter_events[3], "enter")
self.assertEqual(enter_events[5], "exit")
self.assertEqual(enter_events[4][0], "llama")
self.assertTrue(
{
"chat_handler",
"model_path",
"n_ctx",
"image_max_tokens",
"image_min_tokens",
}.issubset(set(enter_events[4][1]))
)
def test_gemma4_chat_handler_does_not_receive_qwen_force_reasoning_kwarg(self):
captured_chat_kwargs = []
Gemma4ChatHandler = type(
"Gemma4ChatHandler",
(),
{"__init__": lambda self, **kwargs: captured_chat_kwargs.append(dict(kwargs))},
)
class DummyLlama:
def __init__(self, **kwargs):
pass
chat_format_module = build_stub_module(
"llama_cpp.llama_chat_format",
Gemma4ChatHandler=Gemma4ChatHandler,
)
installer_module = build_stub_module(
"AILab_LlamaCppInstaller",
ensure_llama_cpp_backend=lambda *args, **kwargs: DummyLlama,
format_llama_cpp_backend_info=lambda info=None: "stub backend",
get_last_llama_cpp_backend_info=lambda: {"gpu_offload": True, "vision_handlers": ["Gemma4ChatHandler"]},
relax_windows_dll_directory_for_long_paths=contextlib.nullcontext,
)
stub_modules = build_loader_test_stubs()
stub_modules.update(
{
"AILab_LlamaCppInstaller": installer_module,
"llama_cpp": build_stub_module("llama_cpp", llama_chat_format=chat_format_module),
"llama_cpp.llama_chat_format": chat_format_module,
}
)
with tempfile.TemporaryDirectory() as temp_dir:
model_path = Path(temp_dir) / "gemma.gguf"
mmproj_path = Path(temp_dir) / "mmproj.gguf"
model_path.write_bytes(b"GGUF")
mmproj_path.write_bytes(b"mmproj")
with mock.patch.dict(sys.modules, stub_modules, clear=False):
module = load_module_from_file(
"AILab_QwenVL_GGUF.py",
"thinkingllm_gemma4_handler_kwargs_test",
)
loader = module.QwenVLGGUFBase()
resolved = module.GGUFVLResolved(
display_name="gemma",
repo_id=None,
alt_repo_ids=[],
author=None,
repo_dirname="gemma",
model_filename=str(model_path),
mmproj_filename=str(mmproj_path),
context_length=4096,
image_max_tokens=1024,
n_batch=64,
gpu_layers=0,
top_k=20,
pool_size=1024,
)
with mock.patch.object(module, "_resolve_model_entry", return_value=resolved):
with mock.patch.object(module, "_pick_device", return_value="cpu"):
with mock.patch.object(module, "read_gguf_architecture", return_value="gemma4"):
loader._load_model(
model_name="google_gemma-4-E4B-it-Q8_0.gguf",
device="cpu",
ctx=None,
n_batch=None,
n_ubatch=None,
gpu_layers=None,
image_max_tokens=None,
top_k=None,
pool_size=None,
n_threads=None,
n_threads_batch=None,
flash_attn=False,
offload_kqv=False,
ctx_checkpoints=None,
enable_thinking=True,
unique_id=None,
)
self.assertEqual(len(captured_chat_kwargs), 1)
self.assertNotIn("force_reasoning", captured_chat_kwargs[0])
self.assertIn("clip_model_path", captured_chat_kwargs[0])
def test_gguf_loader_passes_current_cuda_device_as_llama_main_gpu(self):
captured_llama_kwargs = []
class DummyLlama:
def __init__(self, **kwargs):
captured_llama_kwargs.append(dict(kwargs))
installer_module = build_stub_module(
"AILab_LlamaCppInstaller",
ensure_llama_cpp_backend=lambda *args, **kwargs: DummyLlama,
format_llama_cpp_backend_info=lambda info=None: "stub backend",
get_last_llama_cpp_backend_info=lambda: {"gpu_offload": True, "vision_handlers": []},
relax_windows_dll_directory_for_long_paths=contextlib.nullcontext,
)
stub_modules = build_loader_test_stubs()
stub_modules["AILab_LlamaCppInstaller"] = installer_module
stub_modules["comfy.model_management"].get_torch_device = lambda: types.SimpleNamespace(type="cuda", index=0)
with tempfile.TemporaryDirectory() as temp_dir:
model_path = Path(temp_dir) / "model.gguf"
model_path.write_bytes(b"GGUF")
with mock.patch.dict(sys.modules, stub_modules, clear=False):
module = load_module_from_file(
"AILab_QwenVL_GGUF.py",
"thinkingllm_gguf_main_gpu_test",
)
loader = module.QwenVLGGUFBase()
resolved = module.GGUFVLResolved(
display_name="model",
repo_id=None,
alt_repo_ids=[],
author=None,
repo_dirname="model",
model_filename=str(model_path),
mmproj_filename=None,
context_length=4096,
image_max_tokens=1024,
n_batch=64,
gpu_layers=-1,
top_k=20,
pool_size=1024,
)
with mock.patch.object(module, "_resolve_model_entry", return_value=resolved):
with mock.patch.object(module, "_pick_device", return_value="cuda"):
with mock.patch.object(module, "read_gguf_architecture", return_value="qwen"):
with mock.patch.object(module.torch.cuda, "is_available", return_value=True):
with mock.patch.object(module.torch.cuda, "current_device", return_value=1, create=True):
with mock.patch.object(module.torch.cuda, "synchronize", return_value=None, create=True):
with mock.patch.object(module.torch.cuda, "empty_cache", return_value=None, create=True):
with mock.patch.object(module.torch.cuda, "ipc_collect", return_value=None, create=True):
loader._load_model(
model_name="model.gguf",
device="cuda",
ctx=None,
n_batch=None,
n_ubatch=None,
gpu_layers=-1,
image_max_tokens=None,
top_k=None,
pool_size=None,
n_threads=None,
n_threads_batch=None,
flash_attn=False,
offload_kqv=False,
ctx_checkpoints=None,
enable_thinking=True,
unique_id=None,
)
self.assertEqual(captured_llama_kwargs[0]["main_gpu"], 0)
self.assertIn("main_gpu=0", loader.last_backend_trace)
def test_gemma4_multimodal_load_uses_compatible_image_ubatch_settings(self):
captured_chat_kwargs = []
captured_llama_kwargs = []
Gemma4ChatHandler = type(
"Gemma4ChatHandler",
(),
{"__init__": lambda self, **kwargs: captured_chat_kwargs.append(dict(kwargs))},
)
class DummyLlama:
def __init__(self, **kwargs):
captured_llama_kwargs.append(dict(kwargs))
chat_format_module = build_stub_module(
"llama_cpp.llama_chat_format",
Gemma4ChatHandler=Gemma4ChatHandler,
)
installer_module = build_stub_module(
"AILab_LlamaCppInstaller",
ensure_llama_cpp_backend=lambda *args, **kwargs: DummyLlama,
format_llama_cpp_backend_info=lambda info=None: "stub backend",
get_last_llama_cpp_backend_info=lambda: {"gpu_offload": False, "vision_handlers": ["Gemma4ChatHandler"]},
relax_windows_dll_directory_for_long_paths=contextlib.nullcontext,
)
stub_modules = build_loader_test_stubs()
stub_modules.update(
{
"AILab_LlamaCppInstaller": installer_module,
"llama_cpp": build_stub_module("llama_cpp", llama_chat_format=chat_format_module),
"llama_cpp.llama_chat_format": chat_format_module,
}
)
with tempfile.TemporaryDirectory() as temp_dir:
model_path = Path(temp_dir) / "google_gemma-4-E4B-it-Q8_0.gguf"
mmproj_path = Path(temp_dir) / "mmproj-F16.gguf"
model_path.write_bytes(b"GGUF")
mmproj_path.write_bytes(b"mmproj")
with mock.patch.dict(sys.modules, stub_modules, clear=False):
module = load_module_from_file(
"AILab_QwenVL_GGUF.py",
"thinkingllm_gemma4_cpu_load_downgrade_test",
)
loader = module.QwenVLGGUFBase()
resolved = module.GGUFVLResolved(
display_name="gemma",
repo_id=None,
alt_repo_ids=[],
author=None,
repo_dirname="gemma",
model_filename=str(model_path),
mmproj_filename=str(mmproj_path),
context_length=131072,
image_max_tokens=4096,
n_batch=512,
gpu_layers=-1,
top_k=64,
pool_size=4194304,
)
with mock.patch.object(module, "_resolve_model_entry", return_value=resolved):
with mock.patch.object(module, "_pick_device", return_value="cuda"):
with mock.patch.object(module, "read_gguf_architecture", return_value="gemma4"):
loader._load_model(
model_name="google_gemma-4-E4B-it-Q8_0.gguf",
device="cuda",
ctx=None,
n_batch=None,
n_ubatch=None,
gpu_layers=-1,
image_max_tokens=None,
top_k=None,
pool_size=None,
n_threads=None,
n_threads_batch=None,
flash_attn=False,
offload_kqv=False,
ctx_checkpoints=None,
enable_thinking=True,
unique_id=None,
)
self.assertEqual(captured_chat_kwargs[0]["image_max_tokens"], 1120)
self.assertEqual(captured_llama_kwargs[0]["n_ctx"], 32768)
self.assertEqual(captured_llama_kwargs[0]["n_batch"], 1120)
self.assertEqual(captured_llama_kwargs[0]["n_ubatch"], 1120)
self.assertEqual(captured_llama_kwargs[0]["image_max_tokens"], 1120)
self.assertEqual(loader.current_context_length, 32768)
self.assertEqual(loader.current_batch_size, 1120)
self.assertEqual(loader.current_image_token_budget, 1120)
self.assertIn("applied Gemma 4 multimodal compatibility settings", loader.last_backend_trace)
def test_gemma4_gpu_multimodal_load_clamps_crash_log_settings(self):
captured_chat_kwargs = []
captured_llama_kwargs = []
Gemma4ChatHandler = type(
"Gemma4ChatHandler",
(),
{"__init__": lambda self, **kwargs: captured_chat_kwargs.append(dict(kwargs))},
)
class DummyLlama:
def __init__(self, **kwargs):
captured_llama_kwargs.append(dict(kwargs))
chat_format_module = build_stub_module(
"llama_cpp.llama_chat_format",
Gemma4ChatHandler=Gemma4ChatHandler,
)
installer_module = build_stub_module(
"AILab_LlamaCppInstaller",
ensure_llama_cpp_backend=lambda *args, **kwargs: DummyLlama,
format_llama_cpp_backend_info=lambda info=None: "stub backend",
get_last_llama_cpp_backend_info=lambda: {"gpu_offload": True, "vision_handlers": ["Gemma4ChatHandler"]},
relax_windows_dll_directory_for_long_paths=contextlib.nullcontext,
)
stub_modules = build_loader_test_stubs()
stub_modules.update(
{
"AILab_LlamaCppInstaller": installer_module,
"llama_cpp": build_stub_module("llama_cpp", llama_chat_format=chat_format_module),
"llama_cpp.llama_chat_format": chat_format_module,
}
)
with tempfile.TemporaryDirectory() as temp_dir:
model_path = Path(temp_dir) / "google_gemma-4-E4B-it-Q8_0.gguf"
mmproj_path = Path(temp_dir) / "mmproj-F16.gguf"
model_path.write_bytes(b"GGUF")
mmproj_path.write_bytes(b"mmproj")
with mock.patch.dict(sys.modules, stub_modules, clear=False):
module = load_module_from_file(
"AILab_QwenVL_GGUF.py",
"thinkingllm_gemma4_gpu_load_downgrade_test",
)
loader = module.QwenVLGGUFBase()
resolved = module.GGUFVLResolved(
display_name="gemma",
repo_id=None,
alt_repo_ids=[],
author=None,
repo_dirname="gemma",
model_filename=str(model_path),
mmproj_filename=str(mmproj_path),
context_length=131072,
image_max_tokens=4096,
n_batch=512,
gpu_layers=-1,
top_k=64,
pool_size=4194304,
)
with mock.patch.object(module, "_resolve_model_entry", return_value=resolved):
with mock.patch.object(module, "_pick_device", return_value="cuda"):
with mock.patch.object(module, "read_gguf_architecture", return_value="gemma4"):
loader._load_model(
model_name="google_gemma-4-E4B-it-Q8_0.gguf",
device="cuda",
ctx=None,
n_batch=None,
n_ubatch=None,
gpu_layers=-1,
image_max_tokens=None,
top_k=None,
pool_size=None,
n_threads=None,
n_threads_batch=None,
flash_attn=True,
offload_kqv=True,
ctx_checkpoints=0,
enable_thinking=True,
unique_id=None,
)
self.assertEqual(captured_chat_kwargs[0]["image_max_tokens"], 1120)
self.assertEqual(captured_llama_kwargs[0]["n_ctx"], 32768)
self.assertEqual(captured_llama_kwargs[0]["n_batch"], 1120)
self.assertEqual(captured_llama_kwargs[0]["n_ubatch"], 1120)
self.assertEqual(captured_llama_kwargs[0]["image_max_tokens"], 1120)
def test_gemma4_cpu_only_multimodal_guard_blocks_native_abort_risk(self):
with mock.patch.dict(sys.modules, build_loader_test_stubs(), clear=False):
module = load_module_from_file(
"AILab_QwenVL_GGUF.py",
"thinkingllm_gemma4_cpu_abort_guard_test",
)
loader = module.QwenVLGGUFBase()
loader.chat_handler = object()
loader.gguf_arch = "gemma4"
loader.current_backend_gpu_offload = False
loader.current_context_length = 131072
loader.current_batch_size = 512
loader.current_image_token_budget = 4096
with self.assertRaisesRegex(RuntimeError, "Gemma 4 GGUF multimodal generation was blocked"):
loader._invoke(
system_prompt="You are helpful.",
user_prompt="Describe this image.",
images_b64=["BASE64PNG"],
audio_b64=[],
max_tokens=256,
temperature=0.0,
top_p=1.0,
repetition_penalty=1.0,
seed=1,
model_name="google_gemma-4-E4B-it-Q8_0.gguf",
stream_to_terminal=False,
enable_thinking=False,
auto_finalization_retry=False,
top_k=64,
)
def test_gemma4_audio_guard_blocks_unsupported_native_audio_path(self):
with mock.patch.dict(sys.modules, build_loader_test_stubs(), clear=False):
module = load_module_from_file(
"AILab_QwenVL_GGUF.py",
"thinkingllm_gemma4_audio_abort_guard_test",
)
loader = module.QwenVLGGUFBase()
loader.chat_handler = object()
loader.gguf_arch = "gemma4"
loader.current_backend_gpu_offload = True
loader.current_context_length = 32768
loader.current_batch_size = 1120
loader.current_ubatch_size = 1120
loader.current_image_token_budget = 1120
with self.assertRaisesRegex(RuntimeError, "Gemma 4 GGUF audio input is disabled"):
loader._invoke(
system_prompt="You are helpful.",
user_prompt="Transcribe this audio.",
images_b64=[],
audio_b64=["BASE64WAV"],
max_tokens=256,
temperature=0.0,
top_p=1.0,
repetition_penalty=1.0,
seed=1,
model_name="google_gemma-4-E4B-it-Q8_0.gguf",
stream_to_terminal=False,
enable_thinking=False,
auto_finalization_retry=False,
top_k=64,
)
def test_gguf_thinking_toggle_updates_without_reload_signature(self):
with mock.patch.dict(sys.modules, build_loader_test_stubs(), clear=False):
module = load_module_from_file(
"AILab_QwenVL_GGUF.py",
"thinkingllm_gguf_thinking_toggle_test",
)
source = read_source("AILab_QwenVL_GGUF.py")
self.assertNotIn("ctx_checkpoints_val,\n bool(enable_thinking),", source)
captured_kwargs = []
class DummyLlama:
def __init__(self):
self.chat_template_kwargs = {"enable_thinking": True}
def create_chat_completion(self, **kwargs):
captured_kwargs.append(kwargs)
return {"choices": [{"message": {"content": "ok"}}]}
loader = module.QwenVLGGUFBase()
loader.llm = DummyLlama()
loader.uses_qwen_template_thinking = True
loader._create_chat_completion(
enable_thinking=False,
messages=[],
max_tokens=1,
temperature=0.0,
top_p=1.0,
stream=False,
)
self.assertEqual(loader.llm.chat_template_kwargs["enable_thinking"], False)
self.assertEqual(captured_kwargs[0]["chat_template_kwargs"], {"enable_thinking": False})
def test_create_chat_completion_retries_without_unexpected_kwarg(self):
with mock.patch.dict(sys.modules, build_loader_test_stubs(), clear=False):
module = load_module_from_file(
"AILab_QwenVL_GGUF.py",
"thinkingllm_gguf_unexpected_kwarg_retry_test",
)
captured_kwargs = []
class DummyLlama:
def create_chat_completion(self, **kwargs):
captured_kwargs.append(dict(kwargs))
if "repeat_last_n" in kwargs:
raise TypeError("Llama.create_chat_completion() got an unexpected keyword argument 'repeat_last_n'")
return {"choices": [{"message": {"content": "ok"}}]}
loader = module.QwenVLGGUFBase()
loader.llm = DummyLlama()
loader.uses_qwen_template_thinking = False
result = loader._create_chat_completion(
enable_thinking=False,
messages=[],
max_tokens=1,
temperature=0.0,
top_p=1.0,
stream=True,
repeat_last_n=48,
)
self.assertEqual(result["choices"][0]["message"]["content"], "ok")
self.assertIn("repeat_last_n", captured_kwargs[0])
self.assertNotIn("repeat_last_n", captured_kwargs[1])
def test_gguf_invoke_resets_before_each_completion_call(self):
with mock.patch.dict(sys.modules, build_loader_test_stubs(), clear=False):
module = load_module_from_file(
"AILab_QwenVL_GGUF.py",
"thinkingllm_gguf_reset_before_completion_test",
)
class DummyLlama:
def __init__(self):
self.events = []
self.reset_count = 0
def reset(self):
self.events.append("reset")
self.reset_count += 1
def create_chat_completion(self, **kwargs):
self.events.append("completion")
return iter([
{"choices": [{"delta": {"content": "ok"}}]},
])
loader = module.QwenVLGGUFBase()
loader.llm = DummyLlama()
text, raw_trace = loader._invoke(
system_prompt="system",
user_prompt="user",
images_b64=[],
audio_b64=[],
max_tokens=1,
temperature=0.0,
top_p=1.0,
repetition_penalty=1.0,
seed=1,
stream_to_terminal=False,
enable_thinking=False,
)
self.assertEqual(text, "ok")
self.assertIn("ok", raw_trace)
self.assertEqual(loader.llm.reset_count, 1)
self.assertEqual(loader.llm.events, ["reset", "completion"])
def test_gguf_invoke_does_not_auto_finalize_by_default(self):
with mock.patch.dict(sys.modules, build_loader_test_stubs(), clear=False):
module = load_module_from_file(
"AILab_QwenVL_GGUF.py",
"thinkingllm_gguf_no_default_finalization_test",
)
class DummyLlama:
def __init__(self):
self.events = []
self.calls = 0
def reset(self):
self.events.append(("reset", self.calls))
def create_chat_completion(self, **kwargs):
self.calls += 1
self.events.append(("completion", self.calls))
return iter([
{"choices": [{"delta": {"reasoning_content": "hidden reasoning only"}}]},
])
loader = module.QwenVLGGUFBase()
loader.llm = DummyLlama()
text, raw_trace = loader._invoke(
system_prompt="system",
user_prompt="user",
images_b64=[],
audio_b64=[],
max_tokens=8,
temperature=0.0,
top_p=1.0,
repetition_penalty=1.0,
seed=1,
stream_to_terminal=False,
enable_thinking=True,
)
self.assertEqual(text, "")
self.assertIn("[STREAMING]", raw_trace)
self.assertNotIn("FINALIZATION ATTEMPT", raw_trace)
self.assertEqual(loader.llm.events, [("reset", 0), ("completion", 1)])
def test_gguf_invoke_resets_before_finalization_retry_completion(self):
with mock.patch.dict(sys.modules, build_loader_test_stubs(), clear=False):
module = load_module_from_file(
"AILab_QwenVL_GGUF.py",
"thinkingllm_gguf_reset_finalization_test",
)
class DummyLlama:
def __init__(self):
self.events = []
self.calls = 0
def reset(self):
self.events.append(("reset", self.calls))
def create_chat_completion(self, **kwargs):
self.calls += 1
self.events.append(("completion", self.calls))
if self.calls == 1:
return iter([
{"choices": [{"delta": {"reasoning_content": "hidden reasoning only"}}]},
])
return iter([
{"choices": [{"delta": {"content": "final answer"}}]},
])
loader = module.QwenVLGGUFBase()
loader.llm = DummyLlama()
text, raw_trace = loader._invoke(
system_prompt="system",
user_prompt="user",
images_b64=[],
audio_b64=[],
max_tokens=8,
temperature=0.0,
top_p=1.0,
repetition_penalty=1.0,
seed=1,
stream_to_terminal=False,
enable_thinking=True,
auto_finalization_retry=True,
)
self.assertEqual(text, "final answer")
self.assertIn("[STREAMING]", raw_trace)
self.assertIn("[FINALIZATION ATTEMPT 2/3]", raw_trace)
self.assertEqual(
loader.llm.events,
[("reset", 0), ("completion", 1), ("reset", 1), ("completion", 2)],
)
class _GlobalCheck(ast.NodeVisitor):
def __init__(self):
self.found = False
def visit_Global(self, node):
if "LAST_SAVED_PROMPT" in node.names:
self.found = True
if __name__ == "__main__":
unittest.main(verbosity=2)