feat(config)!: trim Configuration inputs and rewrite tooltips (#150)
Remove settings the user should not have to touch, and make every tooltip plain-language: - knowledge_base path: removed; the Knowledge Base ships in the package, so the path is fixed rather than a prompted input. - prompt_target: removed (reserved; to be replaced in V3). - prompt_separator: removed; always ',' this version. Both keep their compiler defaults, just no longer exposed as node inputs. - analyzer_system_prompt (Configuration) and system_prompt (Scene Analyzer): removed. A user-authored system prompt is not UX-friendly; the built-in analyzer prompt is always used. - Rewrite all node tooltips in plain language reflecting the current nodes. Example workflow and node tests updated; docs/nodes.md refreshed. BREAKING CHANGE: the Configuration node's knowledge_base, prompt_target, prompt_separator, and analyzer_system_prompt inputs and the Scene Analyzer's system_prompt input are removed. Co-authored-by: kunail0804 <kunail0804@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
kunail0804
Claude Opus 4.8
parent
bc0edb1827
commit
f2027a89f8
+2
-2
@@ -18,7 +18,7 @@ default `knowledge_base/` works regardless of ComfyUI's working directory.
|
||||
|
||||
| Node | Inputs | Outputs |
|
||||
|---|---|---|
|
||||
| **Scene Analyzer** | `natural_language` (STRING), `config` (COMPILER_CONFIG, optional — supplies model/temperature/retries/timeout), `system_prompt` (STRING, optional override) | `scene` (SCENE), `warnings` (STRING), `errors` (STRING) |
|
||||
| **Scene Analyzer** | `natural_language` (STRING), `config` (COMPILER_CONFIG, optional — supplies model/temperature/retries/timeout) | `scene` (SCENE), `warnings` (STRING), `errors` (STRING) |
|
||||
| **Scene Validator** | `scene` (SCENE), `config` (COMPILER_CONFIG, optional) | `scene` (SCENE), `warnings` (STRING), `errors` (STRING) |
|
||||
| **Resolver** | `scene` (SCENE), `knowledge_base` (KNOWLEDGE_BASE), `config` (COMPILER_CONFIG, optional) | `resolved_tags` (RESOLVED_TAGS), `warnings` (STRING), `errors` (STRING) |
|
||||
| **Category Splitter** | `resolved_tags` (RESOLVED_TAGS) | `category_map` (CATEGORY_MAP), `warnings` (STRING), `errors` (STRING) |
|
||||
@@ -31,7 +31,7 @@ prompt strings are produced by the **Prompt Builder**.
|
||||
|
||||
| Node | Inputs | Outputs |
|
||||
|---|---|---|
|
||||
| **Configuration** | one input per configuration option (analyzer, resolver, validator, prompt builder, semantic, debug), plus the Knowledge Base `knowledge_base` path, `resolver_knowledge_base_version`, and `knowledge_base_reload` | `config` (COMPILER_CONFIG), `knowledge_base` (KNOWLEDGE_BASE), `warnings` (STRING), `errors` (STRING) |
|
||||
| **Configuration** | the settings a user needs to touch: analyzer (model, temperature, retries, timeout), resolver (strict mode, aliases, expansion, depth, `include_nsfw`, optional `resolver_knowledge_base_version`), validator (`allow_unknown_fields`), `prompt_remove_duplicate_tags`, the opt-in semantic-fallback settings, debug, and `knowledge_base_reload`. The Knowledge Base path is fixed (not user-facing); `prompt_target`/`prompt_separator` are not exposed (fixed defaults). | `config` (COMPILER_CONFIG), `knowledge_base` (KNOWLEDGE_BASE), `warnings` (STRING), `errors` (STRING) |
|
||||
| **Debug Viewer** | `scene` (SCENE, optional), `resolved_tags` (RESOLVED_TAGS, optional), `category_map` (CATEGORY_MAP, optional), `warnings` (STRING, optional), `errors` (STRING, optional) | `report` (STRING) |
|
||||
|
||||
- The **Configuration** node is the single place for compiler settings. Its `config`
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
],
|
||||
"properties": { "Node name for S&R": "SceneCompilerConfiguration" },
|
||||
"widgets_values": [
|
||||
"llama3", 0.0, 3, 300, "knowledge_base/", true, true, true, 8, false, false,
|
||||
"easy_illustrious", ",", true, false, "basic", "", "", false, 0.5, "char_ngram", 0
|
||||
"llama3", 0.0, 3, 300, true, true, true, 8, false, false, true, false, "basic",
|
||||
"", false, 0.5, "char_ngram", 0
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -42,8 +42,7 @@
|
||||
],
|
||||
"properties": { "Node name for S&R": "SceneCompilerAnalyzer" },
|
||||
"widgets_values": [
|
||||
"A blonde girl wearing a dress smiles in a classroom at sunset.",
|
||||
""
|
||||
"A blonde girl wearing a dress smiles in a classroom at sunset."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
+48
-82
@@ -1,10 +1,9 @@
|
||||
"""ComfyUI node for the compiler Configuration (MASTER_SPEC §20, §23).
|
||||
|
||||
A thin interface: it collects the configuration options as node inputs and emits a
|
||||
single COMPILER_CONFIG that the other nodes consume, so behaviour can change without
|
||||
editing the workflow. It also loads the Knowledge Base from the configured path and
|
||||
emits it for the Resolver, so the Knowledge Base directory is entered in exactly one
|
||||
place (there is no separate Knowledge Base Loader node). It never imports ComfyUI.
|
||||
A thin interface: it collects the settings a user actually needs to touch and emits
|
||||
a single COMPILER_CONFIG the other nodes consume, so behaviour can change without
|
||||
editing the workflow. It also loads the Knowledge Base and emits it for the Resolver,
|
||||
so the pipeline needs no separate loader node. It never imports ComfyUI.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -18,9 +17,13 @@ from .kb_loading import load_cached_knowledge_base
|
||||
|
||||
_DEBUG_LEVELS = ["none", "basic", "verbose", "developer"]
|
||||
|
||||
# The Knowledge Base ships inside the package, so its path is fixed rather than a
|
||||
# user-facing setting.
|
||||
_KNOWLEDGE_BASE_PATH = "knowledge_base/"
|
||||
|
||||
|
||||
class ConfigurationNode:
|
||||
"""Centralizes compiler configuration and loads the Knowledge Base for the pipeline."""
|
||||
"""Centralizes compiler settings and loads the Knowledge Base for the pipeline."""
|
||||
|
||||
CATEGORY = "Scene Compiler"
|
||||
FUNCTION = "run"
|
||||
@@ -35,7 +38,7 @@ class ConfigurationNode:
|
||||
"STRING",
|
||||
{
|
||||
"default": "llama3",
|
||||
"tooltip": "Ollama model the Scene Analyzer uses to read the description.",
|
||||
"tooltip": "Which local Ollama model reads your description (e.g. llama3).",
|
||||
},
|
||||
),
|
||||
"analyzer_temperature": (
|
||||
@@ -45,7 +48,7 @@ class ConfigurationNode:
|
||||
"min": 0.0,
|
||||
"max": 2.0,
|
||||
"step": 0.1,
|
||||
"tooltip": "Analyzer sampling temperature. 0 = most repeatable.",
|
||||
"tooltip": "How freely the model interprets. 0 = most consistent.",
|
||||
},
|
||||
),
|
||||
"analyzer_max_retries": (
|
||||
@@ -54,7 +57,7 @@ class ConfigurationNode:
|
||||
"default": 3,
|
||||
"min": 0,
|
||||
"max": 10,
|
||||
"tooltip": "How many times to re-ask when the model returns bad JSON.",
|
||||
"tooltip": "How many times to retry if the model returns a bad answer.",
|
||||
},
|
||||
),
|
||||
"analyzer_timeout": (
|
||||
@@ -64,33 +67,31 @@ class ConfigurationNode:
|
||||
"min": 1,
|
||||
"max": 3600,
|
||||
"tooltip": (
|
||||
"Seconds to wait for the model. 300 s is generous so a model that "
|
||||
"cold-loads into VRAM on the first call does not time out."
|
||||
),
|
||||
},
|
||||
),
|
||||
"knowledge_base": (
|
||||
"STRING",
|
||||
{
|
||||
"default": "knowledge_base/",
|
||||
"tooltip": (
|
||||
"Knowledge Base directory. Relative paths resolve against the node "
|
||||
"package. This is the authoritative path when this Configuration is "
|
||||
"wired into the Knowledge Base Loader."
|
||||
"How long to wait for the model, in seconds. The default is generous "
|
||||
"so the first run (while the model loads) doesn't time out."
|
||||
),
|
||||
},
|
||||
),
|
||||
"resolver_strict_mode": (
|
||||
"BOOLEAN",
|
||||
{"default": True, "tooltip": "Report unknown concepts instead of guessing."},
|
||||
{
|
||||
"default": True,
|
||||
"tooltip": "Report concepts not in the Knowledge Base instead of guessing.",
|
||||
},
|
||||
),
|
||||
"resolver_allow_aliases": (
|
||||
"BOOLEAN",
|
||||
{"default": True, "tooltip": "Resolve aliases to their canonical entry."},
|
||||
{
|
||||
"default": True,
|
||||
"tooltip": "Map synonyms to the same tag (e.g. 'girl' = 'female').",
|
||||
},
|
||||
),
|
||||
"resolver_expansion_enabled": (
|
||||
"BOOLEAN",
|
||||
{"default": True, "tooltip": "Auto-add tags from an entry's expansion list."},
|
||||
{
|
||||
"default": True,
|
||||
"tooltip": "Automatically add related tags (e.g. an outfit's parts).",
|
||||
},
|
||||
),
|
||||
"resolver_max_expansion_depth": (
|
||||
"INT",
|
||||
@@ -98,69 +99,47 @@ class ConfigurationNode:
|
||||
"default": 8,
|
||||
"min": 1,
|
||||
"max": 32,
|
||||
"tooltip": "How deep expansion may recurse before stopping.",
|
||||
"tooltip": "How far related tags are followed. Higher adds more tags.",
|
||||
},
|
||||
),
|
||||
"resolver_include_nsfw": (
|
||||
"BOOLEAN",
|
||||
{
|
||||
"default": False,
|
||||
"tooltip": "Include explicit-rated Knowledge Base entries. Off = SFW only.",
|
||||
"tooltip": "Allow explicit (NSFW) tags. Off keeps results safe-for-work.",
|
||||
},
|
||||
),
|
||||
"validator_allow_unknown_fields": (
|
||||
"BOOLEAN",
|
||||
{
|
||||
"default": False,
|
||||
"tooltip": "Keep unrecognized Scene JSON fields instead of stripping them.",
|
||||
},
|
||||
),
|
||||
"prompt_target": (
|
||||
"STRING",
|
||||
{
|
||||
"default": "easy_illustrious",
|
||||
"tooltip": "Reserved prompt-format label. Currently informational.",
|
||||
},
|
||||
),
|
||||
"prompt_separator": (
|
||||
"STRING",
|
||||
{
|
||||
"default": ",",
|
||||
"tooltip": "String used to join the resolved tags into the final prompt.",
|
||||
"tooltip": "Keep unexpected fields in the scene data. Usually leave off.",
|
||||
},
|
||||
),
|
||||
"prompt_remove_duplicate_tags": (
|
||||
"BOOLEAN",
|
||||
{
|
||||
"default": True,
|
||||
"tooltip": "Drop duplicate tags after expansion (warns SC0007).",
|
||||
"tooltip": "Remove repeated tags from the final prompt. Recommended on.",
|
||||
},
|
||||
),
|
||||
"debug_enabled": (
|
||||
"BOOLEAN",
|
||||
{"default": False, "tooltip": "Enable extra diagnostic logging."},
|
||||
{"default": False, "tooltip": "Turn on extra logging to troubleshoot."},
|
||||
),
|
||||
"debug_level": (
|
||||
_DEBUG_LEVELS,
|
||||
{"default": "basic", "tooltip": "Verbosity of debug logging when enabled."},
|
||||
{"default": "basic", "tooltip": "How much detail the logs include."},
|
||||
),
|
||||
},
|
||||
# Appended at the END so saved workflows keep their positional widget
|
||||
# values (ComfyUI stores widget values positionally).
|
||||
"optional": {
|
||||
"analyzer_system_prompt": (
|
||||
"STRING",
|
||||
{
|
||||
"multiline": True,
|
||||
"default": "",
|
||||
"tooltip": "Advanced: override the analyzer system prompt.",
|
||||
},
|
||||
),
|
||||
"resolver_knowledge_base_version": (
|
||||
"STRING",
|
||||
{
|
||||
"default": "",
|
||||
"tooltip": "Pin a KB dataset version. Empty = unpinned.",
|
||||
"tooltip": "Advanced: pin a Knowledge Base version. Empty = default.",
|
||||
},
|
||||
),
|
||||
"semantic_enabled": (
|
||||
@@ -168,8 +147,8 @@ class ConfigurationNode:
|
||||
{
|
||||
"default": False,
|
||||
"tooltip": (
|
||||
"Opt-in nearest-neighbour fallback for concepts that miss "
|
||||
"deterministic lookup. Off by default; deterministic lookup wins."
|
||||
"Find the closest known tag when a concept isn't recognized. Off by "
|
||||
"default; it never invents tags."
|
||||
),
|
||||
},
|
||||
),
|
||||
@@ -180,14 +159,14 @@ class ConfigurationNode:
|
||||
"min": 0.0,
|
||||
"max": 1.0,
|
||||
"step": 0.05,
|
||||
"tooltip": "Minimum similarity to accept a semantic-fallback match.",
|
||||
"tooltip": "How close a match must be for the closest-tag search.",
|
||||
},
|
||||
),
|
||||
"semantic_backend": (
|
||||
"STRING",
|
||||
{
|
||||
"default": "char_ngram",
|
||||
"tooltip": "Embedding backend for the semantic fallback (offline).",
|
||||
"tooltip": "Advanced: which method the closest-tag search uses.",
|
||||
},
|
||||
),
|
||||
"knowledge_base_reload": (
|
||||
@@ -195,7 +174,7 @@ class ConfigurationNode:
|
||||
{
|
||||
"default": 0,
|
||||
"min": 0,
|
||||
"tooltip": "Bump to force a fresh read of the Knowledge Base from disk.",
|
||||
"tooltip": "Bump this if you edited the Knowledge Base, to reload it.",
|
||||
},
|
||||
),
|
||||
},
|
||||
@@ -207,38 +186,29 @@ class ConfigurationNode:
|
||||
analyzer_temperature: float,
|
||||
analyzer_max_retries: int,
|
||||
analyzer_timeout: int,
|
||||
knowledge_base: str,
|
||||
resolver_strict_mode: bool,
|
||||
resolver_allow_aliases: bool,
|
||||
resolver_expansion_enabled: bool,
|
||||
resolver_max_expansion_depth: int,
|
||||
resolver_include_nsfw: bool,
|
||||
validator_allow_unknown_fields: bool,
|
||||
prompt_target: str,
|
||||
prompt_separator: str,
|
||||
prompt_remove_duplicate_tags: bool,
|
||||
debug_enabled: bool,
|
||||
debug_level: str,
|
||||
analyzer_system_prompt: str = "",
|
||||
resolver_knowledge_base_version: str = "",
|
||||
semantic_enabled: bool = False,
|
||||
semantic_min_similarity: float = 0.5,
|
||||
semantic_backend: str = "char_ngram",
|
||||
knowledge_base_reload: int = 0,
|
||||
) -> tuple[Any, Any, str, str]:
|
||||
analyzer: dict[str, Any] = {
|
||||
"model": analyzer_model,
|
||||
"temperature": analyzer_temperature,
|
||||
"max_retries": analyzer_max_retries,
|
||||
"timeout": analyzer_timeout,
|
||||
}
|
||||
if analyzer_system_prompt:
|
||||
analyzer["system_prompt"] = analyzer_system_prompt
|
||||
|
||||
document = {
|
||||
"analyzer": analyzer,
|
||||
"analyzer": {
|
||||
"model": analyzer_model,
|
||||
"temperature": analyzer_temperature,
|
||||
"max_retries": analyzer_max_retries,
|
||||
"timeout": analyzer_timeout,
|
||||
},
|
||||
"resolver": {
|
||||
"knowledge_base": knowledge_base,
|
||||
"strict_mode": resolver_strict_mode,
|
||||
"allow_aliases": resolver_allow_aliases,
|
||||
"expansion_enabled": resolver_expansion_enabled,
|
||||
@@ -256,11 +226,7 @@ class ConfigurationNode:
|
||||
"min_similarity": semantic_min_similarity,
|
||||
"backend": semantic_backend,
|
||||
},
|
||||
"prompt_builder": {
|
||||
"target": prompt_target,
|
||||
"separator": prompt_separator,
|
||||
"remove_duplicate_tags": prompt_remove_duplicate_tags,
|
||||
},
|
||||
"prompt_builder": {"remove_duplicate_tags": prompt_remove_duplicate_tags},
|
||||
"debug": {"enabled": debug_enabled, "level": debug_level},
|
||||
}
|
||||
|
||||
@@ -269,10 +235,10 @@ class ConfigurationNode:
|
||||
except ConfigError as error:
|
||||
return (None, None, "", format_messages([error.message]))
|
||||
|
||||
# Load the Knowledge Base from the configured path/version and emit it for
|
||||
# the Resolver, so the Knowledge Base directory is entered in one place.
|
||||
# Load the shipped Knowledge Base and emit it for the Resolver, so the
|
||||
# Knowledge Base is loaded in one place with no separate loader node.
|
||||
kb, warnings, errors, _raw = load_cached_knowledge_base(
|
||||
knowledge_base,
|
||||
_KNOWLEDGE_BASE_PATH,
|
||||
resolver_knowledge_base_version,
|
||||
knowledge_base_reload,
|
||||
)
|
||||
|
||||
@@ -28,15 +28,23 @@ class DebugViewerNode:
|
||||
"optional": {
|
||||
"scene": (
|
||||
"SCENE",
|
||||
{"tooltip": "Scene JSON to inspect (Analyzer/Validator output)."},
|
||||
{"tooltip": "Any scene data to inspect (from the Analyzer or Validator)."},
|
||||
),
|
||||
"warnings": (
|
||||
"STRING",
|
||||
{"default": "", "forceInput": True, "tooltip": "A stage's warnings output."},
|
||||
{
|
||||
"default": "",
|
||||
"forceInput": True,
|
||||
"tooltip": "A stage's warnings output to view.",
|
||||
},
|
||||
),
|
||||
"errors": (
|
||||
"STRING",
|
||||
{"default": "", "forceInput": True, "tooltip": "A stage's errors output."},
|
||||
{
|
||||
"default": "",
|
||||
"forceInput": True,
|
||||
"tooltip": "A stage's errors output to view.",
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,21 +30,16 @@ class ResolverNode:
|
||||
def INPUT_TYPES(cls) -> dict[str, Any]:
|
||||
return {
|
||||
"required": {
|
||||
"scene": ("SCENE", {"tooltip": "Validated Scene JSON from the Scene Validator."}),
|
||||
"scene": ("SCENE", {"tooltip": "The checked scene data from the Scene Validator."}),
|
||||
"knowledge_base": (
|
||||
"KNOWLEDGE_BASE",
|
||||
{"tooltip": "Loaded Knowledge Base from the Knowledge Base Loader."},
|
||||
{"tooltip": "The Knowledge Base from the Configuration node."},
|
||||
),
|
||||
},
|
||||
"optional": {
|
||||
"config": (
|
||||
"COMPILER_CONFIG",
|
||||
{
|
||||
"tooltip": (
|
||||
"Optional Configuration node. Supplies resolver options, the prompt "
|
||||
"separator, duplicate handling and the opt-in semantic fallback."
|
||||
)
|
||||
},
|
||||
{"tooltip": "Optional: connect a Configuration node to set resolver options."},
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,12 +4,11 @@ A thin interface: it adapts ComfyUI inputs to the Analyzer module and surfaces t
|
||||
CompilerResult's data, warnings, and errors. It contains no compiler logic and
|
||||
never imports ComfyUI.
|
||||
|
||||
Analyzer settings (model, temperature, retries, timeout, system prompt) live on
|
||||
the **Configuration** node and reach this node through the optional ``config``
|
||||
input, so a setting like the model name is entered in exactly one place. Without a
|
||||
Configuration node the built-in defaults apply (llama3, temperature 0), with a
|
||||
generous 300 s timeout so a local Ollama model that cold-loads into VRAM on the
|
||||
first call does not time out.
|
||||
Analyzer settings (model, temperature, retries, timeout) live on the **Configuration**
|
||||
node and reach this node through its optional ``config`` input, so they are entered in
|
||||
one place. Without a Configuration node the built-in defaults apply (llama3,
|
||||
temperature 0) with a generous 300 s timeout, because a local Ollama model cold-loads
|
||||
into VRAM on the first call and a shorter timeout often fails on first use.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -44,7 +43,7 @@ class SceneAnalyzerNode:
|
||||
{
|
||||
"multiline": True,
|
||||
"default": "",
|
||||
"tooltip": "The scene description to analyze, in plain language.",
|
||||
"tooltip": "Describe your scene in plain language.",
|
||||
},
|
||||
),
|
||||
},
|
||||
@@ -53,23 +52,11 @@ class SceneAnalyzerNode:
|
||||
"COMPILER_CONFIG",
|
||||
{
|
||||
"tooltip": (
|
||||
"Optional Configuration node. Supplies the analyzer model, "
|
||||
"temperature, retries and timeout. If left unconnected, the "
|
||||
"defaults apply (llama3, temperature 0, 300 s timeout)."
|
||||
"Optional: connect a Configuration node to set the model and other "
|
||||
"options. Without it, sensible defaults are used."
|
||||
)
|
||||
},
|
||||
),
|
||||
"system_prompt": (
|
||||
"STRING",
|
||||
{
|
||||
"multiline": True,
|
||||
"default": "",
|
||||
"tooltip": (
|
||||
"Advanced: override the analyzer system prompt. Leave empty to "
|
||||
"use the built-in prompt (or the one from the Configuration node)."
|
||||
),
|
||||
},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -77,9 +64,10 @@ class SceneAnalyzerNode:
|
||||
self,
|
||||
natural_language: str,
|
||||
config: Config | None = None,
|
||||
system_prompt: str = "",
|
||||
) -> tuple[Any, str, str, str]:
|
||||
config = self._resolve_config(config, system_prompt)
|
||||
if config is None:
|
||||
# No Configuration node: use defaults with a cold-load-friendly timeout.
|
||||
config = Config.from_json({"analyzer": {"timeout": _DEFAULT_TIMEOUT}})
|
||||
backend = OllamaBackend.from_config(config)
|
||||
result = analyze(natural_language, backend, config)
|
||||
return (
|
||||
@@ -91,22 +79,3 @@ class SceneAnalyzerNode:
|
||||
# re-serialization of the parsed Scene.
|
||||
result.metadata.get("raw_response", ""),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _resolve_config(config: Config | None, system_prompt: str) -> Config:
|
||||
"""Pick the effective config, applying the optional system-prompt override.
|
||||
|
||||
No Configuration node: build a default config with a cold-load-friendly
|
||||
timeout. A non-empty ``system_prompt`` widget always wins over whatever the
|
||||
Configuration node carries.
|
||||
"""
|
||||
if config is None:
|
||||
analyzer: dict[str, Any] = {"timeout": _DEFAULT_TIMEOUT}
|
||||
if system_prompt:
|
||||
analyzer["system_prompt"] = system_prompt
|
||||
return Config.from_json({"analyzer": analyzer})
|
||||
if system_prompt:
|
||||
document = config.to_json()
|
||||
document.setdefault("analyzer", {})["system_prompt"] = system_prompt
|
||||
return Config.from_json(document)
|
||||
return config
|
||||
|
||||
@@ -27,12 +27,12 @@ class SceneValidatorNode:
|
||||
def INPUT_TYPES(cls) -> dict[str, Any]:
|
||||
return {
|
||||
"required": {
|
||||
"scene": ("SCENE", {"tooltip": "Raw Scene JSON from the Scene Analyzer."}),
|
||||
"scene": ("SCENE", {"tooltip": "The scene data from the Scene Analyzer."}),
|
||||
},
|
||||
"optional": {
|
||||
"config": (
|
||||
"COMPILER_CONFIG",
|
||||
{"tooltip": "Optional Configuration node; supplies the unknown-field policy."},
|
||||
{"tooltip": "Optional: connect a Configuration node."},
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -14,15 +14,12 @@ def default_inputs() -> dict:
|
||||
"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",
|
||||
@@ -43,8 +40,13 @@ def test_node_metadata() -> None:
|
||||
required = ConfigurationNode.INPUT_TYPES()["required"]
|
||||
assert "analyzer_model" in required
|
||||
assert "debug_level" in required
|
||||
# Removed, leaner inputs: no user-facing KB path, prompt target/separator, or
|
||||
# analyzer system-prompt override.
|
||||
optional = ConfigurationNode.INPUT_TYPES()["optional"]
|
||||
assert "analyzer_system_prompt" in optional
|
||||
assert "knowledge_base" not in required
|
||||
assert "prompt_target" not in required
|
||||
assert "prompt_separator" not in required
|
||||
assert "analyzer_system_prompt" not in optional
|
||||
assert "knowledge_base_reload" in optional
|
||||
|
||||
|
||||
@@ -52,7 +54,7 @@ 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.
|
||||
# The node loads the shipped reference Knowledge Base from its fixed path.
|
||||
assert isinstance(kb, KnowledgeBase)
|
||||
assert len(kb) > 100
|
||||
assert (warnings, errors) == ("", "")
|
||||
@@ -64,7 +66,7 @@ def test_inputs_flow_into_config() -> None:
|
||||
inputs["resolver_max_expansion_depth"] = 4
|
||||
inputs["resolver_include_nsfw"] = True
|
||||
inputs["validator_allow_unknown_fields"] = True
|
||||
inputs["prompt_separator"] = " | "
|
||||
inputs["prompt_remove_duplicate_tags"] = False
|
||||
inputs["debug_enabled"] = True
|
||||
inputs["debug_level"] = "verbose"
|
||||
config, _kb, _warnings, _errors = ConfigurationNode().run(**inputs)
|
||||
@@ -72,30 +74,16 @@ def test_inputs_flow_into_config() -> None:
|
||||
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.prompt_builder.remove_duplicate_tags is False
|
||||
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_prompt_target_and_separator_keep_their_defaults() -> None:
|
||||
# These are no longer user-facing; the compiler defaults still apply.
|
||||
config, *_ = ConfigurationNode().run(**default_inputs())
|
||||
assert config.prompt_builder.target == "easy_illustrious"
|
||||
assert config.prompt_builder.separator == ","
|
||||
|
||||
|
||||
def test_debug_level_options_match_config_schema() -> None:
|
||||
|
||||
@@ -12,10 +12,10 @@ from nodes.scene_analyzer_node import SceneAnalyzerNode
|
||||
|
||||
def test_input_types_structure() -> None:
|
||||
inputs = SceneAnalyzerNode.INPUT_TYPES()
|
||||
# Analyzer settings now live on the Configuration node; only the per-run text
|
||||
# and the optional config/override remain.
|
||||
# Analyzer settings live on the Configuration node; only the per-run text and
|
||||
# the optional config connection remain.
|
||||
assert set(inputs["required"]) == {"natural_language"}
|
||||
assert set(inputs["optional"]) == {"config", "system_prompt"}
|
||||
assert set(inputs["optional"]) == {"config"}
|
||||
|
||||
|
||||
def test_node_metadata() -> None:
|
||||
@@ -72,33 +72,6 @@ def test_no_messages_yield_empty_strings(monkeypatch) -> None:
|
||||
assert (warnings, errors) == ("", "")
|
||||
|
||||
|
||||
def test_system_prompt_override_without_config(monkeypatch) -> None:
|
||||
captured = {}
|
||||
|
||||
def fake_analyze(description, backend, config):
|
||||
captured["system_prompt"] = config.analyzer.system_prompt
|
||||
return CompilerResult(data="S")
|
||||
|
||||
monkeypatch.setattr(node_module, "analyze", fake_analyze)
|
||||
SceneAnalyzerNode().run("x", system_prompt="CUSTOM")
|
||||
assert captured["system_prompt"] == "CUSTOM"
|
||||
|
||||
|
||||
def test_system_prompt_override_wins_over_config(monkeypatch) -> None:
|
||||
captured = {}
|
||||
|
||||
def fake_analyze(description, backend, config):
|
||||
captured["system_prompt"] = config.analyzer.system_prompt
|
||||
captured["model"] = config.analyzer.model
|
||||
return CompilerResult(data="S")
|
||||
|
||||
monkeypatch.setattr(node_module, "analyze", fake_analyze)
|
||||
config = Config.from_json({"analyzer": {"model": "mistral", "system_prompt": "FROM_CONFIG"}})
|
||||
SceneAnalyzerNode().run("x", config=config, system_prompt="OVERRIDE")
|
||||
assert captured["system_prompt"] == "OVERRIDE"
|
||||
assert captured["model"] == "mistral"
|
||||
|
||||
|
||||
def test_registered_for_comfyui() -> None:
|
||||
assert nodes.NODE_CLASS_MAPPINGS["SceneCompilerAnalyzer"] is SceneAnalyzerNode
|
||||
assert nodes.NODE_DISPLAY_NAME_MAPPINGS["SceneCompilerAnalyzer"] == "Scene Analyzer"
|
||||
|
||||
Reference in New Issue
Block a user