ComfyUI-Pi-LLM
ComfyUI custom nodes for calling Pi as a text and optional image LLM from a workflow, with local caching for reproducible results and companion text extraction utilities.
ComfyUI Pi LLM
ComfyUI custom nodes for calling Pi as a text and optional image LLM from a workflow.
The main node runs the local pi CLI in print mode and returns the model response as a ComfyUI STRING. It can optionally pass connected ComfyUI images to vision-capable Pi models. A companion extractor node helps remove common LLM wrapper text, such as XML tags or Markdown code fences.
Features
- Call Pi from ComfyUI with a system instruction, model dropdown, prompt, and optional image input.
- Defaults to
minimax/MiniMax-M3. - Includes OpenRouter dropdown options for Grok, Kimi, GLM, and Gemini Flash/Flash Lite models.
- LLM-only execution: tools, context files, and sessions are disabled for safer/predictable workflow behavior.
- Deterministic local response cache keyed by LLM inputs and seed for hands-off reproducible reruns.
- Reproducible saved-response mode for portable/manual freezing when needed.
- Cache-busting
seedinput so the same prompt can be regenerated. - Optional
run_every_queuemode to force reruns. - Extract generated text from
<prompt>...</prompt>, code fences, or custom delimiters. - Compose seeded wildcard prompts from inline lists of likes, dislikes, characters, clothing, styles, and extras.
Requirements
- ComfyUI.
- Pi installed and available on the
PATHused by the ComfyUI process. - Pi already authenticated/configured for the model provider you want to use.
Install Pi if needed:
npm install -g --ignore-scripts @earendil-works/pi-coding-agent
Then authenticate/configure Pi as usual, for example:
pi /login
Or use supported provider API key environment variables. See the Pi docs for provider setup.
Verify Pi works outside ComfyUI:
pi -p --no-tools --no-context-files --no-session "Say hello in five words."
Installation
From your ComfyUI directory:
cd custom_nodes
git clone https://github.com/felixowens/ComfyUI-Pi-LLM.git
Restart ComfyUI, then search for the Pi category.
If ComfyUI cannot find pi, make sure the shell/service that starts ComfyUI has Pi on its PATH. You can also symlink Pi into a common path, for example:
which pi
# then, if needed:
sudo ln -s /path/to/pi /usr/local/bin/pi
Nodes
Pi LLM Text
Category: Pi
Inputs:
system_instruction: system prompt passed to Pi via--system-prompt.model_name: Pi model selector dropdown, passed via--model. Defaults tominimax/MiniMax-M3.prompt: what the LLM should do or produce. Type text here or connect aSTRINGoutput from another node.connected_text: optional extraSTRINGinput appended afterpromptwhen non-empty.image: optional ComfyUIIMAGEinput passed to Pi as a temporary PNG attachment. Use a vision-capable model.response_mode:use_saved_text_if_presentdefault: usesaved_responsewhen non-empty, otherwise use the cache/Pi path.call_pi: ignoresaved_responseand use the cache/Pi path.use_saved_text: always outputsaved_responseand never call Pi.
cache_mode:use_cache_or_generatedefault: use the deterministic local cache when present, otherwise call Pi and save the response.refresh_cache: call Pi and overwrite the cached response for this input/seed.cache_only: only use the cache; error if missing.disable_cache: call Pi without reading or writing cache.
saved_response: optional manual/portable freeze text. Local cache handles normal reproducibility without using this.seed: part of the cache key. Change it to intentionally get/cache a new response for the same prompt.timeout_seconds: maximum time to wait for Pi.run_every_queue: when enabled, disables ComfyUI caching for this node.
Output:
text: generated response as a ComfyUISTRING.
Behavior:
pi -p --no-tools --no-context-files --no-session --system-prompt "..." --model "minimax/MiniMax-M3" "...prompt..."
# With optional image input, the node writes temporary PNGs and passes them before the prompt:
pi -p --no-tools --no-context-files --no-session --system-prompt "..." --model "openrouter/google/gemini-2.5-flash" @/tmp/image-0.png "...prompt..."
Tools, context files, and sessions are disabled so the node behaves like a text/image LLM call and avoids file/system side effects. The seed is used for ComfyUI cache invalidation; Pi does not currently receive it as a model sampling seed.
Reproducible reruns
By default, Pi LLM Text uses a deterministic local cache. The cache key includes the system instruction, model name, combined prompt text, optional image content digests, and seed. On first run it calls Pi and stores the response under cache/<sha256>.txt; on future runs with the same inputs and seed it returns that cached response without calling Pi.
This means dragging an image/workflow back into ComfyUI on the same machine should reproduce the same LLM text as long as the local cache directory is still present.
Use cache_mode = refresh_cache or change seed when you intentionally want a new LLM response. Use cache_mode = disable_cache if you always want live Pi calls.
saved_response remains available for manual/portable freezing when you want the exact text stored in the workflow/image metadata itself.
Pi Text Extractor
Extracts useful text from an LLM response when the model adds fluff around the generated content.
Inputs:
text: LLM response to extract from.extraction_mode:auto: try XML tag, then code fence, then custom delimiters.xml_tag: extract from tags like<prompt>...</prompt>.code_fence: extract from triple backticks liketext ....between_delimiters: extract between custom start/end markers.
xml_tag: tag name for XML extraction, defaults toprompt.fence_language: optional code fence language. Empty matches any code fence.start_delimiter/end_delimiter: custom markers for delimiter extraction.occurrence: choose thefirstorlastmatch.strip_whitespace: trims leading/trailing whitespace from extracted text.fail_if_missing: raise an error if no wrapper is found. If disabled, returns the original text.
Outputs:
text: extracted text, or original text when not found andfail_if_missingis disabled.found: boolean indicating whether an extraction wrapper was found.
Recommended LLM instruction examples:
Return only the final prompt inside <prompt>...</prompt> tags.
or:
Return only the final prompt in a ```text code fence.
Pi Wildcard Prompt
Seeded prompt composer for randomly injecting fragments into positive and negative prompts.
Inputs:
prompt_template: optional positive prompt template. Use placeholders like{base_prompt},{characters},{clothing},{styles},{likes},{extra}, or{all_positive}. If blank, selected fragments are appended tobase_prompt.negative_template: optional negative prompt template. Use placeholders like{base_negative}and{dislikes}. If blank, selected dislikes are appended tobase_negative.base_prompt: base positive text. Used as{base_prompt}in templates, or included at the start whenprompt_templateis blank.base_negative: base negative text. Used as{base_negative}in templates, or included at the start whennegative_templateis blank.likes: positive fragments you like, one per line.dislikes: negative fragments, one per line.characters: character/subject options, one per line.clothing: clothing/accessory options, one per line.styles: visual/style options, one per line.extra: any other fragments, one per line.seed: deterministic random seed. Same inputs + same seed produce the same selections.*_count: how many entries to select from each category. Counts larger than the list are clamped safely.separator:comma,space, ornewline.dedupe: removes duplicate fragments while preserving order.shuffle_positive: shuffles the selected positive fragments while keepingbase_promptfirst.
Template placeholders:
{base_prompt}{base_negative}{likes}{dislikes}{characters}{clothing}{styles}{extra}{all_positive}: base prompt plus all selected positive categories.
Example prompt_template:
{base_prompt} of {characters}, wearing {clothing}, {styles}, {likes}, {extra}
Example negative_template:
{base_negative}, {dislikes}
Outputs:
positive_prompt: composed positive prompt.negative_prompt: composed negative prompt from template/base negative plus selected dislikes.selected: readable debug text showing exactly what was selected for the seed.
List parsing rules:
- Blank lines are ignored.
- Lines starting with
#are ignored as comments.
Example styles list:
# lighting/style options
cinematic lighting
soft watercolor illustration
retro anime screencap
35mm film still
Model list
The dropdown is currently a static list generated from pi --list-models on the original development machine. It includes selected OpenRouter models such as openrouter/x-ai/grok-4.3, openrouter/~moonshotai/kimi-latest, openrouter/z-ai/glm-4.7-flash, openrouter/~google/gemini-flash-latest, and openrouter/google/gemini-2.5-flash-lite.
If your Pi installation has different model access, edit models.py and update AVAILABLE_MODELS.
Development
Run syntax checks and unit tests from this repository:
python -m py_compile __init__.py nodes.py models.py pi_runner.py extractors.py wildcards.py
python -m unittest discover -s tests -v
The ComfyUI node classes in nodes.py are intentionally thin adapters. Most behavior lives in pure modules:
pi_runner.py: prompt joining, saved-response mode decisions, deterministic response caching, Pi command construction, and Pi subprocess execution.image_inputs.py: conversion of optional ComfyUIIMAGEinputs into temporary PNG attachments and cache digests.extractors.py: XML/code-fence/delimiter extraction.wildcards.py: deterministic seeded wildcard prompt composition.models.py: static model dropdown data.
License
No license has been added yet.