fix: 修复API Keys使用趋势图费用计算不准确的问题

- 使用模型级别的详细统计数据计算费用,而非固定模型
- 按实际使用的模型(sonnet/opus/haiku等)分别计算价格
- 累加各模型费用得到准确的总费用
- 降级方案改用sonnet模型(中等价格)而非haiku(最低价格)

问题原因:之前使用固定的haiku模型计算所有token的费用,导致价格偏低
解决方案:获取模型级别的使用数据,按实际模型价格计算

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
shaw
2025-07-30 15:17:59 +08:00
parent 321be986a6
commit 1ca753c79a

View File

@@ -1978,6 +1978,8 @@ router.get('/api-keys-usage-trend', authenticateAdmin, async (req, res) => {
apiKeys: {}
};
// 先收集基础数据
const apiKeyDataMap = new Map();
for (const key of keys) {
const match = key.match(/usage:hourly:(.+?):\d{4}-\d{2}-\d{2}:\d{2}/);
if (!match) continue;
@@ -1992,25 +1994,74 @@ router.get('/api-keys-usage-trend', authenticateAdmin, async (req, res) => {
const cacheReadTokens = parseInt(data.cacheReadTokens) || 0;
const totalTokens = inputTokens + outputTokens + cacheCreateTokens + cacheReadTokens;
// 计算费用 - 使用默认模型价格,因为小时级别的数据没有模型信息
const usage = {
input_tokens: inputTokens,
output_tokens: outputTokens,
cache_creation_input_tokens: cacheCreateTokens,
cache_read_input_tokens: cacheReadTokens
};
const costResult = CostCalculator.calculateCost(usage, 'claude-3-5-haiku-20241022');
hourData.apiKeys[apiKeyId] = {
apiKeyDataMap.set(apiKeyId, {
name: apiKeyMap.get(apiKeyId).name,
tokens: totalTokens,
requests: parseInt(data.requests) || 0,
cost: costResult.costs.total,
formattedCost: costResult.formatted.total
};
inputTokens,
outputTokens,
cacheCreateTokens,
cacheReadTokens
});
}
}
// 获取该小时的模型级别数据来计算准确费用
const modelPattern = `usage:*:model:hourly:*:${hourKey}`;
const modelKeys = await client.keys(modelPattern);
const apiKeyCostMap = new Map();
for (const modelKey of modelKeys) {
const match = modelKey.match(/usage:(.+?):model:hourly:(.+?):\d{4}-\d{2}-\d{2}:\d{2}/);
if (!match) continue;
const apiKeyId = match[1];
const model = match[2];
const modelData = await client.hgetall(modelKey);
if (modelData && apiKeyDataMap.has(apiKeyId)) {
const usage = {
input_tokens: parseInt(modelData.inputTokens) || 0,
output_tokens: parseInt(modelData.outputTokens) || 0,
cache_creation_input_tokens: parseInt(modelData.cacheCreateTokens) || 0,
cache_read_input_tokens: parseInt(modelData.cacheReadTokens) || 0
};
const costResult = CostCalculator.calculateCost(usage, model);
const currentCost = apiKeyCostMap.get(apiKeyId) || 0;
apiKeyCostMap.set(apiKeyId, currentCost + costResult.costs.total);
}
}
// 组合数据
for (const [apiKeyId, data] of apiKeyDataMap) {
const cost = apiKeyCostMap.get(apiKeyId) || 0;
// 如果没有模型级别数据,使用默认模型计算(降级方案)
let finalCost = cost;
let formattedCost = CostCalculator.formatCost(cost);
if (cost === 0 && data.tokens > 0) {
const usage = {
input_tokens: data.inputTokens,
output_tokens: data.outputTokens,
cache_creation_input_tokens: data.cacheCreateTokens,
cache_read_input_tokens: data.cacheReadTokens
};
const fallbackResult = CostCalculator.calculateCost(usage, 'claude-3-5-sonnet-20241022');
finalCost = fallbackResult.costs.total;
formattedCost = fallbackResult.formatted.total;
}
hourData.apiKeys[apiKeyId] = {
name: data.name,
tokens: data.tokens,
requests: data.requests,
cost: finalCost,
formattedCost: formattedCost
};
}
trendData.push(hourData);
currentHour.setHours(currentHour.getHours() + 1);
}
@@ -2035,6 +2086,8 @@ router.get('/api-keys-usage-trend', authenticateAdmin, async (req, res) => {
apiKeys: {}
};
// 先收集基础数据
const apiKeyDataMap = new Map();
for (const key of keys) {
const match = key.match(/usage:daily:(.+?):\d{4}-\d{2}-\d{2}/);
if (!match) continue;
@@ -2049,25 +2102,74 @@ router.get('/api-keys-usage-trend', authenticateAdmin, async (req, res) => {
const cacheReadTokens = parseInt(data.cacheReadTokens) || 0;
const totalTokens = inputTokens + outputTokens + cacheCreateTokens + cacheReadTokens;
// 计算费用 - 使用默认模型价格,因为日级别的汇总数据没有模型信息
const usage = {
input_tokens: inputTokens,
output_tokens: outputTokens,
cache_creation_input_tokens: cacheCreateTokens,
cache_read_input_tokens: cacheReadTokens
};
const costResult = CostCalculator.calculateCost(usage, 'claude-3-5-haiku-20241022');
dayData.apiKeys[apiKeyId] = {
apiKeyDataMap.set(apiKeyId, {
name: apiKeyMap.get(apiKeyId).name,
tokens: totalTokens,
requests: parseInt(data.requests) || 0,
cost: costResult.costs.total,
formattedCost: costResult.formatted.total
};
inputTokens,
outputTokens,
cacheCreateTokens,
cacheReadTokens
});
}
}
// 获取该天的模型级别数据来计算准确费用
const modelPattern = `usage:*:model:daily:*:${dateStr}`;
const modelKeys = await client.keys(modelPattern);
const apiKeyCostMap = new Map();
for (const modelKey of modelKeys) {
const match = modelKey.match(/usage:(.+?):model:daily:(.+?):\d{4}-\d{2}-\d{2}/);
if (!match) continue;
const apiKeyId = match[1];
const model = match[2];
const modelData = await client.hgetall(modelKey);
if (modelData && apiKeyDataMap.has(apiKeyId)) {
const usage = {
input_tokens: parseInt(modelData.inputTokens) || 0,
output_tokens: parseInt(modelData.outputTokens) || 0,
cache_creation_input_tokens: parseInt(modelData.cacheCreateTokens) || 0,
cache_read_input_tokens: parseInt(modelData.cacheReadTokens) || 0
};
const costResult = CostCalculator.calculateCost(usage, model);
const currentCost = apiKeyCostMap.get(apiKeyId) || 0;
apiKeyCostMap.set(apiKeyId, currentCost + costResult.costs.total);
}
}
// 组合数据
for (const [apiKeyId, data] of apiKeyDataMap) {
const cost = apiKeyCostMap.get(apiKeyId) || 0;
// 如果没有模型级别数据,使用默认模型计算(降级方案)
let finalCost = cost;
let formattedCost = CostCalculator.formatCost(cost);
if (cost === 0 && data.tokens > 0) {
const usage = {
input_tokens: data.inputTokens,
output_tokens: data.outputTokens,
cache_creation_input_tokens: data.cacheCreateTokens,
cache_read_input_tokens: data.cacheReadTokens
};
const fallbackResult = CostCalculator.calculateCost(usage, 'claude-3-5-sonnet-20241022');
finalCost = fallbackResult.costs.total;
formattedCost = fallbackResult.formatted.total;
}
dayData.apiKeys[apiKeyId] = {
name: data.name,
tokens: data.tokens,
requests: data.requests,
cost: finalCost,
formattedCost: formattedCost
};
}
trendData.push(dayData);
}
}