From 9e6fa694868c977ba2c69c0dc34e6fcd26608fcd Mon Sep 17 00:00:00 2001 From: digital-garbage <133010426+digital-garbage@users.noreply.github.com> Date: Tue, 10 Nov 2026 20:54:26 +0200 Subject: [PATCH] Alternating prompts: say whether the prompt had no options or no named step was in the run Co-Authored-By: Claude Sonnet 5.5 --- .../prompt_alternatives/__init__.py | 10 ++++++---- .../noise_merge/tests/test_through_the_app.py | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/modules/conditioning/prompt_alternatives/__init__.py b/modules/conditioning/prompt_alternatives/__init__.py index 233effa1..cbadfdfb 100644 --- a/modules/conditioning/prompt_alternatives/__init__.py +++ b/modules/conditioning/prompt_alternatives/__init__.py @@ -71,17 +71,18 @@ def install(patcher, values, key): steps = {**{i - 1: 1 for i in b if i >= 1}, **{i - 1: 0 for i in a if i >= 1}} # step index -> option; A wins an overlap if not steps: return None - live = {"cache": {}, "used": 0} + live = {"cache": {}, "used": 0, "seen": False} def start(executor, *args, **kwargs): - live["cache"], live["used"] = {}, 0 + live["cache"], live["used"], live["seen"] = {}, 0, False try: return executor(*args, **kwargs) finally: live["cache"].clear() if not live["used"]: - _say("Inactive | no step of this run read an option (needs [A|B] in the prompt, " - "and steps inside the schedule)") + _say("Inactive | the prompt had no [A|B] options (a bare A|B counts only between two shortcuts)" + if not live["seen"] else + "Inactive | none of the steps named here is a step of this run") patcher.add_wrapper_with_key(WrappersMP.OUTER_SAMPLE, key, start) @@ -90,6 +91,7 @@ def install(patcher, values, key): to = named.get("transformer_options") or {} where = dit_hooks.current_step(to) simul, context = to.get(SIMUL), named.get("c_crossattn") + live["seen"] = live["seen"] or bool(simul) if (not simul or where is None or where[0] not in steps or to.get(streams.OPTION_PINNED) or dit_hooks.probing(to) or any(int(i) != 0 for i in (to.get("cond_or_uncond") or [0]))): return executor(x, t, *args, **kwargs) diff --git a/modules/sampling/noise_merge/tests/test_through_the_app.py b/modules/sampling/noise_merge/tests/test_through_the_app.py index 3da933ec..572eb3a5 100644 --- a/modules/sampling/noise_merge/tests/test_through_the_app.py +++ b/modules/sampling/noise_merge/tests/test_through_the_app.py @@ -235,3 +235,20 @@ def test_a_merged_step_wins_over_an_assigned_step(server, model): finally: hook.remove() assert len(seen) == 5 and not torch.allclose(seen[1], seen[2]) # step 2 made two calls: A, then B + + +def test_alternating_prompts_say_why_they_did_nothing(server, model): + """No [A|B] in the prompt, or no named step in the run: two different sentences.""" + from comfy.nested_tensor import NestedTensor + from core import log + from modules.sampling.modifiers.nodes import FunUILoadModifiers, FunUIModifierSettings + from modules.sampling.sampler.nodes import FunUISampler + shapes = NestedTensor((torch.zeros(1, 24, 2, 4, 4), torch.zeros(1, 32, 2, 3))) + cond = [[torch.randn(1, 5, 48), {}]] + checked = FunUIModifierSettings.execute(_saved_by_the_app(server, dict( + APP_VALUES, noise_merge={"enabled": False}, prompt_alternatives={"separate": True, "b_steps": "1"}))).result[0] + patched, _ = FunUILoadModifiers.execute(model, checked).result + log._reset() + FunUISampler.execute(patched, cond, cond, {"samples": shapes}, seed=1, steps=3, cfg=1.0, + sampler_name="lcm", scheduler="beta", denoise=1.0) + assert any("the prompt had no [A|B] options" in e["message"] for e in log.history())