ComfyUI Node

coco

A local LLM that writes prompts in your generator's native dialect

By grandju51·Created 2 months ago·Updated 11 days ago· 5
coco
  • image
  • image_2
  • image_3
  • image_4
  • image_5
  • image_6
  • image_7
  • image_8
  • audio
  • video
  • prompt
  • raw_response
base_urlhttp://localhost:1234/v1
model
target_modelAnima base v1
global_directives
system_prompt
user_promptA lone astronaut discovering a glowing forest on an alien planet
temperature0.70
top_p0.90
top_k40
min_p0.00
repeat_penalty1.10
max_tokens1024
seed0
thinkingoff (no thinking)
strip_before_tag</think>,</mm:think>
keep_historyfalse
max_history_turns6
reset_historyfalse
timeout120
image_analysis_size1 MP
api_keylm-studio
no_think_model
audio_rolenone - no audio
video_stride4
video_max_frames8
video_frame_size512 px
video_fps0.0
video_rolenone - no video
enable_min_ptrue
enable_repeat_penaltytrue
unload_afterfalse
context_length0
context_slots0

Type a rough idea - "astronaut, glowing forest, alien planet" - and this node turns it into a fully-formed prompt written the way your actual generator expects: Danbooru tags for Illustrious, natural language for FLUX.2, motion verbs for Wan, the whole [Shot 1] At 00:02.400 ritual for MiniMax H3. It does this with a local LLM (LM Studio or vLLM), no cloud, no API key of your own. It's the "LLM as a node" pattern that went from novelty to routine in this ecosystem, and it fits the 2026 architecture neatly: if your checkpoint's encoder is itself a language model reading an instruction, having a second LLM write that instruction is just translation between two things that speak the same language.

First, a quirk that will save you ten minutes of searching: the node's display name in the Add Node menu is literally "coco" (under the LLM Prompt Studio category). The README calls it "LLM Prompt Studio (LM Studio / vLLM)", the code says "coco" - the code wins. Search "coco" and you'll find it.

How it works

It's a chat-completion client pointed at an OpenAI-compatible server. Set base_url (LM Studio: http://localhost:1234/v1, vLLM: http://localhost:8000/v1), leave api_key as anything for LM Studio or your --api-key for vLLM, and leave model empty - the node auto-detects the chat model loaded at that address (it skips text-encoder and embedding models). The 🔄 button just shows you which one it found.

The interesting part is the cards. The target_model dropdown holds twelve prompt presets - Anima base v1, Illustrious, SDXL, FLUX.2 Klein, Krea 2, Ideogram, LTX-2, Wan 2.2, three MiniMax H3 variants, and a generic one. Pick a target and its English prompt "card" loads into system_prompt, fully editable. Your own rules go in global_directives, which stay put across target switches. user_prompt is your idea. Outputs: prompt (the cleaned text - no thinking, no chat scaffolding) and raw_response (the untouched answer, for debugging).

The reasoning-model handling is the most thought-out bit. Chat LLMs love to emit a <think> block, and that block bleeding into your conditioning is exactly the failure mode the KB warns about. thinking defaults to off and, for reasoning models, states it in every dialect at once - /no_think for Qwen3, enable_thinking=false for GLM, a non-thinking model alias for DeepSeek - then strip_before_tag (default </think>,</mm:think>) cuts any surviving reasoning block out of the output. The model writes like a writer, not like a chat bot.

The inputs a beginner actually sets

  • target_model - the generator you're prompting for. Everything else follows from this.
  • user_prompt - your idea. The one text box you'll touch every run.
  • temperature / max_tokens - 0.7 / 1024 are sane defaults; lower the temperature if output starts repeating.
  • image (optional) - connect one and a vision model can caption or describe it into the prompt, labelled <Picture 1>.

There are up to 8 image sockets, plus video (video with video_stride, video_max_frames, video_frame_size, video_fps, video_role) and audio (audio + audio_role), all speaking MiniMax H3's reference vocabulary. image_analysis_size (default 1 MP) downscales before sending - worth turning down once you're feeding several images.

Install & quick start

ComfyUI Manager: search comfyui-llm-prompt-studio. Or:

cd ComfyUI/custom_nodes
git clone https://github.com/grandju51/comfyui-llm-prompt-studio

Restart, refresh. No pip install - the pack is standard-library only, so nothing to break. For LM Studio, load a model and hit Start Server in the Developer tab. For vLLM:

vllm serve Qwen/Qwen3-8B --port 8000

The traps

The one that actually OOMs people is LM Studio's context reservation. context_length × context_slots is memory the model books next to its weights, and LM Studio's defaults (e.g. 60k tokens × 4 slots) can take a 24 GB card out in the middle of a prompt. The symptom is Channel Error in the LM Studio log and {"error":"terminated"} here. A picture plus a few turns of history fits in ~8k, so set context_length around there and context_slots to 1 (ComfyUI sends one request at a time - 4 slots is paying for nothing).

Second trap: min_p and top_k are non-OpenAI extension fields, and some backends answer 400 on the first one they don't know. A vLLM server running speculative decoding always refuses min_p. The node retries once without all extensions and prints what it dropped, and the two enable_* switches let you park tuned values while keeping them out of a request the backend would reject. If you see a request that looks "cancelled," this is usually it.

Leave unload_after off while you iterate - reloading a big model costs seconds per run. Turn it on once your prompt is right and you want the VRAM back for the Flux/SDXL model downstream.

CategoryLLM Prompt Studio

Inputs (43)

NameTypeDefaultDescription
base_urlSTRINGhttp://localhost:1234/v1OpenAI-compatible base URL. LM Studio: http://localhost:1234/v1 | vLLM: http://localhost:8000/v1
modelSTRINGLeave EMPTY to auto-use the chat model loaded at the address. Only type a name to force a specific model.
target_modelCOMBOAnima base v1Which generator the prompt is FOR. Loads its preset into the system prompt box.
global_directivesSTRINGYour own global rules added on top of the system prompt for EVERY target model (e.g. 'always add cinematic lighting', 'avoid text in the image'). Stays put when you switch target models.
system_promptSTRINGThe LLM 'card'. Leave empty to use the preset of the selected target model.
user_promptSTRINGA lone astronaut discovering a glowing forest on an alien planetYour message / idea (the chat box).
temperatureFLOAT0.700–2
top_pFLOAT0.900–1
top_kINT400–1000
min_pFLOAT0.000–1Min-p sampling: drops tokens below this fraction of the top token's probability. 0 = disabled. Try 0.05-0.1 and raise top_p to 1.0 to use min_p alone. Does NOT work on a vLLM server started with speculative decoding (draft model / n-gram): it answers 400 and generates nothing. Turn enable_min_p off there (it keeps your value) and filter with top_p / top_k instead.
repeat_penaltyFLOAT1.100–2Penalises tokens already produced. 1.0 = neutral and nothing is sent; enable_repeat_penalty (bottom of the node) switches it off without touching this value.
max_tokensINT102416–32768
seedINT00–18446744073709550000
thinkingCOMBOoff (no thinking)Reasoning control. 'off' sends every dialect at once: /no_think (Qwen3.x), enable_thinking/thinking = false, and the non-thinking model alias for DeepSeek. Gemma has no thinking mode; use auto/off.
strip_before_tagSTRING</think>,</mm:think>Everything up to AND including the tag is removed from the output. Put SEVERAL tags separated by commas (e.g. </think>,</thinking>,</reasoning>); the comma is only a separator. Empty = keep everything.
keep_historyBOOLEANfalseMulti-turn chat: remember previous turns of this node.
max_history_turnsINT60–100Context memory depth: how many past user+assistant turns to keep when keep_history is on (0 = none).
reset_historyBOOLEANfalseClear this node's memory before generating.
timeoutINT1205–1800Request timeout in seconds.
image_analysis_sizeCOMBO1 MPDownscale the connected image before sending it to the vision model. Smaller = faster and fewer vision tokens. MP presets keep the aspect ratio; px presets cap the longest side. Images already smaller are left as-is.
api_keyoptSTRINGlm-studioAPI key if required (LM Studio: any value; vLLM: your --api-key). Leave default if none.
imageoptIMAGEOptional image for vision models. The LLM is told this one is <Picture 1>.
image_2optIMAGEExtra reference image. Connected images are numbered in socket order, so this one reaches the LLM as <Picture 2> when every socket above it is used too.
image_3optIMAGEExtra reference image. Connected images are numbered in socket order, so this one reaches the LLM as <Picture 3> when every socket above it is used too.
image_4optIMAGEExtra reference image. Connected images are numbered in socket order, so this one reaches the LLM as <Picture 4> when every socket above it is used too.
image_5optIMAGEExtra reference image. Connected images are numbered in socket order, so this one reaches the LLM as <Picture 5> when every socket above it is used too.
image_6optIMAGEExtra reference image. Connected images are numbered in socket order, so this one reaches the LLM as <Picture 6> when every socket above it is used too.
image_7optIMAGEExtra reference image. Connected images are numbered in socket order, so this one reaches the LLM as <Picture 7> when every socket above it is used too.
image_8optIMAGEExtra reference image. Connected images are numbered in socket order, so this one reaches the LLM as <Picture 8> when every socket above it is used too.
audiooptAUDIOOptional audio reference. Connecting it tells the LLM that an audio signal exists and is labelled <Audio 1>; what it is FOR is set by audio_role. The node does not listen to it - it declares it, so the prompt can carry the reuse or reference relationship H3 expects.
videooptIMAGEOptional source video, as the IMAGE batch a video loader outputs. Frames are sampled from it and shown to the vision model, so the prompt can talk about a video it has really seen. Labelled <Video 1>; how many frames and what for is set by the video_* widgets below.
no_think_modeloptSTRINGNo-think override: the model alias called INSTEAD when thinking = off. DeepSeek-style servers read a request with no thinking field as thinking ON, so naming the non-thinking alias is what really turns reasoning off. Empty = auto (a DeepSeek model becomes 'deepseek-chat' if the server serves it).
audio_roleoptCOMBOnone - no audioWhat the connected audio is FOR, in MiniMax H3's own vocabulary. Picking anything but 'none' declares <Audio 1> to the LLM even if nothing is wired into the audio socket, which is what you want when the track is fed to the video model further down the graph.
video_strideoptINT41–9999Take 1 frame out of N from the connected video. 1 = every frame, 4 = one out of four. The result is then thinned down to video_max_frames, so this mostly sets WHERE the samples come from on a long clip.
video_max_framesoptINT80–999Hard cap on the frames actually sent, spread over the whole clip. 0 = send NO frame: the video is still declared as <Video 1> but the LLM never sees it, which is the mode for a text-only model. Above ~16 the request gets heavy (roughly 700 vision tokens per frame at 512 px) - the node warns and obeys.
video_frame_sizeoptCOMBO512 pxLongest side each frame is downscaled to before sending. Frames go by the dozen, so keep it small: 512 px reads fine, 256 px is enough to follow an action, 128 px only for counting shots.
video_fpsoptFLOAT0.00–240Frame rate of the connected video. Set it and every sampled frame is labelled with its real timestamp, so the model writes 'At MM:SS.mmm' on actual times instead of made-up ones. 0 = unknown, frames are only numbered.
video_roleoptCOMBOnone - no videoWhat the video is FOR, in MiniMax H3's own vocabulary. Picking anything but 'none' declares <Video 1> to the LLM even with nothing wired into the video socket - what you want when the clip goes straight to the video model further down the graph.
enable_min_poptBOOLEANtrueMaster switch for min_p. Off = the field is NEVER sent, whatever the slider says, so you can park a value there and still talk to a backend that rejects it (a vLLM server with speculative decoding answers 400 on it). On = sent as soon as the slider is above 0.
enable_repeat_penaltyoptBOOLEANtrueMaster switch for the repetition penalty. Off = neither repetition_penalty (vLLM) nor repeat_penalty (llama.cpp) is sent, whatever the slider says. On = sent as soon as the slider leaves 1.0, which is the neutral value.
unload_afteroptBOOLEANfalseLM Studio only: drop the model from memory as soon as the answer is in, so the VRAM is free for the Flux/SDXL model further down the graph. Uses LM Studio's own API (/api/v1/models/unload, 0.3.30+), which lives next to the base_url you typed. The next run loads the model again, so leave it off while you iterate on a prompt: reloading costs several seconds per run.
context_lengthoptINT00–1048576LM Studio only: how many tokens the model reserves when the node loads it. 0 = leave LM Studio's own setting alone. This is the VRAM knob: the reservation is context_length x context_slots and it sits NEXT to the weights, so a 17 GB model asking 60k x 4 tokens runs a 24 GB card out of memory in the middle of a prompt ('Channel Error' in the LM Studio log, {"error":"terminated"} here). A prompt with a picture and a few turns of history fits in ~8k. The size is fixed when the model loads, so a copy already in memory at another size is unloaded and loaded again.
context_slotsoptINT00–64LM Studio only: how many requests it keeps room for in parallel ('parallel'), each one costing a full context_length. 0 = leave its own setting alone. ComfyUI sends one request at a time, so 1 divides the reservation by whatever LM Studio had picked - by 4, with its usual default. Works on its own or next to context_length.

Outputs (2)

NameTypeDescription
promptSTRING
raw_responseSTRING