# Questions

> The question object, question ids, the pick, rate and check types, criteria, and how questions in one request relate.

A question is a typed ask about the state. There are three types: `pick` chooses one of your options, `rate` (beta) places the state on your scale, and `check` gives the probability that a statement is true. One request carries 1 to 32 questions, keyed by ids you choose.

## The question object

| Field | Types | Required | Rule |
| --- | --- | --- | --- |
| `type` | all | yes | `"pick"`, `"rate"` or `"check"`. |
| `instructions` | all | yes | What to decide, 1 to 4,000 characters, not only whitespace. May contain [field references](/docs/concepts/field-references/). |
| `criteria` | all | no | Extra rules or definitions. See [Criteria](#criteria). |
| `options` | pick | yes, for pick | Label to description. Rejected on other types. |
| `levels` | rate | yes, for rate | Ordered level names. Rejected on other types. |
| `min_confidence` | all | no | 0 to 1. Enables [abstention](/docs/concepts/abstention/). |

A field that is not in this table is rejected with `422 unknown_field` (or `422 field_not_allowed` for a field of another question type), so a typo such as `instruction` fails loudly instead of being ignored. The same holds for the top level of the request, where an unknown field gives `422 unknown_field`.

## Question ids

The keys of `questions` are your question ids. An id matches `^[A-Za-z][A-Za-z0-9_-]{0,63}$`: it starts with a letter, then has up to 63 more letters, digits, underscores or hyphens. Anything else gives `422 invalid_question_id`.

Ids are for your code only. They never reach the model, so an id such as `angry` tells the model nothing. Put everything the model needs in `instructions`, `criteria`, `options` or `levels`.

Answers come back under the same ids, in the order you sent the questions.

## pick

A `pick` chooses one option from a set you define.

```json
{
  "type": "pick",
  "instructions": "Which team should handle {{ticket.body}}?",
  "options": {
    "billing": "Payments, refunds and invoices",
    "technical": "Bugs, outages and sign-in problems",
    "shipping": "Delivery and returns",
    "other": null
  },
  "min_confidence": 0.5
}
```

`options` maps each label to a description or `null`:

- 1 to 255 options. More gives `422 too_many_options`.
- A label is 1 to 100 characters, with no leading or trailing whitespace.
- Labels must differ by more than case or Unicode normalization. `Billing` and `billing`, or two labels that differ only in how an accented letter is encoded, give `422 duplicate_label`: the model cannot tell them apart.
- A description is `null` or 1 to 1,000 characters, and not only whitespace. Use `null` when the label says it all, as `other` does above. Add a description when a label could be read two ways.

**Order matters.** Options are shown to the model in the order you send them, and a tie between probabilities goes to the earlier option. Sending the same options in a different order is a different request and may give different probabilities. Python dicts keep the order of every key. JavaScript objects, and so `JSON.parse`, do not: they list integer-like keys such as `"1"`, `"2"` and `"10"` first, in numeric order, whatever order your text used. A pick with labels `"10"`, `"2"`, `"1"` that goes through a plain JavaScript object reaches Dex as `"1"`, `"2"`, `"10"`, which is a different request.

- **TypeScript SDK.** Pass labels to `pick()` in the order you want. For an object you built another way, mark its order with `withKeyOrder(obj, keys)` before `decide()`. On the way back, read a pick answer's `probabilities` with `orderedEntries()` to get option order. A request you read from a file keeps its order with `parseRequest(text)`.
- **CLI and VS Code extension.** They read request files in file order and send them in that order.
- **Your own code.** If your labels look like integers, build the JSON text yourself or use a parser that keeps order.

**Include a way out.** If the state might fit none of your options, add one such as `other` or `none`. Otherwise the model must choose among options that do not fit.

**More than 255 options.** Split the choice into stages: first pick a department, then send a second request that picks a queue within it.

A pick answer has:

| Field | Meaning |
| --- | --- |
| `choice` | The most probable label. Ties go to the earlier option. |
| `probabilities` | Label to probability, in option order, summing to exactly 1. |
| `confidence` | The gap between the two largest probabilities. See [Confidence](/docs/concepts/confidence/). |
| `abstained` | `true` when `confidence` is below `min_confidence`. |

On the fallback path, a pick with more than 20 options gets exact probabilities only for the 20 most likely options. See [Fallback and served_by](/docs/concepts/fallback/#what-differs-on-the-fallback).

## rate

A `rate` places the state on an ordered scale.

> **Beta.** `rate` works and is part of the API, but its measured quality misses our bars. On our test set, dex-1.0.1 picks the reference level on 63.7% of `rate` questions (the bar is 68%), and its calibration error is 0.138 (the bar is 0.05). Treat its probabilities as a rough guide, set thresholds from your own data, and set `min_confidence` so an unsure rating abstains. See [Quality bars](/docs/concepts/calibration/#quality-bars).

```json
{
  "type": "rate",
  "instructions": "How fast must we reply to {{ticket.body}}?",
  "levels": ["Can wait", "This week", "Within two working days", "Today"]
}
```

`levels` is a list of 2 to 10 unique strings, each 1 to 200 characters, ordered from lowest (index 0) to highest. Anything else gives `422 invalid_levels`, and so do two levels that differ only in case or Unicode normalization, such as `Low` and `low`. A level follows the label rule: no whitespace at either end and no line breaks, or `422 invalid_value` with the level in `param`, such as `questions.spoed.levels.2`. Write levels that form a real scale, because the rating treats their order as meaningful.

A rate answer has:

| Field | Meaning |
| --- | --- |
| `rating` | The probability-weighted level index, from 0 to the number of levels minus 1. |
| `levels` | The levels you sent, in order. |
| `probabilities` | One probability per level, aligned with `levels`, summing to exactly 1. |
| `confidence` | 1 when all probability sits on one level, 0 when it splits evenly between the two extreme levels. |
| `abstained` | `true` when `confidence` is below `min_confidence`. |

`rating` is not rounded to a level. It is an average: it tells you where the probability centers, and `confidence` tells you how spread out it is. Compare the rating with thresholds of your own, round it when you need one level, or read `probabilities` when you need the whole distribution.

## check

A `check` gives the probability that a statement about the state is true.

```json
{
  "type": "check",
  "instructions": "Is the writer of {{ticket.body}} angry?",
  "min_confidence": 0.6
}
```

A check has no options or levels. Its answer has `probability` (the probability of yes), `confidence` and `abstained`. Phrase the statement so that yes is the case you act on.

## Criteria

`criteria` adds rules and definitions the model must apply to one question. It takes:

- a string of 1 to 4,000 characters, or
- a list of 1 to 20 strings, each 1 to 500 characters, or
- `null`, which is the same as leaving it out.

A criteria string of only whitespace gives `422 invalid_value`, as does such an item in the list.

Criteria may contain field references. Use them for edge cases and house rules. The English moderation example in the contract uses two:

```json
{
  "type": "pick",
  "instructions": "Which community rule does {{post.text}} break, if any?",
  "criteria": [
    "A threat to expose where someone lives counts as harassment even when it is conditional.",
    "Sharing your own phone number in a sale listing is allowed."
  ],
  "options": {
    "none": "Breaks no rule",
    "harassment": "Threats, intimidation or exposing someone's personal details",
    "spam": "Repeated or unsolicited promotion",
    "self_harm": "Encourages or describes self-harm",
    "hate": "Attacks people for a protected characteristic"
  },
  "min_confidence": 0.6
}
```

Criteria are billed as question tokens, once per question that carries them.

## Questions in one request

All questions share the state, which is read and billed once. Each question sees the fixed prompt prefix, the state and itself, not the text of the other questions.

v1 does not promise question isolation. Adding, removing or reordering another question in the same request can move an answer slightly, because the questions are computed together. The same request bytes always give the same answers. If an answer must not depend on the other questions, send that question in a request of its own. See [Determinism](/docs/concepts/determinism/#what-v1-does-not-promise).

This also means Dex does not make answers consistent with each other. Two differently phrased questions about the same fact can disagree.

When exactly one of several outcomes must hold, ask one `pick` instead of several `check`s. Two checks such as "Is this about billing?" and "Is this about shipping?" can both come back above 0.5. A single `pick` with `billing`, `shipping` and `other` always returns exactly one choice, with probabilities that sum to 1.

Some questions are weaker today: sarcasm read literally, date and amount arithmetic, and `criteria` written in Dutch. See [Known limits](/docs/concepts/known-limits/) for the numbers and how to work around each.
