Concepts
Abstention
Let an unsure answer say so with min_confidence, and route it to a person instead of acting on a guess.
Set min_confidence on a question, and an answer whose confidence falls below it comes back with abstained: true. Your code can then send the case to a person, ask again with more information, or log it, instead of acting on a guess.
How it works
min_confidence is an optional number from 0 to 1 on any question. A value outside that range gives 422 invalid_min_confidence.
| Setting | abstained |
|---|---|
No min_confidence |
Always false |
confidence below min_confidence |
true |
confidence equal to or above min_confidence |
false |
Each question has its own threshold, so in one request a team choice can abstain while an urgency rating does not.
What an abstained answer contains
An abstained answer still returns every typed field: choice and probabilities for a pick, rating for a rate, probability for a check. You can log and inspect them.
Do not act on them. Treat code that uses an abstained answer's choice, rating or probability as a bug. The SDK answer types list abstained first to make that hard to miss.
Deterministic and free
- Free. Abstention costs nothing extra. The request is billed the same with or without
min_confidence. - Deterministic. Dex decides abstention in its gateway, from the unrounded confidence. The same request to the same GPU version always abstains, or does not, in the same way. See Determinism.
Example: the Dutch e-bike ticket
A customer writes that their e-bike battery stopped charging after a software update, and asks whether they can exchange the bike or whether a mechanic can come by. The request asks which department should handle it (afdeling, with min_confidence 0.5), how urgent it is (spoed), and whether the customer asks for an exchange (wil_omruilen, with min_confidence 0.6):
{
"model": "dex-1",
"state": {
"bericht": {
"kanaal": "webformulier",
"onderwerp": "Accu laadt niet meer op",
"tekst": "Sinds de software-update van vorige week laadt de accu van mijn e-bike niet meer op. Ik heb de fiets pas drie maanden en heb hem elke dag nodig voor mijn werk. Kan ik hem omruilen of komt er een monteur langs?"
},
"klant": { "klant_sinds": "2026-06-14", "bestellingen": 2 }
},
"questions": {
"afdeling": {
"type": "pick",
"instructions": "Welke afdeling moet {{bericht.tekst}} oppakken?",
"options": {
"garantie": "Omruilen of terugbetalen binnen de garantietermijn",
"reparatie": "Defecten, onderhoud en monteur aan huis",
"bezorging": "Levering en track-and-trace",
"overig": null
},
"min_confidence": 0.5
},
"spoed": {
"type": "rate",
"instructions": "Hoe snel moeten we reageren op {{bericht.tekst}}?",
"levels": ["Kan wachten", "Binnen een week", "Binnen twee werkdagen", "Vandaag"]
},
"wil_omruilen": {
"type": "check",
"instructions": "Vraagt de klant in {{bericht.tekst}} om omruilen of vervanging?",
"min_confidence": 0.6
}
}
}
The response from the contract example:
{
"id": "req_01M5CJXHG0M9S346Q3D25VT4F5",
"object": "decision",
"created": 1792497600,
"model": "dex-1.0.0",
"served_by": "gpu",
"calibration": "cal-20261015-1",
"answers": {
"afdeling": {
"type": "pick",
"choice": "garantie",
"probabilities": { "garantie": 0.6423, "reparatie": 0.3287, "bezorging": 0.0102, "overig": 0.0188 },
"confidence": 0.3136,
"abstained": true
},
"spoed": {
"type": "rate",
"rating": 2.2957,
"levels": ["Kan wachten", "Binnen een week", "Binnen twee werkdagen", "Vandaag"],
"probabilities": [0.0195, 0.1067, 0.4325, 0.4413],
"confidence": 0.5108,
"abstained": false
},
"wil_omruilen": { "type": "check", "probability": 0.8732, "confidence": 0.7465, "abstained": false }
},
"usage": {
"input_tokens": 273,
"state_tokens": 142,
"question_tokens": 131,
"charge_micro_cents": 1365,
"unit_price_micro_cents": 5,
"tier": "t1"
}
}
Reading it:
afdelingabstains. The message fits both warranty (garantie, 0.6423) and repair (reparatie, 0.3287). The gap between them, 0.3136, is below the 0.5 threshold.choiceis stillgarantie, but your code should send this ticket to a person to choose the department.spoeddoes not abstain. It has nomin_confidence. Its rating of 2.2957 sits between "within two working days" and "today".wil_omruilendoes not abstain. Confidence 0.7465 is above 0.6, and the customer asks for an exchange with probability 0.8732.
So the ticket goes to a person for routing, with its urgency and the exchange request already filled in.
Patterns
- Route to a person. The most common pattern. Put abstained cases in a review queue, with the answer's probabilities attached so the reviewer sees what was close.
- Ask again with more information. On a GPU version, the same request always gives the same answer, so repeating it does not help. Send a new request when the state has changed, for example after the customer replies, or ask a narrower question.
- Use a safe default. For an agent's proposed tool call, an abstained "allow, confirm or deny" pick can default to asking the user to confirm.
- Log it. Count abstentions per question. A high rate on one question often means two options overlap and need clearer descriptions or
criteria.
Until the SDKs are on PyPI and npm, install them from Downloads.
afdeling = decision.pick("afdeling")
if afdeling.abstained:
send_to_review_queue(ticket, afdeling.probabilities)
else:
assign(ticket, afdeling.choice)const { afdeling } = decision.answers;
if (afdeling.abstained) {
sendToReviewQueue(ticket, afdeling.probabilities);
} else {
assign(ticket, afdeling.choice);
}Choosing a threshold
- Start from the formula.
confidencemeans something different per type. For acheck,min_confidence0.6 abstains unless the probability is at least 0.8 or at most 0.2. For apick, 0.5 abstains unless the top option leads the second by at least 0.5. See Confidence. - Measure on your own traffic. Run a sample of real cases with a test key, which is free up to 250,000 tokens a day, and look at how many answers abstain at a few thresholds and how often the non-abstained answers match what your team would decide.
- Weigh the cost of a mistake. A threshold is a trade between how many cases a person handles and how many wrong answers get through. Set it higher where a wrong answer is expensive.