The Knowledge Base directory was entered in two places — the Configuration node's knowledge_base path and the Knowledge Base Loader node's path — a duplication with no benefit. Remove the Knowledge Base Loader node and have the Configuration node load the Knowledge Base itself, emitting it on a new knowledge_base output wired into the Resolver. - New nodes/kb_loading.py holds the shared, process-wide load-once cache and package-relative path resolution (extracted from the deleted loader node). - Configuration node: gains a knowledge_base_reload input and a KNOWLEDGE_BASE output; RETURN is now (config, knowledge_base, warnings, errors). A KB load failure surfaces on errors while the config is still emitted (the Analyzer and Validator do not need the KB). - Remove SceneCompilerKnowledgeBaseLoader from the node registrations. - Rebuild the example workflow to wire Configuration.knowledge_base -> Resolver. - Docs (README, docs/nodes.md) updated; tests moved to test_kb_loading.py. BREAKING CHANGE: the Knowledge Base Loader node is removed. Workflows that used it must load the Knowledge Base via the Configuration node's knowledge_base output. Co-authored-by: kunail0804 <kunail0804@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
114 lines
4.2 KiB
Python
114 lines
4.2 KiB
Python
"""Tests for the Configuration ComfyUI node (issue #26)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import nodes
|
|
from compiler.common.config import Config
|
|
from compiler.common.knowledge_base import KnowledgeBase
|
|
from nodes.configuration_node import ConfigurationNode
|
|
|
|
|
|
def default_inputs() -> dict:
|
|
return {
|
|
"analyzer_model": "llama3",
|
|
"analyzer_temperature": 0.0,
|
|
"analyzer_max_retries": 3,
|
|
"analyzer_timeout": 60,
|
|
"knowledge_base": "knowledge_base/",
|
|
"resolver_strict_mode": True,
|
|
"resolver_allow_aliases": True,
|
|
"resolver_expansion_enabled": True,
|
|
"resolver_max_expansion_depth": 8,
|
|
"resolver_include_nsfw": False,
|
|
"validator_allow_unknown_fields": False,
|
|
"prompt_target": "easy_illustrious",
|
|
"prompt_separator": ",",
|
|
"prompt_remove_duplicate_tags": True,
|
|
"debug_enabled": False,
|
|
"debug_level": "basic",
|
|
"semantic_enabled": False,
|
|
"semantic_min_similarity": 0.5,
|
|
"semantic_backend": "char_ngram",
|
|
}
|
|
|
|
|
|
def test_node_metadata() -> None:
|
|
assert ConfigurationNode.RETURN_TYPES == (
|
|
"COMPILER_CONFIG",
|
|
"KNOWLEDGE_BASE",
|
|
"STRING",
|
|
"STRING",
|
|
)
|
|
assert ConfigurationNode.RETURN_NAMES == ("config", "knowledge_base", "warnings", "errors")
|
|
required = ConfigurationNode.INPUT_TYPES()["required"]
|
|
assert "analyzer_model" in required
|
|
assert "debug_level" in required
|
|
optional = ConfigurationNode.INPUT_TYPES()["optional"]
|
|
assert "analyzer_system_prompt" in optional
|
|
assert "knowledge_base_reload" in optional
|
|
|
|
|
|
def test_default_inputs_produce_default_config_and_load_the_kb() -> None:
|
|
config, kb, warnings, errors = ConfigurationNode().run(**default_inputs())
|
|
assert isinstance(config, Config)
|
|
assert config == Config()
|
|
# The node now also loads the shipped reference Knowledge Base.
|
|
assert isinstance(kb, KnowledgeBase)
|
|
assert len(kb) > 100
|
|
assert (warnings, errors) == ("", "")
|
|
|
|
|
|
def test_inputs_flow_into_config() -> None:
|
|
inputs = default_inputs()
|
|
inputs["analyzer_model"] = "mistral"
|
|
inputs["resolver_max_expansion_depth"] = 4
|
|
inputs["resolver_include_nsfw"] = True
|
|
inputs["validator_allow_unknown_fields"] = True
|
|
inputs["prompt_separator"] = " | "
|
|
inputs["debug_enabled"] = True
|
|
inputs["debug_level"] = "verbose"
|
|
config, _kb, _warnings, _errors = ConfigurationNode().run(**inputs)
|
|
assert config.analyzer.model == "mistral"
|
|
assert config.resolver.max_expansion_depth == 4
|
|
assert config.resolver.include_nsfw is True
|
|
assert config.validator.allow_unknown_fields is True
|
|
assert config.prompt_builder.separator == " | "
|
|
assert config.debug.enabled is True
|
|
assert config.debug.level == "verbose"
|
|
|
|
|
|
def test_invalid_knowledge_base_path_surfaces_error_but_keeps_config() -> None:
|
|
inputs = default_inputs()
|
|
inputs["knowledge_base"] = "does/not/exist/"
|
|
config, kb, warnings, errors = ConfigurationNode().run(**inputs)
|
|
# The config is still emitted (the Analyzer/Validator do not need the KB); the
|
|
# empty/missing Knowledge Base is surfaced as a warning.
|
|
assert isinstance(config, Config)
|
|
assert len(kb) == 0
|
|
assert "empty" in warnings.lower()
|
|
|
|
|
|
def test_system_prompt_override_is_applied() -> None:
|
|
config, *_ = ConfigurationNode().run(**default_inputs(), analyzer_system_prompt="CUSTOM")
|
|
assert config.analyzer.system_prompt == "CUSTOM"
|
|
|
|
|
|
def test_empty_system_prompt_is_omitted() -> None:
|
|
config, *_ = ConfigurationNode().run(**default_inputs(), analyzer_system_prompt="")
|
|
assert config.analyzer.system_prompt is None
|
|
|
|
|
|
def test_debug_level_options_match_config_schema() -> None:
|
|
options = ConfigurationNode.INPUT_TYPES()["required"]["debug_level"][0]
|
|
for level in options:
|
|
inputs = default_inputs()
|
|
inputs["debug_level"] = level
|
|
config, _kb, _warnings, errors = ConfigurationNode().run(**inputs)
|
|
assert isinstance(config, Config)
|
|
assert errors == ""
|
|
|
|
|
|
def test_registered_for_comfyui() -> None:
|
|
assert nodes.NODE_CLASS_MAPPINGS["SceneCompilerConfiguration"] is ConfigurationNode
|
|
assert nodes.NODE_DISPLAY_NAME_MAPPINGS["SceneCompilerConfiguration"] == "Configuration"
|