Extensions/comfyui-llm-wildcard-manager
ComfyUI Extension

comfyui-llm-wildcard-manager

A ComfyUI extension with 7 custom nodes.

By pclshmΒ·Created 4 months agoΒ·Updated 3 months agoΒ· 1
pclshm/comfyui-llm-wildcard-manager
Nodes7
On cloudLocal install
Categoryprompt/wildcards
Stars1
Updated3 months ago
Readme

🎲 LLM Wildcard Manager for ComfyUI

A small set of ComfyUI nodes that let an LLM design a prompt template with __wildcard__ placeholders and then fill those placeholders one at a time, in isolation, with explicit anti-repetition β€” backed by reusable on-disk wildcard files.

Core nodes:

| Node | Purpose | |-------------------------------|---------| | 🎲 LLM Server Config | Single place to configure the LLM backend (endpoint, model, key, temperature). Wired into both Manager and Resolver. | | 🎲 LLM Wildcard Manager | Designs the prompt template. Asks the LLM to rewrite your idea as a template with __wildcard__ placeholders, plus a description for each placeholder. | | 🎲 LLM Wildcard Template Builder | Optional add-on for the Manager. A pure structure configurator: you hand-author the prompt's shape in a block editor (sentences + wildcard groups with count sliders) and it outputs a WILDCARD_STRUCTURE. Wire it into the Manager's structure input to steer how the Manager builds the template. Holds no idea/negative/LLM of its own. | | 🎲 LLM Wildcard Resolver | Fills __wildcard__ slots: reuses values from disk or asks the LLM for a fresh, anti-repetition value (one slot at a time). | | 🎲 LLM Wildcard Report | Renders the resolver's per-slot results as a structured collapsible view + raw text panel. Outputs counters for routing. |

Manager + Template Builder: by default the Manager turns a free-text idea into a template and decides the structure for you. Wire a Template Builder into the Manager's optional structure input and you decide the structure instead (a sentence, then 3 character wildcards, an action sentence, 2 pose wildcards, …) β€” the Manager still supplies the idea, negative prompt and steering, and drafts the prose + descriptions along your shape.

Why this exists

When you ask an LLM to "enhance this prompt," it sees the whole prompt and anchors on it. Run it twice and you get two near-identical outputs.

This pack flips that: the Manager turns your idea into a template with small variable parts (__hair__, __location__, …), and the Resolver fills each variable part with a fresh value the LLM has never produced for that category before. The downstream prompt has genuine variance instead of being a re-skin of the same sentence.

Install

Manual

cd ComfyUI/custom_nodes
git clone https://github.com/YOUR_GITHUB_USER/comfyui-llm-wildcard-manager

No extra Python deps β€” uses stdlib urllib.

Restart ComfyUI. Nodes appear under prompt β†’ wildcards.

A starter workflow is in example_workflows/llm_wildcard_basic.json.

Upgrading from 0.2.x: the 0.3 release replaces LLMWildcardPromptConfig with the new Manager + Server Config split. Saved workflows that referenced the old PromptConfig node, or the Resolver's old backend/endpoint/model widgets, will need re-wiring. Open the new example workflow as a starting point.

Recommended wiring

[LLM Server Config] --server--> [LLM Wildcard Manager] --prompt_template--> [LLM Wildcard Resolver] --resolved_prompt--> [CLIP Text Encode (positive)]
                  \--server-----------------------------/                  \--report------> [LLM Wildcard Report]
                                              \--prompts---------/         \--negative_prompt-> [CLIP Text Encode (negative)]
                  \-----------------------------(Manager) --negative_prompt-> [CLIP Text Encode (negative)] (alternative, identical output)

Prefer to design the prompt's structure yourself? Add a Template Builder and wire it into the Manager's optional structure input:

[LLM Wildcard Template Builder] --structure--> [LLM Wildcard Manager] --prompt_template--> [LLM Wildcard Resolver]
[LLM Server Config] --server------------------/                      \--prompts/negative_prompt-->

The negative_prompt output is a stable, deterministic comma-separated deny-list β€” it is not LLM-rewritten β€” so the negative side of your CLIP encode pair stays predictable regardless of how the positive prompt re-rolls each queue.

The Server Config is wired into both Manager and Resolver so endpoint settings live in one node. The Manager hands the Resolver both:

  • prompt_template β€” the template the Manager designed (wire to template).
  • prompts β€” a bundle with the system prompt + flair + per-category descriptions (wire to the optional prompts socket).

Wildcard files

Stored in ComfyUI/wildcards/<name>.txt, one value per line. Compatible with the Impact Pack and Santodan Wildcard Manager file formats. The Resolver creates files automatically as it generates new values.

Template syntax

__hair__       reuse a stored value (or generate if file empty / mode=force_new)
__!hair__      force LLM to generate a new value, append to file

Modes (Resolver)

| Mode | Behavior | |------------------|------------------------------------------------------------| | reuse_existing | Always pick from file. Generates only if file is empty. | | force_new | Every slot is regenerated and appended to its file. | | hybrid | Reuse by default; only __!name__ slots force generation. |

Backends (Server Config)

  • ollama β€” endpoint = http://localhost:11434, model = llama3.1 (or any pulled model), api_key blank.
  • llamacpp β€” endpoint = http://localhost:8080/v1, model can be left blank, api_key blank.
  • openai_compatible β€” works with OpenAI, LM Studio, vLLM, OpenRouter, etc. Set endpoint to the base URL ending in /v1, set model, set api_key.

Using llama.cpp in Docker

docker run --rm -p 8080:8080 \
  -v /path/to/models:/models \
  ghcr.io/ggml-org/llama.cpp:server \
  -m /models/your-model.gguf -c 4096 --host 0.0.0.0 --port 8080

In the Server Config node, set backend = llamacpp and endpoint = http://localhost:8080/v1.

Networking gotchas:

  • If ComfyUI also runs in Docker, localhost inside the ComfyUI container points to itself, not to the llama.cpp container. Use one of:
    • http://host.docker.internal:8080/v1 (Docker Desktop on Windows/macOS, and Linux with --add-host=host.docker.internal:host-gateway)
    • The llama.cpp container's name on a shared user-defined network: http://llamacpp:8080/v1
  • If only llama.cpp is in Docker and ComfyUI runs on the host, http://localhost:8080/v1 works as long as you published the port with -p 8080:8080.

Sanity check from your terminal:

curl http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"local-model","messages":[{"role":"user","content":"say hi"}]}'

If that returns JSON with choices[0].message.content, the nodes will work.

Node reference

🎲 LLM Server Config

The single source of truth for LLM settings. One output: server (a LLM_SERVER bundle). Wire into both Manager and Resolver.

Fields: backend, endpoint, model, api_key, temperature.

🎲 LLM Wildcard Manager

Designs the prompt template. On each run it asks the LLM to rewrite your example_prompt as a template with __wildcard__ placeholders, and to return a description for each placeholder it invented.

Inputs:

  • server β€” wire from the Server Config.
  • example_prompt β€” your idea. The Manager turns this into a template with variable parts replaced by __wildcards__.
  • lock_template β€” when on, the Manager skips the LLM entirely and reuses the last generated template + categories. Re-queue to get fresh random wildcard fills from the Resolver without changing the prompt. When off (default), the Manager regenerates every queue (modulated by seed).
  • seed β€” 0 re-rolls the template every queue run (new template, new category set). Non-zero is reproducible: same inputs β†’ same template.
  • direction β€” free-text with autocomplete. Pick a built-in preset (photoreal, cinematic, editorial, vintage_film, noir, cyberpunk, fantasy, anime, dreamlike, minimal, sfw_strict) or type your own steering text directly.
  • negative_prompt β€” traits to avoid, applied at every step:
    1. The drafted sentence won't include them (with a retry + clause-strip backstop when the LLM ignores the instruction β€” no phones now actually keeps "holding a phone" out of the template).
    2. Aspects you pin here won't become wildcards (e.g. no old or middle-aged people stops __age__ from being created when your idea already says "young woman").
    3. Each wildcard description gets an explicit exclusion clause so the resolver's per-slot LLM call can't drift into them either.
    4. The same list is compiled into a deterministic negative_prompt output you wire into CLIP Text Encode (negative).
  • min_categories β€” minimum number of __wildcard__ placeholders the Manager will accept. Enforced by retrying the wildcardify step (up to twice) with the previous result and an explicit "you produced X, need at least N β€” add more" instruction if the LLM falls short.
  • max_categories β€” hard cap on how many __wildcard__ placeholders the template may contain. The LLM is asked to pick the most impactful variables and leave the rest as concrete words; if it exceeds the cap, the surplus placeholders are demoted to plain words deterministically. Lower values = more focused prompts.
  • system_prompt_override β€” leave empty for the built-in template-design system prompt; fill to fully replace it (advanced).
  • categories β€” JSON object of {name: description}. User overrides only. Edited via the table UI on the node. User overrides win over LLM-suggested descriptions.
  • structure (optional input) β€” wire from a 🎲 LLM Wildcard Template Builder to hand-author the prompt's shape. When connected, the Manager drafts each sentence block and emits the exact wildcard counts you defined instead of letting the LLM invent the structure (min_categories / max_categories no longer apply β€” the counts come from your sliders). The idea, negative prompt and steering still come from this node.

Outputs:

  • prompt_template β€” wire into the Resolver's template input.
  • prompts β€” WILDCARD_PROMPTS bundle. Wire into the Resolver's optional prompts input. Carries the system prompt, flair, and the merged category descriptions.
  • negative_prompt β€” deterministic comma-separated string built from your negative_prompt widget + the parsed scene_bans + axis bans + forbidden_placeholders. Not LLM-rewritten β€” wire it straight into your CLIPTextEncode (negative) so the image model sees the same forbidden list every run. Also passed through to the Resolver in the prompts bundle.

UI:

  • Generated prompt panel at the top shows the template the LLM produced (with wildcard tokens highlighted).
  • Categories table beneath shows every category in the current template
    • every user override + every category that has entries on disk. Each row: expand chevron Β· name Β· description Β· entry-count badge Β· OVERRIDE tag if user-edited Β· remove button.
  • ↻ Refresh disk re-reads the wildcards folder without re-queuing.
  • + Add category appends a fresh override row.
  • The disk path of the wildcards folder is shown so you always know where values are written.

🎲 LLM Wildcard Template Builder

A pure structure configurator that plugs into the Manager. Instead of letting the LLM decide the prompt's shape, you compose it from an ordered list of blocks and wire the result into the Manager's optional structure input. The node itself holds no idea, negative prompt, server or LLM β€” it only emits the shape. When a structure is wired, the Manager drafts each sentence block and emits the exact wildcard counts you defined (resolved deterministically in Python, so the LLM can't miscount your "3 character / 2 pose" structure), using its own idea + negative + steering.

Block types:

  • Sentence block β€” a literal sentence (no wildcards). Type the text to use it verbatim, or leave it empty and the Manager's LLM writes one biased to the block's abstract role (scene / action / setting / …).
  • Wildcard group β€” a count slider (1–12), an abstract role (pick from a predefined list, or leave undefined for pure structure), and a new checkbox (emits __!name__ to force a fresh value every run). A group of count N emits N uniquely-numbered placeholders (__character_1__, __character_2__, …) so the Resolver fills each with a different value.

Roles are structural labels only β€” they name the dimension a slot covers, never its content. The content comes from the Manager's example_prompt.

Input:

  • structure β€” hidden JSON widget holding the block list. Edited via the block editor on the node; it's also the source of truth for headless/API runs.

Output:

  • structure β€” a WILDCARD_STRUCTURE payload. Wire into the Manager's optional structure input.

UI: a live skeleton preview (e.g. Sentence(scene) Β· __character__ Γ—3 Β· …), one row per block with its controls, + Sentence / + Wildcard group buttons, and ↑/↓/βœ• to reorder or remove. Everything is edited live β€” the node performs no LLM work itself.

🎲 LLM Wildcard Resolver

Fills __wildcard__ slots in the template.

Inputs:

  • server β€” wire from the Server Config.
  • template β€” wire from the Manager's prompt_template, or type your own template directly into the widget.
  • mode β€” hybrid (recommended), reuse_existing, or force_new.
  • max_per_category β€” soft cap. Once a category file hits this many entries, the resolver stops appending and starts reusing.
  • min_pool_size β€” pool floor. If a category's on-disk pool is below this number when the resolver runs, it tops up the pool with fresh values before picking. Default 5. Bump higher (e.g. 20–50) when outputs feel same-y across runs β€” wider pool = more combinatoric variety. Tops up silently once each pool is seeded.
  • values_per_call β€” how many values to request per LLM call when the resolver does generate or top up. Default 10. Higher = fewer calls to reach the floor but each call is bigger.
  • seed β€” random seed for the choice between existing values.
  • fix_seed β€” when off (default), every queue run re-rolls the fills (regardless of seed). When on, the resolver is fully deterministic: same template + same seed = same final values.
  • trigger_words β€” optional text spliced onto the final prompt (after the grammar-alignment pass, so LoRA trigger tokens stay verbatim). Empty means no triggers are added. Joined to the resolved prompt with , .
  • trigger_position β€” prefix (default) puts the trigger words at the start of the prompt; suffix appends them at the end.
  • prompts (optional input) β€” wire from the Manager. Carries the system prompt + flair + category descriptions used per-slot.

Outputs:

  • resolved_prompt β€” the final template with all wildcards replaced. Wire into your CLIP Text Encode positive input. After substitution and the alignment pass, any comma-separated clause that still contains a forbidden term from the negative prompt is stripped β€” so "girl, holding a phone, on a bench" becomes "girl, on a bench" when phone is on the deny-list.
  • report β€” full text report. Wire into the Report node.
  • negative_prompt β€” the same deterministic deny-list string the Manager produced (or rebuilt from prompts if the Manager wasn't wired). Wire into your CLIP Text Encode (negative) input.

🎲 LLM Wildcard Report

Renders the resolver's run results inside the node body and re-emits parsed counters for routing.

Inputs:

  • report β€” wire from the Resolver's report output.

Outputs:

| Output | Type | Meaning | |-------------|--------|----------------------------------------------------------------| | summary | STRING | The complete report text. | | generated | INT | Slots that produced new values (and were appended). | | reused | INT | Slots that reused an existing value (incl. cap-hits). | | errors | INT | Slots where the LLM call failed. | | total | INT | Total wildcard slots resolved. |

UI:

  • Header bar with total / generated / reused / errors counters.
  • One row per slot: status badge Β· name Β· final value (truncated). Click the expand chevron to reveal the prompt sent to the LLM, the LLM's raw reply, any retry, and any error.
  • Raw report textarea at the bottom for copy-paste.

Useful for routing β€” e.g. only save the workflow image when errors == 0.

Anti-repetition guarantees

Each per-slot LLM call receives:

  1. Only the category name and category description.
  2. The full list of existing values flagged as forbidden / do not paraphrase.
  3. No other context from the surrounding prompt.

The system prompt also asks the LLM to identify the implicit dimensions of the description (e.g. for hair: color Γ— length Γ— texture Γ— style) and produce values that combine choices across dimensions instead of varying along a single axis. Anti-duplication and combinatoric breadth are different problems; the Resolver enforces both.

If the model returns a duplicate anyway, the Resolver retries once with a higher temperature.

Seed behavior at a glance

| Where | Setting | Effect | |---------------|------------------------|-----------------------------------------------------------------------| | Manager | lock_template = true | Skip the LLM. Reuse the last cached template + categories. | | Manager | seed = 0 | Re-roll the prompt template + category set every queue run. | | Manager | seed != 0 | Reproducible: same inputs + same seed β†’ same template + categories. | | Resolver | fix_seed = false | Re-roll the per-slot fills every queue run (regardless of seed). | | Resolver | fix_seed = true | Fully deterministic: same template + same seed β†’ same final values. |

Reuse the same prompt with fresh wildcards every queue: turn on Manager lock_template and leave Resolver fix_seed off. The Manager won't call the LLM; the Resolver re-rolls each slot every run.