Eval Prompts
Put an if-statement inside your prompt
- positive
- negative
The problem this solves
You have one prompt you like and two checkpoints that don't speak the same language. Score tags do nothing on Illustrious and SDXL - write score_9, score_8_up on a Danbooru model and you've added noise. Danbooru quality tags carry almost no weight on Pony in return. So you keep two workflows, or you keep one and remember to hand-edit the prefix every time you swap models. You will forget.
Eval Prompts is the third option: branch logic baked into the prompt text as <if> blocks, where you can still read it.
[positive]
<if model="Pony">score_9, score_8_up, score_7_up,</if>
1girl, school uniform, standing
<if all="outdoors,rain" any="drenched,wet">wet clothes,</if>
[negative]
worst quality,
<if pos any="holding,grabbing">bad hands, bad fingers,</if>
Those [positive] / [negative] / [model_name] headings are the README telling you which input each block goes in - not syntax. The <if> tags are.
How it actually works
It's a plain string node. Pure Python, no JavaScript companion - in this pack the frontend code only hooks Translate Prompt and Prompt Palette, so Eval Prompts keeps working on the Nodes 2.0 frontend, unlike the pack's flagged modal-UI nodes.
At run time, before anything reaches a text encoder:
- Strip, then snapshot. Every
<if …>…</if>block is deleted from both the positive and the negative, and those stripped copies become what the conditions get tested against. This explains most confusing behaviour: a block can never satisfy its own condition. - Positive first, then negative. The negative pass runs after the positive is fully resolved, so
pos any="holding"in your negative tests the finished positive. - Substitute or delete. A true block becomes its contents; a false one becomes nothing.
- Normalize in and out. The same pass as the pack's Normalize Prompt: comments stripped, newlines collapsed, commas tidied.
Four things you can test, all ANDed together - <if all="rain" any="wet,damp" matches="\d+mm"> needs all three:
model="regex"- case-insensitive regex againstmodel_name. Backslashes are flipped to forward slashes first, so a Windows path still matchesPony.all="a,b"- every item present in the target.any="a,b"- at least one present.matches="regex"- free regex against the target.
The all/any tests are literal word-boundary searches, not substrings. Case doesn't matter.
The "target" the words are searched in depends on where the block lives: in the positive, it's the stripped positive; in the negative, the stripped negative. Add pos or neg to force a side. not inverts the entire result.
Inputs and outputs
Three inputs, all strings, all forced inputs - there's no text box on the node, you wire text in:
positiveandnegative- required. Feed them from anything that emits a string: a core string/primitive node, or this pack's own Split Prompt (which pulls-(tag)segments out of one prompt into a negative) or Normalize Prompt.model_name- optional, and only useful if you wire something into it. Empty doesn't match any regex, so everymodel=block silently drops.
Two outputs, both STRING: positive and negative, straight into your CLIP Text Encode nodes. If you're using this pack anyway, Load Checkpoint or Diffusion Model and Load Checkpoint Preset both output a model_name string - wire that into the third socket and your model branches get the checkpoint filename for free.
Installing it
ComfyUI Manager → search ComfyUI-GadgetNodes, or by hand:
cd ComfyUI/custom_nodes
git clone https://github.com/2daadv/ComfyUI-GadgetNodes
pip install -r ComfyUI-GadgetNodes/requirements.txt
Restart, then look under Gadget/prompt. MIT-licensed, no model downloads, and no config file needed for this node.
The repo's requirements.txt is exactly two lines: dynamicprompts==0.31.0 and requests. The README's third entry, PyYAML, isn't in there at all - presumably it's already in your env. Eval Prompts needs none of them; that pin is for Expand Wildcards. If pip offers to downgrade something you already have, that's the pack asking.
Where people get burned
Your condition can't see itself. <if all="beach">beach, sunset</if> never fires, because the check runs against the prompt with that block removed. Put the triggering tags outside the block.
A broken regex fails quietly. If matches= or model= won't compile, the block is treated as false and dropped. It does log [GadgetNodes] if-matches regex error to the console, so check there before concluding the logic is wrong.
any is a word search, not a substring search. any="arm" fires on arm, arm up and (arm:1.2), but not on arms up and not on farmer. Singular and plural are different words. Write the tag the way it actually appears.
not is greedy. It inverts the whole AND, not the last criterion. <if not all="rain" any="wet"> means "neither".
pos and neg together: pos wins. Nesting <if> inside <if> isn't supported at all - the author says so, and the source agrees, matching with a single regex pass.
Normalize steps on your text. Both inputs get the Normalize Prompt cleanup, which strips # and // to end-of-line as comments - a prompt with a URL in it loses the rest of that line. Handy for knocking out a candidate block, a surprise otherwise. Everything also comes out as one line.
There's no community signal to lean on here - a corpus search for this pack comes back empty, which is what a small utility pack looks like. Its README is unusually complete, though, with a full syntax table. Read that once; it saves an hour of guessing.
Inputs (3)
| Name | Type | Default | Description |
|---|---|---|---|
| positive | STRING | — | |
| negative | STRING | — | |
| model_nameopt | STRING | — |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| positive | STRING | — |
| negative | STRING | — |