Nodes/ComfyUI_RS_NAI_API_Request/NAI Multi-Character Detailer
ComfyUI Node

NAI Multi-Character Detailer

A Detailer That Knows Which Character It's Fixing

By raspie10032·Created about a year ago·Updated 2 days ago· 8
NAI Multi-Character Detailer
  • image
  • detectors
  • characterPrompts
  • tagger
  • vibes
  • references
  • nai_context
  • image
  • mask_visualization
  • matching_report
◄promptsmiling face, highly detailed►
◄negative_promptlowres, bad anatomy►
◄modelNAI Diffusion V5 Full►
◄strength0.55►
◄seed-1►
◄show_advancedfalse►
◄limit_opus_freetrue►
◄detail_modeall►
◄matching_modeshared►
◄match_min_score0.25►
◄match_min_margin0.08►
◄match_crop_factor2.0►
◄max_regions5►
◄normalize_vibe_strengthstrue►
◄save_formatPNG►
◄webp_quality90►
◄samplerk_euler►
◄steps28►
◄cfg_scale6.0►
◄schedulerkarras►
◄threshold0.30►
◄crop_factor3.0►
◄bbox_threshold0.30►
◄closingtrue►
◄fill_holestrue►
◄dilation8►
◄mask_feather8►
◄request_delay_seconds0.0►
◄use_context_settingstrue►
◄cfg_rescale0.00►
◄prefer_brownianfalse►
◄variety_boosttrue►
◄context_modeinherit_all►
◄override_promptfalse►
◄override_negative_promptfalse►
◄override_modelfalse►
◄override_samplerfalse►
◄override_stepsfalse►
◄override_cfg_scalefalse►
◄override_schedulerfalse►
◄override_cfg_rescalefalse►
◄override_prefer_brownianfalse►
◄override_variety_boostfalse►
◄override_characterPromptsfalse►

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 - the NAI_DETECTION_BUNDLE output 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 connected nai_context automatically flip matching_mode to wd14 and warn if no tagger is plugged in.
  • strength (0.55), seed (-1, so random per region unless you pin it), and max_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.

CategoryRS_NovelAI_API

Inputs (51)

NameTypeDefaultDescription
imageIMAGE—
detectorsNAI_DETECTION_BUNDLE—
promptSTRINGsmiling face, highly detailed—
negative_promptSTRINGlowres, bad anatomy—
modelCOMBONAI Diffusion V5 FullV5 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.
strengthFLOAT0.550–1—
seedINT-1-1–4294967295—
show_advancedoptBOOLEANfalse—
limit_opus_freeoptBOOLEANtrue—
detail_modeoptCOMBOall2 options: all, first
matching_modeoptCOMBOshared2 options: shared, wd14
characterPromptsoptLIST—
taggeroptRS_WD_TAGGER—
match_min_scoreoptFLOAT0.250.01–1—
match_min_marginoptFLOAT0.080.01–1—
match_crop_factoroptFLOAT2.01–5—
max_regionsoptINT51–64Maximum number of regions to process. Quality cannot be guaranteed as the number of processed regions increases.
vibesoptRS_NAI_VIBES—
referencesoptRS_NAI_REFERENCES—
normalize_vibe_strengthsoptBOOLEANtrueV4/V4.5 Vibe: scale strengths down if their absolute sum exceeds 1.0.
save_formatoptCOMBOPNGAutosave format. Preserves NovelAI generation information. No ComfyUI workflow is embedded.
webp_qualityoptINT901–100Used only for lossy WebP. Lossless WebP preserves pixels.
sampleroptCOMBOk_euler7 options: k_dpmpp_2m, k_dpmpp_sde, k_dpmpp_2m_sde, k_dpmpp_2s_ancestral, k_euler_ancestral, k_euler, +1
stepsoptINT281–50—
cfg_scaleoptFLOAT6.00–30—
scheduleroptCOMBOkarras4 options: native, karras, exponential, polyexponential
thresholdoptFLOAT0.300–1—
crop_factoroptFLOAT3.01–10—
bbox_thresholdoptFLOAT0.300–1—
closingoptBOOLEANtrueFill small notches before dilation.
fill_holesoptBOOLEANtrueFill enclosed holes within the repair limits, before dilation.
dilationoptINT80–128Extra editing room in original-image pixels, after hole/notch filling.
mask_featheroptINT80–64Blend inside the expanded mask. Separate from crop context and dilation.
nai_contextoptNAI_CONTEXTConnect the previous NAI generation context. Carries prompts, characters and generation settings; seed, size, strength, references and Anlas protection stay local.
request_delay_secondsoptFLOAT0.00–3600Wait 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_settingsoptBOOLEANtrueOn: 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_rescaleoptFLOAT0.000–1—
prefer_brownianoptBOOLEANfalse—
variety_boostoptBOOLEANtrue—
context_modeoptCOMBOinherit_allWith context enabled, inherit everything or replace only checked fields with this node's values. Unchecked fields still come from context.
override_promptoptBOOLEANfalseUse this node's prompt in override_selected mode. An empty string, zero or False is an intentional replacement.
override_negative_promptoptBOOLEANfalseUse this node's negative_prompt in override_selected mode. An empty string, zero or False is an intentional replacement.
override_modeloptBOOLEANfalseUse this node's model in override_selected mode. An empty string, zero or False is an intentional replacement.
override_sampleroptBOOLEANfalseUse this node's sampler in override_selected mode. An empty string, zero or False is an intentional replacement.
override_stepsoptBOOLEANfalseUse this node's steps in override_selected mode. An empty string, zero or False is an intentional replacement.
override_cfg_scaleoptBOOLEANfalseUse this node's cfg_scale in override_selected mode. An empty string, zero or False is an intentional replacement.
override_scheduleroptBOOLEANfalseUse this node's scheduler in override_selected mode. An empty string, zero or False is an intentional replacement.
override_cfg_rescaleoptBOOLEANfalseUse this node's cfg_rescale in override_selected mode. An empty string, zero or False is an intentional replacement.
override_prefer_brownianoptBOOLEANfalseUse this node's prefer_brownian in override_selected mode. An empty string, zero or False is an intentional replacement.
override_variety_boostoptBOOLEANfalseUse this node's variety_boost in override_selected mode. An empty string, zero or False is an intentional replacement.
override_characterPromptsoptBOOLEANfalseReplace the complete character list with characterPrompts, including negatives and positions. No connection clears the list.

Outputs (3)

NameTypeDescription
imageIMAGE—
mask_visualizationIMAGE—
matching_reportSTRING—