Primitive
Choice
Pick exactly one option from a set you define: which team, which intent, which next action. The answer is always one of your option ids, with a probability for every option.
Signature
type: "choice"
Returns
one option id + probabilities
Use it for
Pick one of many.
Which peril is this claim about?
→ water_damage0.99
Next action on this page?
→ type0.89
What kind of document is this?
→ invoice0.93
When to use choice#
Use choice when the answer is one item from a fixed list and the items are simply different: a queue, a category, a document type, the next step for an agent. If the options have an order (low, medium, high), use score so that being one step off counts as closer than three. If the question is yes or no, use noul. If there is no fixed list, use generate.
Question object#
Example#
A home insurer gets claims as free text: an email, a web form, a chat transcript. Before anyone reads them, each claim has to go to the team that handles that kind of damage. Water leaks go to one team, storm damage to another and break-ins to a third. That is a choice question: one message and a fixed list of perils, and the answer is exactly one of them.
The request has three parts:
stateis the customer's message exactly as it arrived. There is no need to clean it up or pull out keywords first.instructionsasks the question in plain words, the way you would ask a colleague.criterialists the six perils. The ids (water_damage,storm, …) are what your code branches on. The descriptions are what Wity actually reads. Look atwater_damageandstorm: both involve water, so the descriptions draw the line at where the water came from, inside or outside.othergives an unusual claim somewhere to land instead of being forced into the closest peril.
Save the body as request.json and send it with curl, or post the same body from Python.
{"state": "Water has been dripping through my kitchen ceiling since last night. The flat upstairs had a burst pipe. The plaster is sagging and the floor is soaked.","questions": {"claim_type": {"type": "choice","instructions": "Which peril is this home insurance claim about?","criteria": {"water_damage": "Escape of water from inside the building: burst or leaking pipes, tanks, appliances","storm": "Wind, rain or flood coming in from outside","fire": "Fire, smoke or explosion","theft": "Break-in, stolen items, or damage from a forced or attempted entry","accidental_damage": "A one-off accident inside the home, like a spill or a dropped TV","other": "Anything that fits none of the above"}}},"reasoning": "auto"}
Wity puts almost all of the probability on water_damage. The message names a burst pipe in the flat upstairs, which is exactly what that option describes. storm gets a sliver because water through a ceiling can also mean a roof leak, but nothing in the message points outside. The claim can go straight to the escape-of-water team.
"claim_type": {"type": "choice","choice": "water_damage","probabilities": { "water_damage": 0.9871, "storm": 0.0052, "fire": 0.0011, "theft": 0.0006, "accidental_damage": 0.0037, "other": 0.0023 },"confidence": 0.95}
The answer comes back under the name you gave the question, claim_type. Because nearly all the probability sits on one option, confidence is high too.
Reading the answer#
choiceis the option with the highest probability. Branch on it.probabilitiescovers every option you offered and sums to 1. Useprobabilities[choice]as the threshold for acting on your own.- Look at the runner-up too: a 0.55 / 0.40 split between two queues is a case for a person, even though
choiceis set. confidencesays how concentrated the whole distribution is. It is useful for sorting, but it is not the chance of being right; see Probabilities & confidence.- With
reasoning: "auto", Wity thinks first only when a question needs it. When it did, the answer also carriesdirect_probabilities, what it would have said without thinking. See Reasoning & auto mode.
Several questions, one call#
Real messages rarely answer only one question. Put every question you have about the same state in one request: you make one round trip instead of five and get every answer back under its own name.
Each question is billed with the state it reads, so five questions cost about the same as five separate requests. What you save is latency and glue code, not tokens. Keep the state to what the questions need (see Billing).
Many options
A messier example#
The first claim was easy. Most are not. Here is one that fits no single box:
The claim
Several things are going on at once. There was a break-in, but the damage to the belongings came from rain. The customer has already paid a locksmith and wants that money back. They also say nothing about the police, and they are clearly unhappy about how the last claim went. A claims handler would notice all of this in one read. To get the same picture from Wity, ask one question for each thing you need to know:
claim_type: which team opens the claim. Same six perils as before.damage_cause: what actually damaged the boxes. This decides which part of the policy pays for them.requested_outcome: what the customer is asking for, so the reply answers the right question.police_report: theft claims usually need a crime reference number. The message may not say, so there is anot_mentionedoption.tone: whether the claim should jump the queue.
The state is an object this time. The customer's message and the policy details travel together, and every question sees both. The claim_type criteria are shortened here to keep the example readable.
{"state": {"message": "Our garage was forced open over the weekend while we were away. Nothing seems to be missing, but the lock is smashed and rain got in through the gap and soaked the boxes stored in there. I already paid a locksmith £140 to secure it, can I claim that back? Honestly the last claim took two months so I'm not holding my breath.","policy": "Home and contents, policy HC-44817, garage covered as an outbuilding"},"questions": {"claim_type": {"type": "choice","instructions": "Which peril is this claim mainly about?","criteria": { "water_damage": "…", "storm": "…", "fire": "…", "theft": "…", "accidental_damage": "…", "other": "…" }},"damage_cause": {"type": "choice","instructions": "What directly damaged the stored belongings?","criteria": {"break_in": "The forced entry itself broke or damaged them","weather": "Rain or wind got in and damaged them","unknown": "The message does not say"}},"requested_outcome": {"type": "choice","instructions": "What does the customer want from us?","criteria": {"reimburse_expense": "Pay back money they have already spent","repair": "Send someone to fix the damage","replace_items": "Pay for damaged or stolen belongings","information": "Only wants to know whether they are covered"}},"police_report": {"type": "choice","instructions": "Has the break-in been reported to the police?","criteria": {"reported": "Says it was reported, or gives a crime reference","not_reported": "Says it was not reported","not_mentioned": "The message does not say either way"}},"tone": {"type": "choice","instructions": "How does the customer come across?","criteria": {"calm": "Matter-of-fact, no complaint","frustrated": "Annoyed or doubtful, but still polite","angry": "Hostile, threatening to leave or complain","distressed": "Upset, anxious or asking for urgent help"}}},"reasoning": "auto"}
All five answers come back in one response, each under its own question name. Unlike the first example, several of them are spread across two options. That spread is information, not a failure: it shows where the message really is ambiguous.
{"answers": {"claim_type": {"type": "choice", "choice": "theft", "confidence": 0.51,"probabilities": { "water_damage": 0.05, "storm": 0.24, "fire": 0.00, "theft": 0.68, "accidental_damage": 0.02, "other": 0.01 }},"damage_cause": {"type": "choice", "choice": "weather", "confidence": 0.54,"probabilities": { "break_in": 0.07, "weather": 0.86, "unknown": 0.07 }},"requested_outcome": {"type": "choice", "choice": "reimburse_expense", "confidence": 0.43,"probabilities": { "reimburse_expense": 0.71, "repair": 0.02, "replace_items": 0.04, "information": 0.23 }},"police_report": {"type": "choice", "choice": "not_mentioned", "confidence": 0.73,"probabilities": { "reported": 0.04, "not_reported": 0.03, "not_mentioned": 0.93 }},"tone": {"type": "choice", "choice": "frustrated", "confidence": 0.36,"probabilities": { "calm": 0.31, "frustrated": 0.62, "angry": 0.04, "distressed": 0.03 }}}}
What the answers say
claim_type: theft at 0.68 with storm at 0.24. The break-in is the main event, but the rain is a real second signal. That is whyconfidenceis only 0.51. Treat the runner-up as a possible second peril, not noise.damage_cause: weather at 0.86. The message says plainly that rain soaked the boxes, so the question that was ambiguous at the claim level is clear here. Splitting one fuzzy question into sharper ones often helps.requested_outcome: reimburse_expense at 0.71, information at 0.23. "Can I claim that back?" reads as both a request and a question about cover.police_report: not_mentioned at 0.93. A question the message cannot really answer still returns something useful when you give it an honest "not stated" option to land on.tone: frustrated at 0.62, calm at 0.31. Tone is subjective, so expect spread-out answers and act on sums of options rather than the top one.
Acting on it
Here is how an intake service might turn those five answers into actions. The rules are the insurer's own; Wity only supplies the probabilities they run on.
- If the top peril is below 0.6, a person decides. Here theft is at 0.68, so the claim opens automatically as theft. Storm is at 0.24, above the 0.15 bar, so it is added as a second peril for the handler to confirm.
- The customer wants money back, so the service asks for the locksmith's receipt straight away.
- It is a theft claim with no crime reference, so the service asks for one before a handler has to chase it.
- Frustrated and angry together come to 0.66, so the claim gets high priority. Adding up options like this is steadier than relying on which one came top.
a = r.json()["answers"]claim = a["claim_type"]p = claim["probabilities"]if p[claim["choice"]] < 0.6:route_to_handler(reason="peril unclear")else:open_claim(peril=claim["choice"])# A strong runner-up is often a second peril, not noise.for peril, prob in p.items():if peril != claim["choice"] and prob >= 0.15:add_peril(peril)if a["requested_outcome"]["choice"] == "reimburse_expense":request_receipt()if claim["choice"] == "theft" and a["police_report"]["choice"] != "reported":ask_for_crime_reference()tone = a["tone"]["probabilities"]if tone["frustrated"] + tone["angry"] >= 0.5:set_priority("high")
Without a person reading it, the claim is now open with the right team and flagged for a second peril. The receipt and crime reference are already requested, and the claim is near the front of the queue.
Writing good options#
- Describe when each option applies, not just its name. The description is the definition Wity uses.
- Make options mutually exclusive. If two can both be true, ask two questions instead.
- Add a catch-all such as
other,unknownornot_mentionedwhenever a real input might fit nothing. Wity picks it when the evidence does not support the others. - Option order does not matter: Wity checks that its answer holds whichever order the options come in.
Separating close options
When two options sound alike, a one-word description leaves Wity guessing where the line is. Spell it out inside the description: what the option covers, what it does not, and a short example.
Say the insurer's inbox also needs to separate new claims from questions about existing ones. With these descriptions, a message like "I reported the leak last month under CLM-20931, and now there's mould coming through the wall" could fit either option. It mentions an old claim, but it reports new damage.
"criteria": {"new_claim": "New claim","claim_status": "Claim status"}
The clearer version says where the line is. New damage is a new claim, even if the customer mentions an old one. A claim reference on its own does not make a message a status question. Each description also has an example, so Wity can see what a typical message looks like.
"criteria": {"new_claim": "Reporting damage or a loss for the first time. Not for questions about a claim that already exists. E.g. 'a tree came down on my shed last night'.","claim_status": "Asking about a claim that was already reported: progress, payment date, assessor visit. Usually has a CLM- reference. Not for new damage, even if they mention an old claim. E.g. 'any update on CLM-20931?'"}
Note
Choosing from an image#
Every question in a request also sees the request's image, if it has one. The same claim_type question works on a photo of the damage, and an agent can pick its next action from a screenshot. Use the state to say what the image is. See Images.