NAI Multi-Character Detailer
A Detailer That Knows Which Character It's Fixing
- image
- detectors
- characterPrompts
- tagger
- vibes
- references
- nai_context
- image
- mask_visualization
- matching_report
Normal detailers fix faces by re-rendering each one with your local sampler. This one fixes them by calling the NovelAI API. Same detect-crop-inpaint-paste loop, except the "inpaint" half is a paid remote request - and the part that makes it interesting is that each detected region gets the original character prompt it belongs to, plucked out of your multi-character setup, instead of one shared prompt smeared across every face.
That matters specifically for multi-character NovelAI work. If you generate four characters and their faces come back mushy, a plain FaceDetailer has one blunt instrument: one prompt for all four crops, which is how you get a room of clones. Here, a local tagger looks at each crop and asks "is this A or B?", then sends that character's own prompt - the exact text you typed, franchise tags and all - to the inpainting request.
How it works
The pipeline is the standard detailing loop with the API swapped in for the sampler. A detector bundle from the NAI Detection Combine Loader finds regions, then the node repairs each mask locally: fill small notches with a morphological close, fill enclosed holes, dilate. That repair is deliberately conservative - geometry only, no model, and added pixels are accepted only inside a face-shaped band and under a size budget, so a large occluded area never gets blindly painted over. Then it crops and sends the crop out as a Focused Inpainting request with the matched character's positive and negative prompt.
Matching is where the optional tagger comes in. With matching_mode=wd14 and the node pack's local PyTorch WD tagger connected, each region is scored against your original character slots. The tagger's output never becomes prompt text - it only selects a slot. That's the whole point: the character's own original prompt goes out verbatim. Weak matches are retried with a tighter crop (match_crop_factor) and skipped entirely if they stay ambiguous, and there is no manual mapping input, so a skipped face keeps its original pixels rather than getting the wrong identity.
The inputs that actually matter
Everything is set on the node itself except detection:
image- the finished render.detectors- theNAI_DETECTION_BUNDLEoutput from the combine loader. This socket only accepts that type, which is why you can't feed Impact Pack SEGS straight in.characterPrompts+tagger+matching_mode- the trio that turns a detailer into a multi-character detailer. Multiple characters in a connectednai_contextautomatically flipmatching_modetowd14and warn if no tagger is plugged in.strength(0.55),seed(-1, so random per region unless you pin it), andmax_regions(5 by default, up to 64). The node's own warning is worth repeating: quality falls off as region count climbs.limit_opus_free- on by default, and the reason half of your first runs will stop before spending anything.
detail_mode controls how many regions get processed at all - all (the default, ordered left to right then top to bottom) or first, which takes the highest-priority detection and stops. Also on the panel: dilation (8px of extra editing room), mask_feather (8px blend, separate from crop context), crop_factor, thresholds, and save_format for the autosaves.
Outputs are image, mask_visualization (your check on what it actually decided to touch) and matching_report, a JSON string listing each region's assigned slot, score, margin and whether it was edited or skipped. Wire the image into a Save Image node; read the report when a face comes back untouched.
Install
Via Manager, search ComfyUI_RS_NAI_API_Request. Manually:
cd ComfyUI/custom_nodes
git clone https://github.com/raspie10032/ComfyUI_RS_NAI_API_Request.git
python -m pip install -r ComfyUI_RS_NAI_API_Request/requirements.txt
You also need ComfyUI-Impact-Pack and ComfyUI-Impact-Subpack for the detector provider, plus a .env in the pack folder with NAI_ACCESS_TOKEN=... before any region can be corrected. For character matching, install the optional tagger requirements with the same Python that runs ComfyUI (python -m pip install -r requirements-tagger.txt) and let the WD tagger loader pull its weights - about 377 MB for the default WD ViT v3, stored under models/rs_wd_tagger.
Where people get burned
Executing this node performs correction - the old preview-only switch is gone, so a workflow that still has that widget checked is no longer protected by it. With limit_opus_free=True the node allows only validated Focused Inpainting crops (smaller than the source, ≤1,048,576 pixels, ≤28 steps) and blocks everything else before it reads your token. Set that to False deliberately. The upstream examples ship with protection on, so a workflow that appears to do nothing is usually doing exactly what it was told.
Second trap: the pack is small and Korean-community-rooted, and NovelAI-in-ComfyUI barely registers on the big subreddits, so the repo's own docs are a better help source than any thread you'll find. The pattern those docs encode - detail at native resolution, then upscale - still applies.
Inputs (51)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| detectors | NAI_DETECTION_BUNDLE | — | |
| prompt | STRING | smiling face, highly detailed | — |
| negative_prompt | STRING | lowres, bad anatomy | — |
| model | COMBO | NAI Diffusion V5 Full | V5 Full Medium Effort always uses 14 steps, Euler Ancestral, zero CFG Rescale, and fixed Heavy Undesired Content. Custom base/character negative prompts are ignored. Prompt Guidance remains adjustable. Image-to-image and inpainting require limit_opus_free=False and can spend Anlas. |
| strength | FLOAT | 0.550–1 | — |
| seed | INT | -1-1–4294967295 | — |
| show_advancedopt | BOOLEAN | false | — |
| limit_opus_freeopt | BOOLEAN | true | — |
| detail_modeopt | COMBO | all | 2 options: all, first |
| matching_modeopt | COMBO | shared | 2 options: shared, wd14 |
| characterPromptsopt | LIST | — | |
| taggeropt | RS_WD_TAGGER | — | |
| match_min_scoreopt | FLOAT | 0.250.01–1 | — |
| match_min_marginopt | FLOAT | 0.080.01–1 | — |
| match_crop_factoropt | FLOAT | 2.01–5 | — |
| max_regionsopt | INT | 51–64 | Maximum number of regions to process. Quality cannot be guaranteed as the number of processed regions increases. |
| vibesopt | RS_NAI_VIBES | — | |
| referencesopt | RS_NAI_REFERENCES | — | |
| normalize_vibe_strengthsopt | BOOLEAN | true | V4/V4.5 Vibe: scale strengths down if their absolute sum exceeds 1.0. |
| save_formatopt | COMBO | PNG | Autosave format. Preserves NovelAI generation information. No ComfyUI workflow is embedded. |
| webp_qualityopt | INT | 901–100 | Used only for lossy WebP. Lossless WebP preserves pixels. |
| sampleropt | COMBO | k_euler | 7 options: k_dpmpp_2m, k_dpmpp_sde, k_dpmpp_2m_sde, k_dpmpp_2s_ancestral, k_euler_ancestral, k_euler, +1 |
| stepsopt | INT | 281–50 | — |
| cfg_scaleopt | FLOAT | 6.00–30 | — |
| scheduleropt | COMBO | karras | 4 options: native, karras, exponential, polyexponential |
| thresholdopt | FLOAT | 0.300–1 | — |
| crop_factoropt | FLOAT | 3.01–10 | — |
| bbox_thresholdopt | FLOAT | 0.300–1 | — |
| closingopt | BOOLEAN | true | Fill small notches before dilation. |
| fill_holesopt | BOOLEAN | true | Fill enclosed holes within the repair limits, before dilation. |
| dilationopt | INT | 80–128 | Extra editing room in original-image pixels, after hole/notch filling. |
| mask_featheropt | INT | 80–64 | Blend inside the expanded mask. Separate from crop context and dilation. |
| nai_contextopt | NAI_CONTEXT | Connect the previous NAI generation context. Carries prompts, characters and generation settings; seed, size, strength, references and Anlas protection stay local. | |
| request_delay_secondsopt | FLOAT | 0.00–3600 | Wait before each NovelAI request, including each detail region and uncached Vibe encoding. 0 adds no delay. The built-in 2-second gap overlaps this timer; a longer server retry wait still applies. Preview-only runs do not wait. Stop cancels the countdown. |
| use_context_settingsopt | BOOLEAN | true | On: inherit prompts, characters, model and sampling settings from connected NAI context. Off: use this node's manual values. Both modes perform NovelAI correction; the seed stays local. |
| cfg_rescaleopt | FLOAT | 0.000–1 | — |
| prefer_brownianopt | BOOLEAN | false | — |
| variety_boostopt | BOOLEAN | true | — |
| context_modeopt | COMBO | inherit_all | With context enabled, inherit everything or replace only checked fields with this node's values. Unchecked fields still come from context. |
| override_promptopt | BOOLEAN | false | Use this node's prompt in override_selected mode. An empty string, zero or False is an intentional replacement. |
| override_negative_promptopt | BOOLEAN | false | Use this node's negative_prompt in override_selected mode. An empty string, zero or False is an intentional replacement. |
| override_modelopt | BOOLEAN | false | Use this node's model in override_selected mode. An empty string, zero or False is an intentional replacement. |
| override_sampleropt | BOOLEAN | false | Use this node's sampler in override_selected mode. An empty string, zero or False is an intentional replacement. |
| override_stepsopt | BOOLEAN | false | Use this node's steps in override_selected mode. An empty string, zero or False is an intentional replacement. |
| override_cfg_scaleopt | BOOLEAN | false | Use this node's cfg_scale in override_selected mode. An empty string, zero or False is an intentional replacement. |
| override_scheduleropt | BOOLEAN | false | Use this node's scheduler in override_selected mode. An empty string, zero or False is an intentional replacement. |
| override_cfg_rescaleopt | BOOLEAN | false | Use this node's cfg_rescale in override_selected mode. An empty string, zero or False is an intentional replacement. |
| override_prefer_brownianopt | BOOLEAN | false | Use this node's prefer_brownian in override_selected mode. An empty string, zero or False is an intentional replacement. |
| override_variety_boostopt | BOOLEAN | false | Use this node's variety_boost in override_selected mode. An empty string, zero or False is an intentional replacement. |
| override_characterPromptsopt | BOOLEAN | false | Replace the complete character list with characterPrompts, including negatives and positions. No connection clears the list. |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |
| mask_visualization | IMAGE | — |
| matching_report | STRING | — |