"""The prompt enhancer: what the model is asked, how its answer is cleaned, and that every failure hands the ORIGINAL prompt on.""" import json import pytest from core import config, shortcuts @pytest.fixture(autouse=True) def _library(tmp_path, monkeypatch): monkeypatch.setattr(config, "SHORTCUTS_FILE", tmp_path / "s.json") monkeypatch.setattr(config, "SHORTCUT_CATEGORIES_FILE", tmp_path / "c.json") monkeypatch.setattr(config, "ROOT", tmp_path) def en(): from modules.conditioning.prompt_enhancer import enhance return enhance class FakeClip: """ComfyUI's generate/decode pair, recording what it was asked.""" def __init__(self, answer="A fox walks.", fail=None): self.answer, self.fail, self.seen = answer, fail, {} def tokenize(self, text, **kw): self.seen = {"text": text, **kw} return "tok" def generate(self, tokens, **kw): if self.fail: raise self.fail self.seen["gen"] = kw return "ids" def decode(self, ids, skip_special_tokens=True): return self.answer # --- cleaning --------------------------------------------------------------- @pytest.mark.parametrize("raw,want", [ ("hmmA fox.", "A fox."), ("never ends and eats the answer", ""), ("```text\nA fox.\n```", "A fox."), ("Here is the prompt: A fox.", "A fox."), ('"A fox says \\"hi\\" and runs."', 'A fox says \\"hi\\" and runs.'), ('A fox says "hi" and runs.', 'A fox says "hi" and runs.'), ]) def test_clean(raw, want): assert en().clean(raw) == want def test_thinking_is_pulled_out_even_unterminated_or_headless(): e = en() assert e.extract_thinking("ab") == "a" assert e.extract_thinking("still going") == "still going" assert e.extract_thinking("opened by templateanswer") == "opened by template" assert e.extract_thinking("plain") == "" def test_a_looping_model_is_cut_at_a_sentence_and_a_long_one_by_the_users_limit(): e = en() loop = "A fox walks across the snowy field. " * 5 out, cut = e.trim_runaway("Intro sentence here. " + loop) assert cut assert out.count("A fox walks across the snowy field.") == 2 long_text = "A sentence that goes on a bit. " * 40 out, cut = e.trim_runaway(long_text, max_length=16) # cap 64 chars assert cut and len(out) <= 64 and out.endswith(".") assert e.trim_runaway("short", max_length=400) == ("short", False) # --- chat ------------------------------------------------------------------- def test_chat_shows_only_the_latest_rewrite_and_every_comment(): e = en() rounds = [{"rewrites": {"whole": "v1"}, "comment": "warmer"}, {"rewrites": {"whole": "v2"}, "comment": "no rain"}] block = e.chat_block(rounds) assert "v2" in block and "v1" not in block and "- warmer\n- no rain" in block assert e.chat_block([{"comment": " "}, "junk", None]) == "" assert e.chat_block([{"rewrites": {"3": "only scene 3"}, "comment": "x"}], scene=3).count("only scene 3") == 1 def test_an_echoed_conversation_is_cut(): e = en() assert e.cut_chat_echo("The fox runs.\n\nUSER FEEDBACK (oldest first):\n- warmer") == "The fox runs." assert e.cut_chat_echo("YOUR LATEST REWRITE: The fox runs.") == "The fox runs." # --- reference -------------------------------------------------------------- def test_reference_uses_what_the_prompt_mentions_else_the_whole_group(): e = en() shortcuts.save({"name": "Rain city", "triggers": ["rc"], "replacements": ["heavy neon rain on asphalt"]}) shortcuts.save({"name": "Fox", "triggers": ["fx"], "replacements": ["a red fox"]}) groups = e.sources(["Rain city", "Fox", "Missing"], [], say=lambda m: None) assert len(groups) == 1 and len(groups[0]) == 2 only_rain = e.reference("a walk in the neon street", groups) assert "[Shortcut] Rain city" in only_rain and "[Shortcut] Fox" not in only_rain # a word from its content both = e.reference("nothing related", groups) assert "Rain city" in both and "Fox" in both # none mentioned: all assert e.reference("x", []) == "" def test_a_missing_shortcut_or_file_is_said_not_silent(): said = [] assert en().sources(["Nope"], ["/no/such/file.json"], say=said.append) == [] assert len(said) == 2 and "Nope" in said[0] and "/no/such/file.json" in said[1] def test_lorebooks_match_by_keyword_whole_word_and_constants_ride_along(tmp_path): e = en() book = tmp_path / "lore.json" book.write_text(json.dumps({"entries": [ {"keys": ["sea"], "content": "The sea is cold.", "comment": "Sea"}, {"keys": ["/ra+in/"], "content": "Rain is soft."}, {"keys": ["x"], "content": "Always true.", "constant": True}]})) groups = e.sources([], [str(book)]) got = e.reference("a season of rain", groups) # "sea" is NOT in "season" assert "Rain is soft." in got and "The sea is cold." not in got and "Always true." in got # --- enhance ---------------------------------------------------------------- def test_the_model_gets_instructions_then_the_prompt_and_the_answer_is_returned(): clip = FakeClip("Here is the prompt: A red fox walks through snow.") out, status, info = en().enhance(clip, "a fox", seed=5, max_length=100, do_sample=False) assert out == "A red fox walks through snow." and info["ok"] assert clip.seen["text"].startswith(en().SYSTEM_PROMPT) and clip.seen["text"].endswith("a fox") assert clip.seen["gen"]["seed"] == 5 and clip.seen["gen"]["do_sample"] is False assert clip.seen["skip_template"] is False and clip.seen["min_length"] == 1 @pytest.mark.parametrize("clip", [FakeClip(fail=RuntimeError("boom")), FakeClip(""), FakeClip("x"), None, object()]) def test_every_failure_returns_the_original_prompt(clip): out, status, info = en().enhance(clip, "a fox") assert out == "a fox" and not info["ok"] and status @pytest.mark.parametrize("answer", [",,,,,,,,,,,,,,,,,,,,", "... --- !!!", "1234 5678", "x ,,,,,,,,,,,,,,,,,,,, ,,,,,"]) def test_an_answer_with_no_words_is_a_failure_not_a_prompt(answer): """A text encoder that is not a chat model answered with 400 commas; the run used them as the prompt.""" out, status, info = en().enhance(FakeClip(answer), "a red fox in the snow") assert out == "a red fox in the snow" and not info["ok"] and "no usable words" in status def test_an_empty_prompt_is_skipped_not_sent(): clip = FakeClip() out, status, _ = en().enhance(clip, " ") assert out == " " and "empty" in status and clip.seen == {} class _Tok: """ComfyUI's H3 tokenizer shape: takes `images`, swallows everything else.""" def tokenize_with_weights(self, text, return_word_ids=False, images=[], **kwargs): return [] class RealShapeClip(FakeClip): """sd.CLIP.generate's real signature: no no_repeat_ngram_size, seed=None default.""" tokenizer = _Tok() def generate(self, tokens, do_sample=True, max_length=256, temperature=1.0, top_k=50, top_p=0.95, min_p=0.0, repetition_penalty=1.0, seed=None, presence_penalty=0.0): assert seed is not None or not do_sample, "torch.Generator.manual_seed(None) raises" self.seen["gen"] = dict(do_sample=do_sample, seed=seed, temperature=temperature) return "ids" def test_the_real_generate_signature_gets_a_seed_and_no_unknown_arguments(): clip = RealShapeClip("A fox runs.") out, status, info = en().enhance(clip, "fox", seed=0) assert out == "A fox runs." and info["ok"] and clip.seen["gen"]["seed"] assert en().enhance(RealShapeClip("A fox runs."), "fox", seed=7)[2]["ok"] def test_temperature_zero_is_greedy_not_a_crash(): clip = RealShapeClip("A fox runs.") assert en().enhance(clip, "fox", temperature=0.0)[2]["ok"] and clip.seen["gen"]["do_sample"] is False def test_a_tokenizer_without_picture_or_template_input_says_so(): clip = RealShapeClip("A fox runs.") _, status, _ = en().enhance(clip, "fox", image=object()) assert "takes no picture" in status and "no chat template" in status assert "image" not in clip.seen and "skip_template" not in clip.seen def test_chat_revision_goes_in_the_system_turn_and_an_echo_is_cut(): e = en() clip = FakeClip("The fox runs.\nUSER FEEDBACK: warmer") chat = e.chat_block([{"rewrites": {"whole": "old"}, "comment": "warmer"}]) out, _, info = e.enhance(clip, "a fox", chat=chat) assert out == "The fox runs." and e.CHAT_SYSTEM in clip.seen["text"] assert "ORIGINAL PROMPT:\na fox" in clip.seen["text"] and info["sent"] # --- the node --------------------------------------------------------------- @pytest.fixture def node(comfyui): from modules.conditioning.prompt_enhancer import nodes nodes.RUNS.clear() return nodes def test_the_node_passes_through_when_off_and_never_touches_the_model(node): clip = FakeClip() out = node.FunPackEnhancePrompt.execute(clip, "a fox", enabled=False) assert tuple(out.args) == ("a fox", "off") and clip.seen == {} assert node.RUNS[-1]["after"] == "a fox", "the Enhance tab still shows the final prompt" def test_the_node_enhances_records_the_run_and_warns_on_failure(node, caplog): out = node.FunPackEnhancePrompt.execute(FakeClip("A red fox."), "a fox", enabled=True, seed=3) assert out.args[0] == "A red fox." and node.RUNS[-1]["after"] == "A red fox." from core import log log._reset() bad = node.FunPackEnhancePrompt.execute(FakeClip(fail=RuntimeError("x")), "a fox", enabled=True) assert bad.args[0] == "a fox" and node.RUNS[-1]["ok"] is False assert any("original prompt" in r["message"] for r in log.history()) def test_seed_zero_is_never_cached_a_set_seed_is(node): f = node.FunPackEnhancePrompt.fingerprint_inputs assert isinstance(f(seed=0, enabled=True), float) assert f(seed=7, enabled=True) == "" and f(seed=0, enabled=False) == "" def test_the_readout_routes_are_provided_and_answer(): from modules.conditioning import prompt_enhancer as mod assert mod.PROVIDES["routes"] is mod.routes table = {} class T: def get(self, path): return lambda fn: table.setdefault(path, fn) class W: @staticmethod def json_response(d): return d mod.routes(T(), "/m", W) assert set(table) == {"/m/defaults", "/m/runs"} def test_a_tokenizer_that_reads_the_picture_out_of_kwargs_gets_it(): class Qwen: # Qwen3-VL's shape: names only `images`, reads kwargs.get("image") def tokenize_with_weights(self, text, return_word_ids=False, images=[], **kwargs): return kwargs.get("image") class Clip(RealShapeClip): tokenizer = Qwen() clip = Clip("A fox runs.") _, status, _ = en().enhance(clip, "fox", image=object()) assert "takes no picture" not in status and "image" in clip.seen