Ollama Option — Refusal Retry
Your local LLM refused again? This node just rerolls the seed
- options
- options
If you've used a local LLM to write prompts or caption images in ComfyUI, you know the pattern: the model that was happily generating a second ago suddenly gets shy. "I can't help with that," "I'm not comfortable with this request," or some hedge about being an AI. Not a crash, not a blank response - just a polite deflection, and the output is useless. This node detects that deflection and automatically retries with a new seed, because half the time the model will answer fine on the second draw.
It's an "Ollama Option" node from the comfydv pack (Comfy DV Nodes) by darth-veitcher - a small AGPL-licensed utility pack whose whole gimmick is composable, backend-agnostic local-LLM nodes. Option nodes chain together: each takes an optional options socket, merges one setting, and passes the dict out its options output into Chat Completion's options input. This one is the smartest of the family, and the one that requires the most thought.
How it detects a refusal
The author built this after watching an abliterated Qwen variant answer "politically sensitive" requests with hedging refusal language instead of erroring or going blank - the two things the pack's existing retry logic already caught. So this node drives a separate, opt-in retry trigger with a hybrid, cheapest-check-first detector:
- Lexical pass (free, no network): a pile of regexes catch the blatant majority - "I cannot generate...", "I'm sorry, but I can't...", "as an AI model I must avoid..." - case-insensitively.
- Your
custom_phrases: comma-separated phrases checked as exact case-insensitive substrings. Lets you teach it a new deflection phrasing without waiting for a pattern update. - Embedding similarity (opt-in): set
embedding_modelto a real embedding-capable model (likenomic-embed-text- not the chat model, most of those can't produce usable embeddings) and ambiguous responses get compared by cosine similarity against canonical refusal exemplars.thresholddefaults to 0.82 - above that, it's a refusal.
The inputs, then: enabled (default true), embedding_model (default blank), threshold (0–1, default 0.82), custom_phrases (default blank). Leave embedding_model blank and you get detection that's cheap but only catches literal phrasing; set it and subtle, on-topic-looking deflections get caught too. Output is the usual options socket.
Like Disable Thinking, refusal_retry is the pack's own convention rather than an Ollama-native param: each provider pops it out and runs its own retry loop. Retries use a deterministic bumped seed (your seed plus attempt number) and an escalating timeout - a still-loading model gets time to finish rather than being hammered. max_retries on Chat Completion (default 2, cap 5) bounds the whole thing. And if embeddings fail for any reason, detection degrades gracefully to lexical-only; it's never a hard failure.
Why you'd reach for it
Local models doing prompt-writing duty are exactly where refusals bite - ask the community and "my local LLM refused / censored this" is a constant refrain, which is why abliterated and uncensored variants are so popular for prompt generation. Those variants help, but they still hedge sometimes. This node automates the "just run it again" fix and wires it into a graph, so a refusal becomes a seed bump instead of a dead run.
Installing and wiring
ComfyUI Manager, search comfydv, install. Or:
cd /path/to/ComfyUI/custom_nodes
git clone https://github.com/darth-veitcher/comfydv.git
Restart, then start Ollama (ollama serve) and pull an embedding model if you want the similarity check: ollama pull nomic-embed-text. The node lives under dv/ollama/options. Chain its options output into Chat Completion's options socket alongside any other option nodes.
Common issues
Two traps. First, if detection isn't catching anything, you almost certainly left embedding_model blank - subtle deflections sail through lexical-only mode. Second, don't wire your chat model into embedding_model; embeddings silently come back useless. If you get false positives (legit answers retried), nudge threshold up; misses, nudge it down. And the usual pack gotcha: an unwired option chain does nothing - it must reach Chat Completion's options input.
Inputs (5)
| Name | Type | Default | Description |
|---|---|---|---|
| enabled | BOOLEAN | true | — |
| embedding_model | STRING | Name of a separate embedding-capable model (e.g. "nomic-embed-text") — NOT the chat model itself; most chat models can't produce usable embeddings. Leave blank to skip the embedding-similarity check and detect only blatant, literal refusal phrases (still useful, cheaper, catches less). | |
| threshold | FLOAT | 0.820–1 | Cosine-similarity threshold against canonical refusal exemplars, above which an ambiguous (short/hedge-y) response is treated as a refusal. Only consulted when embedding_model is set and the cheap lexical pass didn't already catch it. |
| custom_phrases | STRING | Comma-separated phrases you want treated as refusals too, e.g. "I am restricted from, as an AI model, I must avoid". Checked as free, exact case-insensitive substrings (no embedding model needed) and, when embedding_model is set, also folded in as extra exemplars for the similarity check — lets you extend detection at runtime without waiting on a shipped pattern update. | |
| optionsopt | OLLAMA_OPTIONS | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| options | OLLAMA_OPTIONS | — |