成本
随着用量增长,让模型开销保持可预测
把每个任务都走同一个 OpenAI-compatible endpoint,路由到能完成任务的最便宜模型,并记录每次响应返回的 token 用量。
复制 prompt
把方括号里的字段替换掉,然后粘贴到 Claude Code、Codex 或任何能访问终端的 AI。它会安装 Felo skill、在花额度前询问,并执行这套 workflow。
审查我们的模型支出,并为这些任务提出路由方案:[任务或功能列表]。 约束:月度预算 [金额]、延迟目标 [目标]、质量门槛 [门槛]。 在提出任何建议前,先读取实时模型目录。 输出: 1. 任务 -> 模型推荐表。 2. 哪些任务可以降一档,以及质量风险。 3. 按任务和功能记录的用量日志方案。 4. 改动生产前需要衡量什么。 规则: - 从实时目录读取当前模型 ID 和价格;不要凭记忆引用费率。 - 不要改动生产配置或发送真实流量。 - 在消耗额度做测试调用前先询问。 - 保留回滚方案和按功能的支出上限。 - 将方案写入 ./routing-plan.md,并在对话中总结。
复制的内容也包含安装步骤和安全规则。
你会得到什么
一次运行的示例输出,不是实时生成。
| 任务 | 当前 | 建议 | 风险 |
|---|---|---|---|
| 工单分类 | 强模型 | 快模型 | 低 |
| 回复草稿 | 强模型 | 强模型 | 无 |
| 变更日志摘要 | 强模型 | 快模型 | 中 |
改动之前: 按任务记录一周用量,并在 50 条抽样输出上比较质量。
实际调用
给开发者:同一套 workflow 的可复制代码,按执行顺序排列。
展开实际 API 调用
所有任务走同一个 endpoint
统一用 OpenAI-compatible endpoint 和 Key,避免每家供应商一套 SDK。
routing.ts const BASE_URL = "https://openapi.felo.ai/api/v1"; const headers = { Authorization: `Bearer ${process.env.FELO_API_KEY}`, "Content-Type": "application/json", }; type Completion = { choices: { message: { content: string } }[]; usage: { prompt_tokens: number; completion_tokens: number; total_tokens: number }; }; async function chat(model: string, prompt: string): Promise<Completion> { const res = await fetch(`${BASE_URL}/chat/completions`, { method: "POST", headers, body: JSON.stringify({ model, messages: [{ role: "user", content: prompt }] }), }); if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`); return res.json() as Promise<Completion>; } const summary = await chat( "claude-haiku-5-5", "Summarize this changelog in three bullets: …", ); console.log(summary.choices[0].message.content);示例输出
{ "choices": [{ "message": { "content": "• …" } }], "usage": { "prompt_tokens": 812, "completion_tokens": 96, "total_tokens": 908 } }按任务路由模型
按任务复杂度选择快模型或强模型,先记录当前基线。
routing.ts type TaskKind = "classify" | "extract" | "summarize" | "draft" | "review"; const MODEL_ROUTES: Record<TaskKind, string> = { classify: "claude-haiku-5-5", extract: "claude-haiku-5-5", summarize: "claude-haiku-5-5", draft: "claude-sonnet-5-5", review: "claude-sonnet-5-5", }; export async function route(kind: TaskKind, prompt: string) { const model = MODEL_ROUTES[kind]; const result = await chat(model, prompt); return { model, text: result.choices[0].message.content, usage: result.usage, }; }示例输出
{ "model": "claude-haiku-5-5", "text": "• …", "usage": { "prompt_tokens": 812, "completion_tokens": 96, "total_tokens": 908 } }读取并记录用量
从每次响应里读 usage,按功能记录 token 和延迟。
routing.ts type UsageRecord = { model: string; kind: TaskKind; promptTokens: number; completionTokens: number; totalTokens: number; }; const usageLog: UsageRecord[] = []; function logUsage(kind: TaskKind, model: string, usage: Completion["usage"]) { const entry: UsageRecord = { model, kind, promptTokens: usage.prompt_tokens, completionTokens: usage.completion_tokens, totalTokens: usage.total_tokens, }; usageLog.push(entry); console.log(JSON.stringify(entry)); // ship this line to your metrics store return entry; } export function tokensByModel() { return usageLog.reduce<Record<string, number>>((totals, entry) => { totals[entry.model] = (totals[entry.model] ?? 0) + entry.totalTokens; return totals; }, {}); } const result = await route("summarize", "Summarize this changelog in three bullets: …"); logUsage("summarize", result.model, result.usage); console.log(tokensByModel());示例输出
{"model":"claude-haiku-5-5","kind":"summarize","promptTokens":812,"completionTokens":96,"totalTokens":908} { "claude-haiku-5-5": 18234, "claude-sonnet-5-5": 4201 }一个 Key 覆盖所有供应商
用同一个 Felo Key 调用目录里的模型,减少供应商管理。
routing.ts async function chatOnChannel(model: string, prompt: string, channel?: string) { const res = await fetch(`${BASE_URL}/chat/completions`, { method: "POST", headers: { ...headers, ...(channel ? { "X-Felo-Channel": channel } : {}), }, body: JSON.stringify({ model, messages: [{ role: "user", content: prompt }] }), }); if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`); return res.json() as Promise<Completion>; } // Leave `channel` undefined for the default supplier. export function routePinned(kind: TaskKind, prompt: string, channel?: string) { return chatOnChannel(MODEL_ROUTES[kind], prompt, channel); }价格从实时目录读
不要写死费率;从模型目录和价格页读取当前价格。
routing.ts type Catalog = { data: { id: string; owned_by: string; model_kind: string }[]; }; export async function chatModelIds() { const res = await fetch( `${BASE_URL}/models?exclude_video_and_decisions=true`, { headers }, ); if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`); const catalog = (await res.json()) as Catalog; return catalog.data .filter((model) => model.model_kind === "chat") .map((model) => model.id); } // Price `tokensByModel()` against https://openapi.felo.ai/pricing and // https://openapi.felo.ai/models. Keep the links, not the rates, in your code.示例输出
["claude-haiku-5-5", "claude-sonnet-5-5"]
包含什么
- 统一 endpoint 和 Key
- 按任务选择快模型或强模型
- 每次响应的 token 用量日志
- 从实时目录读取的价格,而不是写死在代码里
不做什么
- 不保证一定降低账单
- 不自动切换生产流量
- 模型 id 和价格会变;始终读实时目录
- 不替你评估质量,需要你自己的样本
- 不替代账单或可观测系统
常见问题
会降低账单吗?
它指出哪些任务可能可以降到更便宜的档位,但只有在你对比样本输出并记录用量之后,节省才是真的。
会自动调用模型吗?
只有你批准测试调用时才会。默认只读目录并写建议,不碰生产流量。
怎么知道哪个模型更便宜?
读实时模型目录和价格页;workflow 不写死费率,因为价格会变。
可以固定供应商吗?
可以,当延迟比价格更重要时。固定是选择,不是重试策略;fallback 需要你自己加。
应该记录什么?
每次调用记录:任务、功能、模型、prompt token、completion token、总 token 和延迟。按功能聚合后再设预算。
一次 review 要花多少?
读目录不产生模型调用;测试调用按用量计费,prompt 会在调用前请求确认。
相关 workflow
改成你的版本
同一套配方,按你实际的交付方式调整。
先只记录不切换
上线前先记录一周用量,再决定是否降档。
按功能设预算
给每个功能设置月度上限,超出时告警或降级。
固定供应商换延迟
当延迟比价格重要时,固定供应商并加自己的 fallback。
继续深入
这套配方背后的参考页面,以及下一步可以看什么。