Intent classifier
Classify free-text into a structured intent label, confidence score, and category using a typed Pydantic output model.
What it builds
An agent that reads a line of free text and returns a typed classification: an intent label, a confidence float between 0 and 1, and a category. Instead of parsing the model's prose, you pass a Pydantic model as output= and read the validated instance off result.output.
This example shows:
- Structured output —
output=PydanticModelmakes the model return a typed object. - Agent — constructing and running an agent.
The agent
The whole classifier is the output schema plus an Agent configured with it. Define a pydantic.BaseModel, hand it to the agent via output=, and result.output comes back as an instance of that model.
from agentroute import Agent
from pydantic import BaseModel, Field
class Intent(BaseModel):
"""Structured classification of an input utterance."""
intent: str = Field(description="Short snake_case label, e.g. 'book_flight'")
confidence: float = Field(ge=0.0, le=1.0, description="Certainty from 0 to 1")
category: str = Field(description="High-level group, e.g. 'travel' or 'billing'")
agent = Agent(
name="intent-classifier",
model="claude-sonnet-4",
output=Intent,
instructions=(
"You classify the user's message. "
"Pick the single most likely intent as a short snake_case label. "
"Assign it to a high-level category and rate your confidence from 0 to 1."
),
)
def classify(text: str) -> Intent:
result = agent.run(text)
return result.output # an Intent instance
if __name__ == "__main__":
samples = [
"I want to change my flight to next Tuesday",
"Why was I charged twice this month?",
"do you have these sneakers in size 10?",
]
for text in samples:
intent = classify(text)
print(f"{text!r}")
print(f" -> {intent.intent} ({intent.category}, {intent.confidence:.2f})")Because output=Intent, result.output is a fully validated Intent instance — intent.intent, intent.confidence, and intent.category are typed attributes, not strings you have to parse. The Field constraints (ge=0.0, le=1.0) are part of the schema the model sees, and Pydantic validates the response against them.
The description on each Field is included in the schema sent to the model, so it doubles as per-field instructions. Specific descriptions ("short snake_case label") produce cleaner labels than a bare str.
Run it
Set your OpenRouter key once, then run the script.
export AGENTROUTE_API_KEY="sk-or-..."
python intent_classifier.pyExpected output (labels will vary slightly by run):
'I want to change my flight to next Tuesday'
-> change_flight (travel, 0.95)
"Why was I charged twice this month?"
-> billing_dispute (billing, 0.92)
'do you have these sneakers in size 10?'
-> product_availability (shopping, 0.88)Constraining the label set
Free-text labels are convenient, but most real classifiers route into a fixed set of intents. Use a Python Enum for the field type so the model can only return a known value — the allowed members become part of the schema.
from enum import Enum
from pydantic import BaseModel, Field
class Category(str, Enum):
travel = "travel"
billing = "billing"
shopping = "shopping"
support = "support"
other = "other"
class Intent(BaseModel):
intent: str = Field(description="Short snake_case label")
confidence: float = Field(ge=0.0, le=1.0)
category: CategoryIf you also want to reject low-confidence answers or enforce business rules the schema can't express, add an output validator and raise Retry to make the model try again — see the structured output guide for that pattern.