万能模型提示词优化
Turn \"girl on a beach, make it cinematic\" Into a Prompt Your Model Can Actually Use
- clip
- image
- media_bundle
- prompt_r2v
- conditioning
- prompt_finite
- segments_json
What this node actually is
It's a prompt writer sitting in your graph. You type the lazy version of your idea - "girl on a beach, cinematic, slow push in" - and a language model rewrites it into the structured, model-specific prompt your checkpoint actually wants. The output is still just a string, but a much better-behaved one, and it goes straight into your text encoder.
The reason this exists: 2026 models don't want tags or vibes, they want a paragraph of instruction. Flux, Z-Image, Qwen-Image and the video models all wrap your prompt in a chat template and hand it to an LLM encoder, so having a language model write the prompt stopped being a hack and became the normal move. This node is that pattern with a UI: a rich-text editor, nine image slots, three video and three audio slots, @图1 / @video 1 / @audio 1 references so the writer knows which asset is which, and a per-slot weight (0.1–2.0) for attention.
The part that makes it more than a generic text box is the template list. You pick the target model - MiniMax H3, LTX 2.5, Wan 2.2, FLUX.2, Z-Image, Seedance 2.5, Kling, Veo 3, Sora 2, HunyuanVideo, generic Image Prompt, or your own custom system prompt - and the optimizer writes to that model's house style instead of generic "cinematic lighting" mush. Video and image mode keep separate caches and history (three results each), so experimenting with one doesn't blow away the other.
How the optimization actually runs
Worth knowing, because it isn't what the canvas implies: the rewrite does not happen when you hit Queue. prompt_optimizer.py registers a small HTTP API on ComfyUI's own server (/universal-prompt-optimizer/optimize, plus start / status / cancel), the editor calls it when you click ✦, and the result is written back into the node's text field. At execution time the node is a pass-through - it resizes, packs, encodes. So an optimization you ran yesterday still ships in today's workflow, and a dead API key breaks the ✦ button rather than the queue.
Under the hood it calls an online provider (OpenAI, Gemini, OpenRouter, DashScope, SiliconFlow, or RunningHub by default in both .cn and overseas flavours) or runs a local vision model. read_media is on by default: reference images really do get sent to the model, and the RunningHub path pre-processes reference video through ffmpeg.
Inputs and outputs you'll actually touch
Required: width, height, resize_mode (cover / contain / stretch), and prompt - where the optimized text lands, written by the editor rather than by you.
Optional, and this is the interesting bit: clip, nine ref_image_*, three ref_video_*, three ref_audio_*, plus two hidden plumbing fields (gh_state_json, segments_json) the UI manages for you.
None of it does anything unless you wire the outputs:
image- the first reference image, resized, or a blank frame if you uploaded nothing. A lazy img2img source.conditioning- only populated if you plugged aclipin; otherwise it's an empty list.prompt- the optimized string. Feed it to a R2V bridge or any text encoder.prompt_finite- the same string under a wrapper type (GH_PROMPT_STR). Use this one for the Finite Segment bridge, because that node's prompt input won't accept the plainpromptoutput.media_bundle- one dict carrying all uploaded images and audio, 0-based indices matching the Director's convention. The context-bus pattern: one wire instead of twelve.segments_json- the multi-segment plan for long generations.
Install
Via ComfyUI Manager, search ComfyUI Universal Prompt Optimizer, or by hand:
cd ComfyUI/custom_nodes
git clone https://github.com/aihaipan/ComfyUI-Universal_Prompt_Optimizer.git
Restart ComfyUI. Dependencies are genuinely light - pyproject.toml lists exactly one, aiohttp, which ComfyUI already ships. No requirements.txt, no model download, nothing to compile. The pack is GPL-3.0-or-later, inherited from goohai's Goohai-MiniMax-H3_Integration - the author credits it as the source of both the optimization pipeline and the editor.
Local mode is where it gets heavy: GGUF vision models live in ComfyUI/models/llm (LLM also resolves, case-insensitively) and need llama-cpp-python plus an mmproj projector - the settings panel links the JamePeng wheels for your CUDA tag. The Transformers path wants transformers + torch and a VLM folder; Qwen2.5-VL, Qwen3-VL, Gemma3, MiniCPM-V and InternVL are the families it detects.
Where people get burned
Videos don't get packed. The node is named for media and its editor accepts ref_video_* slots, but _build_media_bundle only fills images and audios - the videos dict ships empty. Your video reference can steer the prompt, but it won't reach a bridge node downstream.
Silent audio dropouts. Audio is loaded through ComfyUI's loader; if pyav refuses the container (FLAC and some wrappers show up here), the slot is skipped with a log line and the bundle quietly contains one fewer audio. If H3 ignores your reference voice, check the console before you blame the model.
An API key with a per-call bill. The default provider is RunningHub, not a local model, so clicking ✦ does send your prompt and reference images to a third party - that's the mechanism, not a bug. Keys are stored per provider, so switching from OpenAI to SiliconFlow without pasting a new key just looks like a broken node.
No community track record yet. The author has effectively no Reddit footprint, the optimizer core is a GPL fork of goohai's project, and it's at v0.2.1 - read the code before pasting a key into a node whose whole job is phoning home.
Inputs (22)
| Name | Type | Default | Description |
|---|---|---|---|
| width | INT | 102464–16384 | — |
| height | INT | 102464–16384 | — |
| resize_mode | COMBO | cover | 3 options: cover, contain, stretch |
| prompt | STRING | — | |
| clipopt | CLIP | 可选:接入文本编码器后节点输出 conditioning | |
| ref_image_1opt | STRING | — | |
| ref_image_2opt | STRING | — | |
| ref_image_3opt | STRING | — | |
| ref_image_4opt | STRING | — | |
| ref_image_5opt | STRING | — | |
| ref_image_6opt | STRING | — | |
| ref_image_7opt | STRING | — | |
| ref_image_8opt | STRING | — | |
| ref_image_9opt | STRING | — | |
| ref_video_1opt | STRING | — | |
| ref_video_2opt | STRING | — | |
| ref_video_3opt | STRING | — | |
| ref_audio_1opt | STRING | — | |
| ref_audio_2opt | STRING | — | |
| ref_audio_3opt | STRING | — | |
| gh_state_jsonopt | STRING | — | |
| segments_jsonopt | STRING | — |
Outputs (6)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |
| media_bundle | GH_MEDIA_BUNDLE | — |
| prompt_r2v | STRING | — |
| conditioning | CONDITIONING | — |
| prompt_finite | GH_PROMPT_STR | — |
| segments_json | GH_SEGMENTS_STR | — |