开发者用 TypeScript 实现模型选择路由器,通过智能调度不同 LLM,直接减少 41% 的 API 调用成本。
我跟踪自己在三个项目中的 AI 支出。在三月份,增长最快的不是 Claude 或 GPT 调用。而是我的 Cursor 席位加上 Copilot 席位加上我从个人 CLI 调用的 Anthropic API。三个订阅、三个计费表,同一个 Opus token 被重复计费,因为 Cursor 将相同的上下文发送到它自己的后端,而我已经直接传给 Anthropic 了。
这些包装器不会宣传这个。路由代码不是它们的产品。产品是不用考虑哪个模型处理哪个提示的便利性。你用座位价格中烤入的利润支付了编排税。
我厌倦了支付这笔钱。所以我写了路由。它是 200 行 TypeScript。我的四月账单比三月低 41%,工作量基本相同。
这 41% 的节省来自一件事:停止让 Sonnet 处理 Haiku 能以十分之一成本完成的任务。大多数编码查询其实是伪装成问题的查找。按意图路由,而不是按习惯。
每个包装器都做同样的权衡。它们为你选择一个模型,它们前置一个你无法编辑的系统提示,它们持有一个你无法检查的上下文窗口。作为回报,你不必考虑。
不思考的成本以两种方式出现:
包装器调用最贵的能满足其 SLA 的模型,因为这样更容易让演示看起来很好
包装器向你收费代表你发送的上下文,包括它自己的系统提示和工具定义
我在 Anthropic 仪表板中记录了 30 天的 Cursor 使用情况。Cursor 平均每次聊天回合发送 8,400 个输入 token。我对相同聊天的直接 API 调用平均 1,900。这 6,500 token 的差值是 Cursor 的框架、索引上下文和它的 agent 脚手架。有用,但不是免费的。
当你自己构建路由器时,你选择发送什么。这就是整个游戏。
这是这个文件。把它放到项目中,给它你的 API 密钥,它根据你控制的规则为每个请求选择一个模型。
// router.ts
import Anthropic from "@anthropic-ai/sdk";
import OpenAI from "openai";
type Intent = "trivial" | "code" | "plan" | "embed";
interface RouteRule {
match: (prompt: string) => boolean;
intent: Intent;
}
interface ModelConfig {
provider: "anthropic" | "openai";
model: string;
maxTokens: number;
}
const ROUTES: Record<Intent, ModelConfig> = {
trivial: { provider: "anthropic", model: "claude-haiku-4-5-20251001", maxTokens: 1024 },
code: { provider: "anthropic", model: "claude-sonnet-4-6", maxTokens: 4096 },
plan: { provider: "anthropic", model: "claude-opus-4-7", maxTokens: 8192 },
embed: { provider: "openai", model: "gpt-5-mini", maxTokens: 512 },
};
const RULES: RouteRule[] = [
{ intent: "trivial", match: (p) => p.length < 200 && /\?$/.test(p.trim()) },
{ intent: "trivial", match: (p) => /^(what is|define|fix typo|rename)/i.test(p) },
{ intent: "plan", match: (p) => /(refactor|design|architect|migrate|plan)/i.test(p) },
{ intent: "code", match: (p) => /(```|function |class |const |let )/i.test(p) },
{ intent: "embed", match: (p) => p.startsWith("CLASSIFY:") },
];
function pickIntent(prompt: string): Intent {
for (const rule of RULES) {
if (rule.match(prompt)) return rule.intent;
}
return "code";
}
const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
export interface RouteResult {
text: string;
model: string;
inputTokens: number;
outputTokens: number;
costUsd: number;
}
const PRICING: Record<string, { in: number; out: number }> = {
"claude-haiku-4-5-20251001": { in: 0.8, out: 4 },
"claude-sonnet-4-6": { in: 3, out: 15 },
"claude-opus-4-7": { in: 5, out: 25 },
"gpt-5-mini": { in: 0.5, out: 2 },
};
function priceCall(model: string, inTok: number, outTok: number): number {
const p = PRICING[model];
if (!p) return 0;
return (inTok * p.in + outTok * p.out) / 1_000_000;
}
export async function route(prompt: string): Promise<RouteResult> {
const intent = pickIntent(prompt);
const cfg = ROUTES[intent];
if (cfg.provider === "anthropic") {
const r = await anthropic.messages.create({
model: cfg.model,
max_tokens: cfg.maxTokens,
messages: [{ role: "user", content: prompt }],
});
const text = r.content
.filter((b) => b.type === "text")
.map((b) => (b as { text: string }).text)
.join("");
return {
text,
model: cfg.model,
inputTokens: r.usage.input_tokens,
outputTokens: r.usage.output_tokens,
costUsd: priceCall(cfg.model, r.usage.input_tokens, r.usage.output_tokens),
};
}
const r = await openai.chat.completions.create({
model: cfg.model,
max_tokens: cfg.maxTokens,
messages: [{ role: "user", content: prompt }],
});
const usage = r.usage ?? { prompt_tokens: 0, completion_tokens: 0 };
return {
text: r.choices[0]?.message?.content ?? "",
model: cfg.model,
inputTokens: usage.prompt_tokens,
outputTokens: usage.completion_tokens,
costUsd: priceCall(cfg.model, usage.prompt_tokens, usage.completion_tokens),
};
}
就这样。两个提供商、四个意图、五个规则和一个成本计算器。这样使用它:
import { route } from "./router";
const out = await route("rename this function from getUser to fetchUser");
console.log(out.model, out.costUsd.toFixed(5));
// claude-haiku-4-5-20251001 0.00012
这些规则故意很简单。长度加正则表达式覆盖了大约 70% 的路由决策。对于另外 30%,用前缀覆盖:
await route("[force:opus] design a permissions model for ...");
在 pickIntent 中添加一行来读取前缀。我为了保持例子简洁而省略了它。
天真的方法是发送一个小分类器调用到便宜模型并让它选择路由。这听起来很聪明,但成本比节省更多,因为每个请求现在消耗两个 API 调用。pickIntent 的成本必须为零。
五个正则表达式规则覆盖了我的大部分工作量:
短且以问号结尾:trivial
以"what is"、"define"、"fix typo"、"rename"开头:trivial
包含"refactor"、"design"、"architect"、"migrate"、"plan":plan
包含代码围栏或 function 关键字:code
以"CLASSIFY:"前缀开头:embed(便宜分类器)
默认为 code。一个从 trivial 到 code 的错误路由在那一个请求上成本多 4 倍左右。一个从 code 到 opus 的错误路由成本多 1.6 倍。都不是灾难。要避免的 bug 是给 Haiku 发送它无法维持上下文的多步骤计划,这意味着要谨慎地默认。
我还记录每一次失误。两周后我有一个小的 CSV,记录"这个提示路由到 X 但应该是 Y"。我添加了两个正则表达式规则,失误率从 8% 降到 2% 以下。
三月账单,没有路由器:
四月账单,有路由器(取消了 Cursor,仅保留 Copilot 用于 IDE 内联):
在总调用增加 30% 的情况下,低 41%。路由器将 62% 的调用转移到了 Haiku,这些调用原本由 Sonnet 处理。每次调用的平均成本从 $0.024 降到 $0.013。
Cursor 的取消做了主要的节省。路由器做了较小的、重复的、复合的节省。两者都来自同一个想法:包装器隐藏了你可以更好自己做的决策。
这不是 agent 框架。它不流式传输。它不重试。它不缓存。它不处理速率限制。它不做工具使用。它不知道你的代码库。
添加任何这些都需要工作。流式传输需要两个改变。使用 Anthropic prompt cache 缓存是每次调用多一个 header。带指数退避的重试是 20 行。工具使用需要你无论如何都要写的架构管道。
如果你需要所有这些,使用真正的框架。如果你想停止在 80% 的调用上支付编排税,上面的 200 行会做到。在你实际遇到每个问题时添加其余部分。
包装器存在是因为路由 AI 调用很烦人。这也是你在自己的代码中能够拥有的最高杠杆的东西。上面的 200 行不是护城河。它们是周二下午。写它们的原因是你无法改进看不到的账单。
你当前的便宜模型调用与昂贵模型调用的比例是多少?如果你不知道,那是首先要修复的事情。在你设置路由器之前先设置成本日志。这些数字会让你惊讶。
GDS K S · thegdsks.com · building Glincker · follow on X @thegdsks
编排税是不会出现在定价页面上的 AI 账单的一部分。