Route image tag generation to a secondary LLM — cheaper, faster, uncensored.
A SillyTavern extension that intercepts SD image prompt generation and replaces it with booru-style tags from any OpenAI-compatible LLM endpoint.
| Problem | Solution |
|---|---|
| Claude/Kimi return empty content for image prompts | Tag Pilot uses a separate model that actually generates tags |
| Censored models refuse NSFW visual tags | Use an uncensored model (DeepSeek, Ollama, etc.) |
| Main chat model is expensive for simple tag generation | Route to a cheap/fast model instead |
| Model output isn't in proper booru format | formatBooru() normalizes any output into Pony/SDXL-ready tags |
- Auto-intercept — Automatically replaces SD prompts with booru-style tags whenever SillyTavern generates one
- Character-aware — Reads the character card description for accurate appearance tags (hair, eyes, body, etc.)
- POV-optimized — Default prompt template generates first-person POV tags tuned for Pony/SDXL models
- Post-processing —
formatBooru()normalizes any model output into proper booru tags with quality prefixes (score_9,score_8_up, etc.) and auto-detected NSFW ratings - Per-character tag overrides — Always-include and always-exclude tags saved per character, auto-switching when you change chats
- Style preset compatible — "Skip quality prefix" toggle avoids duplicate quality tags when using SD style presets
- Slash commands —
/gentagsfor standalone tag generation, pipe-able into/sdand other commands - Graceful fallback — On any failure, the original SD prompt passes through unchanged
In SillyTavern, go to Extensions > Install Extension and paste:
https://github.com/J3tze/ST-Tag-Pilot
- Open Extensions > Tag Pilot
- Enter your secondary LLM's API URL (any OpenAI-compatible endpoint)
- Enter the API Key (if required)
- Enter the Model ID (e.g.
deepseek/deepseek-v3.2) - Click Test Connection to verify
- Check Enable Tag Pilot and Auto-intercept SD prompt generation
That's it — Tag Pilot will now automatically generate booru tags for every image.
With auto-intercept enabled, Tag Pilot automatically replaces the SD image prompt every time SillyTavern generates one. No manual action needed — just use /sd or click "Generate Image" as usual.
Generate tags manually:
/gentags a romantic sunset scene
Pipe directly into SD generation:
/gentags a beach scene | /sd free {{pipe}}
Aliases: /tags, /tagpilot
In the Tag Pilot settings, you'll see the current character name and two fields:
- Always Include Tags — These tags are always prepended to the output (e.g.
white_hair, red_eyes, short_hair) - Always Exclude Tags — These tags are always removed from the output (e.g.
blonde_hair, long_hair)
Tags are saved per character and auto-switch when you change chats. Exclude uses substring matching, so excluding blonde_hair also removes long_blonde_hair.
If you use SD style presets (like "EuroAnime POV") that already include quality tags, enable Skip quality prefix to avoid duplicates. Tag Pilot will only output the character/scene tags, leaving quality and rating to your style preset.
| Setting | Default | Description |
|---|---|---|
| Enable Tag Pilot | Off | Master toggle |
| Auto-intercept SD | On | Replace SD prompts automatically |
| Skip quality prefix | Off | Omit score_9, rating_* etc. (use with style presets) |
| API URL | — | OpenAI-compatible endpoint URL |
| API Key | — | Bearer token (optional for local models) |
| Model | — | Model ID (e.g. deepseek/deepseek-v3.2) |
| Max Tokens | 600 | Max response length |
| Temperature | 0.4 | Lower = more consistent formatting |
| Context Depth | 5 | Number of recent chat messages to include |
Customize the system prompt and user template in settings:
| Variable | Description |
|---|---|
{{char}} |
Character name |
{{user}} |
User/persona name |
{{description}} |
Character card description (contains appearance) |
{{personality}} |
Character personality field |
{{scenario}} |
Current scenario |
{{context}} |
Recent chat messages (configurable depth) |
{{trigger}} |
SD prompt or /gentags argument |
All standard SillyTavern macros are also supported.
Any OpenAI-compatible /v1/chat/completions endpoint:
| Provider | API URL | Notes |
|---|---|---|
| OpenRouter | https://openrouter.ai/api/v1 |
Access to many models |
| DeepSeek | https://api.deepseek.com/v1 |
Recommended for NSFW |
| Ollama (local) | http://localhost:11434/v1 |
Free, uncensored |
| LM Studio (local) | http://localhost:1234/v1 |
Free, uncensored |
| vLLM, text-gen-webui, etc. | Varies | Any OpenAI-compatible endpoint |
| Model | Strengths | Weaknesses |
|---|---|---|
DeepSeek V3.2 (deepseek/deepseek-v3.2) |
Uncensored, cheap, 80+ tags | Occasional formatting quirks (handled by formatBooru) |
| Ollama (local) | Free, fully uncensored | Requires local setup |
| Gemini Flash | Fast, good format following | Refuses NSFW content |
flowchart TD
A["🖼️ SD Extension generates prompt"] --> B["⚡ SD_PROMPT_PROCESSING event fires"]
B --> C["🔌 Tag Pilot intercepts the event"]
C --> D["🤖 Sends chat context + character description\nto secondary LLM"]
D --> E["🔧 formatBooru post-processes output"]
E --> F["Normalize to underscore format\nRemove duplicates"]
F --> G["Prepend quality tags\n(score_9, masterpiece, etc.)"]
G --> H["Auto-detect NSFW rating\nApply per-character include/exclude\nStrip forbidden tags"]
H --> I["✅ Processed tags replace the SD prompt"]
I --> J["🎨 ComfyUI / SD backend receives\nproper booru tags"]
| Issue | Solution |
|---|---|
| Tags not generating | Check console (F12) for [Tag Pilot] logs. Verify API URL, key, and model are configured. |
| Extension not updating | After git pull on the server, hard-refresh your browser (Ctrl+Shift+R). Firefox may require "Disable Cache" in DevTools. |
| Style preset adding duplicate quality tags | Enable Skip quality prefix in Tag Pilot settings. |
| Include/exclude tags not working | Make sure a character chat is open (tags are saved per character). Check console for [Tag Pilot] formatBooru — log. |
| LLM refuses to generate tags | Try an uncensored model like DeepSeek V3 or a local model via Ollama. |
| Tags in wrong format | That's normal — formatBooru() handles normalization regardless of model output format. |
MIT