Jev AI Hub
Start Learning

Classification · Choice

Detect Customer Intent with Jev

A Jev Choice example for intent detection: closed intents, an other escape hatch, and code that owns the handler map.

Published
Sep 20, 2026
Updated
Sep 20, 2026
Last verified
Sep 20, 2026

Quick answer

Classify a customer message as refund, rebooking, information, or other with a Choice.

Problem

A travel or commerce bot needs to know what the user wants before it calls a tool. Asking an LLM to invent a label produces extra tokens and the occasional new intent you never shipped.

Why Jev fits this task

Intent here is one label from a list you already implement. TypeSafe's intent-routing pattern is exactly this: classify, then send the request to deterministic logic, a specialist model, or a human.

Input state

Send only the fields the questions name. Official docs warn that extra unrelated state costs accuracy.

{
  "channel": "email",
  "message": "Flight TS-441 was cancelled last night. Can I get the fare back to the same card?"
}

Question

What is the main request in this message?

Question type: Choice.

Jev schema

{
  "model": "jev-latest",
  "state": {
    "channel": "email",
    "message": "Flight TS-441 was cancelled last night. Can I get the fare back to the same card?"
  },
  "questions": {
    "intent": {
      "type": "choice",
      "instructions": "What is the main request in `message`?",
      "criteria": {
        "refund": "The customer wants money returned.",
        "rebooking": "The customer wants a replacement flight or date change.",
        "information": "The customer is asking what happened or what options exist, without asking to act.",
        "other": "None of the above, or several conflicting asks."
      }
    }
  }
}

Python example

from typesafe_sdk import Choice, TypeSafeClient

state = {
    "channel": "email",
    "message": "Flight TS-441 was cancelled last night. Can I get the fare back to the same card?",
}

with TypeSafeClient() as client:
    response = client.system_one(
        state=state,
        questions={
        "intent": Choice(
            instructions="What is the main request in `message`?",
            criteria={
                            "refund": "The customer wants money returned.",
                            "rebooking": "The customer wants a replacement flight or date change.",
                            "information": "The customer is asking what happened or what options exist, without asking to act.",
                            "other": "None of the above, or several conflicting asks.",
                        },
        ),
        },
    )

print(response.answers["intent"].choice)
print(response.model)

TypeScript example

import { choice, TypeSafeClient } from "@typesafe-ai/sdk";

const client = new TypeSafeClient();

const response = await client.systemOne({
  state: {
    "channel": "email",
    "message": "Flight TS-441 was cancelled last night. Can I get the fare back to the same card?"
  },
  questions: {
    intent: choice("What is the main request in `message`?", {
      refund: "The customer wants money returned.",
      rebooking: "The customer wants a replacement flight or date change.",
      information: "The customer is asking what happened or what options exist, without asking to act.",
      other: "None of the above, or several conflicting asks.",
    }),
  },
});

console.log(response.answers.intent.choice);
console.log(response.model);

JavaScript example

import { choice, TypeSafeClient } from "@typesafe-ai/sdk";

const client = new TypeSafeClient();

const response = await client.systemOne({
  state: {
    "channel": "email",
    "message": "Flight TS-441 was cancelled last night. Can I get the fare back to the same card?"
  },
  questions: {
    intent: choice("What is the main request in `message`?", {
      refund: "The customer wants money returned.",
      rebooking: "The customer wants a replacement flight or date change.",
      information: "The customer is asking what happened or what options exist, without asking to act.",
      other: "None of the above, or several conflicting asks.",
    }),
  },
});

console.log(response.answers.intent.choice);
console.log(response.model);

cURL example

curl -s https://api.typesafe.ai/v1/systemone \
  -H "Authorization: Bearer $TYPESAFE_API_KEY" \
  -H "Content-Type: application/json" \
  -d @- <<'EOF'
{
  "model": "jev-latest",
  "state": {
    "channel": "email",
    "message": "Flight TS-441 was cancelled last night. Can I get the fare back to the same card?"
  },
  "questions": {
    "intent": {
      "type": "choice",
      "instructions": "What is the main request in `message`?",
      "criteria": {
        "refund": "The customer wants money returned.",
        "rebooking": "The customer wants a replacement flight or date change.",
        "information": "The customer is asking what happened or what options exist, without asking to act.",
        "other": "None of the above, or several conflicting asks."
      }
    }
  }
}
EOF

Expected output

{
  "model": "jev-1.13.0",
  "answers": {
    "intent": {
      "type": "choice",
      "choice": "refund",
      "probabilities": {
        "refund": 0.86,
        "rebooking": 0.08,
        "information": 0.04,
        "other": 0.02
      },
      "confidence": 0.81
    }
  },
  "usage": {
    "input_tokens": 210,
    "output_tokens": 28
  }
}

Confidence handling

If confidence is low, do not call a mutating tool. Ask a clarifying question or route to a person. Do not treat a low-confidence refund as safe to execute.

Production considerations

Keep the handler map in code: intent.choice -> function. If you later need both refund and rebooking on one message, add a second Noul instead of inventing a combo label.

Chat intake, email triage, and agent routers that dispatch to a refund or booking tool.

When to use Jev

The product already has a small set of intents with real handlers.

When not to use Jev

You want open-ended 'what should we build next' clustering, or you need the model to invent new intents from scratch.

A Choice cannot extract the flight number or the card last-four. Official generation notes: extract candidates with regex or an LLM, then let Jev pick if needed.

Common mistakes

  • Writing overlapping criteria so refund and information both fit every polite email.
  • Skipping other, then forcing a rebooking on a complaint that is only venting.
  • Calling this page and document classification the same problem. Document type is a different example.

FAQ

Intent vs routing vs document classification?

Intent is what the person wants. Routing is which team or model should handle it. Document classification is what kind of file it is. Each has its own page on this site.

Should I add a Noul for refund_requested too?

Only if your code needs an absolute P(yes) in addition to a relative Choice. Official jaggedness notes say those two numbers are not interchangeable.

Sources

  1. Primitives (Questions)TypeSafe · accessed 2026-09-20 · documentation
  2. Intent routingTypeSafe · accessed 2026-09-20 · documentation
  3. API referenceTypeSafe · accessed 2026-09-20 · documentation

All Jev examples