docs: Intent routing, in English and Chinese — when the classifier is asked and when not, the exact prompt and what of the message it sees, how its answer is read, what happens when it fails or is slow, the rest of the turn (held, failover, outgrown, waits, subagents), reading the trace, latency/cost/privacy, writing intents, the numbers, and a FAQ; with screenshots of the rules and the trace

This commit is contained in:
yetone committed 2026-09-26 11:30:09 +08:00
1 parent 9d092087a1
commit d9ff79ae44
15 files changed
+679 -2

No files matched your search

+3
View File
@@ -140,6 +140,9 @@ th { font-weight: 560; color: var(--ink3); font-size: 12.5px; letter-spacing: .0
td { color: var(--ink2); }
td:first-child { white-space: nowrap; }
td:first-child code { font-weight: 600; }
table.loose { table-layout: fixed; }
table.loose td:first-child { white-space: normal; }
pre.wrapped { white-space: pre-wrap; }
.req { font-size: 11.5px; color: var(--ink3); border: 1px solid var(--line); border-radius: 999px; padding: 0 7px; margin-left: 6px; white-space: nowrap; }
.chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 20px; }
+2
View File
@@ -42,6 +42,7 @@
<div class="links">
<a href="/">Home</a>
<a href="/docs/start">Get started</a>
<a href="/docs/intent">Intent routing</a>
<a href="/docs/import">Add to magpie</a>
<a href="https://github.com/yetone/magpie-releases/releases">Releases</a>
</div>
@@ -241,6 +242,7 @@ href = <span class="s">"https://usemagpie.ai/import#"</span> + urlencode({
<div class="links">
<a href="/">Home</a>
<a href="/docs/start">Get started</a>
<a href="/docs/intent">Intent routing</a>
<a href="https://github.com/yetone/magpie">GitHub</a>
<a href="https://discord.gg/vGSnD3ZKQF">Discord</a>
<a href="https://github.com/yetone/magpie-releases/releases">Releases</a>
+333
View File
@@ -0,0 +1,333 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Intent routing · magpie docs</title>
<meta name="description" content="How magpie routes a turn by what the user asks for: when the classifier is asked, exactly what it sees, how its answer is used, what happens when it fails, and a detailed FAQ.">
<meta name="color-scheme" content="light dark">
<meta name="theme-color" content="#fafafa">
<script>
(() => {
let mode = "system";
try { mode = localStorage.getItem("theme") || "system"; } catch {}
const q = matchMedia("(prefers-color-scheme: dark)");
const apply = () => {
const t = mode === "system" ? (q.matches ? "dark" : "light") : mode;
document.documentElement.dataset.theme = t;
document.querySelector('meta[name="theme-color"]').content = t === "dark" ? "#0b0b0c" : "#fafafa";
document.querySelectorAll("button.theme").forEach((b) => { b.dataset.mode = mode; b.title = "Theme: " + mode; });
};
q.addEventListener("change", () => mode === "system" && apply());
window.setTheme = (m) => { mode = m; try { m === "system" ? localStorage.removeItem("theme") : localStorage.setItem("theme", m); } catch {} apply(); };
window.themeMode = () => mode;
apply();
})();
</script>
<link rel="icon" href="/favicon.png" type="image/png">
<link rel="apple-touch-icon" href="/img/icon-256.png">
<link rel="alternate" hreflang="en" href="https://usemagpie.ai/docs/intent">
<link rel="alternate" hreflang="zh" href="https://usemagpie.ai/docs/zh/intent">
<link rel="stylesheet" href="/docs.css">
<meta property="og:title" content="Intent routing">
<meta property="og:description" content="Send each turn to a model by what it asks for.">
<meta property="og:image" content="https://usemagpie.ai/img/og.png">
<meta property="og:url" content="https://usemagpie.ai/docs/intent">
<meta name="twitter:card" content="summary_large_image">
</head>
<body>
<nav><div class="wrap">
<a class="brand" href="/"><img class="bird" src="/img/magpie-small.svg" alt="">magpie <small>Docs</small></a>
<div class="links">
<a href="/">Home</a>
<a href="/docs/start">Get started</a>
<a href="/docs/intent">Intent routing</a>
<a href="/docs/import">Add to magpie</a>
<a href="https://github.com/yetone/magpie-releases/releases">Releases</a>
</div>
<a class="icon-btn lang" href="/docs/zh/intent" hreflang="zh" lang="zh" title="中文版">中文</a>
<a class="icon-btn" href="https://discord.gg/vGSnD3ZKQF" aria-label="magpie on Discord" title="Join us on Discord"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z"/></svg></a>
<a class="icon-btn" href="https://github.com/yetone/magpie" aria-label="magpie on GitHub" title="Source on GitHub"><svg viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8Z"/></svg></a>
<button class="icon-btn theme" type="button" aria-label="Theme" title="Theme: system"><svg class="i-system" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1.75" y="2.75" width="12.5" height="8.5" rx="1.5"/><path d="M5.5 14h5M8 11.25V14"/></svg><svg class="i-light" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><circle cx="8" cy="8" r="3"/><path d="M8 1v1.5M8 13.5V15M1 8h1.5M13.5 8H15M3 3l1 1M12 12l1 1M3 13l1-1M12 4l1-1"/></svg><svg class="i-dark" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"><path d="M13.5 9.5A5.5 5.5 0 0 1 6.5 2.5a5.5 5.5 0 1 0 7 7Z"/></svg></button>
<a class="btn small" href="/#get">Download</a>
</div></nav>
<main class="wrap doc">
<aside class="toc">
<p>Intent routing</p>
<a href="#overview">Overview</a>
<a href="#setup">Set it up</a>
<a href="#how">How a turn is routed</a>
<a href="#prompt">What the classifier sees</a>
<a href="#failures">When it can’t say</a>
<a href="#turn">The rest of the turn</a>
<a href="#trace">Reading the trace</a>
<a href="#cost">Latency, cost, privacy</a>
<a href="#tips">Writing good intents</a>
<a href="#limits">Numbers</a>
<a href="#faq">FAQ</a>
</aside>
<article>
<p class="eyebrow" id="overview">Docs · Routing groups</p>
<h1>Intent routing</h1>
<p class="lede">Send a turn to a model by what the user asks for: tests to the strong model, quick questions to the fast cheap one. You describe each kind in a few words. When a turn begins, a small model you choose picks which kind the message is.</p>
<figure class="figure">
<img class="for-light" src="/img/intent-rules-light.png" width="2060" height="1590" alt="A routing group's editor: three rules, two with an intent (asks for “a quick question”, asks for “writing or fixing tests”), and Intent told by llama-3.1-8b-instant on Groq">
<img class="for-dark" src="/img/intent-rules-dark.png" width="2060" height="1590" alt="A routing group's editor: three rules, two with an intent (asks for “a quick question”, asks for “writing or fixing tests”), and Intent told by llama-3.1-8b-instant on Groq">
<figcaption>The Coder group: long turns go to Kimi, quick questions to DeepSeek Flash, tests to DeepSeek Pro. Llama 3.1 8B on Groq picks the intent.</figcaption>
</figure>
<p>Intent is one of the conditions a <a href="/docs/start#rules">routing group’s rule</a> can have, next to length, images, reasoning and agent. This page describes exactly what magpie does with it, as of v0.1.96, so you can predict every decision and check it in the Routing tab.</p>
<div class="callout"><b>In one paragraph.</b> magpie decides once per turn: when you send a message, not while the agent works through tool calls. If a rule with an intent could be the first to match, magpie asks the group’s <em>classifier</em> model one short question through its own gateway. The question holds the intents as a numbered list plus the text of your latest message, and the classifier answers with a number. magpie reads that number and checks the rules top first with it. The first rule that matches puts its model first, and the group’s other models stay behind it for failover. The turn then stays on that model until you speak again. If the classifier fails, times out or answers something else, no intent matches and the turn is routed as if those rules weren’t there. There are no embeddings, no keyword lists and no training: the model you pick reads your message and picks a number.</div>
<h2 id="setup"><a class="anchor" href="#setup">Set it up</a></h2>
<p>You need a <a href="/docs/start#routing">routing group</a> with at least two models, and one more model to classify with. The classifier can be any model magpie has, in the group or not.</p>
<ol class="steps">
<li><b>Open the group</b>Go to <span class="ui">Routing</span>, find the group under <em>Routing groups</em> and click <span class="ui">Edit</span>.</li>
<li><b>Add a rule with an intent</b>Click <span class="ui">Add a rule</span>, choose the model under <span class="ui">send to</span>, click <span class="ui">asks for</span> and type what the message asks for, such as <em>writing or fixing tests</em>. Other conditions on the same rule must hold too.</li>
<li><b>Pick the classifier</b><span class="ui">Intent told by</span> appears once a rule has an intent. Choose a small, fast model without reasoning. A group can’t be saved with an intent and no classifier.</li>
<li><b>Save, and send a message</b>Changes apply from the next turn, with no restart. The <span class="ui">Routing</span> tab shows what the classifier said and where the turn went.</li>
</ol>
<p>The same from a terminal:</p>
<div class="code">
<span class="lang">Terminal</span>
<button class="copy" data-copy="ex-cli">Copy</button>
<pre id="ex-cli">magpie group rule add coder use=deepseek/deepseek-v4-flash intent=<span class="s">"a quick question"</span> classifier=groq/llama-3.1-8b-instant
magpie group rule add coder use=deepseek/deepseek-v4-pro intent=<span class="s">"writing or fixing tests"</span>
magpie group rule coder <span class="c"># the rules, numbered, and the classifier</span>
magpie group rule classifier coder groq/llama-3.1-8b-instant <span class="c"># change the classifier</span>
magpie group rule mv coder 3 1 <span class="c"># order matters: top first</span>
magpie group rule rm coder 2</pre>
</div>
<p><code>intent=</code> also takes the spellings <code>asks=</code> and <code>about=</code>. <code>classifier=</code> also takes <code>classify=</code> and <code>by=</code>. <code>classifier=</code> on <code>rule add</code> sets the group’s classifier and is needed only the first time. The classifier is saved in the group’s record in <code>~/.config/magpie/providers.json</code> as <code>"classifier"</code>, and each rule as <code>{"use": …, "intent": …}</code>.</p>
<h2 id="how"><a class="anchor" href="#how">How a turn is routed</a></h2>
<p>Every request an agent sends to a group with rules goes through these steps, in this order:</p>
<ol class="steps">
<li><b>Is this a new turn?</b>magpie looks at the request’s last user message. If it carries text or an image and no tool result, you just spoke, so a new turn begins. If it carries tool results, the agent is continuing a turn already under way (see <a href="#turn">The rest of the turn</a>). Turns are counted per conversation. A subagent is its own conversation: it shares its agent’s session but starts with different words.</li>
<li><b>Which intents could decide?</b>magpie walks the rules top first and ignores intent for now: a rule is a candidate if its other conditions (tokens, images, reasoning, agents) hold for this request. Each candidate’s intent is collected, and duplicates spelled the same apart from case count once. The walk stops at the first candidate <em>without</em> an intent, because that rule matches outright and nothing below it could come first. If no intents were collected, the classifier isn’t asked and costs nothing.</li>
<li><b>Ask the classifier</b>magpie sends the classifier the collected intents and the text of your message. The exact prompt is shown <a href="#prompt">below</a>. The call goes through magpie’s own gateway, like a request from an agent, so it uses that model’s provider with its keys or accounts and their failover. It has 8 seconds to answer.</li>
<li><b>Read the answer</b>magpie takes the first whole number in the reply. <code>1</code> to <code>n</code> names that intent, and <code>0</code> means none of them. Anything else, such as no number or one out of range, counts as a failure. The answer is kept for 10 minutes for the same message and intents.</li>
<li><b>Match the rules</b>The rules are checked top first with the answer. A rule with an intent matches only when the classifier named its intent, compared without case, and all its other conditions hold. The first rule that matches wins. If none matches, the group routes the turn as it always does.</li>
<li><b>Order the models</b>The winning rule’s model goes first, and the group’s other models follow in their usual order as failover. This holds even if another model answered the conversation’s last turn: a rule outranks the <span class="ui">Stays</span> setting as a turn begins. If the rule’s model has nothing ready right now (every key resting, the provider off), the group’s usual order stands and the trace says so.</li>
<li><b>Remember the decision</b>magpie keeps the decision for this conversation (the group, the agent’s session and the conversation’s first words) with the classifier’s answer, so the rest of the turn follows it.</li>
</ol>
<h3>An example</h3>
<p>The Coder group above has three rules:</p>
<div class="table-wrap"><table>
<tr><th>#</th><th>When</th><th>Send to</th></tr>
<tr><td>1</td><td>≥ 128000 tokens</td><td>kimi/kimi-k2.5</td></tr>
<tr><td>2</td><td>asks for “a quick question”</td><td>deepseek/deepseek-v4-flash</td></tr>
<tr><td>3</td><td>asks for “writing or fixing tests”</td><td>deepseek/deepseek-v4-pro</td></tr>
</table></div>
<ul>
<li>A 20k-token turn: rule 1 doesn’t hold, so both intents are candidates. The classifier is asked to choose between “a quick question” and “writing or fixing tests”.</li>
<li>A 150k-token turn: rule 1 has no intent and holds, so the walk stops there. The classifier isn’t asked, and the turn goes to Kimi.</li>
<li>The classifier says <code>0</code> (none): no rule matches, and the turn goes by the group’s routing (here <em>In order</em>, so deepseek-v4-pro first).</li>
</ul>
<h2 id="prompt"><a class="anchor" href="#prompt">What the classifier sees</a></h2>
<p>This is the whole request, and nothing else is sent. The system message is fixed. The user message lists your intents in rule order, as they were collected in step 2:</p>
<div class="code">
<span class="lang">System</span>
<pre class="wrapped">You route a user's message to a coding assistant by what it asks for. Given numbered kinds of request and the user's message, answer with the number of the kind the message is, or 0 if it is none of them. Answer with the number only.</pre>
</div>
<div class="code">
<span class="lang">User</span>
<pre>Kinds:
1. a quick question
2. writing or fixing tests
The user's message:
&lt;message&gt;
Write unit tests for the parseTokens function and fix any that fail.
&lt;/message&gt;
The number of its kind (0 for none):</pre>
</div>
<ul>
<li><b>Only your latest message.</b> magpie sends the text parts of the last user message and nothing else: no earlier turns, no system prompt, no tool definitions, no files.</li>
<li><b>Images aren’t sent.</b> A message that is only an image has no words to classify, so no intent matches. To route by images, use the rule’s <span class="ui">has an image</span> condition.</li>
<li><b>Agent reminders are removed.</b> <code>&lt;system-reminder&gt;…&lt;/system-reminder&gt;</code> blocks, which Claude Code adds to your message, are stripped. Other text an agent puts in the same message is kept.</li>
<li><b>Long messages are shortened.</b> Over 4,000 characters, magpie keeps the first 3,000 and the last 1,000, joined by <code>…</code>. The start and the end usually say what is being asked.</li>
<li><b>The request settings.</b> An OpenAI-style chat completion, not streamed, with <code>temperature 0</code>, <code>max_tokens 512</code> and User-Agent <code>magpie-router/1</code>. magpie translates it for the classifier’s provider, like any other request, so an Anthropic, Gemini or other model works too.</li>
</ul>
<h2 id="failures"><a class="anchor" href="#failures">When it can’t say</a></h2>
<p>A classifier is a helper, not a gate. Whatever happens to it, the turn is still answered: the intent rules just don’t match.</p>
<div class="table-wrap"><table class="loose">
<tr><th>What happens</th><th>What magpie does</th></tr>
<tr><td>It answers <code>0</code></td><td>No intent matches. Rules without an intent and the group’s usual order decide.</td></tr>
<tr><td>No answer within 8 s</td><td>Treated as a failure: no intent matches, and the turn goes on at once.</td></tr>
<tr><td>An error (quota, 5xx, bad key)</td><td>Its provider’s own failover is tried first, within the 8 s. If it still fails, no intent matches.</td></tr>
<tr><td>An answer that isn’t a number from 0 to n</td><td>A failure, for example when a reasoning model spends its 512 tokens thinking and answers nothing.</td></tr>
<tr><td>Any failure</td><td>That classifier model is left alone for 30 s: turns in that time don’t wait on it, and the trace says it wasn’t asked. The first good answer clears this.</td></tr>
<tr><td>The message has no words</td><td>Not asked. No intent matches.</td></tr>
</table></div>
<p>Each case is written in the turn’s trace with the reason. An intent rule can therefore make a turn go to a different model than you hoped, but it can’t make it fail.</p>
<h2 id="turn"><a class="anchor" href="#turn">The rest of the turn</a></h2>
<p>A turn is often dozens of requests: the agent calls a tool, sends the result back, calls another, and so on. magpie deals with them like this:</p>
<ul>
<li><b>Held.</b> Every request within the turn goes to the model the turn began on, and the classifier isn’t asked again. Switching models mid-turn would throw away what the vendor cached of the conversation (prompt caching), and some vendors reject reasoning that another one wrote.</li>
<li><b>Failover sticks.</b> If the rule’s model failed and another member answered, that member keeps the rest of the turn (with <span class="ui">Stays</span> on <em>Auto</em>, <em>Session</em> or <em>Within a turn</em>). The rule decides again at your next message.</li>
<li><b>Outgrown.</b> The one exception: if the conversation grows within the turn to 95% of the current model’s context, magpie checks the rules again, keeping the classifier’s earlier answer. If a rule sends it to a model with <em>more</em> context, it moves. It never moves to a model with less.</li>
<li><b>Waits.</b> If magpie didn’t see the turn begin (it was started or restarted in the middle of one), or the rule that decided it was removed or now sends to another model, no rule moves the turn. The rules decide again at your next message.</li>
<li><b>Subagents.</b> A subagent’s turns are decided on their own, from the subagent’s own messages. A rule can send a subagent elsewhere while its agent stays where it is.</li>
</ul>
<p>Decisions are kept in memory for 24 hours after a conversation’s last request. Restarting magpie clears them, and the next message decides afresh.</p>
<h2 id="trace"><a class="anchor" href="#trace">Reading the trace</a></h2>
<p>The <span class="ui">Routing</span> tab shows each group’s latest request: who answered it, and under <span class="ui">How the last request was routed</span>, why.</p>
<figure class="figure">
<img class="for-light" src="/img/intent-trace-light.png" width="2064" height="898" loading="lazy" alt="The Routing tab after a request: Claude Code to magpie to DeepSeek deepseek-v4-pro, and the trace saying rule 3 matched and what the classifier said">
<img class="for-dark" src="/img/intent-trace-dark.png" width="2064" height="898" loading="lazy" alt="The Routing tab after a request: Claude Code to magpie to DeepSeek deepseek-v4-pro, and the trace saying rule 3 matched and what the classifier said">
<figcaption>A message asking for tests: the classifier said “writing or fixing tests” in 323 ms, so rule 3 put deepseek-v4-pro first.</figcaption>
</figure>
<p>The lines you can see about intent:</p>
<div class="table-wrap"><table class="loose">
<tr><th>The trace says</th><th>Meaning</th></tr>
<tr><td>… was asked which of “A”, “B” turn 1 is, and said “B” (in 323 ms).</td><td>The classifier was asked, with these candidates, and named B.</td></tr>
<tr><td>… and said none (…).</td><td>It answered 0.</td></tr>
<tr><td>… (said before, for the same message).</td><td>The answer came from the 10-minute cache, so no call was made.</td></tr>
<tr><td>… was to tell which … but couldn’t — <em>reason</em> — so no rule with an intent matches it.</td><td>It failed. The reason is the error, the timeout, what it answered, or that it is resting after a failure.</td></tr>
<tr><td>Turn N begins and rule R matches — asks for “B” — so M goes first …</td><td>The rule that won, and the model put first.</td></tr>
<tr><td>Rule R matches, but M has nothing ready now, so the group’s order stands.</td><td>The rule’s model has no key or account available right now.</td></tr>
<tr><td>Rule R (…) sent turn N to M as it began; the turn stays there.</td><td>A tool round within the turn, held on its decision.</td></tr>
<tr><td>This turn began before magpie saw it, so the rules wait for the next one.</td><td>See <em>Waits</em> above.</td></tr>
</table></div>
<p>The classifier’s own calls appear in <span class="ui">Usage</span> under the agent <em>magpie</em>, with their tokens and cost, so you can see what classifying costs.</p>
<h2 id="cost"><a class="anchor" href="#cost">Latency, cost, privacy</a></h2>
<ul>
<li><b>Latency.</b> Only the first request of a turn waits for the classifier, typically a few hundred milliseconds on a fast provider (323 ms in the screenshot) and never more than 8 seconds. Tool rounds within the turn, turns where no intent could decide, and cached answers add nothing.</li>
<li><b>Cost.</b> One short call per turn: roughly the size of your message plus the intents in, and one or two tokens out. In the example above that was 212 tokens in and 1 out. With a subscription model as the classifier, these calls count against that subscription’s limits.</li>
<li><b>Privacy.</b> The text of your message (up to about 4,000 characters) goes to the classifier’s provider. To keep it on your machine, use a local model from Ollama or LM Studio as the classifier. magpie keeps only a hash of the message and the answer, in memory, for 10 minutes. The message itself isn’t stored or logged.</li>
</ul>
<h2 id="tips"><a class="anchor" href="#tips">Writing good intents</a></h2>
<ul>
<li><b>Describe the request, not the model.</b> Write “writing or fixing tests”, not “hard tasks for Opus”. The classifier sees only your words and the message.</li>
<li><b>Keep kinds apart.</b> If two intents overlap (“a question”, “a question about the code”), a small model will choose between them unpredictably. Merge them, or make the difference plain.</li>
<li><b>Few and broad beats many and narrow.</b> Two to five intents are easy for an 8B model. Up to 200 characters each is allowed, but a few words are usually enough.</li>
<li><b>Order still matters.</b> Only the first matching rule counts, so put a rule that must win (such as a long-context rule) above the intent rules. The classifier is then skipped whenever that rule holds.</li>
<li><b>“None” is a real answer.</b> A turn that fits no intent goes to the group’s usual order, so let that order be your default choice.</li>
<li><b>Any language.</b> Intents and messages can be in any language the classifier understands. The fixed prompt is in English, and small models read mixed languages well.</li>
<li><b>Choosing a classifier.</b> Pick a small, fast model without reasoning: Llama 3.1 8B on Groq, a Flash-Lite, nano or mini model, Claude Haiku, DeepSeek’s non-reasoning chat model, or a local model. A reasoning model is slower and may use its 512 tokens up before it answers. Check the trace for a few turns: if it guesses wrong, rewrite the intents before switching models.</li>
</ul>
<h2 id="limits"><a class="anchor" href="#limits">Numbers</a></h2>
<div class="table-wrap"><table class="loose">
<tr><th>What</th><th>Value</th></tr>
<tr><td>Classifier timeout</td><td>8 s, including its provider’s failover</td></tr>
<tr><td>Rest after a failure</td><td>30 s, per classifier model</td></tr>
<tr><td>Answer cache</td><td>10 min, per classifier + intents + message; in memory</td></tr>
<tr><td>Turn decisions kept</td><td>24 h after the conversation’s last request; in memory</td></tr>
<tr><td>Message sent</td><td>Up to 4,000 characters; longer is first 3,000 + last 1,000</td></tr>
<tr><td>Classifier request</td><td>temperature 0, max_tokens 512, not streamed</td></tr>
<tr><td>Intent length</td><td>Up to 200 characters; spaces collapsed, compared without case</td></tr>
<tr><td>Outgrown</td><td>At 95% of the current model’s context, only to a model with more</td></tr>
</table></div>
<h2 id="faq"><a class="anchor" href="#faq">FAQ</a></h2>
<h3>Is this embeddings or a vector search?</h3>
<p>No. There is no embedding model, no similarity threshold, no keyword list and nothing to train. The classifier is an ordinary chat model that reads your intents and your message and answers with a number. It is only as good as that model and your wording, and the trace shows every answer.</p>
<h3>Does it look at the whole conversation?</h3>
<p>No, only the message you just sent. So a follow-up like “now do the same for the other file” is judged on those words alone. Give that turn a clearer message, or add an intent that fits follow-ups.</p>
<h3>My agent made 40 requests for one message. Was the classifier asked 40 times?</h3>
<p>No, once. The other 39 carried tool results, so they belong to the same turn and follow its decision. You can see this in the trace (“the turn stays there”) and in Usage (one <em>magpie</em> call).</p>
<h3>Can a turn switch models halfway?</h3>
<p>Only in two cases: the model it is on fails and another member takes over, or the conversation outgrows the model’s context and a rule sends it to one with more room. Intent is never re-asked mid-turn.</p>
<h3>What if the classifier is down or slow?</h3>
<p>The turn still goes through. After up to 8 s, no intent matches and the group’s other rules and order decide. For the next 30 s magpie doesn’t ask that classifier again, so turns don’t each wait out the timeout. The trace gives the reason.</p>
<h3>Why did the turn not go to the rule’s model when the classifier named its intent?</h3>
<p>Check the trace. The usual causes are:</p>
<ul>
<li>a rule above it matched first;</li>
<li>another condition on the rule didn’t hold (tokens, images, reasoning or agent);</li>
<li>the rule’s model had nothing ready (“has nothing ready now”);</li>
<li>it was a tool round in a turn that began elsewhere;</li>
<li>magpie didn’t see the turn begin (“the rules wait”).</li>
</ul>
<h3>Two rules have the same intent. What happens?</h3>
<p>The intent is offered to the classifier once. If it is chosen, the first of those rules whose other conditions hold wins. Use this to send, for example, “writing or fixing tests” from Codex to one model and from Claude Code to another.</p>
<h3>Does changing a rule affect a turn already running?</h3>
<p>No. Saving applies from the next turn. A turn whose rule you removed, or pointed at another model, keeps where it is until then; the trace says the rules wait.</p>
<h3>Can the classifier be a routing group, or one of the group’s own models?</h3>
<p>It can’t be a group: it must be one model, <code>provider/model</code>, which magpie checks when you save. It can be any model magpie has, including one of the group’s members, a subscription model or a local model.</p>
<h3>Can I use a reasoning model?</h3>
<p>It works if it answers with a number within 8 s and 512 tokens. A model that thinks at length may run out and answer nothing, which counts as a failure. A small model without reasoning is faster, cheaper and just as good at this.</p>
<h3>Does retrying the same message ask again?</h3>
<p>Not within 10 minutes: the same message with the same intents and the same classifier is answered from the cache, and the trace says “said before”. Changing an intent or the classifier makes it ask again.</p>
<h3>Which agents does it work with?</h3>
<p>Any agent that sends to a routing group through magpie: Claude Code, Codex, OpenCode, Gemini CLI and the rest. Turns are detected from the request itself (a message with text and no tool results), whatever API the agent speaks.</p>
<h3>Is my message stored anywhere?</h3>
<p>magpie keeps a hash of it (for the cache) and the classifier’s answer, in memory only. The text goes to the classifier’s provider, like any request, under that provider’s terms. The classifier’s usage record holds its token counts, not the text.</p>
<h3>How accurate is it?</h3>
<p>That depends on the classifier and on how distinct your intents are. With a few clearly different kinds, a small model picks well; with overlapping ones it will sometimes guess. Since a wrong or missing answer only changes which model answers, start with two or three broad intents, watch the trace for a day and adjust the wording.</p>
<p style="margin-top:48px">Questions or a setup worth sharing? Ask on <a href="https://discord.gg/vGSnD3ZKQF">Discord</a>. Routing groups in general are covered in <a href="/docs/start#routing">Get started</a>.</p>
</article>
</main>
<footer><div class="wrap">
<span>© 2026 magpie · MIT</span>
<div class="links">
<a href="/">Home</a>
<a href="/docs/intent">Intent routing</a>
<a href="/docs/import">Add to magpie</a>
<a href="https://github.com/yetone/magpie">GitHub</a>
<a href="https://discord.gg/vGSnD3ZKQF">Discord</a>
<a href="https://github.com/yetone/magpie-releases/releases">Releases</a>
</div>
</div></footer>
<script>
(() => {
const next = { system: "light", light: "dark", dark: "system" };
document.querySelectorAll("button.theme").forEach((b) => {
b.dataset.mode = themeMode();
b.title = "Theme: " + themeMode();
b.addEventListener("click", () => setTheme(next[themeMode()]));
});
document.querySelectorAll(".copy").forEach((b) => b.addEventListener("click", async () => {
try {
await navigator.clipboard.writeText(document.getElementById(b.dataset.copy).textContent);
b.textContent = "Copied"; setTimeout(() => (b.textContent = "Copy"), 1400);
} catch {}
}));
// The table of contents follows the reading position.
const links = [...document.querySelectorAll(".toc a")];
const heads = links.map((a) => document.getElementById(a.hash.slice(1)));
const spy = () => {
let i = 0;
heads.forEach((h, j) => { if (h.getBoundingClientRect().top < 140) i = j; });
if (innerHeight + scrollY >= document.body.scrollHeight - 4) i = heads.length - 1;
links.forEach((a, j) => a.classList.toggle("on", j === i));
};
addEventListener("scroll", spy, { passive: true }); spy();
})();
</script>
</body>
</html>
+3 -1
View File
@@ -42,6 +42,7 @@
<div class="links">
<a href="/">Home</a>
<a href="/docs/start">Get started</a>
<a href="/docs/intent">Intent routing</a>
<a href="/docs/import">Add to magpie</a>
<a href="https://github.com/yetone/magpie-releases/releases">Releases</a>
</div>
@@ -172,7 +173,7 @@
<li>A rule decides as a turn begins, and the turn stays on that model while the agent sends tool results back, so what the vendor cached isn’t lost halfway. The one exception: a turn that grows past its model’s context moves to a member that takes it.</li>
<li>Tokens are estimated from the request’s size, or what the vendor counted for the conversation’s last request, whichever is more. Leave some margin: a rule at 150k for a 200k model, not 199k.</li>
<li>A subagent is a conversation of its own, so a rule can send it elsewhere while its agent keeps where it is.</li>
<li>An intent is a few words for what the user asks for — “writing or fixing tests”, “a quick question”. As a turn begins, the group’s classifier — any model magpie has; a small, fast one without reasoning is best — is asked once which of the intents that could match the user’s message is. Only the message is sent, not the conversation. If it fails, is slow (8 s) or can’t say, no intent matches and the turn goes by the other rules. It isn’t asked when a rule without an intent would decide anyway, and the same message isn’t asked about twice. Its calls show in Usage as magpie’s own.</li>
<li>An intent is a few words for what the user asks for — “writing or fixing tests”, “a quick question”. As a turn begins, the group’s classifier — any model magpie has; a small, fast one without reasoning is best — is asked once which of the intents that could match the user’s message is. Only the message is sent, not the conversation. If it fails, is slow (8 s) or can’t say, no intent matches and the turn goes by the other rules. It isn’t asked when a rule without an intent would decide anyway, and the same message isn’t asked about twice. Its calls show in Usage as magpie’s own. Exactly what it is asked, how its answer is used and what happens when it fails: <a href="/docs/intent">Intent routing</a>.</li>
<li>The Routing tab says which rule sent each turn where, and what the classifier said.</li>
</ul>
<p>The <span class="ui">Routing</span> tab shows the gateway’s decisions live: who answered each request, and why. The same groups from a terminal:</p>
@@ -237,6 +238,7 @@ magpie claude group/opus-anywhere <span class="c"># use it</span></pre>
<span>© 2026 magpie · MIT</span>
<div class="links">
<a href="/">Home</a>
<a href="/docs/intent">Intent routing</a>
<a href="/docs/import">Add to magpie</a>
<a href="https://github.com/yetone/magpie">GitHub</a>
<a href="https://discord.gg/vGSnD3ZKQF">Discord</a>
+2
View File
@@ -42,6 +42,7 @@
<div class="links">
<a href="/">首页</a>
<a href="/docs/zh/start">快速上手</a>
<a href="/docs/zh/intent">意图路由</a>
<a href="/docs/zh/import">Add to magpie</a>
<a href="https://github.com/yetone/magpie-releases/releases">版本发布</a>
</div>
@@ -241,6 +242,7 @@ href = <span class="s">"https://usemagpie.ai/import#"</span> + urlencode({
<div class="links">
<a href="/">首页</a>
<a href="/docs/zh/start">快速上手</a>
<a href="/docs/zh/intent">意图路由</a>
<a href="https://github.com/yetone/magpie">GitHub</a>
<a href="https://discord.gg/vGSnD3ZKQF">Discord</a>
<a href="https://github.com/yetone/magpie-releases/releases">版本发布</a>
+333
View File
@@ -0,0 +1,333 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>意图路由 · magpie 文档</title>
<meta name="description" content="magpie 如何按用户想做什么来路由每一轮:何时询问意图判断模型、它具体看到什么、回答怎么用、失败时怎么办,以及详细答疑。">
<meta name="color-scheme" content="light dark">
<meta name="theme-color" content="#fafafa">
<script>
(() => {
let mode = "system";
try { mode = localStorage.getItem("theme") || "system"; } catch {}
const q = matchMedia("(prefers-color-scheme: dark)");
const apply = () => {
const t = mode === "system" ? (q.matches ? "dark" : "light") : mode;
document.documentElement.dataset.theme = t;
document.querySelector('meta[name="theme-color"]').content = t === "dark" ? "#0b0b0c" : "#fafafa";
document.querySelectorAll("button.theme").forEach((b) => { b.dataset.mode = mode; b.title = "Theme: " + mode; });
};
q.addEventListener("change", () => mode === "system" && apply());
window.setTheme = (m) => { mode = m; try { m === "system" ? localStorage.removeItem("theme") : localStorage.setItem("theme", m); } catch {} apply(); };
window.themeMode = () => mode;
apply();
})();
</script>
<link rel="icon" href="/favicon.png" type="image/png">
<link rel="apple-touch-icon" href="/img/icon-256.png">
<link rel="alternate" hreflang="en" href="https://usemagpie.ai/docs/intent">
<link rel="alternate" hreflang="zh" href="https://usemagpie.ai/docs/zh/intent">
<link rel="stylesheet" href="/docs.css">
<meta property="og:title" content="意图路由">
<meta property="og:description" content="按用户想做什么,把每一轮交给合适的模型。">
<meta property="og:image" content="https://usemagpie.ai/img/og.png">
<meta property="og:url" content="https://usemagpie.ai/docs/zh/intent">
<meta name="twitter:card" content="summary_large_image">
</head>
<body>
<nav><div class="wrap">
<a class="brand" href="/"><img class="bird" src="/img/magpie-small.svg" alt="">magpie <small>文档</small></a>
<div class="links">
<a href="/">首页</a>
<a href="/docs/zh/start">快速上手</a>
<a href="/docs/zh/intent">意图路由</a>
<a href="/docs/zh/import">Add to magpie</a>
<a href="https://github.com/yetone/magpie-releases/releases">版本发布</a>
</div>
<a class="icon-btn lang" href="/docs/intent" hreflang="en" lang="en" title="English version">EN</a>
<a class="icon-btn" href="https://discord.gg/vGSnD3ZKQF" aria-label="magpie on Discord" title="Join us on Discord"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z"/></svg></a>
<a class="icon-btn" href="https://github.com/yetone/magpie" aria-label="magpie on GitHub" title="Source on GitHub"><svg viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8Z"/></svg></a>
<button class="icon-btn theme" type="button" aria-label="Theme" title="Theme: system"><svg class="i-system" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1.75" y="2.75" width="12.5" height="8.5" rx="1.5"/><path d="M5.5 14h5M8 11.25V14"/></svg><svg class="i-light" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><circle cx="8" cy="8" r="3"/><path d="M8 1v1.5M8 13.5V15M1 8h1.5M13.5 8H15M3 3l1 1M12 12l1 1M3 13l1-1M12 4l1-1"/></svg><svg class="i-dark" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"><path d="M13.5 9.5A5.5 5.5 0 0 1 6.5 2.5a5.5 5.5 0 1 0 7 7Z"/></svg></button>
<a class="btn small" href="/#get">下载</a>
</div></nav>
<main class="wrap doc">
<aside class="toc">
<p>意图路由</p>
<a href="#overview">概览</a>
<a href="#setup">设置</a>
<a href="#how">一轮是怎么路由的</a>
<a href="#prompt">意图判断模型看到什么</a>
<a href="#failures">判断不出来时</a>
<a href="#turn">这一轮的后续请求</a>
<a href="#trace">看懂路由记录</a>
<a href="#cost">延迟、费用、隐私</a>
<a href="#tips">怎么写意图</a>
<a href="#limits">数值一览</a>
<a href="#faq">答疑</a>
</aside>
<article>
<p class="eyebrow" id="overview">文档 · 路由组</p>
<h1>意图路由</h1>
<p class="lede">按用户想做什么来选模型:写测试交给强模型,随口一问交给又快又便宜的模型。你用几个词描述每一类请求,每一轮开始时,由你指定的一个小模型判断这条消息属于哪一类。</p>
<figure class="figure">
<img class="for-light" src="/img/intent-rules-zh-light.png" width="2060" height="1590" alt="路由组编辑页:三条规则,其中两条带意图(意图是“a quick question”、意图是“writing or fixing tests”),意图判断模型为 Groq 上的 llama-3.1-8b-instant">
<img class="for-dark" src="/img/intent-rules-zh-dark.png" width="2060" height="1590" alt="路由组编辑页:三条规则,其中两条带意图(意图是“a quick question”、意图是“writing or fixing tests”),意图判断模型为 Groq 上的 llama-3.1-8b-instant">
<figcaption>Coder 分组:长对话交给 Kimi,简单提问交给 DeepSeek Flash,写测试交给 DeepSeek Pro;由 Groq 上的 Llama 3.1 8B 判断意图。</figcaption>
</figure>
<p>意图是<a href="/docs/zh/start#rules">路由组规则</a>的条件之一,与长度、图片、推理、Agent 并列。本页按 v0.1.96 的实际实现,逐条说明 magpie 拿它做了什么,让你能预判每一次路由决定,并在「路由」页里核对。</p>
<div class="callout"><b>一段话说清楚。</b>magpie 每一轮只做一次决定:在你发消息的时候做,Agent 调用工具的过程中不再做。如果某条带意图的规则有可能第一个命中,magpie 就通过自己的网关向分组的<em>意图判断模型</em>发一个很短的问题:给它带编号的意图列表和你最新一条消息的文字,让它回答一个数字。magpie 用这个数字从上往下检查规则,第一条命中的规则把它的模型排到第一,组里其他模型排在后面做故障转移。之后这一轮一直留在那个模型上,直到你再次发言。意图判断模型出错、超时或答非所问时,所有意图都不命中,这一轮按没有这些规则的方式路由。整个过程没有向量嵌入、没有关键词表,也不需要训练,就是让你选的那个模型读一遍消息,选一个编号。</div>
<h2 id="setup"><a class="anchor" href="#setup">设置</a></h2>
<p>你需要一个至少有两个模型的<a href="/docs/zh/start#routing">路由组</a>,再加一个用来判断意图的模型。这个模型可以是 magpie 里的任意模型,在不在组里都行。</p>
<ol class="steps">
<li><b>打开分组</b>进入<span class="ui">路由</span>页,在「路由组」里找到分组,点<span class="ui">编辑</span>。</li>
<li><b>添加带意图的规则</b>点<span class="ui">添加规则</span>,在<span class="ui">先发给</span>里选模型,点<span class="ui">意图是</span>,写下这类消息想做什么,例如 <em>writing or fixing tests</em> 或「写测试或修测试」。同一条规则上的其他条件也必须同时满足。</li>
<li><b>选择意图判断模型</b>有规则带意图后,下方会出现<span class="ui">意图判断模型</span>。建议选小而快、不带推理的模型。有意图却没有判断模型时,分组无法保存。</li>
<li><b>保存,然后发条消息</b>从下一轮起生效,不用重启。<span class="ui">路由</span>页会显示判断模型说了什么、这一轮去了哪里。</li>
</ol>
<p>命令行里也可以设置:</p>
<div class="code">
<span class="lang">终端</span>
<button class="copy" data-copy="ex-cli">复制</button>
<pre id="ex-cli">magpie group rule add coder use=deepseek/deepseek-v4-flash intent=<span class="s">"a quick question"</span> classifier=groq/llama-3.1-8b-instant
magpie group rule add coder use=deepseek/deepseek-v4-pro intent=<span class="s">"writing or fixing tests"</span>
magpie group rule coder <span class="c"># 带编号的规则列表和意图判断模型</span>
magpie group rule classifier coder groq/llama-3.1-8b-instant <span class="c"># 更换意图判断模型</span>
magpie group rule mv coder 3 1 <span class="c"># 顺序很重要:从上往下</span>
magpie group rule rm coder 2</pre>
</div>
<p><code>intent=</code> 也可以写成 <code>asks=</code> 或 <code>about=</code>;<code>classifier=</code> 也可以写成 <code>classify=</code> 或 <code>by=</code>。在 <code>rule add</code> 上加 <code>classifier=</code> 会设置整个分组的意图判断模型,只有第一次需要。它保存在 <code>~/.config/magpie/providers.json</code> 该分组的 <code>"classifier"</code> 字段里,每条规则保存为 <code>{"use": …, "intent": …}</code>。</p>
<h2 id="how"><a class="anchor" href="#how">一轮是怎么路由的</a></h2>
<p>Agent 发往带规则的分组的每个请求,都按下面的顺序处理:</p>
<ol class="steps">
<li><b>是不是新的一轮?</b>magpie 看请求里最后一条用户消息:带文字或图片、不带工具结果,说明是你刚刚发言,新的一轮开始;带工具结果,说明是 Agent 在继续进行中的一轮(见<a href="#turn">这一轮的后续请求</a>)。轮次按会话分别计数。子 Agent 是单独的会话:它和主 Agent 共用一个 session,但第一句话不同。</li>
<li><b>哪些意图可能起作用?</b>magpie 从上往下看规则,先不管意图:一条规则的其他条件(tokens、图片、推理、Agent)对这个请求都成立,它就是候选,把它的意图收集起来。只有大小写不同的意图算同一个。遇到第一条<em>不带</em>意图的候选规则就停止,因为它会直接命中,排在它下面的规则都轮不到。一个意图也没收集到时,不会询问意图判断模型,也不会产生费用。</li>
<li><b>询问意图判断模型</b>把收集到的意图和你这条消息的文字发给它,完整提示词见<a href="#prompt">下文</a>。这个调用和 Agent 的请求一样,走 magpie 自己的网关,所以用的是该模型所属供应商的 Key 或账号,以及它们的故障转移。它有 8 秒时间作答。</li>
<li><b>读取回答</b>取回答里出现的第一个整数:<code>1</code> 到 <code>n</code> 表示对应的意图,<code>0</code> 表示都不是。其他情况(没有数字、数字超出范围)都算失败。同一条消息、同一组意图的回答会缓存 10 分钟。</li>
<li><b>匹配规则</b>带着这个回答从上往下检查规则。带意图的规则,只有判断模型说的正是它的意图(不区分大小写),并且其他条件也都满足时才命中。第一条命中的规则生效;一条都不命中时,分组按平常的方式路由这一轮。</li>
<li><b>给模型排序</b>命中规则指定的模型排第一,组里其他模型按平常的顺序排在后面做故障转移。即使上一轮是别的模型回答的,也是这样:新一轮开始时,规则优先于<span class="ui">会话保持</span>。如果规则指定的模型此刻没有可用的 Key 或账号(都在冷却、供应商已关闭),就按分组原本的顺序,路由记录里会写明原因。</li>
<li><b>记住这次决定</b>magpie 把这次决定和判断模型的回答记在这个会话名下(由分组、Agent 的 session 和会话的第一句话确定),这一轮后面的请求都照它走。</li>
</ol>
<h3>举个例子</h3>
<p>上图的 Coder 分组有三条规则:</p>
<div class="table-wrap"><table>
<tr><th>#</th><th>条件</th><th>先发给</th></tr>
<tr><td>1</td><td>≥ 128000 tokens</td><td>kimi/kimi-k2.5</td></tr>
<tr><td>2</td><td>意图是“a quick question”</td><td>deepseek/deepseek-v4-flash</td></tr>
<tr><td>3</td><td>意图是“writing or fixing tests”</td><td>deepseek/deepseek-v4-pro</td></tr>
</table></div>
<ul>
<li>2 万 tokens 的一轮:规则 1 不成立,两个意图都是候选,判断模型在“a quick question”和“writing or fixing tests”之间选。</li>
<li>15 万 tokens 的一轮:规则 1 不带意图且成立,收集到这里就停止。不询问判断模型,这一轮直接发给 Kimi。</li>
<li>判断模型回答 <code>0</code>(都不是):没有规则命中,按分组的路由方式走(这里是「按顺序」,deepseek-v4-pro 排第一)。</li>
</ul>
<h2 id="prompt"><a class="anchor" href="#prompt">意图判断模型看到什么</a></h2>
<p>下面就是完整的请求,此外不发送任何内容。系统消息是固定的;用户消息按第 2 步收集的顺序(也就是规则顺序)列出你的意图:</p>
<div class="code">
<span class="lang">System</span>
<pre class="wrapped">You route a user's message to a coding assistant by what it asks for. Given numbered kinds of request and the user's message, answer with the number of the kind the message is, or 0 if it is none of them. Answer with the number only.</pre>
</div>
<div class="code">
<span class="lang">User</span>
<pre>Kinds:
1. a quick question
2. writing or fixing tests
The user's message:
&lt;message&gt;
Write unit tests for the parseTokens function and fix any that fail.
&lt;/message&gt;
The number of its kind (0 for none):</pre>
</div>
<ul>
<li><b>只发你最新的一条消息。</b>magpie 只发送最后一条用户消息里的文字部分:不含之前的轮次、系统提示词、工具定义或文件。</li>
<li><b>不发送图片。</b>只有图片的消息没有文字可判断,所有意图都不命中。想按图片路由,请用规则的<span class="ui">带图片</span>条件。</li>
<li><b>去掉 Agent 插入的提醒。</b>Claude Code 附加在消息里的 <code>&lt;system-reminder&gt;…&lt;/system-reminder&gt;</code> 会被去掉;Agent 放在同一条消息里的其他文字会保留。</li>
<li><b>长消息会截短。</b>超过 4000 个字符时,保留前 3000 个和后 1000 个,中间用 <code>…</code> 连接。请求想做什么,一般在开头和结尾都能看出来。</li>
<li><b>请求参数。</b>OpenAI 格式的 chat completion,不流式,<code>temperature 0</code>,<code>max_tokens 512</code>,User-Agent 为 <code>magpie-router/1</code>。magpie 会像处理其他请求一样把它转换成判断模型所属供应商的协议,所以 Anthropic、Gemini 等模型也能用。</li>
</ul>
<h2 id="failures"><a class="anchor" href="#failures">判断不出来时</a></h2>
<p>意图判断模型只是辅助,不是关卡。它出什么问题,这一轮都照常有回答,只是带意图的规则不命中。</p>
<div class="table-wrap"><table class="loose">
<tr><th>情况</th><th>magpie 的处理</th></tr>
<tr><td>回答 <code>0</code></td><td>所有意图都不命中,由不带意图的规则和分组平常的顺序决定</td></tr>
<tr><td>8 秒内没有回答</td><td>算失败:意图都不命中,这一轮立即继续</td></tr>
<tr><td>报错(额度用完、5xx、Key 无效)</td><td>先在 8 秒内尝试它所属供应商自己的故障转移,仍然失败则意图都不命中</td></tr>
<tr><td>回答不是 0 到 n 之间的数字</td><td>算失败。例如推理模型把 512 个 token 都花在思考上,最后没有给出答案</td></tr>
<tr><td>任何失败</td><td>这个判断模型会冷却 30 秒:这段时间里的轮次不再等它,路由记录里会写 “not asked again for now”。之后第一次答对就解除</td></tr>
<tr><td>消息里没有文字</td><td>不询问,意图都不命中</td></tr>
</table></div>
<p>每种情况都会连同原因写进这一轮的路由记录。所以带意图的规则可能让这一轮去了你没想到的模型,但不会让它失败。</p>
<h2 id="turn"><a class="anchor" href="#turn">这一轮的后续请求</a></h2>
<p>一轮往往包含几十个请求:Agent 调用工具,把结果发回来,再调用下一个工具……magpie 对它们的处理如下:</p>
<ul>
<li><b>保持。</b>这一轮里的请求都发给这一轮开始时选定的模型,不再询问意图判断模型。中途换模型会丢掉厂商为这段会话做的缓存(prompt caching),有些厂商还会拒绝别家模型写的推理内容。</li>
<li><b>故障转移后留在接手的模型上。</b>规则指定的模型失败、由组里另一个成员接手后,这一轮剩下的请求都交给接手的成员(<span class="ui">会话保持</span>为「自动」「整个会话」或「一轮之内」时)。等你下次发言,规则再重新决定。</li>
<li><b>超出上下文。</b>唯一的例外:这一轮进行中,会话涨到当前模型上下文的 95% 时,magpie 会沿用之前的判断结果重新检查一遍规则。如果有规则把它发往上下文<em>更大</em>的模型,就换过去;不会换到上下文更小的模型。</li>
<li><b>等待下一轮。</b>如果 magpie 没看到这一轮开始(magpie 是在某一轮进行中启动或重启的),或者当初决定这一轮的规则被删除、或改成了发往别的模型,那么不会有规则移动这一轮,等你下次发言时再重新决定。</li>
<li><b>子 Agent。</b>子 Agent 的轮次单独决定,依据它自己的消息。规则可以把子 Agent 发往别处,而主 Agent 留在原来的模型上。</li>
</ul>
<p>这些决定保存在内存里,保留到会话最后一个请求之后 24 小时。重启 magpie 会清空它们,下一条消息会重新决定。</p>
<h2 id="trace"><a class="anchor" href="#trace">看懂路由记录</a></h2>
<p><span class="ui">路由</span>页会显示每个分组最近的一个请求由谁回答;<span class="ui">最近一个请求是怎么路由的</span>下面写着原因。</p>
<figure class="figure">
<img class="for-light" src="/img/intent-trace-zh-light.png" width="2064" height="898" loading="lazy" alt="一次请求后的路由页:Claude Code 经 magpie 到 DeepSeek deepseek-v4-pro,记录里写着命中了规则 3 以及判断模型的回答">
<img class="for-dark" src="/img/intent-trace-zh-dark.png" width="2064" height="898" loading="lazy" alt="一次请求后的路由页:Claude Code 经 magpie 到 DeepSeek deepseek-v4-pro,记录里写着命中了规则 3 以及判断模型的回答">
<figcaption>一条要求写测试的消息:判断模型 323 毫秒后说是“writing or fixing tests”,于是规则 3 把 deepseek-v4-pro 排到第一。</figcaption>
</figure>
<p>与意图相关的几种记录:</p>
<div class="table-wrap"><table class="loose">
<tr><th>记录里写的</th><th>含义</th></tr>
<tr><td>问 … 第 1 轮属于 “A”, “B” 中的哪一个,它说是“B”(用时 323 毫秒)。</td><td>询问了判断模型,候选是这几个,它选了 B</td></tr>
<tr><td>… 它说都不是(…)。</td><td>它回答了 0</td></tr>
<tr><td>…(同一条消息之前已判断过)。</td><td>回答来自 10 分钟缓存,这次没有发出调用</td></tr>
<tr><td>… 本应判断 … 但没能给出答案(<em>原因</em>),所以带意图的规则都不命中。</td><td>判断失败;原因是报错、超时、它的原始回答,或者它还在失败后的冷却期</td></tr>
<tr><td>第 N 轮开始,命中规则 R(意图是“B”),所以 M 排第一 …</td><td>生效的规则,以及被排到第一的模型</td></tr>
<tr><td>命中规则 R,但 M 现在没有可用的账号或 Key,所以按分组原本的顺序。</td><td>规则指定的模型此刻没有可用的 Key 或账号</td></tr>
<tr><td>第 N 轮开始时命中规则 R(…),发给了 M;这一轮一直留在那里。</td><td>这一轮里的工具往返请求,沿用开始时的决定</td></tr>
<tr><td>这一轮在 magpie 看到之前就已开始,规则等下一轮再判断。</td><td>见上文「等待下一轮」</td></tr>
</table></div>
<p>意图判断模型自己的调用,在<span class="ui">用量</span>页里记在 Agent <em>magpie</em> 名下,带 token 数和费用,判断意图花了多少一看便知。</p>
<h2 id="cost"><a class="anchor" href="#cost">延迟、费用、隐私</a></h2>
<ul>
<li><b>延迟。</b>只有每一轮的第一个请求需要等判断模型,在快的供应商上通常是几百毫秒(截图里是 323 毫秒),最长 8 秒。这一轮后面的工具往返、没有意图需要判断的轮次、命中缓存的回答,都不增加延迟。</li>
<li><b>费用。</b>每轮一次很短的调用:输入大约是你的消息加上意图列表,输出一两个 token。上面的例子是输入 212、输出 1。用订阅里的模型做判断模型时,这些调用会占用该订阅的额度。</li>
<li><b>隐私。</b>你这条消息的文字(最多约 4000 个字符)会发给判断模型所属的供应商。想让它不离开本机,可以用 Ollama 或 LM Studio 里的本地模型来判断。magpie 只在内存里保存消息的哈希值和判断结果,保留 10 分钟;消息原文不存储,也不写日志。</li>
</ul>
<h2 id="tips"><a class="anchor" href="#tips">怎么写意图</a></h2>
<ul>
<li><b>描述请求本身,而不是模型。</b>写「写测试或修测试」,不要写「交给 Opus 的难题」。判断模型只看得到你写的这几个词和那条消息。</li>
<li><b>类别之间要分得开。</b>两个意图有重叠(「提问」和「关于代码的提问」)时,小模型会在两者之间随机摇摆。要么合并,要么把区别写清楚。</li>
<li><b>少而宽,胜过多而窄。</b>2 到 5 个意图对 8B 级别的模型很轻松。每个意图最多 200 个字符,但通常几个词就够了。</li>
<li><b>顺序依然重要。</b>只有第一条命中的规则生效,所以必须优先的规则(例如长上下文规则)要放在带意图的规则上面。它成立时,连判断模型都不用问。</li>
<li><b>“都不是”也是有效回答。</b>不属于任何意图的轮次按分组平常的顺序走,所以把你的默认模型放在这个顺序的第一位。</li>
<li><b>中文英文都行。</b>意图和消息可以用判断模型能理解的任何语言书写。固定的提示词是英文的,小模型处理中英混合没有问题。</li>
<li><b>怎么选判断模型。</b>选小而快、不带推理的模型:Groq 上的 Llama 3.1 8B、各家的 Flash-Lite / nano / mini 系列、Claude Haiku、DeepSeek 的非推理对话模型,或者本地模型。推理模型更慢,还可能在给出答案之前就把 512 个 token 用完。先观察几轮路由记录,判断不准时,先改意图的措辞,再考虑换模型。</li>
</ul>
<h2 id="limits"><a class="anchor" href="#limits">数值一览</a></h2>
<div class="table-wrap"><table class="loose">
<tr><th>项目</th><th>数值</th></tr>
<tr><td>判断超时</td><td>8 秒,包含其供应商的故障转移</td></tr>
<tr><td>失败后冷却</td><td>30 秒,按判断模型计</td></tr>
<tr><td>回答缓存</td><td>10 分钟,按判断模型 + 意图 + 消息;仅内存</td></tr>
<tr><td>轮次决定保留</td><td>会话最后一个请求之后 24 小时;仅内存</td></tr>
<tr><td>发送的消息长度</td><td>最多 4000 字符;更长时取前 3000 + 后 1000</td></tr>
<tr><td>判断请求参数</td><td>temperature 0,max_tokens 512,不流式</td></tr>
<tr><td>意图长度</td><td>最多 200 字符;多余空白会合并,比较时不区分大小写</td></tr>
<tr><td>超出上下文</td><td>达到当前模型上下文的 95% 时,只换到上下文更大的模型</td></tr>
</table></div>
<h2 id="faq"><a class="anchor" href="#faq">答疑</a></h2>
<h3>这是向量嵌入 / 语义检索吗?</h3>
<p>不是。没有嵌入模型,没有相似度阈值,没有关键词表,也不需要训练。判断模型就是一个普通的对话模型:读你的意图和消息,回答一个编号。效果取决于这个模型和你的措辞,每一次的回答都能在路由记录里看到。</p>
<h3>它会看整段对话吗?</h3>
<p>不会,只看你刚发的那一条。所以像「另一个文件也照这样改」这样的追问,只按这几个字判断。要么把这一轮的消息写清楚一些,要么加一个适合追问的意图。</p>
<h3>我发一条消息,Agent 发了 40 个请求,判断模型被问了 40 次吗?</h3>
<p>不是,只问一次。另外 39 个请求带的是工具结果,属于同一轮,沿用这一轮的决定。路由记录(「这一轮一直留在那里」)和用量页(只有一次 <em>magpie</em> 调用)都能看到这一点。</p>
<h3>一轮进行到一半会换模型吗?</h3>
<p>只有两种情况:当前模型失败、由组里另一个成员接手;或者会话超出当前模型的上下文,有规则把它发往上下文更大的模型。一轮之中不会重新判断意图。</p>
<h3>判断模型挂了或者很慢怎么办?</h3>
<p>这一轮照常进行。最多等 8 秒后,意图都不命中,由分组的其他规则和顺序决定。接下来 30 秒内 magpie 不再询问这个判断模型,免得每一轮都要等满超时。路由记录里会写明原因。</p>
<h3>判断模型说的就是这条规则的意图,为什么没有发给规则指定的模型?</h3>
<p>看路由记录。常见原因有:</p>
<ul>
<li>上面有别的规则先命中了;</li>
<li>这条规则的其他条件不满足(tokens、图片、推理、Agent);</li>
<li>规则指定的模型此刻没有可用的 Key 或账号(「现在没有可用的账号或 Key」);</li>
<li>这是一轮里的工具往返请求,而这一轮开始时去的是别的模型;</li>
<li>magpie 没看到这一轮开始(「规则等下一轮再判断」)。</li>
</ul>
<h3>两条规则写了同一个意图会怎样?</h3>
<p>这个意图只给判断模型看一次。选中它时,这几条规则里其他条件也满足的第一条生效。可以借此把 Codex 发来的「写测试」交给一个模型,把 Claude Code 发来的交给另一个。</p>
<h3>改了规则,会影响正在进行的一轮吗?</h3>
<p>不会。保存后从下一轮开始生效。如果决定当前这一轮的规则被删除了,或者改成发往别的模型,这一轮会留在原处直到结束,路由记录里写着规则等下一轮再判断。</p>
<h3>判断模型可以是一个路由组吗?可以是组里的成员吗?</h3>
<p>不能是路由组:它必须是单个模型(<code>provider/model</code>),保存时 magpie 会校验。它可以是 magpie 里的任何模型,包括组里的成员、订阅里的模型或本地模型。</p>
<h3>可以用推理模型吗?</h3>
<p>只要它能在 8 秒、512 个 token 以内回答一个数字就可以。想得太久的模型可能把 token 用完而没有回答,这会算作失败。做这件事,不带推理的小模型更快、更便宜,效果也一样好。</p>
<h3>重试同一条消息,会再问一次吗?</h3>
<p>10 分钟内不会:同一条消息、同一组意图、同一个判断模型,直接用缓存的回答,路由记录里写「同一条消息之前已判断过」。改了意图或者换了判断模型,就会重新询问。</p>
<h3>支持哪些 Agent?</h3>
<p>凡是通过 magpie 使用路由组的 Agent 都支持:Claude Code、Codex、OpenCode、Gemini CLI 等。轮次是从请求本身识别的(带文字、不带工具结果的消息),和 Agent 用哪种 API 无关。</p>
<h3>我的消息会被存下来吗?</h3>
<p>magpie 只在内存里保存它的哈希值(用于缓存)和判断结果。原文会像其他请求一样发给判断模型所属的供应商,适用该供应商的条款。判断调用的用量记录里只有 token 数,没有原文。</p>
<h3>准确率怎么样?</h3>
<p>取决于判断模型,也取决于你的意图之间区分得是否清楚。几个明显不同的类别,小模型能判断得很好;有重叠的类别,它有时会猜错。判断错或没判断出来,影响的只是由哪个模型回答,所以建议先写两三个宽泛的意图,看一天路由记录,再调整措辞。</p>
<p style="margin-top:48px">有问题,或者有值得分享的配置?欢迎到 <a href="https://discord.gg/vGSnD3ZKQF">Discord</a> 交流。路由组的基础用法见<a href="/docs/zh/start#routing">快速上手</a>。</p>
</article>
</main>
<footer><div class="wrap">
<span>© 2026 magpie · MIT</span>
<div class="links">
<a href="/">首页</a>
<a href="/docs/zh/intent">意图路由</a>
<a href="/docs/zh/import">Add to magpie</a>
<a href="https://github.com/yetone/magpie">GitHub</a>
<a href="https://discord.gg/vGSnD3ZKQF">Discord</a>
<a href="https://github.com/yetone/magpie-releases/releases">版本发布</a>
</div>
</div></footer>
<script>
(() => {
const next = { system: "light", light: "dark", dark: "system" };
document.querySelectorAll("button.theme").forEach((b) => {
b.dataset.mode = themeMode();
b.title = "Theme: " + themeMode();
b.addEventListener("click", () => setTheme(next[themeMode()]));
});
document.querySelectorAll(".copy").forEach((b) => b.addEventListener("click", async () => {
try {
await navigator.clipboard.writeText(document.getElementById(b.dataset.copy).textContent);
b.textContent = "已复制"; setTimeout(() => (b.textContent = "复制"), 1400);
} catch {}
}));
// The table of contents follows the reading position.
const links = [...document.querySelectorAll(".toc a")];
const heads = links.map((a) => document.getElementById(a.hash.slice(1)));
const spy = () => {
let i = 0;
heads.forEach((h, j) => { if (h.getBoundingClientRect().top < 140) i = j; });
if (innerHeight + scrollY >= document.body.scrollHeight - 4) i = heads.length - 1;
links.forEach((a, j) => a.classList.toggle("on", j === i));
};
addEventListener("scroll", spy, { passive: true }); spy();
})();
</script>
</body>
</html>
+3 -1
View File
@@ -42,6 +42,7 @@
<div class="links">
<a href="/">首页</a>
<a href="/docs/zh/start">快速上手</a>
<a href="/docs/zh/intent">意图路由</a>
<a href="/docs/zh/import">Add to magpie</a>
<a href="https://github.com/yetone/magpie-releases/releases">版本发布</a>
</div>
@@ -172,7 +173,7 @@
<li>规则只在一轮开始时决定;Agent 回传工具结果的过程中一直留在那个模型上,不会半途换掉而丢掉厂商的缓存。唯一例外:这一轮涨到超过所在模型的上下文,会换到装得下的成员。</li>
<li>token 数取请求大小的估算与厂商对这个会话上一个请求的计数中较大的一个。请留出余量:给 200k 的模型设 150k,而不是 199k。</li>
<li>子 Agent 是单独的会话,规则可以把它发到别处,主 Agent 仍留在原处。</li>
<li>意图是用几个词描述用户想做什么,例如“写或修测试”“一个简单问题”。每一轮开始时,magpie 会问一次组里指定的意图判断模型:用户这条消息属于哪个可能命中的意图。这个模型可以是 magpie 里的任意模型,建议选小而快、不带推理的。发过去的只有这条消息,不包括整段对话。它出错、超时(8 秒)或给不出答案时,意图条件都不命中,这一轮按其他规则走。如果前面有不带意图的规则已经能决定,就不会去问;同一条消息也不会问两次。它的调用在用量里显示为 magpie 自己的调用。</li>
<li>意图是用几个词描述用户想做什么,例如“写或修测试”“一个简单问题”。每一轮开始时,magpie 会问一次组里指定的意图判断模型:用户这条消息属于哪个可能命中的意图。这个模型可以是 magpie 里的任意模型,建议选小而快、不带推理的。发过去的只有这条消息,不包括整段对话。它出错、超时(8 秒)或给不出答案时,意图条件都不命中,这一轮按其他规则走。如果前面有不带意图的规则已经能决定,就不会去问;同一条消息也不会问两次。它的调用在用量里显示为 magpie 自己的调用。它具体被问了什么、回答怎么用、失败时怎么办,详见<a href="/docs/zh/intent">意图路由</a>。</li>
<li>路由标签页会显示每一轮是被哪条规则送到哪里的,以及意图判断模型给出的答案。</li>
</ul>
<p><span class="ui">路由</span> 标签页实时显示网关的决定:每个请求由谁回答、为什么。在终端里也能管理路由组:</p>
@@ -237,6 +238,7 @@ magpie claude group/opus-anywhere <span class="c"># 使用它</span></pre>
<span>© 2026 magpie · MIT</span>
<div class="links">
<a href="/">首页</a>
<a href="/docs/zh/intent">意图路由</a>
<a href="/docs/zh/import">Add to magpie</a>
<a href="https://github.com/yetone/magpie">GitHub</a>
<a href="https://discord.gg/vGSnD3ZKQF">Discord</a>
Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB