mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
fix: 修复会话窗口使用统计问题
This commit is contained in:
@@ -1431,7 +1431,13 @@ class RedisClient {
|
||||
const startDate = new Date(windowStart)
|
||||
const endDate = new Date(windowEnd)
|
||||
|
||||
// 添加日志以调试时间窗口
|
||||
logger.debug(`📊 Getting session window usage for account ${accountId}`)
|
||||
logger.debug(` Window: ${windowStart} to ${windowEnd}`)
|
||||
logger.debug(` Start UTC: ${startDate.toISOString()}, End UTC: ${endDate.toISOString()}`)
|
||||
|
||||
// 获取窗口内所有可能的小时键
|
||||
// 重要:需要使用配置的时区来构建键名,因为数据存储时使用的是配置时区
|
||||
const hourlyKeys = []
|
||||
const currentHour = new Date(startDate)
|
||||
currentHour.setMinutes(0)
|
||||
@@ -1439,9 +1445,12 @@ class RedisClient {
|
||||
currentHour.setMilliseconds(0)
|
||||
|
||||
while (currentHour <= endDate) {
|
||||
const dateStr = `${currentHour.getUTCFullYear()}-${String(currentHour.getUTCMonth() + 1).padStart(2, '0')}-${String(currentHour.getUTCDate()).padStart(2, '0')}`
|
||||
const hourStr = String(currentHour.getUTCHours()).padStart(2, '0')
|
||||
const key = `account_usage:hourly:${accountId}:${dateStr}:${hourStr}`
|
||||
// 使用时区转换函数来获取正确的日期和小时
|
||||
const tzDateStr = getDateStringInTimezone(currentHour)
|
||||
const tzHour = String(getHourInTimezone(currentHour)).padStart(2, '0')
|
||||
const key = `account_usage:hourly:${accountId}:${tzDateStr}:${tzHour}`
|
||||
|
||||
logger.debug(` Adding hourly key: ${key}`)
|
||||
hourlyKeys.push(key)
|
||||
currentHour.setHours(currentHour.getHours() + 1)
|
||||
}
|
||||
@@ -1462,18 +1471,31 @@ class RedisClient {
|
||||
let totalRequests = 0
|
||||
const modelUsage = {}
|
||||
|
||||
logger.debug(` Processing ${results.length} hourly results`)
|
||||
|
||||
for (const [error, data] of results) {
|
||||
if (error || !data || Object.keys(data).length === 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
// 处理总计数据
|
||||
totalInputTokens += parseInt(data.inputTokens || 0)
|
||||
totalOutputTokens += parseInt(data.outputTokens || 0)
|
||||
totalCacheCreateTokens += parseInt(data.cacheCreateTokens || 0)
|
||||
totalCacheReadTokens += parseInt(data.cacheReadTokens || 0)
|
||||
totalAllTokens += parseInt(data.allTokens || 0)
|
||||
totalRequests += parseInt(data.requests || 0)
|
||||
const hourInputTokens = parseInt(data.inputTokens || 0)
|
||||
const hourOutputTokens = parseInt(data.outputTokens || 0)
|
||||
const hourCacheCreateTokens = parseInt(data.cacheCreateTokens || 0)
|
||||
const hourCacheReadTokens = parseInt(data.cacheReadTokens || 0)
|
||||
const hourAllTokens = parseInt(data.allTokens || 0)
|
||||
const hourRequests = parseInt(data.requests || 0)
|
||||
|
||||
totalInputTokens += hourInputTokens
|
||||
totalOutputTokens += hourOutputTokens
|
||||
totalCacheCreateTokens += hourCacheCreateTokens
|
||||
totalCacheReadTokens += hourCacheReadTokens
|
||||
totalAllTokens += hourAllTokens
|
||||
totalRequests += hourRequests
|
||||
|
||||
if (hourAllTokens > 0) {
|
||||
logger.debug(` Hour data: allTokens=${hourAllTokens}, requests=${hourRequests}`)
|
||||
}
|
||||
|
||||
// 处理每个模型的数据
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
@@ -1513,6 +1535,14 @@ class RedisClient {
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug(`📊 Session window usage summary:`)
|
||||
logger.debug(` Total allTokens: ${totalAllTokens}`)
|
||||
logger.debug(` Total requests: ${totalRequests}`)
|
||||
logger.debug(` Input: ${totalInputTokens}, Output: ${totalOutputTokens}`)
|
||||
logger.debug(
|
||||
` Cache Create: ${totalCacheCreateTokens}, Cache Read: ${totalCacheReadTokens}`
|
||||
)
|
||||
|
||||
return {
|
||||
totalInputTokens,
|
||||
totalOutputTokens,
|
||||
|
||||
@@ -1524,7 +1524,10 @@ router.get('/claude-accounts', authenticateAdmin, async (req, res) => {
|
||||
cache_read_input_tokens: usage.cacheReadTokens
|
||||
}
|
||||
|
||||
logger.debug(`💰 Calculating cost for model ${modelName}:`, JSON.stringify(usageData))
|
||||
const costResult = CostCalculator.calculateCost(usageData, modelName)
|
||||
logger.debug(`💰 Cost result for ${modelName}: total=${costResult.costs.total}`)
|
||||
|
||||
modelCosts[modelName] = {
|
||||
...usage,
|
||||
cost: costResult.costs.total
|
||||
|
||||
@@ -45,6 +45,7 @@ class PricingService {
|
||||
'claude-sonnet-3-5': 0.000006,
|
||||
'claude-sonnet-3-7': 0.000006,
|
||||
'claude-sonnet-4': 0.000006,
|
||||
'claude-sonnet-4-20250514': 0.000006,
|
||||
|
||||
// Haiku 系列: $1.6/MTok
|
||||
'claude-3-5-haiku': 0.0000016,
|
||||
@@ -260,6 +261,7 @@ class PricingService {
|
||||
|
||||
// 尝试直接匹配
|
||||
if (this.pricingData[modelName]) {
|
||||
logger.debug(`💰 Found exact pricing match for ${modelName}`)
|
||||
return this.pricingData[modelName]
|
||||
}
|
||||
|
||||
@@ -304,6 +306,20 @@ class PricingService {
|
||||
return null
|
||||
}
|
||||
|
||||
// 确保价格对象包含缓存价格
|
||||
ensureCachePricing(pricing) {
|
||||
if (!pricing) return pricing
|
||||
|
||||
// 如果缺少缓存价格,根据输入价格计算(缓存创建价格通常是输入价格的1.25倍,缓存读取是0.1倍)
|
||||
if (!pricing.cache_creation_input_token_cost && pricing.input_cost_per_token) {
|
||||
pricing.cache_creation_input_token_cost = pricing.input_cost_per_token * 1.25
|
||||
}
|
||||
if (!pricing.cache_read_input_token_cost && pricing.input_cost_per_token) {
|
||||
pricing.cache_read_input_token_cost = pricing.input_cost_per_token * 0.1
|
||||
}
|
||||
return pricing
|
||||
}
|
||||
|
||||
// 获取 1 小时缓存价格
|
||||
getEphemeral1hPricing(modelName) {
|
||||
if (!modelName) {
|
||||
|
||||
@@ -32,6 +32,14 @@ const MODEL_PRICING = {
|
||||
cacheRead: 1.5
|
||||
},
|
||||
|
||||
// Claude Opus 4.1 (新模型)
|
||||
'claude-opus-4-1-20250805': {
|
||||
input: 15.0,
|
||||
output: 75.0,
|
||||
cacheWrite: 18.75,
|
||||
cacheRead: 1.5
|
||||
},
|
||||
|
||||
// Claude 3 Sonnet
|
||||
'claude-3-sonnet-20240229': {
|
||||
input: 3.0,
|
||||
|
||||
Reference in New Issue
Block a user