Skip to content

模型元資料與成本計算

源码版本v0.73.1

models.ts 是 pi-ai 的模型目錄入口——getModel 查註冊表拿 Model,calculateCost 按 usage 算錢,getSupportedThinkingLevels 列某模型支援的思考等級,clampThinkingLevel 把請求等級吸附到最近的合法值。模型資料本身不在本檔案,而在 models.generated.ts(自動產生,本頁不展開),models.ts 只做查詢與計算。

職責

  1. 目錄初始化:模組載入時把 MODELS(來自 models.generated.ts)展平成 Map<provider, Map<id, Model>>,見 packages/ai/src/models.ts:4-13
  2. 查模型:getModel(provider, modelId) 按 provider + id 拿 Model,帶泛型保留 api 型別。見 packages/ai/src/models.ts:20-26
  3. 列 provider / 模型:getProviders()getModels(provider) 給 UI 列表用。見 packages/ai/src/models.ts:28-37
  4. 成本計算:calculateCost(model, usage) 按百萬 token 單價 × 用量,填 usage.cost 各欄位後回傳。見 packages/ai/src/models.ts:39-46
  5. 思考等級:getSupportedThinkingLevels 過濾 model.thinkingLevelMapnull 的等級,clampThinkingLevel 把請求等級吸附到合法值。見 packages/ai/src/models.ts:48-80
  6. 模型相等:modelsAreEqualid + provider,UI 切換時用。見 packages/ai/src/models.ts:86-92

設計動機

為什麼 models.generated.tsmodels.ts 分開?因為模型目錄(定價、上下文長度、thinking 支援)是高頻變化資料——廠商改價、出新模型、調 API 欄位,都得改。把它從 models.ts 的查詢/計算邏輯裡剝出來,產生器跑一次產出靜態資料,查詢邏輯穩定不動。本頁不寫產生器,只關心查的那一面。

為什麼 ModelThinkingLeveloff | minimal | low | medium | high | xhigh 六檔?因為不同 provider 的 thinking 控制粒度差異大——Anthropic 給 budget tokens、OpenAI Responses 給 effort、Google 給 thinking budget。統一抽象成 6 檔,thinkingLevelMap 每檔對應到 provider 特定參數,null 表示「這檔本模型不支援」。clampThinkingLevel 處理「請求 high 但模型只到 medium」這類情況,先向下找最近的合法值,保證請求不報錯。

關鍵檔案

calculateCost 是純算術,四檔單價除以百萬乘用量:

typescript
// packages/ai/src/models.ts:39-46
export function calculateCost<TApi extends Api>(model: Model<TApi>, usage: Usage): Usage["cost"] {
	usage.cost.input = (model.cost.input / 1000000) * usage.input;
	usage.cost.output = (model.cost.output / 1000000) * usage.output;
	usage.cost.cacheRead = (model.cost.cacheRead / 1000000) * usage.cacheRead;
	usage.cost.cacheWrite = (model.cost.cacheWrite / 1000000) * usage.cacheWrite;
	usage.cost.total = usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite;
	return usage.cost;
}

clampThinkingLevel 先向下找,再向上回找,保證總回傳合法值:

typescript
// packages/ai/src/models.ts:71-79
	for (let i = requestedIndex; i < EXTENDED_THINKING_LEVELS.length; i++) {
		const candidate = EXTENDED_THINKING_LEVELS[i];
		if (availableLevels.includes(candidate)) return candidate;
	}
	for (let i = requestedIndex - 1; i >= 0; i--) {
		const candidate = EXTENDED_THINKING_LEVELS[i];
		if (availableLevels.includes(candidate)) return candidate;
	}
	return availableLevels[0] ?? "off";

資料流

模型查詢與 thinking 等級解析:

邊界與失敗

  • 模型不存在:getModel 回傳 undefined,呼叫方需自行處理,見 packages/ai/src/models.ts:24-25
  • 無 reasoning:getSupportedThinkingLevels!model.reasoning 時直接回傳 ["off"],見 packages/ai/src/models.ts:51-51
  • xhigh 特例:只當 thinkingLevelMap.xhigh !== undefined 才支援,與其他檔位的 null 過濾不同,見 packages/ai/src/models.ts:56-57
  • 請求等級在列表外:clampThinkingLevelrequestedIndex === -1 時回傳 availableLevels[0] ?? "off",見 packages/ai/src/models.ts:68-69
  • calculateCost 原地修改:usage.cost 直接被寫,回傳同一個物件,呼叫方別期望不可變。

小結

models.ts 是查詢/計算層,目錄資料在 models.generated.tsgetModel 查、calculateCost 算錢、getSupportedThinkingLevels / clampThinkingLevel 處理 thinking 等級對應。Model 實例拿到後,怎麼交給 provider 看 stream/complete 門面,Anthropic 怎麼用 thinkingLevelMapAnthropic SSE 實作