LLM classifier (default)
All steps
"The app crashes on login"
"I was charged twice"
Parts of this pattern
- Request Input / output
- The incoming customer message, such as 'The app crashes on login' or 'I was charged twice'.
- Classifier Router
- Classifies the message into exactly one category (billing, tech or general) and returns it as JSON.
- Billing LLM call
- Specialist prompt for charges, invoices, refunds and plan changes, with account context.
- Tech support LLM call
- Specialist prompt for errors, crashes and how-to questions, with knowledge-base search and error-log tools.
- General LLM call
- Fallback handler for anything the classifier does not clearly place as billing or tech.
- Reply Input / output
- The reply written by whichever specialist handled the message.
- Request → Classifier
- The raw customer message is placed in the classifier prompt.
Classify the customer message into exactly one category. <message>The app crashes on login</message>
- Classifier → Billing
- Taken when the label is billing; the original message goes to the billing prompt.
I was charged twice for my March invoice
- Classifier → Tech support
- Taken when the label is tech; the original message goes to the tech-support prompt.
The app crashes on login
- Classifier → General
- Taken for any other or unclear label; the message goes to the general handler.
Do you have an office in Berlin?
- Billing → Reply
- The billing specialist's reply goes back to the customer.
Sorry about that. I can see two charges of $29 on March 3. I've flagged the duplicate charge for a refund; you'll hear from us within 3 days.
- Tech support → Reply
- The tech-support reply goes back to the customer.
1. Update to version 4.2.1. 2. Clear the app cache. 3. Log in again. If it still crashes, send us the log from Settings > Help.
- General → Reply
- The general handler's reply goes back to the customer.
Thanks for reaching out. We are a fully remote team, so we do not have a Berlin office, but you can book a call at any time.
Embedding router
No LLM picks the route: the message is embedded and matched to the nearest example utterances, and a weak match goes to the fallback.
All steps
"The app crashes on login"
"Do you have a Berlin office?"
Parts of this pattern
- Request Input / output
- The incoming customer message, such as 'The app crashes on login' or 'I was charged twice'.
- Nearest match Router
- No LLM: embeds the message, compares it with every stored example and picks the route whose top-5 matches score highest in total.
- Route examples Memory
- Example utterances for each route, embedded once at startup and stored as vectors with their route name.
- Billing LLM call
- Specialist prompt for charges, invoices, refunds and plan changes, with account context.
- Tech support LLM call
- Specialist prompt for errors, crashes and how-to questions, with knowledge-base search and error-log tools.
- General LLM call
- Fallback handler for any message whose chosen route has no single match above the similarity threshold.
- Reply Input / output
- The reply written by whichever handler took the message.
- Request → Nearest match
- The raw message is embedded with the same model used for the examples.
"The app crashes on login" -> [0.012, -0.087, 0.231, ...] (1,024 dimensions)
- Nearest match → Route exampleslookup
- The message vector is compared with every stored example by cosine similarity; the closest examples come back with their scores.
tech: 'app won't open' 0.91, 'crash after update' 0.89; billing: 'charged twice' 0.22
- Nearest match → Billing
- Taken when billing is the highest-scoring route among the top 5 matches and its own best match clears the threshold.
I was charged twice for my March invoice (best match 'double charge on invoice', 0.93)
- Nearest match → Tech support
- Taken when tech is the highest-scoring route among the top 5 matches and its own best match clears the threshold.
The app crashes on login (best match 'app won't open', 0.91)
- Nearest match → Generalno match
- Taken when the chosen route's best match is below the threshold, so no route is a confident fit.
Do you have an office in Berlin? (best match 'change my plan', 0.41, below 0.75)
- Billing → Reply
- The billing specialist's reply goes back to the customer.
Sorry about that. I can see two charges of $29 on March 3. I've flagged the duplicate for a refund; you'll hear from us within 3 days.
- Tech support → Reply
- The tech-support reply goes back to the customer.
1. Update to version 4.2.1. 2. Clear the app cache. 3. Log in again. If it still crashes, send us the log from Settings > Help.
- General → Reply
- The general handler's reply goes back to the customer.
Thanks for reaching out. We are a fully remote team, so we do not have a Berlin office, but you can book a call at any time.
Rules first
Deterministic rules settle the obvious messages for free, and only the messages no rule matches go to the LLM classifier.
All steps
"I was charged twice"
"The app crashes on login"
Parts of this pattern
- Request Input / output
- The incoming customer message, such as 'The app crashes on login' or 'I was charged twice'.
- Rules Guard
- Plain code, not a model: an ordered list of keyword and pattern rules; the first match decides the route at no model cost.
- Classifier Router
- An LLM classifier that only sees the messages no rule matched, and returns a label as JSON.
- Billing LLM call
- Specialist prompt for charges, invoices, refunds and plan changes, with account context.
- Tech support LLM call
- Specialist prompt for errors, crashes and how-to questions, with knowledge-base search and error-log tools.
- General LLM call
- Fallback handler for anything the classifier does not clearly place as billing or tech.
- Reply Input / output
- The reply written by whichever specialist handled the message.
- Request → Rules
- Every message is checked against the rules first.
I was charged twice for my March invoice
- Rules → Billingrule: billing
- A rule matched, so the message goes straight to its handler without any model call.
Rule 'duplicate-charge' (/charged (twice|two times)/i) matched: route billing.
- Rules → Classifierno rule
- No rule matched, so the message goes to the LLM classifier.
Classify the customer message into exactly one category. <message>The app crashes on login</message>
- Classifier → Billing
- Taken when the classifier's label is billing.
My plan renewed at the wrong price
- Classifier → Tech support
- Taken when the classifier's label is tech.
The app crashes on login
- Classifier → General
- Taken for any other or unclear label.
Do you have an office in Berlin?
- Billing → Reply
- The billing specialist's reply goes back to the customer.
Sorry about that. I can see two charges of $29 on March 3. I've flagged the duplicate for a refund; you'll hear from us within 3 days.
- Tech support → Reply
- The tech-support reply goes back to the customer.
1. Update to version 4.2.1. 2. Clear the app cache. 3. Log in again. If it still crashes, send us the log from Settings > Help.
- General → Reply
- The general handler's reply goes back to the customer.
Thanks for reaching out. We are a fully remote team, so we do not have a Berlin office, but you can book a call at any time.
← → neighboursesc atlas
Routing
Also known as Classifier/dispatcher · Intent routing · Triage
Classify the input, then send it to a specialized prompt, model or tool.
LLM classifier, Rules first: Popularized by Schluntz & Zhang (2024) · Anthropic
Anthropic named the pattern in 2024. Classify-then-dispatch is much older than LLMs, and no single inventor is known.
Embedding router: No single inventor · see source
The open-source semantic-router library popularized embedding-based routing for LLM apps. Nearest-neighbour intent matching is older, so who first applied it to LLM routing is unclear. The repository was created in October 2023.
Measured over one run
LLM classifier
- Nodes
- 6. Boxes in the diagram: models, tools, stores, people and plain code.
- LLM calls
- 2. Model invocations in the first scenario: each time a model or agent node starts thinking, plus each branch it decides.
- Tool calls
- 0. Messages sent to a tool node in the first scenario.
- Steps
- 3. Steps in the first scenario, with repeated steps counted every time they run.
- Scenarios
- 2. Distinct example runs you can switch between on the diagram.
- Loop
- no. Whether the flow cycles back on itself until a condition is met.
- Parallel
- no. Whether two or more messages are in flight at the same time.
- Branch
- yes. Whether the flow takes one path out of several, chosen at run time.
- Human in the loop
- no. Whether a person appears as a node, to approve, correct or answer.
- Autonomy
- low. How much the model decides the control flow itself, from fixed code paths (low) to open-ended (high).
- Cost
- low. Relative token spend per request.
- Latency
- low. Relative wall-clock time per request.
Embedding router
- Nodes
- 7. Boxes in the diagram: models, tools, stores, people and plain code.
- LLM calls
- 1. Model invocations in the first scenario: each time a model or agent node starts thinking, plus each branch it decides.
- Tool calls
- 0. Messages sent to a tool node in the first scenario.
- Steps
- 3. Steps in the first scenario, with repeated steps counted every time they run.
- Scenarios
- 2. Distinct example runs you can switch between on the diagram.
- Loop
- no. Whether the flow cycles back on itself until a condition is met.
- Parallel
- no. Whether two or more messages are in flight at the same time.
- Branch
- yes. Whether the flow takes one path out of several, chosen at run time.
- Human in the loop
- no. Whether a person appears as a node, to approve, correct or answer.
- Autonomy
- low. How much the model decides the control flow itself, from fixed code paths (low) to open-ended (high).
- Cost
- low. Relative token spend per request.
- Latency
- low. Relative wall-clock time per request.
Rules first
- Nodes
- 7. Boxes in the diagram: models, tools, stores, people and plain code.
- LLM calls
- 1. Model invocations in the first scenario: each time a model or agent node starts thinking, plus each branch it decides.
- Tool calls
- 0. Messages sent to a tool node in the first scenario.
- Steps
- 3. Steps in the first scenario, with repeated steps counted every time they run.
- Scenarios
- 2. Distinct example runs you can switch between on the diagram.
- Loop
- no. Whether the flow cycles back on itself until a condition is met.
- Parallel
- no. Whether two or more messages are in flight at the same time.
- Branch
- yes. Whether the flow takes one path out of several, chosen at run time.
- Human in the loop
- no. Whether a person appears as a node, to approve, correct or answer.
- Autonomy
- low. How much the model decides the control flow itself, from fixed code paths (low) to open-ended (high).
- Cost
- low. Relative token spend per request.
- Latency
- low. Relative wall-clock time per request.
Counted from the first scenario's timeline when the site is built.
Problem
One prompt cannot be best at everything. A support assistant that must handle refunds, crash reports and sales questions ends up with a sprawling prompt, a pile of tools it rarely needs, and answers that are mediocre across the board. Tuning it for billing quietly makes it worse at debugging.
How it works
A classifier reads the incoming request and picks exactly one category. Code then sends the request down the matching path: a billing prompt with access to account records, a tech-support prompt with the knowledge base and error logs, or a general fallback. Each specialist has its own instructions, tools and, if you like, its own model. Its answer becomes the reply.
The classifier can be an LLM call, a small fine-tuned model or a plain rules engine. The same shape also routes by difficulty: send easy questions to a small, cheap model and hard ones to a larger one.
The variants swap out the classifier. An embedding router makes no LLM call at all: it compares the message with stored example utterances for each route and falls back to the general handler when nothing is close. A rules-first router lets plain keyword and pattern rules settle the obvious cases for free, and only sends what they miss to the LLM classifier.
When to use
- The input falls into distinct categories that are better handled separately.
- A classifier, LLM or traditional, can sort them reliably.
- Specialization pays off, and you can measure it per category: different tools, different tone, or a different model per route.
When to avoid
- Categories blur or overlap, so the classifier guesses and the wrong specialist answers confidently.
- One well-written prompt already handles every case acceptably.
- Requests routinely need several categories at once. Consider orchestrator-workers instead.
Trade-offs
Routing is the cheapest workflow to add. It adds one cheap, fast call before the real work, and often saves money by keeping most traffic on small models. The cost is maintenance: every new category means a new handler, and the classifier must be re-tested whenever the categories change.
Failure modes
- Misrouting: a billing complaint phrased as a bug report lands with tech support. Evaluate the classifier on labelled real traffic and log its choices.
- No fallback: an unexpected label crashes the flow. Always default to a general handler.
- Category drift: products change, but the category list does not. Review misrouted examples regularly and update the labels.
Build it with your AI
LLM classifier
Paste this into Claude Code, Cursor, ChatGPT or any AI coding assistant.
Implement the Routing pattern in this codebase, using the LLM client and language we already use.
## Goal
Send each customer message to exactly one specialist handler, chosen by a cheap LLM classifier, with a general handler as the fallback for anything unclear.
## How it works
1. Define the route table in code: each label (billing, tech, general) maps to one handler with its own prompt, tools and model.
2. Call a small, fast model with the Classifier prompt, filling in the message, and parse the JSON reply into a label.
3. If the reply is not valid JSON, or the label is not in the route table, use the general route.
4. Call the chosen handler with the original message, not the classifier's output; for the tech label that is the Tech-support handler prompt with its knowledge-base and log tools.
5. Return the handler's reply together with the label that was used.
## Prompts
### Classifier prompt
```markdown
Classify the customer message into exactly one category:
- billing: charges, invoices, refunds, plan changes
- tech: errors, crashes, bugs, how-to questions about the product
- general: anything else
<message>{{request}}</message>
Respond in JSON: {"label": "billing" | "tech" | "general"}
If the message fits no category clearly, answer "general".
```
### Tech-support handler prompt
```markdown
You are a support engineer for {{product}}. You can search the
knowledge base and read the customer's recent error logs.
<message>{{request}}</message>
Diagnose the problem, give numbered steps to fix it, and say what
to send us if the steps do not work.
```
## Stop rule
Each message makes one classifier call and one handler call, then stops. Nothing re-routes: if the handler cannot help, it says so in its reply.
## Requirements
- Keep the labels in one list that both the classifier prompt and the route table are built from, so they cannot drift apart.
- Never throw on a bad classification: a parse failure or an unknown label goes to the general handler, and a classifier timeout (default 5 s) does too.
- Run the classifier at temperature 0 with its own model setting, separate from the handlers' models.
- Log the message id, the raw classifier reply, the label used and whether it fell back, and sample the logs into a labelled test set.
- Add a test with a stub LLM whose classifier returns {"label": "refunds"}; assert the general handler was called once and the billing and tech handlers were not called.
Embedding router
Paste this into Claude Code, Cursor, ChatGPT or any AI coding assistant.
Implement the Routing pattern (Embedding router variant) in this codebase, using the LLM client and language we already use.
## Goal
Route each message to a specialist handler by comparing its embedding with stored example utterances for every route, with no LLM call in the routing step and a fallback when nothing is close.
## How it works
1. For each route (billing, tech), write 10 to 30 example utterances taken from real traffic.
2. At startup, embed every example once and store each vector with its route name.
3. For each incoming message, embed it with the same embedding model and compute its cosine similarity to every stored example.
4. Take the top 5 matches, group them by route, score each route by the sum of its matches' similarities, and pick the highest-scoring route.
5. If the chosen route's own best similarity is below the threshold, use the general handler; otherwise call the chosen route's handler with the original message, such as the Tech-support handler prompt for tech.
## Prompts
### Classifier prompt
```markdown
Classify the customer message into exactly one category:
- billing: charges, invoices, refunds, plan changes
- tech: errors, crashes, bugs, how-to questions about the product
- general: anything else
<message>{{request}}</message>
Respond in JSON: {"label": "billing" | "tech" | "general"}
If the message fits no category clearly, answer "general".
```
### Tech-support handler prompt
```markdown
You are a support engineer for {{product}}. You can search the
knowledge base and read the customer's recent error logs.
<message>{{request}}</message>
Diagnose the problem, give numbered steps to fix it, and say what
to send us if the steps do not work.
```
## Stop rule
Each message makes one embedding call and one handler call. There is no retry: a weak match goes to the general handler rather than being routed again.
## Requirements
- Make the similarity threshold (default 0.75) and the number of neighbours (default 5) parameters, and tune the threshold on labelled messages.
- Embed the examples and the messages with the same model; re-embed every example when that model changes.
- If the embedding call errors or times out, send the message to the general handler instead of failing.
- Log the chosen route with its score and best similarity, and the runner-up route with its score, so near-ties can be found and new examples added.
- Test with a stub embedder and fixed scores: a best match of 0.40 goes to general; tech matches 0.92 + 4 × 0.50 go to tech; billing 0.93 plus tech 3 × 0.60 goes to general (tech wins 1.80 to 0.93, but its best is under 0.75).
Rules first
Paste this into Claude Code, Cursor, ChatGPT or any AI coding assistant.
Implement the Routing pattern (Rules first variant) in this codebase, using the LLM client and language we already use.
## Goal
Route each message to one specialist handler, letting cheap deterministic rules decide the clear cases and calling an LLM classifier only for messages no rule matches.
## How it works
1. Keep an ordered list of rules as data, each a pattern (a keyword, a regular expression or a metadata field) and the route it selects, such as 'charged twice' to billing.
2. Run the rules against each message in order; the first rule that matches decides the route.
3. If no rule matches, call the Classifier prompt and parse its JSON reply into a label; an invalid reply, an unknown label, or a classifier error or timeout (default 5 s) means general.
4. Call the chosen handler with the original message, such as the Tech-support handler prompt for tech, and return its reply with the route and whether a rule or the classifier chose it.
## Prompts
### Classifier prompt
```markdown
Classify the customer message into exactly one category:
- billing: charges, invoices, refunds, plan changes
- tech: errors, crashes, bugs, how-to questions about the product
- general: anything else
<message>{{request}}</message>
Respond in JSON: {"label": "billing" | "tech" | "general"}
If the message fits no category clearly, answer "general".
```
### Tech-support handler prompt
```markdown
You are a support engineer for {{product}}. You can search the
knowledge base and read the customer's recent error logs.
<message>{{request}}</message>
Diagnose the problem, give numbered steps to fix it, and say what
to send us if the steps do not work.
```
## Stop rule
Each message is decided by at most one rule match or one classifier call, then handled once. Nothing re-routes.
## Requirements
- Keep the rules as data, in order, each with a name, so a rule can be added or removed without a code change and every decision can name its rule.
- Only add a rule when it is precise on labelled traffic (default bar: 98% of its matches are correct); vague keywords belong to the classifier.
- A rule that throws (such as a bad regular expression) is skipped and logged, never fatal.
- Log which rule fired, or that the classifier was used, so the share of free decisions can be tracked.
- Add a test with a stub LLM: assert "I was charged twice" reaches billing with zero classifier calls, and "The app crashes on login" makes exactly one classifier call.
Prompt templates
LLM classifier · Embedding router · Rules first
Classify the customer message into exactly one category:
- billing: charges, invoices, refunds, plan changes- tech: errors, crashes, bugs, how-to questions about the product- general: anything else
<message>{{request}}</message>
Respond in JSON: {"label": "billing" | "tech" | "general"}If the message fits no category clearly, answer "general".You are a support engineer for {{product}}. You can search theknowledge base and read the customer's recent error logs.
<message>{{request}}</message>
Diagnose the problem, give numbered steps to fix it, and say whatto send us if the steps do not work.Related patterns
Composes well with:
Sources
From the Agentic Atlas by Joran Kikke · Edit on GitHub · Suggest a correction