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 outputoutput=PydanticModel makes 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.

intent_classifier.py
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.

Field descriptions are prompt material

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.py

Expected 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: Category

If 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.

Concepts used