Typed Decision
Ask one question about your images and branch on the answer
- model
- images
- videos
- value
- label
- index
- pass
- abstained
- result
Every other LLM node in ComfyUI writes you something. This one answers a question: give it an image (or two), a question, and the options, and you get a value between 0 and 1, a label, and a boolean you can wire into the rest of the graph.
Why that beats a captioner: prose is not what a graph needs. Sorting 300 photos into "has people" and "no people" doesn't need a paragraph you then have to parse - a probability and a threshold is the whole job. The KB says it twice: dataset curation beats every training knob, and captioning VLMs reliably botch multi-subject attribution. A node that decides rather than describes dodges both. It's typed for a reason: noul is true/false, choice picks one of your options, score rates a position on ordered levels.
How it decides - no token is ever written
Nothing is sampled. The node builds the imajev model's exact prompt - your state, your question, each candidate answer as a labelled line, plus one extra candidate reading unknown - cannot be determined from the available evidence - renders it into Qwen3.5's chat format with the image tokens, and reads the final-normed hidden state of the last token. The adapter's trained readout matrix turns that vector into one score per candidate; softmax turns the scores into probabilities, using a temperature from calibration-rot4-modality.json keyed to the mode and the option count.
Two consequences. The classic local-LLM failure mode from the KB - "Here is your answer:" preamble and role delimiters bleeding into your data, because nothing constrained the decode - is impossible when there are no tokens to leak. And abstention is a competitor, not an error: if "unknown" wins, the evidence wasn't there, and abstained says so. That's a hallucination budget you can measure instead of hope about.
The inputs you actually touch
mode is a dynamic combo - switching it rebuilds the fields below. noul gives you instructions plus optional criteria_true and criteria_false; fill those in, because "five or more people" versus "four or fewer" pins the cutoff down instead of leaving it to the model. choice and score give you instructions and criteria, one option or level per line, label: description to explain a line, levels lowest first.
model comes from Load Typed Decision Model. images is autogrow - up to two slots for imajev, first as reference, second as the one judged; a batch in one slot counts per frame, so four frames blows the limit. state carries facts to judge against, as text or JSON, and with JSON you can name its fields in backticks inside your instructions. Then threshold, debias, calibration. Write the questions in English - the adapter was trained on that prompt shape and prompt tricks don't help it.
The six outputs
value means something different in each mode, and this trips everyone: probability the claim is true (noul), probability of the winning option (choice), expected level with 0 as your first line (score - not a probability at all). label is the answer, unaffected by threshold. index is 1/0 for noul or the zero-based line number, which is handy for filenames. pass is the branch valve: true when value >= threshold and the model didn't abstain. abstained is what you wire to your review pile. result is the full answer as JSON in the official Jev shape - every probability, unknown_probability, confidence.
Two traps in one number
Default threshold is 0.5 - right for noul, wrong-feeling elsewhere. On score, 0.5 means "better than the level halfway between your first two lines", so set the level you actually want. On choice, the winner's probability can sit under 0.5 with several options, so pass goes false on a confident pick. And in the source, noul's value is P(true) plus half the unknown mass, so a genuine abstention lands dead on 0.5 - the default threshold. Read abstained next to pass rather than trusting the float.
Install
Manager, search ComfyUI-TypedDecision, or:
cd ComfyUI/custom_nodes
git clone https://github.com/nomadoor/ComfyUI-TypedDecision
No pip dependencies; ComfyUI 0.37.0+ (V3 schema). Then a Qwen3.5-4B text encoder in models/text_encoders and the imajev-4b adapter folder in models/typed_decision/imajev-4b:
cd ComfyUI/models
hf download nomadoor/Qwen3.5 text_encoders/qwen3.5_4b_int8_convrot.safetensors --local-dir .
hf download mohit67890/imajev-4b --local-dir typed_decision/imajev-4b
The repo ships five example workflows (noul, choice, score, two-image, dataset sort) and they're the fastest way to see the wiring. Leave debias off at first; the author's own tooltip admits it changes little in practice. Keep calibration on - it changes probabilities, never the answer, which is what you want if you're thresholding.
Where people get burned
The validators are strict and the errors are unusually good, so read them. Empty instructions or criteria throws, with a message noting the grey placeholder is only an example. choice needs two unique options; score needs two levels; more than two images is a hard error. State over 128 KB, or containing NaN/Infinity, is rejected, and the whole request caps at 4096 tokens - long state plus many options is where you hit it, and the fix is always "shorten the state or the options".
The honest caveat: there's one model family here (imajev-4b, a 4B Qwen3.5 backbone with a 490 MB adapter) and no community signal yet - the reddit corpus has zero threads mentioning it. Calibrated probabilities are calibrated on its training distribution, which may not be your photos. Before gating a dataset on a threshold, label twenty images yourself and check the node agrees; ten minutes of that beats any setting in the panel.
Inputs (6)
| Name | Type | Default | Description |
|---|---|---|---|
| mode | COMBO | noul: true or false. choice: pick one option. score: rate on ordered levels. Write in English. | |
| model | TYPED_DECISION_MODEL | — | |
| state | STRING | With JSON, instructions can name its fields in `backticks`, e.g. `listing.color`. | |
| threshold | FLOAT | 0.500–10 | pass is true when value >= threshold and the model did not abstain. |
| imagesopt | COMFY_AUTOGROW_V3 | Images looked at together, in slot order. imajev: first = reference, second = the one to judge. | |
| videosopt | COMFY_AUTOGROW_V3 | Videos looked at together with the images, in slot order (d3, Clef-flash). Read at the loader's fps over the whole video. |
Outputs (6)
| Name | Type | Description |
|---|---|---|
| value | FLOAT | noul: probability the claim is true (0-1). choice: probability of the chosen option (0-1). score: expected level, 0 = first line (not a probability). |
| label | STRING | The answer: yes / no, the chosen option, or the most likely level. Not affected by threshold. |
| index | INT | noul: yes = 1, no = 0. choice / score: line number of the answer, from 0. |
| pass | BOOLEAN | True when value >= threshold and the model did not abstain. Use it to branch. |
| abstained | BOOLEAN | True when the images and state are not enough to answer, e.g. to send the case to a person. Always false for d3 and Clef-flash, which have no unknown. |
| result | STRING | Full answer as JSON, in the model's official answer shape: every probability and confidence (imajev also unknown probability). |