Grok Text Refine (xAI)
A rewrite node whose job you get to define
- refined_text
What it is
Grok Text Refine takes your text, sends it to xAI's chat API alongside a system_prompt, and gives you back whatever the system prompt asked for. That's it. "Refine" is the author's use case, not a constraint - the same node is a caption refiner, a prompt generator, a translator or a summarizer, and which one you get is entirely decided by the text you put in system_prompt.
If you've seen the "LLM in a graph" pattern, this is it - and note what it is not. It is not the text encoder inside your checkpoint, which is frozen, part of the model, and runs every generation. This is a separate language model bolted onto the canvas as a tool, working upstream of the sampler. The two get conflated constantly. The community settled on the pattern a while ago - "prompt enhancer" mentions went from 12 in 2023 to 253 in the first half of 2026 - and the two shapes it takes here are:
[Load Image] → [JoyCaption] → [Grok Text Refine (xAI)] → [Save Text File]
[Grok Text File Loader] → [Grok Text Refine (xAI)] → [CLIP Text Encode] → [KSampler]
The first is deterministic caption cleanup. The second is prompt generation with real variety, one idea per queue run. Keep that split in mind, because it decides your seed setting later.
How it works
It's an OpenAI-compatible POST to https://api.x.ai/v1/chat/completions - your text as the user message, your system prompt as the system message - and it does a decent amount of housekeeping you'd otherwise be writing yourself.
It retries: three attempts total, with backoff, for timeouts and the usual transient HTTP codes. It cleans the reply before handing it to your graph, stripping stray markdown fences and wrapping quotes that chat models love to add. And it handles the reasoning-model parameter problem in two ways. Neutral sampling values (top_p at 1.0, penalties at 0.0) are never sent to the API at all - which is why models like grok-4.5 that reject presencePenalty outright don't break. If a model still rejects one, the node reads the error, strips that parameter, and resends without it, logging which one it dropped. The queue keeps running.
The inputs that matter
system_prompt- the job. This is the one you'll spend your time on. Feed it fromGrok System Prompt Loaderso you can edit it in a text editor instead of a widget.text- the user message. A raw caption to refine, or a short idea to expand.temperature- 0.2 by default, which is right for caption refinement. For prompt generation push it to 1.0–1.15. Above ~1.3, grammar starts to go.seed- not sent to the API. It's purely a cache-breaker. ComfyUI only re-runs a node when an input changed, so withfixedand identical inputs you get the cached result back and no API call. Set it to randomize for prompt generation, or you'll generate every image from literally the same prompt text. Leave itfixedfor caption refinement and repeated runs cost you nothing.model- defaults togrok-4. Model ids change; checkcurl https://api.x.ai/v1/models -H "Authorization: Bearer YOUR_KEY"rather than trusting the default.fallback_to_input- on by default. If the call fails, your input text passes through unchanged instead of killing the queue. Leave it on for batch work.
Output is a single refined_text string. Wire it to CLIP Text Encode for a seed prompt, or to Save Text File for dataset work.
Install
Manager: search Grok Prompt Forge (xAI). Manual:
cd ComfyUI/custom_nodes
git clone https://github.com/Slartibart23/comfyui-grok-prompt-forge
Restart; nodes land under Grok → Text. This pack is the successor to comfyui-grok-text-refine with the same node class IDs, so old workflows load unchanged - but delete the old comfyui-grok-text-refine folder when you upgrade, or you'll register the same class twice.
Get a key at console.x.ai and put it in grok_api_key.txt in your ComfyUI root with the api_key widget empty - a key typed there gets saved into any workflow you export.
Common issues
Replace the default system prompt. The one baked into this node is the author's LoRA dataset-captioning prompt, complete with a specific trigger token and rules about what to strip from captions. Connect a text source and wonder why your prompts come back looking like training captions - that's why. Good template, not a neutral default.
The penalty dials can silently do nothing. On reasoning models that reject them, the node strips and retries - so the run succeeds and the console is the only place you'll see that your presence_penalty had no effect. On those models you get variety from temperature plus an explicit ban list in the system prompt, and that ceiling is worth knowing anyway: sampling works at the word level, and it can't stop the model loving the same motifs - the same centered poses, the same lighting clichés. Ban clauses and genuinely different input text (see Grok Text File Loader cycling a folder) are the fix.
Inputs (14)
| Name | Type | Default | Description |
|---|---|---|---|
| text | STRING | The input text sent to Grok as the user message. Connect a text source here: a raw caption to refine, or a short idea / CONTENT line to expand into a full image prompt. | |
| system_prompt | STRING | You are a dataset captioning specialist for LoRA training on the Krea2-raw model. You will receive exactly ONE raw image caption per request, generated by a vision model (JoyCaption). Refine it into a clean final training caption. ## Named subject - The woman in the image is named "M3l4nie" — exact spelling, this is her trigger token. It must appear in the caption. - Replace generic references to her ("a woman", "the woman") with "M3l4nie". Never normalize the spelling to "Melanie". If the raw caption is missing the name entirely, insert it as the subject. - Streamline appositive phrases: "M3l4nie, a nude woman with dark hair, lying..." becomes "M3l4nie, nude, lying..." ## REMOVE — fixed identity traits of M3l4nie (these bind to her trigger token) - Hair descriptions: color, texture, curl, length (e.g. "short curly dark hair") - Face descriptions: eye color, facial features, skin tone - Fixed body characteristics: breast size, areola color/size, body type, and any description of pubic hair (e.g. "small patch of pubic hair") - General attractiveness or age descriptors ## KEEP — variable attributes - Glasses: if the raw caption mentions glasses, the final caption MUST keep them. If not mentioned, do not add them. - Nudity state and clothing: "nude", "topless", specific garments — always keep, stated plainly. - Earrings and other jewelry/accessories, if mentioned. - Pose, body position, actions, facial expression (mouth, eyes), gaze direction. - Setting, location, visible objects. - Camera information: shot type, camera angle, vantage height. - Concrete lighting descriptions. ## Rules for men (only if a man appears in the caption) - Men have NO trigger token. Refer to them as "a man", "a second man", etc. - Keep their visual traits as given in the raw caption. Do not invent traits not in the raw caption. ## General refinement - Ensure unambiguous attribution in interaction scenes: WHO does WHAT to WHOM. Fix vague pronouns. - Preserve explicit sexual content as direct factual description; never censor, soften, or moralize. - Strip any mood language, aesthetic judgments, and meta phrases. - Plain natural-language sentences. Keep the raw caption's level of detail for everything in the KEEP list; do not shorten by dropping kept information. ## Output Output ONLY the single refined caption. No numbering, no commentary, no confirmation, no quotation marks, no markdown. | The instructions that define WHAT this node does (refine captions, generate prompts, translate, ...). Edit freely, or feed one in from the Grok System Prompt Loader node. |
| model | STRING | grok-4 | Which xAI Grok model handles the request, e.g. grok-4 or grok-4.5. Note: some reasoning models partially ignore sampling settings like temperature. |
| temperature | FLOAT | 0.200–2 | Main creativity dial. Low = the model always picks its favorite words (predictable, repetitive) - use 0.0-0.3 for caption refinement. High = unusual words get a real chance (varied, surprising) - use 1.0-1.15 for prompt generation. Above ~1.3 grammar starts to fall apart. |
| top_p | FLOAT | 1.000.05–1 | Cutoff filter (nucleus sampling). Before picking a word, the model throws away the least likely candidates: 0.9 keeps only words covering the top 90% of probability - everything below becomes impossible, no matter the temperature. 1.0 = off (nothing is cut, value not sent to the API). Rule of thumb: keep this HIGH (0.98-1.0) and steer variety with temperature instead. Lowering it makes output safer but blander. |
| presence_penalty | FLOAT | 0.00-2–2 | Pushes the model away from words it has ALREADY USED in this output, regardless of how often. Good against stock phrases (golden hour, neon lights, ...). 0.0 = off (not sent to the API). Try 0.3-0.6 for prompt generation; above ~1.0 output can get incoherent. If the model rejects this parameter (some reasoning models do), the node automatically retries without it - check the console log. |
| frequency_penalty | FLOAT | 0.00-2–2 | Like presence_penalty, but the push grows THE MORE OFTEN a word repeats - targets literal word repetition within one output (she... she... she...). 0.0 = off (not sent to the API). Try 0.2-0.5 if outputs loop on the same words. If the model rejects this parameter, the node automatically retries without it - check the console log. |
| max_tokens | INT | 102464–8192 | Upper limit for the response length (roughly 3/4 of a word per token). 1024 is plenty for a 150-200 word image prompt. Too low = truncated, cut-off prompts. |
| seed | INT | 00–18446744073709550000 | Cache-breaker, NOT sent to the API. ComfyUI only re-runs a node when an input changed. Set the control below to 'randomize' to force a fresh API call every queue run (required for prompt generation!). 'fixed' reuses the cached result for identical requests (saves money on repeated caption refinement runs). |
| api_keyopt | STRING | Your xAI API key. Leave EMPTY if possible - keys typed here end up in exported workflows! Prefer the XAI_API_KEY environment variable or the key file below. | |
| api_key_fileopt | STRING | grok_api_key.txt | Path to a text file containing only your xAI API key. Relative paths are searched in the ComfyUI root, the portable root, and this node pack's folder. |
| base_urlopt | STRING | https://api.x.ai/v1 | API endpoint. Only change this if you route requests through a proxy or a different OpenAI-compatible server. |
| fallback_to_inputopt | BOOLEAN | true | If the API call fails (no key, network error, ...), pass the input text through unchanged instead of stopping the queue. Keeps long batch runs alive. |
| timeout_secondsopt | INT | 12010–600 | How long to wait for the API before retrying (3 attempts total). Increase for slow reasoning models. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| refined_text | STRING | — |