mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-02 10:50:49 +08:00
feat: add cache token pricing support in usage calculation
- Add cache price field to ModelPrice interface - Support both cached_tokens and cache_tokens fields for compatibility - Separate prompt, cache, and completion pricing in cost calculation - Deduct cached tokens from input tokens before prompt pricing - Refactor getApiStats/getModelStats to reuse calculateCost function - Update i18n labels for model pricing
This commit is contained in:
@@ -511,8 +511,9 @@
|
||||
"model_price_model_label": "Model",
|
||||
"model_price_select_placeholder": "Choose a model",
|
||||
"model_price_select_hint": "Models come from usage details",
|
||||
"model_price_prompt": "Prompt price ($/1M tokens)",
|
||||
"model_price_completion": "Completion price ($/1M tokens)",
|
||||
"model_price_prompt": "Prompt price",
|
||||
"model_price_completion": "Completion price",
|
||||
"model_price_cache": "Cache price",
|
||||
"model_price_save": "Save Price",
|
||||
"model_price_empty": "No model prices set",
|
||||
"model_price_model": "Model",
|
||||
|
||||
@@ -521,8 +521,9 @@
|
||||
"model_price_model_label": "选择模型",
|
||||
"model_price_select_placeholder": "选择模型",
|
||||
"model_price_select_hint": "模型列表来自使用统计明细",
|
||||
"model_price_prompt": "提示价格 ($/1M tokens)",
|
||||
"model_price_completion": "补全价格 ($/1M tokens)",
|
||||
"model_price_prompt": "提示价格",
|
||||
"model_price_completion": "补全价格",
|
||||
"model_price_cache": "缓存价格",
|
||||
"model_price_save": "保存价格",
|
||||
"model_price_empty": "暂未设置任何模型价格",
|
||||
"model_price_model": "模型",
|
||||
|
||||
@@ -31,6 +31,7 @@ export interface RateStats {
|
||||
export interface ModelPrice {
|
||||
prompt: number;
|
||||
completion: number;
|
||||
cache: number;
|
||||
}
|
||||
|
||||
export interface UsageDetail {
|
||||
@@ -42,6 +43,7 @@ export interface UsageDetail {
|
||||
output_tokens: number;
|
||||
reasoning_tokens: number;
|
||||
cached_tokens: number;
|
||||
cache_tokens?: number;
|
||||
total_tokens: number;
|
||||
};
|
||||
failed: boolean;
|
||||
@@ -214,11 +216,15 @@ export function extractTotalTokens(detail: any): number {
|
||||
if (typeof tokens.total_tokens === 'number') {
|
||||
return tokens.total_tokens;
|
||||
}
|
||||
const tokenKeys = ['input_tokens', 'output_tokens', 'reasoning_tokens', 'cached_tokens'];
|
||||
return tokenKeys.reduce((sum, key) => {
|
||||
const value = tokens[key];
|
||||
return sum + (typeof value === 'number' ? value : 0);
|
||||
}, 0);
|
||||
const inputTokens = typeof tokens.input_tokens === 'number' ? tokens.input_tokens : 0;
|
||||
const outputTokens = typeof tokens.output_tokens === 'number' ? tokens.output_tokens : 0;
|
||||
const reasoningTokens = typeof tokens.reasoning_tokens === 'number' ? tokens.reasoning_tokens : 0;
|
||||
const cachedTokens = Math.max(
|
||||
typeof tokens.cached_tokens === 'number' ? Math.max(tokens.cached_tokens, 0) : 0,
|
||||
typeof tokens.cache_tokens === 'number' ? Math.max(tokens.cache_tokens, 0) : 0
|
||||
);
|
||||
|
||||
return inputTokens + outputTokens + reasoningTokens + cachedTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -235,9 +241,10 @@ export function calculateTokenBreakdown(usageData: any): TokenBreakdown {
|
||||
|
||||
details.forEach(detail => {
|
||||
const tokens = detail?.tokens || {};
|
||||
if (typeof tokens.cached_tokens === 'number') {
|
||||
cachedTokens += tokens.cached_tokens;
|
||||
}
|
||||
cachedTokens += Math.max(
|
||||
typeof tokens.cached_tokens === 'number' ? Math.max(tokens.cached_tokens, 0) : 0,
|
||||
typeof tokens.cache_tokens === 'number' ? Math.max(tokens.cache_tokens, 0) : 0
|
||||
);
|
||||
if (typeof tokens.reasoning_tokens === 'number') {
|
||||
reasoningTokens += tokens.reasoning_tokens;
|
||||
}
|
||||
@@ -311,11 +318,23 @@ export function calculateCost(detail: any, modelPrices: Record<string, ModelPric
|
||||
return 0;
|
||||
}
|
||||
const tokens = detail?.tokens || {};
|
||||
const promptTokens = Number(tokens.input_tokens) || 0;
|
||||
const completionTokens = Number(tokens.output_tokens) || 0;
|
||||
const rawInputTokens = Number(tokens.input_tokens);
|
||||
const rawCompletionTokens = Number(tokens.output_tokens);
|
||||
const rawCachedTokensPrimary = Number(tokens.cached_tokens);
|
||||
const rawCachedTokensAlternate = Number(tokens.cache_tokens);
|
||||
|
||||
const inputTokens = Number.isFinite(rawInputTokens) ? Math.max(rawInputTokens, 0) : 0;
|
||||
const completionTokens = Number.isFinite(rawCompletionTokens) ? Math.max(rawCompletionTokens, 0) : 0;
|
||||
const cachedTokens = Math.max(
|
||||
Number.isFinite(rawCachedTokensPrimary) ? Math.max(rawCachedTokensPrimary, 0) : 0,
|
||||
Number.isFinite(rawCachedTokensAlternate) ? Math.max(rawCachedTokensAlternate, 0) : 0
|
||||
);
|
||||
const promptTokens = Math.max(inputTokens - cachedTokens, 0);
|
||||
|
||||
const promptCost = (promptTokens / TOKENS_PER_PRICE_UNIT) * (Number(price.prompt) || 0);
|
||||
const cachedCost = (cachedTokens / TOKENS_PER_PRICE_UNIT) * (Number(price.cache) || 0);
|
||||
const completionCost = (completionTokens / TOKENS_PER_PRICE_UNIT) * (Number(price.completion) || 0);
|
||||
const total = promptCost + completionCost;
|
||||
const total = promptCost + cachedCost + completionCost;
|
||||
return Number.isFinite(total) && total > 0 ? total : 0;
|
||||
}
|
||||
|
||||
@@ -349,14 +368,27 @@ export function loadModelPrices(): Record<string, ModelPrice> {
|
||||
const normalized: Record<string, ModelPrice> = {};
|
||||
Object.entries(parsed).forEach(([model, price]: [string, any]) => {
|
||||
if (!model) return;
|
||||
const prompt = Number(price?.prompt);
|
||||
const completion = Number(price?.completion);
|
||||
if (!Number.isFinite(prompt) && !Number.isFinite(completion)) {
|
||||
const promptRaw = Number(price?.prompt);
|
||||
const completionRaw = Number(price?.completion);
|
||||
const cacheRaw = Number(price?.cache);
|
||||
|
||||
if (!Number.isFinite(promptRaw) && !Number.isFinite(completionRaw) && !Number.isFinite(cacheRaw)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const prompt = Number.isFinite(promptRaw) && promptRaw >= 0 ? promptRaw : 0;
|
||||
const completion = Number.isFinite(completionRaw) && completionRaw >= 0 ? completionRaw : 0;
|
||||
const cache =
|
||||
Number.isFinite(cacheRaw) && cacheRaw >= 0
|
||||
? cacheRaw
|
||||
: Number.isFinite(promptRaw) && promptRaw >= 0
|
||||
? promptRaw
|
||||
: prompt;
|
||||
|
||||
normalized[model] = {
|
||||
prompt: Number.isFinite(prompt) && prompt >= 0 ? prompt : 0,
|
||||
completion: Number.isFinite(completion) && completion >= 0 ? completion : 0
|
||||
prompt,
|
||||
completion,
|
||||
cache
|
||||
};
|
||||
});
|
||||
return normalized;
|
||||
@@ -404,14 +436,7 @@ export function getApiStats(usageData: any, modelPrices: Record<string, ModelPri
|
||||
if (price) {
|
||||
const details = Array.isArray(modelData.details) ? modelData.details : [];
|
||||
details.forEach((detail: any) => {
|
||||
const tokens = detail?.tokens || {};
|
||||
const promptTokens = Number(tokens.input_tokens) || 0;
|
||||
const completionTokens = Number(tokens.output_tokens) || 0;
|
||||
const cost = (promptTokens / TOKENS_PER_PRICE_UNIT) * (Number(price.prompt) || 0) +
|
||||
(completionTokens / TOKENS_PER_PRICE_UNIT) * (Number(price.completion) || 0);
|
||||
if (Number.isFinite(cost) && cost > 0) {
|
||||
totalCost += cost;
|
||||
}
|
||||
totalCost += calculateCost({ ...detail, __modelName: modelName }, modelPrices);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -454,14 +479,7 @@ export function getModelStats(usageData: any, modelPrices: Record<string, ModelP
|
||||
if (price) {
|
||||
const details = Array.isArray(modelData.details) ? modelData.details : [];
|
||||
details.forEach((detail: any) => {
|
||||
const tokens = detail?.tokens || {};
|
||||
const promptTokens = Number(tokens.input_tokens) || 0;
|
||||
const completionTokens = Number(tokens.output_tokens) || 0;
|
||||
const cost = (promptTokens / TOKENS_PER_PRICE_UNIT) * (Number(price.prompt) || 0) +
|
||||
(completionTokens / TOKENS_PER_PRICE_UNIT) * (Number(price.completion) || 0);
|
||||
if (Number.isFinite(cost) && cost > 0) {
|
||||
existing.cost += cost;
|
||||
}
|
||||
existing.cost += calculateCost({ ...detail, __modelName: modelName }, modelPrices);
|
||||
});
|
||||
}
|
||||
modelMap.set(modelName, existing);
|
||||
|
||||
Reference in New Issue
Block a user