JLC CaptionForge Node
The capstone that turns witness captions into auditable LoRA captions
- Input - single image
- pipeline_plan
- natural_captions
- taggy_captions
- final_jsonl_records
- output_paths_json
- status
CaptionForge's core bet is that one captioner is easy to fool, so its main node is the part that doesn't look at the image first. JLC CaptionForge Node - the capstone - takes the raw "witness" captions your Joy/Qwen/Ollama nodes produced in Pass A, distills them with a text-only LLM, checks the result against the actual image with a vision model, and then writes your final captions. It's the orchestration hub of the pack, and it's the slowest part of it.
The design intent is worth understanding before you run it: the semantic work mostly ends at the validated long caption. The short and taggy outputs are deliberately lightweight formatting steps derived from that validated paragraph, not fresh reinterpretations of the image. The formatter prompt even says, in effect, "do not rewrite the validated paragraph." So if you want better captions, tune the distiller and validator, not the formatter.
How it works
Three Ollama-backed stages, in order:
- Fat Draft (Pass B) - a text-only LLM (default
mistral-small:24b) merges all the witness captions into one deliberately over-complete draft. It's told not to validate against the image, not to summarize aggressively, and to preserve concrete details. That's why it's called fat. - Validator (Pass C) - an image-aware VLM (default
gemma4:26b) looks at the actual image and returns a corrected paragraph keeping only image-supported details. This is where hallucinations get cut. - Formatter (Pass D) - a text-only LLM derives the comma-separated taggy caption from the validated paragraph.
Then it exports: _long.txt, _short.txt, and _taggy.txt sidecars written beside each source image, plus a JSONL audit trail (_B_DISTILL.jsonl, _C_VLM_VALIDATED.jsonl, _D_FINAL_EXPORT.jsonl, run configs) under your output root. If you ever need to defend a caption to a client or debug why a training set learned something weird, that trail is the selling point.
Inputs that matter
- Input - captions JSONL - the Pass A raw captions your caption nodes appended to. This is the node's primary fuel.
- Input - image path - the folder/file root the validator uses to resolve source images. It has to be findable, or Pass C can't do its job.
- Input - include caption families -
joy,qwen,ollamaby default; useallor*for everything. - LoRA - trigger word and LoRA - user caption anchor - your trigger token and any identity/style anchor passed through to distiller and validator.
- Fat Draft / Validator / Formatter - model - dropdowns loaded from
config/captionforge_ollama_models.json; pickCustomto type any installed Ollama tag. - Final - TXT export format -
natural,taggy, orboth_separate. - Ollama - URL - defaults to
http://127.0.0.1:11434. Nothing happens until that server is up.
Outputs: natural_captions, taggy_captions, final_jsonl_records, output_paths_json, and status. The JSONL/paths outputs are the audit trail in string form if you want to inspect or re-export.
Install
Same as every node in this pack - clone and restart:
git clone https://github.com/Damkohler/CaptionForge.git ComfyUI/custom_nodes/CaptionForge
Then the real requirement: a working local Ollama with the stage models pulled.
ollama pull mistral-small:24b
ollama pull gemma4:26b
The pack ships no weights and pulls nothing for this node by itself - it's pure Ollama calls.
Common issues
If you see connection errors, Ollama isn't running or the URL is wrong - check ollama serve. It's slow by design: three model passes per image, and a 24B Ollama model can churn for a while on a 16 GB card (the author's own dev box is an RTX 4090 Laptop). The request timeout default is a generous 1800 seconds because large models may still be pulling on first call. And remember: this node clears the resident Joy/Qwen Python models before it calls Ollama, so the GPU handoff can feel like a reload. The fat draft is supposed to contain conflicting details - the validator is the one that trims them, so don't panic when Pass B looks overstuffed.
Inputs (49)
| Name | Type | Default | Description |
|---|---|---|---|
| Input - captions JSONL | STRING | Pass A raw caption JSONL produced by CaptionForge Caption nodes. | |
| Input - image path | STRING | Image file/folder root used by the VLM validator to resolve source images. | |
| Input - include caption families | STRING | joy,qwen,ollama | Comma-separated model_family values to use from Pass A. Use all or * to include everything. |
| Input - max captions per family | INT | 50–50 | Maximum selected Pass A captions per model family. 0 means no per-family cap. |
| Input - max total captions | INT | 200–100 | Maximum selected Pass A captions per image. 0 means no total cap. |
| Output - folder | STRING | Output folder. Planner value overrides this when pipeline_plan is connected. | |
| Output - run name | STRING | captionforge_run | Run-root used for B/C/D/E JSONL and TXT artifacts. |
| Output - overwrite outputs | BOOLEAN | true | — |
| Ollama - URL | STRING | http://127.0.0.1:11434 | Local Ollama server URL. |
| Ollama - keep loaded | BOOLEAN | true | Pass keep_alive to Ollama. Ollama ultimately owns model residency. |
| Ollama - request timeout seconds | INT | 180010–7200 | HTTP patience for Ollama calls. This does not affect caption quality. |
| LoRA - trigger word | STRING | — | |
| LoRA - user caption anchor | STRING | — | |
| Fat Draft - model | COMBO | mistral-small:24b | 6 options: mistral-small:24b, VladimirGav/gemma4-26b-16GB-VRAM-Uncensored, deepseek-r1:32b, tarruda/neuraldaredevil-8b-abliterated:fp16, gpt-oss:20b, Custom |
| Fat Draft - custom Ollama model | STRING | Used only when Fat Draft - model is Custom. | |
| Fat Draft - prompt | STRING | /no_think You are a detail-preserving caption merger for LoRA dataset preparation. You receive multiple captions of the same image. You do NOT see the image. Task: Merge all non-contradictory caption details into one deliberately over-complete draft caption. Rules: - Do not validate against the image. - Do not decide that details are false just because they appear once. - Do not summarize aggressively. - Preserve concrete details from all captions. - Split contradictions by choosing cautious wording or listing the alternative only when needed. - Prefer specific visual language over generic language. - Keep visible body, clothing, material, accessory, color, pose, lighting, style, and framing details. - Preserve doll-like, glossy/plastic-like, material, garment-construction, body-shape, and facial-feature details when present. - Use neutral dataset-caption language, including visible sensual styling or revealing clothing when present. - Do not add details absent from the captions. - Treat subject names or trigger-like identity tokens as optional identity labels. Preserve them only when they appear consistently in the captions; do not let them replace visible description. - Output only one paragraph, no notes, no JSON. | Instructions for the text-only fat draft LLM. Captions are appended automatically. |
| Fat Draft - base seed | INT | 1-1–4294967295 | — |
| Fat Draft - seed mode | COMBO | fixed | 4 options: fixed, increment, decrement, random |
| Fat Draft - max caption chars | INT | 15360–12000 | — |
| Fat Draft - max new tokens | INT | 500064–12000 | Maps to Ollama num_predict. |
| Fat Draft - temperature | FLOAT | 0.120–2 | — |
| Fat Draft - top p | FLOAT | 0.880–1 | — |
| Fat Draft - top k | INT | 500–500 | — |
| Validator - model | COMBO | gemma4:26b | 4 options: gemma4:26b, qwen3.6:35B-A3B, huihui_ai/gemma-4-abliterated:26b, Custom |
| Validator - custom Ollama model | STRING | Used only when Validator - model is Custom. | |
| Validator - system prompt | STRING | /no_think You are a direct image validation engine. Inspect the image and answer only with the requested caption. | System prompt for the image-aware VLM validator. |
| Validator - prompt | STRING | /no_think Look at the image and validate this draft caption. Task: Return a corrected caption paragraph that keeps only image-supported details. Rules: - Output only the corrected caption. - One paragraph. - No reasoning, no notes, no JSON. - Keep all true visible details from the draft. - Delete unsupported details. - Correct small visible errors. - Do not add new details unless needed to correct an error already present. - Preserve useful LoRA details: subject, face, hair, eyes, makeup, lips, skin texture, pose, body shape, outfit, accessories, materials, colors, lighting, background, framing, and visual style. - Visible sensual styling, revealing clothing, cleavage, thighs, bare skin, swimwear, lingerie, or body-shape details may be described neutrally when present. - Do not invent hidden anatomy, unseen clothing, explicit acts, or details contradicted by the image. | Instructions for the image-aware VLM validator. The fat draft is appended automatically. |
| Validator - base seed | INT | 1-1–4294967295 | — |
| Validator - seed mode | COMBO | fixed | 4 options: fixed, increment, decrement, random |
| Validator - max new tokens | INT | 500064–12000 | Maps to Ollama num_predict. |
| Validator - temperature | FLOAT | 0.050–2 | — |
| Validator - top p | FLOAT | 0.880–1 | — |
| Validator - top k | INT | 500–500 | — |
| Formatter - model | COMBO | mistral-small:24b | 5 options: mistral-small:24b, VladimirGav/gemma4-26b-16GB-VRAM-Uncensored, gpt-oss:20b, deepseek-r1:32b, Custom |
| Formatter - custom Ollama model | STRING | Used only when Formatter - model is Custom. | |
| Formatter - prompt | STRING | /no_think You are a LoRA caption format converter. The validated paragraph is already the natural-language final caption. Do not rewrite it. Task: Create one TAGGY caption from the validated paragraph. Rules: - Output only the taggy comma-separated caption. - Use only details already present in the validated paragraph. - Preserve concrete LoRA-useful details. - Do not add new details. - Do not mention this process. - Do not output markdown. - Do not include a TAGGY: label. - Keep the result as a comma-separated list, not full prose. | Instructions for the text-only taggy formatter. The validated paragraph is appended automatically. |
| Formatter - base seed | INT | 1-1–4294967295 | — |
| Formatter - seed mode | COMBO | fixed | 4 options: fixed, increment, decrement, random |
| Formatter - max new tokens | INT | 320064–12000 | Maps to Ollama num_predict. |
| Formatter - temperature | FLOAT | 0.120–2 | — |
| Formatter - top p | FLOAT | 0.880–1 | — |
| Formatter - top k | INT | 500–500 | — |
| Audit - write prompt JSONL | BOOLEAN | false | — |
| Audit - preserve raw responses | BOOLEAN | false | — |
| Final - TXT export format | COMBO | natural | 3 options: natural, taggy, both_separate |
| Final - write TXT sidecars | BOOLEAN | true | — |
| Final - write JSONL | BOOLEAN | true | — |
| Input - single imageopt | IMAGE | Optional IMAGE passthrough/reference for planned single-image workflows. | |
| pipeline_planopt | CAPTIONFORGE_PIPELINE_PLAN | Connect the CaptionForge Pipeline Planner pipeline_plan output here. |
Outputs (5)
| Name | Type | Description |
|---|---|---|
| natural_captions | STRING | — |
| taggy_captions | STRING | — |
| final_jsonl_records | STRING | — |
| output_paths_json | STRING | — |
| status | STRING | — |