成本
隨著用量成長,讓模型開銷保持可預測
把每個任務都走同一個 OpenAI-compatible endpoint,路由到能完成任務的最便宜模型,並記錄每次回應回傳的 token 用量。
複製 prompt
把方括號裡的欄位替換掉,然後貼到 Claude Code、Codex 或任何能存取終端機的 AI。它會安裝 Felo skill、在花額度前詢問,並執行這套工作流程。
審查我們的模型支出,並為這些任務提出路由方案:[任務或功能清單]。 限制:月度預算 [金額]、延遲目標 [目標]、品質門檻 [門檻]。 在提出任何建議前,先讀取即時模型目錄。 輸出: 1. 任務 -> 模型推薦表。 2. 哪些任務可以降一階,以及品質風險。 3. 依任務和功能記錄的用量日誌方案。 4. 改動生產前需要衡量什麼。 規則: - 從即時目錄讀取目前模型 ID 和價格;不要憑記憶引用費率。 - 不要改動生產設定或傳送真實流量。 - 在消耗額度做測試呼叫前先詢問。 - 保留回滾方案和依功能的支出上限。 - 將方案寫入 ./routing-plan.md,並在對話中總結。
複製的內容也包含安裝步驟和安全規則。
你會得到什麼
一次執行的範例輸出,不是即時生成。
| 任務 | 目前 | 建議 | 風險 |
|---|---|---|---|
| 工單分類 | 強模型 | 快模型 | 低 |
| 回覆草稿 | 強模型 | 強模型 | 無 |
| 變更日誌摘要 | 強模型 | 快模型 | 中 |
改動之前: 依任務記錄一週用量,並在 50 條抽樣輸出上比較品質。
實際呼叫
給開發者:同一套工作流程的可複製程式碼,依執行順序排列。
展開實際 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 和價格會變;始終讀即時目錄
- 不替你評估品質,需要你自己的樣本
- 不取代帳單或可觀測系統
常見問題
會降低帳單嗎?
它指出哪些任務可能可以降到更便宜的層級,但只有在你對比樣本輸出並記錄用量之後,節省才是真的。
會自動呼叫模型嗎?
只有你批准測試呼叫時才會。預設只讀目錄並寫建議,不碰生產流量。
怎麼知道哪個模型更便宜?
讀即時模型目錄和價格頁;工作流程不寫死費率,因為價格會變。
可以固定供應商嗎?
可以,當延遲比價格更重要時。固定是選擇,不是重試策略;fallback 需要你自己加。
應該記錄什麼?
每次呼叫記錄:任務、功能、模型、prompt token、completion token、總 token 和延遲。依功能聚合後再設預算。
一次 review 要花多少?
讀目錄不產生模型呼叫;測試呼叫按用量計費,prompt 會在呼叫前請求確認。
相關工作流程
改成你的版本
同一套配方,依你實際的交付方式調整。
先只記錄不切換
上線前先記錄一週用量,再決定是否降檔。
依功能設預算
給每個功能設定月度上限,超出時告警或降級。
固定供應商換延遲
當延遲比價格重要時,固定供應商並加自己的 fallback。
繼續深入
這套配方背後的參考頁面,以及下一步可以看什麼。