Quick Answer — Intent Classification for Chatbots
Send the user's message and your list of allowed intents to POST /api/v1/ai/intent-detection. You get back one intent, a confidence score and any extracted entities. Auto-route when confidence is high, ask a clarifying question when it is middling, and fall back to a general chat model or a human when it is low.
Sending every chatbot message straight to a general-purpose language model is slow, hard to control and hard to test. A better design puts a small, structured decision in front: first work out what the user wants, then decide which handler should deal with it. This guide shows how to implement intent classification in a conversational agent: designing your intents, calling the API, setting confidence thresholds, using conversation history and building a routing layer you can test.
Why route by intent instead of prompting for everything
When a user types "I want to change my billing email", your bot has to do something specific: open the account settings flow. An open-ended chat model could produce a friendly paragraph about how to do it, but your application needs a decision it can act on. Intent classification turns free text into exactly that decision: a label such as update_profile that your code can switch on.
| Open-ended chat model per message | Intent classification first | |
|---|---|---|
| Output | Unstructured text you must parse | Label + confidence + entities |
| Predictability | Varies run to run | Limited to the intents you define |
| Testing | Hard: judge free-text answers | Easy: compare a label with the expected label |
| Cost | Charged on every message | Utility endpoint with a free monthly quota; only complex messages go on to chat |
| Best for | Open-ended conversation | Routing, commands, FAQs, ticket triage |
The benefit is control. Routine requests such as checking an order, changing a setting or asking for a refund go to deterministic code. Only the messages that genuinely need free-form language generation go on to a chat model.
Design your intents before you write code
The quality of intent routing depends far more on your intent list than on the code. Poorly defined intents overlap, so even a perfect classifier will disagree with your expectations. Follow a few rules:
- Name intents by what the user wants, not by department.
cancel_subscriptionis better thanbilling, because the handler for it is specific. - Keep the list short. Start with 5 to 10 intents. The API accepts up to 10 allowed intents per request, which is a healthy limit for a first version.
- Make intents mutually exclusive. If a human could not tell
billing_issuefrompayment_problem, merge them. - Plan for the unknown. Include a way to say "none of the above" so odd messages do not get forced into the nearest label.
- Decide the entities up front. For each intent, list the details your handler needs, such as
invoice_numberoreffective_time.
Calling the Intent Detection API
Post the message together with the intents you allow and the entities to extract for each. Only the text field is required; the others sharpen the result:
{
"text": "I want to cancel my Pro subscription starting next week because it is too expensive.",
"intent_types": ["cancel_subscription", "upgrade_plan", "billing_issue"],
"intent_entities": {
"cancel_subscription": ["reason", "effective_time", "plan_type"],
"billing_issue": ["invoice_number"]
},
"allow_fallback": true
}
The response contains the winning intent, how sure the model is, and the entities it found:
{
"success": true,
"request_id": "req_7f3a9c2d1e4b8a6f",
"data": {
"result": {
"intent": "cancel_subscription",
"confidence": 0.98,
"entities": {
"reason": "too expensive",
"effective_time": "next week",
"plan_type": "Pro"
}
}
},
"meta": { "version": "v1", "timestamp": "2026-09-27T12:34:56+00:00" }
}
Setting allow_fallback to true makes the API answer unknown when confidence is low or nothing in your list fits, which is usually safer than a forced best guess. The text field accepts up to 5,000 characters.
Use conversation history, examples and multi-intent
Real conversations are not isolated messages. A user who writes "yes, that one" only makes sense in context, and a single message often contains two requests. The endpoint has options for all three situations:
| Option | Use it when | Limits |
|---|---|---|
messages | Short replies depend on earlier turns | Up to 5 recent messages, 1,000 characters each |
examples | Your product uses unusual wording or two intents are easily confused | Up to 3 few-shot examples |
multi_intent | Users often combine requests ("billing problem and cancel") | Returns an intents array instead of a single value |
min_confidence | You want weak matches filtered out by the API | A number between 0 and 1 |
{
"messages": [
{ "role": "user", "content": "I am having billing problems. Also, can I cancel my subscription?" }
],
"intent_types": ["cancel_subscription", "upgrade_plan", "billing_issue"],
"examples": [
{ "text": "payment declined and cancel my account", "intent": "billing_issue", "entities": {} }
],
"multi_intent": true,
"min_confidence": 0.5,
"allow_fallback": true
}
Everything you pass is processed by the AI and counts toward your content size, so send only the history and examples that matter rather than the whole transcript.
Setting confidence thresholds
The confidence score is your safety control. A sensible starting policy for production is:
- 0.85 and above: auto-route. Execute the matched action immediately.
- 0.5 to 0.85: confirm. Ask a quick "Did you mean X?" before acting.
- Below 0.5: fall back. Hand over to a general chat model or a human instead of guessing.
Acting on a 0.4-confidence guess frustrates users more than a clarifying question. Auto-route only on high confidence, and always offer a "something else" path. These numbers are starting points: adjust them after looking at how your real traffic scores, and use stricter thresholds for destructive actions such as cancellations or refunds.
A routing layer in Node.js
The code that uses the result is short. It calls the API, applies the thresholds, and dispatches to a handler:
const INTENTS = ['cancel_subscription', 'upgrade_plan', 'billing_issue', 'order_status'];
async function detectIntent(text, history = []) {
const res = await fetch('https://rsflowhub.com/api/v1/ai/intent-detection', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-API-Key': process.env.RSFLOWHUB_KEY },
body: JSON.stringify({
text,
messages: history.slice(-5),
intent_types: INTENTS,
allow_fallback: true,
}),
signal: AbortSignal.timeout(30_000),
});
if (!res.ok) return { intent: 'unknown', confidence: 0, entities: {} };
return (await res.json()).data.result;
}
const handlers = {
cancel_subscription: (entities) => startCancellationFlow(entities),
upgrade_plan: () => showPlans(),
billing_issue: (entities) => openBillingTicket(entities),
order_status: () => lookUpLatestOrder(),
};
export async function route(text, history) {
const { intent, confidence, entities } = await detectIntent(text, history);
if (intent === 'unknown' || confidence < 0.5) return escalateToChatOrHuman(text, history);
if (confidence < 0.85) return askToConfirm(intent);
return handlers[intent]?.(entities) ?? escalateToChatOrHuman(text, history);
}
Notice what happens when the intent API is unavailable: the function returns unknown with zero confidence, so the user is passed to the fallback rather than seeing an error.
The two-tier routing architecture
- Every message goes to intent detection first.
- High-confidence routine intents (order status, plan changes, FAQ-style questions) are handled directly by your own code or a FAQ lookup.
- Low-confidence, unknown or open-ended messages go to a chat model, with the detected intent included as context.
- Sensitive or angry conversations are escalated to a human regardless of intent.
How much traffic stays out of the chat model depends entirely on your product, so measure it. Log the intent, the confidence and the route taken for every message, then look at the share of conversations handled without a model call. That number, not a generic claim, tells you what the routing layer is saving.
Test your intents like any other code
Because the output is a label, intent routing is easy to test. Build a table of 50 to 100 real messages with the intent a human would assign, run them all through detectIntent, and count the matches:
const CASES = [
['Where is my parcel?', 'order_status'],
['I was charged twice this month', 'billing_issue'],
['Stop my subscription at the end of the month', 'cancel_subscription'],
['What is the weather like?', 'unknown'],
];
let correct = 0;
for (const [text, expected] of CASES) {
const { intent, confidence } = await detectIntent(text);
const ok = intent === expected;
if (ok) correct++;
else console.log(`MISS: "${text}" -> ${intent} (${confidence}), expected ${expected}`);
}
console.log(`${correct}/${CASES.length} correct`);
The misses are the useful part. If two intents are constantly confused, add a few-shot example for the pair or merge them. If out-of-scope messages get forced into an intent, make sure allow_fallback is on and raise your confidence threshold.
Common mistakes to avoid
- Too many overlapping intents. More labels mean more confusion. Start small and split only when you have evidence.
- No fallback path. Every router needs somewhere to send messages it does not understand.
- Trusting confidence blindly. A high score on a wrong label still happens. Add confirmation steps before irreversible actions.
- Ignoring history. Single words such as "yes" or "the second one" cannot be classified without context. Pass recent messages.
- Never reviewing misclassifications. Sample low-confidence and escalated messages every week; they reveal missing intents.
- Sending too much. Intents, entities, examples and history all count as input. Pass only what the decision needs.
Add Intent Routing in One Endpoint
Use the Intent Detection API to route messages, then send open-ended ones to the AI Chat API. One key covers both.
curl -X POST "https://rsflowhub.com/api/v1/ai/intent-detection" \
-H "X-API-Key: YOUR_FLOWHUB_API_KEY" \
-H "Content-Type: application/json" \
-d '{"text": "How much does the pro plan cost?", "intent_types": ["pricing_question", "cancel_subscription"]}'
Try Intent Detection →
Key Takeaways
- Intent classification turns free text into a label your code can act on, with a confidence score and entities.
- Design short, mutually exclusive intents and plan an unknown path before writing code.
- Auto-route on high confidence (about 0.85 and up), confirm in the middle, and fall back below about 0.5.
- Pass recent
messages, a fewexamplesandmulti_intentwhen conversations are messy. - Two-tier routing keeps routine requests out of the chat model; measure the real share in your own logs.
- Test intents with a table of real messages and review the misses every week.
David Kim Member
2 days agoThe camera motion keywords for video prompts saved me hours of trial and error with Sora. Highly recommended guide!
Reply to David Kim
Elena Rostova Member
2 days agoThis formula structure is gold! Combining subject, environment, rim lighting, and lens parameters completely transformed my Midjourney outputs.
Reply to Elena Rostova
Marcus Brody Member
2 days agoWe integrated the RSFlowHub AI Chat API to auto-expand user prompts in our mobile app and response latency is under 200ms. Exceptional speed!
Reply to Marcus Brody