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
-> workflowThat 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?"
-> FeatureInquiryOnce the intent is known, the app can route.
CheckDeploymentStatus -> fetch pipeline logs
RollbackDeployment -> trigger rollback flow
FeatureInquiry -> search docs
ReportBug -> open issueWithout 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
RollbackDeploymentA 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 executeThat 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"
-> RollbackDeploymentRegex 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 stableThe 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
RollbackIntentWith an enum, the route is stable:
RollbackDeploymentStable 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 ambiguityExample:
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
-> executionThe 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 safelyThe 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.