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 实现