Intent routing
Classify each request in one fast call and route it to the cheapest capable handler — code, a specialist LLM or a person. Built on the Customer Service Router template.
Not every request needs the same handler. Some are answered by a database lookup, some need an LLM with domain-specific context, some need a person. Put Dcision in front of all of them as a fast, cheap classifier: classify first, then route — and only pay for the expensive handler when the request actually needs it.
Benefits: cost and speed. TypeSafe's guide: Intent routing.
Why it works
- A choice is a closed set. The answer is always one of your intents — never free text to parse — with a probability for every option.
- "None of these" is an answer. The reserved
otheroption lets the engine say a request fits no intent instead of forcing the least wrong one; Dcision then returns the fallback action. - One call answers the routing and the risk. Ask for the intent and its complexity together, in parallel.
- The expensive part runs last. Lookups go straight to code, without any LLM; specialist models get only their own requests, with their own context.
When to use it
Inbound messages and tickets, chat and voice assistants, an agent choosing its first tool, any queue where requests differ in what they need — before you pay for an LLM call.
How it maps to Dcision
| In TypeSafe's guide | In Dcision |
|---|---|
| A choice for the intent | A choice question, plus the reserved other |
| A score for complexity | A score question; its weighted level (scores) is usable in rules |
if intent.confidence < 0.5: human | minConfidence on the intent question, or a rule on its confidence |
complaint → human when complex or unsure | Rules that escalate the cases a person must see |
handle_order_status(), handle_with_llm(…) | Your code routes on result.intent when the action is continue |
Build it
In the app
Open Templates and choose Customer Service Router (intent routing). Name the decision Customer Service Router so its slug is customer-service-router, then Create decision.
The State is Text: the customer's message.
Two questions, answered in the same call:
| Question | Type | Answers |
|---|---|---|
intent | choice | order_status (answered by a database lookup), billing_question, technical_issue, complaint, other |
complexity | score | simple, moderate, complex |
In Policies, escalate what a person must see:
- IF
intent· answer · = ·complaint— THENescalate. - IF
intent· answer · = ·technical_issue, + ANDcomplexity· weighted level · ≥ · 2.5 — THENescalate. - IF
complexity· confidence · < · 0.5 — THENescalate: when the decision can't tell how hard a request is, don't automate it.
The fallback action is escalate, so other reaches a person too.
Test real messages in the Playground, then Deploy v1.
The decision schema
{
"stateSchema": { "kind": "text" },
"questions": [
{
"key": "intent",
"type": "choice",
"instructions": "What is the customer asking for?",
"options": [
{ "value": "order_status", "description": "where is my order, tracking, delivery date (answered by a database lookup)" },
{ "value": "billing_question", "description": "charges, invoices, refunds, payment methods" },
{ "value": "technical_issue", "description": "the product or app is not working as expected" },
{ "value": "complaint", "description": "dissatisfaction, wants to escalate or talk to a manager" }
]
},
{
"key": "complexity",
"type": "score",
"instructions": "How complex is it to resolve this request?",
"scale": ["simple", "moderate", "complex"]
}
],
"policies": [
{ "field": "intent", "on": "output", "operator": "eq", "value": "complaint", "action": "escalate" },
{
"field": "intent",
"on": "output",
"operator": "eq",
"value": "technical_issue",
"and": [{ "field": "complexity", "on": "score", "operator": "gte", "value": 2.5 }],
"action": "escalate"
},
{ "field": "complexity", "on": "confidence", "operator": "lt", "value": 0.5, "action": "escalate" }
],
"settings": { "fallbackAction": "escalate" }
}Call it
curl -X POST https://api.dcision.io/v1/decisions/customer-service-router \
-H "Authorization: Bearer $DCISION_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "state": "My order #4821 was supposed to arrive yesterday. Can you tell me where it is?" }'{
"decision_id": "dec_Lr5Tw9Mc2Qx7Hb4Nz1Kg",
"execution_id": "exec_Fv1Wd6Kp3Zs8Qm2Tx7Lh",
"schema": "customer-service-router",
"version": 1,
"result": { "intent": "order_status", "complexity": "simple" },
"confidence": { "intent": 0.925, "complexity": 0.76 },
"scores": { "complexity": 1.16 },
"action": "continue",
"action_reason": { "type": "default" },
"metrics": {
"latency_ms": 233,
"engine": "jev",
"model": "jev-1.13.0",
"estimated_cost_usd": 0.00000966,
"input_tokens": 230,
"output_tokens": 21
}
}No rule matched: a simple order-status question that code can answer without any LLM.
Act on it
type Intent = "order_status" | "billing_question" | "technical_issue" | "complaint" | "other";
async function route(message: Message) {
const response = await fetch("https://api.dcision.io/v1/decisions/customer-service-router", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.DCISION_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ state: message.text }),
});
if (!response.ok) return humanQueue.add(message); // errors: a person decides
const decision = await response.json();
if (decision.action !== "continue") return humanQueue.add(message, { reason: decision.action_reason });
switch (decision.result.intent as Intent) {
case "order_status":
return orders.replyWithTracking(message); // deterministic code, no LLM
case "billing_question":
return llm.answer(message, BILLING_SPECIALIST);
case "technical_issue":
return llm.answer(message, SUPPORT_SPECIALIST); // complex ones were escalated by rule 1
default:
return humanQueue.add(message);
}
}Tune it
- Gate the intent itself. TypeSafe's version sends anything below 0.5 intent confidence to a person: set
"minConfidence": 0.5onintentto do the same. - Make the boundaries explicit. Describe each intent by what it covers and what it doesn't — option descriptions accept JSON rubrics such as
{ "what": "…", "not_for": "…", "examples": ["…"] }. See Questions. - Check the option order. Jev can lean toward the option listed first. Reorder the options in the Playground and check that the answers stay the same.
- Describe
otherwhen "none of these" has a meaning for you — "not about an order, a bill or the app" — so off-topic messages reach the fallback action. - Grow the set as you learn. A choice holds up to 254 options plus
other. Watch theother_optionreasons in Executions to find the intents you are missing. - Route tools the same way. The Agent routing template applies this pattern to an AI agent's first tool.
Related: Confidence-gated routing · Support routing · Spam detection
Composite scoring
Break a judgment into atomic scores and combine them with weights you control — composites computed by Dcision and usable in policies. Built on the Resume Screening template.
Destinations overview
What happens after a decision — per option, score level, probability threshold or final action — fixed replies, LLM answers, your agent, workflows, signed webhooks, HTTP requests and functions.