344 lines
34 KiB
HTML
344 lines
34 KiB
HTML
<!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.99 的实际实现,逐条说明 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>
|
||
|
||
<h3>在界面里看</h3>
|
||
<p>一个真实请求:分组里有两条带意图的规则(“a quick question or explanation, with no code to write or change”和“writing or fixing tests”),从 Claude Code 发出一条消息,要它给 cache.go 里的 LRU 缓存加单元测试,覆盖淘汰和并发读写。<span class="ui">路由</span>页实时显示它的去向。</p>
|
||
<figure class="figure">
|
||
<img class="for-light" src="/img/intent-routing-hit-zh-light.png" width="2080" height="1116" loading="lazy" alt="路由页:Claude Code 经 magpie 到 aihubmix/glm-5.3 并由它回答;记录里写着命中规则 2(意图是“writing or fixing tests”),deepseek-flash 用时 964 毫秒作出判断">
|
||
<img class="for-dark" src="/img/intent-routing-hit-zh-dark.png" width="2080" height="1116" loading="lazy" alt="路由页:Claude Code 经 magpie 到 aihubmix/glm-5.3 并由它回答;记录里写着命中规则 2(意图是“writing or fixing tests”),deepseek-flash 用时 964 毫秒作出判断">
|
||
<figcaption>判断模型 deepseek-flash 用 964 毫秒判断为“writing or fixing tests”,于是规则 2 把 glm-5.3 排到分组原本的第一个模型前面,5.6 秒完成回答。</figcaption>
|
||
</figure>
|
||
|
||
<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:
|
||
<message>
|
||
Write unit tests for the parseTokens function and fix any that fail.
|
||
</message>
|
||
|
||
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><system-reminder>…</system-reminder></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 2048</code>,User-Agent 为 <code>magpie-router/1</code>。magpie 知道判断模型支持哪些推理档位时,会要最低的一档:支持 <code>none</code> 就用 <code>none</code>,否则用它最低的档位(比如 <code>low</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>这一轮意图都不命中。判断模型确实回答了,所以不冷却,下一轮照常询问</td></tr>
|
||
<tr><td>报错或 8 秒超时</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 的非推理对话模型,或者本地模型。推理模型更慢;magpie 会要它最低的推理档位并留 2048 个 token,但慢的模型仍可能超过 8 秒。先观察几轮路由记录,判断不准时,先改意图的措辞,再考虑换模型。</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 2048,模型最低的推理档位,不流式</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 秒内回答。magpie 会要它最低的推理档位(有 <code>none</code> 用 <code>none</code>,否则用最低一档),并留 2048 个 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>
|
||
<h3>用真实模型测过吗?</h3>
|
||
<p>测过。我们把 20 条真实的编程消息(中英文都有:简单提问、写测试/修测试、普通编码任务,还有 “Fix the bug in parseTokens and add a test for it” 这种混合的)发给一个带两条意图规则的分组。DeepSeek V4 Flash、GLM-5.3 Flash、MiMo v2.6 Flash 作为判断模型都是 20/20 全对,判断耗时中位数分别是 0.75 秒、1.3 秒、1.9 秒。某家中转上的 Gemini 3.8 Flash 即使用 <code>low</code> 推理档位也要 11 秒,每一轮都超时,于是都按没有意图规则的方式路由,路由记录每次都写明了原因。这也说明:判断模型选得不好,损失的只是路由效果,不会让这一轮失败。</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>
|