Meriem B.
·Intent routing·8 min

Intent classification is the routing layer for LLM apps

Natural language is not executable. Intent classification turns messy requests into structured actions, metadata, validation, and workflow routing.

The moment an LLM app does more than chat, natural language becomes a routing problem.

A user writes:

My dashboard stopped loading after this morning's deployment. I think I need to roll back.

The backend cannot execute that sentence.

It needs something closer to:

User message
-> intent
-> metadata
-> validation
-> workflow

That is the useful version of intent classification.

Not "what should the assistant say?"

More like:

What is this request trying to make the product do?

Intent

An intent is the action hiding inside the message.

"What is the status of my latest deployment?"
-> CheckDeploymentStatus
 
"Roll back the last release."
-> RollbackDeployment
 
"Does your CLI support Windows?"
-> FeatureInquiry

Once the intent is known, the app can route.

CheckDeploymentStatus -> fetch pipeline logs
RollbackDeployment    -> trigger rollback flow
FeatureInquiry        -> search docs
ReportBug             -> open issue

Without this layer, the model can reply nicely while the product does nothing.

With it, the model becomes part of the control plane.

Metadata

Intent says what action.

Metadata says which details.

Roll back the payment service to version 2.4.1.
{
  "intent": "RollbackDeployment",
  "service": "payment-service",
  "targetVersion": "2.4.1"
}

Another one:

Assign the open auth bug to Priya and set it to high priority.
{
  "intent": "UpdateIssue",
  "issueType": "bug",
  "topic": "authentication",
  "assignee": "Priya",
  "priority": "high"
}

The intent chooses the route.

The metadata fills the route.

The app still has to check whether the result is complete and allowed.

Why plain prompts break

For a demo, this can look enough:

Classify this message into an intent.

Real messages are messier.

My dashboard stopped loading after this morning's deployment. I think I need to roll back.

Possible intents:

ReportBug
CheckDeploymentStatus
RollbackDeployment

A weak classifier might return:

The user is having a deployment problem.

That is a summary, not a route.

The app needs a contract:

{
  "primaryIntent": "ReportBug",
  "secondaryIntent": "RollbackDeployment",
  "service": "dashboard",
  "trigger": "deployment",
  "confidence": 0.82
}

Now the system can decide:

high confidence + safe action     -> continue
missing required field            -> ask a question
high-risk action                  -> require confirmation
low confidence                    -> do not execute

That is the difference between an AI answer and a product workflow.

Ways to build it

There is no single correct classifier. The boring mix usually works better than the clever single layer.

Rules and regex

Rules are useful when the surface area is small and predictable.

if message contains "roll back"
-> RollbackDeployment

Regex is useful for hard values:

const match = message.match(/v?(\d+\.\d+\.\d+)/);
const version = match?.[1]; // "2.4.1"

Rules are fast, cheap, testable, and strict.

They also miss natural phrasing.

The release from this morning is causing problems. Maybe undo it?

That is probably a rollback, even without the phrase roll back.

Classic NLU

Before LLMs became the default answer to everything, intent systems often used NLU tools like Rasa, Dialogflow, or spaCy classifiers.

The idea is still solid:

RollbackDeployment examples:
- Roll back the deployment.
- Undo the last release.
- Revert the pipeline to the previous build.
- Go back to version 2.4.1.

The classifier learns that different phrasing can map to the same intent.

This works well when:

intents are known
flows repeat often
training examples exist
the domain is stable

The cost is maintenance. Product language drifts. Users invent new phrasing. Someone has to keep the examples fresh.

LLM classification

LLMs are useful when requests are longer, messier, and more conditional.

Deploy the recommendation service to production, but only after the staging
health checks pass, and auto-rollback if the error rate exceeds 1% within
the first ten minutes.
{
  "intent": "DeployService",
  "service": "recommendation-service",
  "targetEnvironment": "production",
  "precondition": "staging_health_checks_pass",
  "rollbackThreshold": {
    "metric": "error_rate",
    "value": 1,
    "unit": "percent",
    "windowMinutes": 10
  }
}

The upside is flexibility.

The risk is that flexibility can become imagination.

LLMs can invent fields, soften constraints, misread values, or return a format the backend did not ask for.

So the output has to be structured and validated.

Structured outputs

Free text is not a great interface between a model and a backend.

This is nice for a human:

The user wants to roll back their deployment.

This is useful for software:

{
  "intent": "RollbackDeployment",
  "service": "payment-service",
  "targetVersion": "2.4.1"
}

Now the backend can do normal backend things:

if intent == Intent.RollbackDeployment:
    trigger_rollback(service, target_version)

The important part is not the JSON. It is the contract.

The model produces data.

The application validates data.

The workflow runs only after that validation passes.

Schema example

With LangChain and Pydantic, the shape can be explicit.

from enum import Enum
from pydantic import BaseModel
from langchain_anthropic import ChatAnthropic
 
 
class Intent(str, Enum):
    CheckDeploymentStatus = "CheckDeploymentStatus"
    RollbackDeployment = "RollbackDeployment"
    FeatureInquiry = "FeatureInquiry"
    ReportBug = "ReportBug"
 
 
class UserIntent(BaseModel):
    intent: Intent
    service: str | None = None
    target_version: str | None = None
 
 
model = ChatAnthropic(model="claude-sonnet-4-6")
classifier = model.with_structured_output(UserIntent)
 
result = classifier.invoke("Roll back the payment service to version 2.4.1")
print(result.intent)          # Intent.RollbackDeployment
print(result.service)         # "payment-service"
print(result.target_version)  # "2.4.1"

The useful part is that the model cannot casually return five different names for the same thing.

Without enums, the app might receive:

rollback deployment
Rollback
DeploymentRollback
UserWantsRollback
RollbackIntent

With an enum, the route is stable:

RollbackDeployment

Stable names make stable workflows.

Hybrid systems

The strongest setup is usually layered.

regex for obvious values
rules for hard constraints
LLMs for flexible language
schemas for structure
validation for correctness
fallback questions for ambiguity

Example:

Roll back the payment service to version 2.4.1.

Regex extracts:

{
  "targetVersion": "2.4.1"
}

The LLM classifies:

{
  "intent": "RollbackDeployment",
  "service": "payment-service"
}

Validation combines and checks:

{
  "intent": "RollbackDeployment",
  "service": "payment-service",
  "targetVersion": "2.4.1",
  "status": "ready_to_execute"
}

That is much better than asking one model call to be parser, policy engine, router, and safety system at the same time.

High-risk actions

The risk changes when the app can execute.

Bad answers are annoying.

Bad actions are dangerous.

This matters in infrastructure, finance, healthcare, legal workflows, permissions, and anything that changes real state.

Schedule a follow-up scan for the patient, but only with Dr. Reyes,
and only after at least three weeks have passed since the last procedure
on June 20th.

The hard constraints:

{
  "earliestAllowedDate": "2026-07-11",
  "requiredDoctor": "Dr. Reyes"
}

The system should never let the model turn that into:

{
  "earliestAllowedDate": "2026-06-22",
  "requiredDoctor": null
}

The safer architecture:

user input
-> deterministic extraction
-> LLM classification
-> schema validation
-> policy checks
-> user confirmation if needed
-> execution

The LLM helps understand language.

The system owns correctness.

Metadata tagging

Metadata tagging is the cousin of intent classification.

Intent classification asks:

What action is the user trying to perform?

Metadata tagging asks:

What labels can we extract from this content?

Example support ticket:

The CSV export hangs on reports with more than 50,000 rows. Smaller exports
work fine. We're on Enterprise and this is blocking our weekly reporting cycle.
{
  "category": "bug",
  "severity": "high",
  "feature": "csv-export",
  "plan": "enterprise",
  "impact": "blocking"
}

This helps retrieval because embeddings are not the only signal.

Find high-severity bugs in the export feature from Enterprise customers.

Filters can narrow the search:

{
  "severity": "high",
  "feature": "csv-export",
  "plan": "enterprise"
}

Then semantic search can work inside a cleaner slice of data.

Short version

Intent classification is the translation layer between human phrasing and software execution.

message
-> classify intent
-> extract metadata
-> validate
-> route
-> execute safely

The pattern I keep coming back to:

Use LLMs for language.
Use schemas for contracts.
Use rules for hard constraints.
Use validation before action.

The model can understand the request.

The application still has to decide what happens next.