From 1ca753c79a98105a7126c76eb2207721420809bd Mon Sep 17 00:00:00 2001 From: shaw Date: Wed, 30 Jul 2025 15:17:59 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8DAPI=20Keys=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E8=B6=8B=E5=8A=BF=E5=9B=BE=E8=B4=B9=E7=94=A8=E8=AE=A1?= =?UTF-8?q?=E7=AE=97=E4=B8=8D=E5=87=86=E7=A1=AE=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 使用模型级别的详细统计数据计算费用,而非固定模型 - 按实际使用的模型(sonnet/opus/haiku等)分别计算价格 - 累加各模型费用得到准确的总费用 - 降级方案改用sonnet模型(中等价格)而非haiku(最低价格) 问题原因:之前使用固定的haiku模型计算所有token的费用,导致价格偏低 解决方案:获取模型级别的使用数据,按实际模型价格计算 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/routes/admin.js | 154 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 128 insertions(+), 26 deletions(-) diff --git a/src/routes/admin.js b/src/routes/admin.js index 213efd54..f0a06205 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -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); } }