---
title: "Intent routing"
type: pattern
tags: [patterns, routing, intent, classification, llm-orchestration]
created: 2026-09-17
updated: 2026-09-17
confidence: high
sources:
  - raw/docs/patterns__intent-routing.md
  - raw/docs/patterns.md
  - raw/github/skills/skills/typesafe-ai/SKILL.md
jev_version: "jev-1.13.0"
summary: "Classify the request with one cheap Jev call, then invoke the cheapest adequate handler: deterministic code, a specialist LLM, or a human."
---

# Intent routing

> **TL;DR** Put Jev in front of your handlers. One call returns `intent` (Choice) and `complexity` (Score); code sends `order_status` to a plain database lookup, two intents to different specialist LLMs, and complex or low-confidence cases to a human. "The expensive resources only get invoked for the requests that actually need them."

## Problem

From `raw/docs/patterns__intent-routing.md`:

> Not every user request needs the same kind of handler. Some can be answered with a database lookup. Some need an LLM with domain-specific context. Some need a human.

If every inbound message goes through an expensive LLM just to discover *what kind* of message it is, you pay frontier-model cost and latency on requests a `SELECT` could have answered — and you still have to parse the LLM's answer to route on it.

## Pattern

> TypeSafe can sit in front of all of these as a fast, cheap classifier that determines which handler to invoke.

Two steps: one classification call, then a dispatch table in code. The classification call carries more than the intent — a second question about how hard the request is to resolve turns "which handler" into "which handler, and is automation safe here at all."

This is [[patterns/fan-out|speculative fan-out]] (two questions, one call) with a [[patterns/confidence-routing|confidence gate]] on top.

## Implementation

The documented example is customer service routing: "Messages come in and need to be routed to the right handler. Rather than sending every message through an expensive LLM to figure out what kind of request it is, you classify first and route accordingly."

### Step 1: classify intent and complexity

Questions, verbatim from the source (the customer message is the `state`; add `"state": ...` and `"model": "jev-latest"` for a complete request, see [[reference/http-api]]):

```json title="questions"
{
  "intent": {
    "type": "choice",
    "instructions": "The primary intent of this customer message",
    "criteria": {
      "order_status": "Asking about an existing order",
      "product_question": "Asking about a product before buying",
      "return_exchange": "Wants to return or exchange something",
      "complaint": "Unhappy with experience, wants resolution"
    }
  },
  "complexity": {
    "type": "score",
    "instructions": "How complex is this request to resolve",
    "criteria": [
      "Simple lookup or standard procedure",
      "Requires some judgment or multi-step process",
      "Unusual situation, edge case, or escalation needed"
    ]
  }
}
```

### Step 2: route to the optimal handler

```python title="routing.py"
def route_ticket(ticket_id, response):
    intent = response.answers["intent"]
    complexity = response.answers["complexity"]

    if intent.confidence < 0.5:
        # If we don't have enough confidence to classify, route to a human agent
        return route_to_human_agent(ticket_id)

    if intent.choice == "order_status":
        handle_order_status(ticket_id)

    elif intent.choice == "product_question":
        handle_with_llm(ticket_id, PRODUCT_SPECIALIST)

    elif intent.choice == "return_exchange":
        handle_with_llm(ticket_id, RETURNS_SPECIALIST)

    elif intent.choice == "complaint":
        low_confidence = complexity.confidence < 0.5
        # A higher complexity.score leans toward the "escalation needed" end of the scale.
        if complexity.score > 1 or low_confidence:
            # Too complex for safe automation, or we're not sure about the complexity; route to a human.
            route_to_human_agent(ticket_id)
        else:
            handle_with_llm(ticket_id, COMPLAINT_RESOLUTION)
```

The same router in TypeScript with `@typesafe-ai/sdk` 0.6.0 — **adapted from the Python sample (not in upstream docs)**, same questions, thresholds, and branches. `choice()` / `score()` build the questions; answers carry `choice` / `probabilities` / `confidence` and `score` / `confidence`; see [[reference/javascript-sdk]].

```ts title="routing.ts"
import { choice, score, TypeSafeClient } from "@typesafe-ai/sdk";

const client = new TypeSafeClient(); // reads TYPESAFE_API_KEY

export async function routeTicket(ticketId: string, message: string) {
  const { answers } = await client.systemOne({
    state: message,
    questions: {
      intent: choice("The primary intent of this customer message", {
        order_status: "Asking about an existing order",
        product_question: "Asking about a product before buying",
        return_exchange: "Wants to return or exchange something",
        complaint: "Unhappy with experience, wants resolution",
      }),
      complexity: score("How complex is this request to resolve", [
        "Simple lookup or standard procedure",
        "Requires some judgment or multi-step process",
        "Unusual situation, edge case, or escalation needed",
      ]),
    },
  });

  const { intent, complexity } = answers;

  if (intent.confidence < 0.5) {
    // If we don't have enough confidence to classify, route to a human agent
    return routeToHumanAgent(ticketId);
  }

  if (intent.choice === "order_status") {
    handleOrderStatus(ticketId);
  } else if (intent.choice === "product_question") {
    handleWithLlm(ticketId, PRODUCT_SPECIALIST);
  } else if (intent.choice === "return_exchange") {
    handleWithLlm(ticketId, RETURNS_SPECIALIST);
  } else if (intent.choice === "complaint") {
    const lowConfidence = complexity.confidence < 0.5;
    // A higher complexity.score leans toward the "escalation needed" end of the scale.
    if (complexity.score > 1 || lowConfidence) {
      // Too complex for safe automation, or we're not sure about the complexity; route to a human.
      routeToHumanAgent(ticketId);
    } else {
      handleWithLlm(ticketId, COMPLAINT_RESOLUTION);
    }
  }
}
```

The source's summary:

> One intent routes to deterministic code with no LLM involved. Two route to different specialist LLMs, each loaded with different context. One uses the complexity score to decide between an LLM and a human. TypeSafe handles the classification all in a single quick call; the expensive resources only get invoked for the requests that actually need them.

And on the second gate:

> Note the additional confidence check on the complexity score. As discussed in [Confidence](/confidence), it is always important to consider the meaning of a low confidence score in the context of the system and the stakes of the decision.

Three things worth copying: `complexity` is asked speculatively (only the `complaint` branch reads it); *uncertainty about the complexity* is treated the same as *high complexity*, both meaning "a human should look"; and `0.5` appears twice as two independent policy constants, not one model constant.

## When it fails

- **No escape hatch in `criteria`.** These four options cover the documented domain; a message that fits none of them still produces a `choice`. The agent skill: "Include a no-match outcome when nothing may fit; use a separate presence judgment when it is independently useful." Compare the `other` option in [[patterns/confidence-routing]].
- **Overlapping intents.** Choice "picks one option; its distribution compares competing options." A message that is genuinely two requests splits probability and trips the `< 0.5` gate. If several labels can be true at once, use one Noul per label instead — see [[concepts/noul]] and [[guides/choosing-a-primitive]].
- **Confidence read as permission.** "Choice/Score confidence summarizes distribution concentration, not overall workflow correctness or permission to act." The gate filters ambiguity, not incorrectness.
- **Thresholds carried over from this page.** `0.5` and `complexity.score > 1` are this example's numbers; evaluate yours on your data — see [[guides/testing-and-evaluation]]. The agent skill: "Treat cookbook thresholds and demo results as examples to evaluate, not universal rules."
- **Routing that needs facts the state does not contain.** If picking a handler requires looking up the order first, that is a second request by design — "A second request is warranted when an earlier answer is needed to fetch evidence, construct new state, or determine the next options."
- **Too many intents for one Choice.** High-cardinality routing is better done hierarchically; see [[cookbooks/hierarchical-classification]] and the cardinality note in [[concepts/system-one]].

## Variants

- **Route and fill arguments together.** Ask for the handler's typed parameters in the same call as the intent: "A request can select a handler and its typed parameters." See [[cookbooks/function-calling]].
- **Hierarchical routing.** Coarse category first, then a second call into that category's sub-intents — see [[cookbooks/hierarchical-classification]].
- **LLM fallback for the conversational branch.** [[guides/smart-home-demo]] routes "general information or conversation" to a generative LLM and everything else to deterministic device commands.
- **Guardrail in front of the router.** Screen the message before routing it; see [[cookbooks/llm-guardrails]].
- **A third dimension beyond intent and complexity.** Urgency or sentiment added to the same call, consumed by whichever branch wants it (inferred; mirrors the always-consumed `frustration` question in [[patterns/fan-out]]).

## Related

- [[patterns/overview]] — the four-pattern catalog
- [[patterns/fan-out]] — why both questions ride in one call
- [[patterns/confidence-routing]] — the gating layer this pattern reuses
- [[concepts/choice]] — `choice`, `probabilities`, `confidence`
- [[cookbooks/function-calling]] — routing plus typed arguments
- [[cookbooks/hierarchical-classification]] — routing over many categories
- [[guides/smart-home-demo]] — intent routing with an LLM fallback

## Sources

- raw/docs/patterns__intent-routing.md (https://docs.typesafe.ai/patterns/intent-routing)
- raw/docs/patterns.md (https://docs.typesafe.ai/patterns)
- raw/github/skills/skills/typesafe-ai/SKILL.md (https://github.com/typesafe-ai/skills/blob/main/skills/typesafe-ai/SKILL.md)
