Sign-up and API keys are open. Paid top-ups open soon.What changed
Docs menu

Concepts

Abstention

Let an unsure answer say so with min_confidence, and route it to a person instead of acting on a guess.

View as Markdown

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:

  • afdeling abstains. 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. choice is still garantie, but your code should send this ticket to a person to choose the department.
  • spoed does not abstain. It has no min_confidence. Its rating of 2.2957 sits between "within two working days" and "today".
  • wil_omruilen does 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. confidence means something different per type. For a check, min_confidence 0.6 abstains unless the probability is at least 0.8 or at most 0.2. For a pick, 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.