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 startDate = new Date(windowStart)
|
||||||
const endDate = new Date(windowEnd)
|
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 hourlyKeys = []
|
||||||
const currentHour = new Date(startDate)
|
const currentHour = new Date(startDate)
|
||||||
currentHour.setMinutes(0)
|
currentHour.setMinutes(0)
|
||||||
@@ -1439,9 +1445,12 @@ class RedisClient {
|
|||||||
currentHour.setMilliseconds(0)
|
currentHour.setMilliseconds(0)
|
||||||
|
|
||||||
while (currentHour <= endDate) {
|
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 tzDateStr = getDateStringInTimezone(currentHour)
|
||||||
const key = `account_usage:hourly:${accountId}:${dateStr}:${hourStr}`
|
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)
|
hourlyKeys.push(key)
|
||||||
currentHour.setHours(currentHour.getHours() + 1)
|
currentHour.setHours(currentHour.getHours() + 1)
|
||||||
}
|
}
|
||||||
@@ -1462,18 +1471,31 @@ class RedisClient {
|
|||||||
let totalRequests = 0
|
let totalRequests = 0
|
||||||
const modelUsage = {}
|
const modelUsage = {}
|
||||||
|
|
||||||
|
logger.debug(` Processing ${results.length} hourly results`)
|
||||||
|
|
||||||
for (const [error, data] of results) {
|
for (const [error, data] of results) {
|
||||||
if (error || !data || Object.keys(data).length === 0) {
|
if (error || !data || Object.keys(data).length === 0) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理总计数据
|
// 处理总计数据
|
||||||
totalInputTokens += parseInt(data.inputTokens || 0)
|
const hourInputTokens = parseInt(data.inputTokens || 0)
|
||||||
totalOutputTokens += parseInt(data.outputTokens || 0)
|
const hourOutputTokens = parseInt(data.outputTokens || 0)
|
||||||
totalCacheCreateTokens += parseInt(data.cacheCreateTokens || 0)
|
const hourCacheCreateTokens = parseInt(data.cacheCreateTokens || 0)
|
||||||
totalCacheReadTokens += parseInt(data.cacheReadTokens || 0)
|
const hourCacheReadTokens = parseInt(data.cacheReadTokens || 0)
|
||||||
totalAllTokens += parseInt(data.allTokens || 0)
|
const hourAllTokens = parseInt(data.allTokens || 0)
|
||||||
totalRequests += parseInt(data.requests || 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)) {
|
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 {
|
return {
|
||||||
totalInputTokens,
|
totalInputTokens,
|
||||||
totalOutputTokens,
|
totalOutputTokens,
|
||||||
|
|||||||
@@ -1524,7 +1524,10 @@ router.get('/claude-accounts', authenticateAdmin, async (req, res) => {
|
|||||||
cache_read_input_tokens: usage.cacheReadTokens
|
cache_read_input_tokens: usage.cacheReadTokens
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.debug(`💰 Calculating cost for model ${modelName}:`, JSON.stringify(usageData))
|
||||||
const costResult = CostCalculator.calculateCost(usageData, modelName)
|
const costResult = CostCalculator.calculateCost(usageData, modelName)
|
||||||
|
logger.debug(`💰 Cost result for ${modelName}: total=${costResult.costs.total}`)
|
||||||
|
|
||||||
modelCosts[modelName] = {
|
modelCosts[modelName] = {
|
||||||
...usage,
|
...usage,
|
||||||
cost: costResult.costs.total
|
cost: costResult.costs.total
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ class PricingService {
|
|||||||
'claude-sonnet-3-5': 0.000006,
|
'claude-sonnet-3-5': 0.000006,
|
||||||
'claude-sonnet-3-7': 0.000006,
|
'claude-sonnet-3-7': 0.000006,
|
||||||
'claude-sonnet-4': 0.000006,
|
'claude-sonnet-4': 0.000006,
|
||||||
|
'claude-sonnet-4-20250514': 0.000006,
|
||||||
|
|
||||||
// Haiku 系列: $1.6/MTok
|
// Haiku 系列: $1.6/MTok
|
||||||
'claude-3-5-haiku': 0.0000016,
|
'claude-3-5-haiku': 0.0000016,
|
||||||
@@ -260,6 +261,7 @@ class PricingService {
|
|||||||
|
|
||||||
// 尝试直接匹配
|
// 尝试直接匹配
|
||||||
if (this.pricingData[modelName]) {
|
if (this.pricingData[modelName]) {
|
||||||
|
logger.debug(`💰 Found exact pricing match for ${modelName}`)
|
||||||
return this.pricingData[modelName]
|
return this.pricingData[modelName]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,6 +306,20 @@ class PricingService {
|
|||||||
return null
|
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 小时缓存价格
|
// 获取 1 小时缓存价格
|
||||||
getEphemeral1hPricing(modelName) {
|
getEphemeral1hPricing(modelName) {
|
||||||
if (!modelName) {
|
if (!modelName) {
|
||||||
|
|||||||
@@ -31,6 +31,14 @@ const MODEL_PRICING = {
|
|||||||
cacheWrite: 18.75,
|
cacheWrite: 18.75,
|
||||||
cacheRead: 1.5
|
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
|
||||||
'claude-3-sonnet-20240229': {
|
'claude-3-sonnet-20240229': {
|
||||||
|
|||||||
Reference in New Issue
Block a user