mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user