# 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](/docs/concepts/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):

```json
{
  "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:

```json
{
  "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](/docs/reference/sdks/#downloads).

```python tab="Python"
afdeling = decision.pick("afdeling")
if afdeling.abstained:
    send_to_review_queue(ticket, afdeling.probabilities)
else:
    assign(ticket, afdeling.choice)
```

```ts tab="TypeScript"
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](/docs/concepts/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.
