fix: re-run dynamic prompts fed through linked text inputs

IS_CHANGED only receives constant inputs, so a linked text always
arrived as None and the node kept serving its cached first expansion.
Declare hidden PROMPT/UNIQUE_ID inputs and walk the prompt graph to
the upstream node: rerun only when its constants contain dynamic
syntax or cannot be statically resolved, keep caching for static
linked text.

Fixes #1120
This commit is contained in:
Will Miao
2026-09-24 09:33:27 +08:00
parent 2f9bd3ee7d
commit dae18b3d1d
4 changed files with 186 additions and 5 deletions
+97
View File
@@ -84,3 +84,100 @@ def test_prompt_lm_is_changed_forces_rerun_without_seed_when_text_is_dynamic():
def test_prompt_lm_is_changed_keeps_cache_for_seeded_or_static_text():
assert PromptLM.IS_CHANGED("__flower__", clip="clip", seed=11) is False
assert PromptLM.IS_CHANGED("plain text", clip="clip", seed=None) is False
def _linked_prompt(upstream_inputs):
return {
"1": {"class_type": "TextMultiline", "inputs": upstream_inputs},
"2": {
"class_type": "PromptLM",
"inputs": {"text": ["1", 0], "clip": ["3", 0]},
},
}
def test_prompt_lm_is_changed_forces_rerun_for_linked_dynamic_text():
prompt = _linked_prompt({"text": "{red|blue|green}"})
result = PromptLM.IS_CHANGED(None, clip="clip", seed=None, prompt=prompt, unique_id="2")
assert result != result
def test_prompt_lm_is_changed_keeps_cache_for_linked_static_text():
prompt = _linked_prompt({"text": "a plain static prompt"})
assert PromptLM.IS_CHANGED(None, clip="clip", seed=None, prompt=prompt, unique_id="2") is False
assert PromptLM.IS_CHANGED(None, clip="clip", seed=5, prompt=prompt, unique_id="2") is False
def test_prompt_lm_is_changed_forces_rerun_when_linked_text_unresolvable():
chained = _linked_prompt({"text": ["9", 0]})
result = PromptLM.IS_CHANGED(None, clip="clip", seed=None, prompt=chained, unique_id="2")
assert result != result
assert PromptLM.IS_CHANGED(None, clip="clip", seed=None, prompt=None, unique_id="2") != 0
missing_upstream = _linked_prompt({"text": "static"})
missing_upstream["2"]["inputs"]["text"] = ["99", 0]
assert (
PromptLM.IS_CHANGED(None, clip="clip", seed=None, prompt=missing_upstream, unique_id="2")
!= 0
)
def test_text_lm_is_changed_forces_rerun_for_linked_dynamic_text():
prompt = _linked_prompt({"text": "__flower__"})
result = TextLM.IS_CHANGED(None, seed=None, prompt=prompt, unique_id="2")
assert result != result
def test_text_lm_is_changed_keeps_cache_for_linked_static_text():
prompt = _linked_prompt({"text": "a plain static prompt"})
assert TextLM.IS_CHANGED(None, seed=None, prompt=prompt, unique_id="2") is False
def test_text_lm_process_accepts_hidden_inputs(monkeypatch):
node = TextLM()
class StubService:
def expand_text(self, text, seed=None):
return text
monkeypatch.setattr("py.nodes.text.get_wildcard_service", lambda: StubService())
assert node.process("hello", seed=None, prompt={}, unique_id="2") == ("hello",)
def test_prompt_lm_encode_accepts_hidden_inputs(monkeypatch):
node = PromptLM()
class StubService:
def expand_text(self, text, seed=None):
return text
class StubEncoder:
def encode(self, clip, prompt):
return ("conditioning",)
monkeypatch.setattr("py.nodes.prompt.get_wildcard_service", lambda: StubService())
monkeypatch.setattr("nodes.CLIPTextEncode", lambda: StubEncoder(), raising=False)
result = node.encode("hello", "clip", seed=None, prompt={}, unique_id="2")
assert result == ("conditioning", "hello")
def test_prompt_lm_input_types_declare_hidden_prompt_inputs():
hidden = PromptLM.INPUT_TYPES()["hidden"]
assert hidden == {"prompt": "PROMPT", "unique_id": "UNIQUE_ID"}
def test_text_lm_input_types_declare_hidden_prompt_inputs():
hidden = TextLM.INPUT_TYPES()["hidden"]
assert hidden == {"prompt": "PROMPT", "unique_id": "UNIQUE_ID"}