fix: 添加对 ephemeral 5m 和 1h 令牌的支持,优化费用计算逻辑

This commit is contained in:
sczheng189
2026-02-23 20:12:42 +08:00
parent 3b25cf01ad
commit 5376428dd9
7 changed files with 131 additions and 11 deletions

2
.gitignore vendored
View File

@@ -247,3 +247,5 @@ web/apiStats/
# Admin SPA build files
web/admin-spa/dist/
.serena/

View File

@@ -1084,6 +1084,9 @@ class RedisClient {
pipeline.hincrby(modelDaily, 'cacheReadTokens', finalCacheReadTokens)
pipeline.hincrby(modelDaily, 'allTokens', totalTokens)
pipeline.hincrby(modelDaily, 'requests', 1)
// 详细缓存类型统计
pipeline.hincrby(modelDaily, 'ephemeral5mTokens', ephemeral5mTokens)
pipeline.hincrby(modelDaily, 'ephemeral1hTokens', ephemeral1hTokens)
// 按模型统计 - 每月
pipeline.hincrby(modelMonthly, 'inputTokens', finalInputTokens)
@@ -1092,6 +1095,9 @@ class RedisClient {
pipeline.hincrby(modelMonthly, 'cacheReadTokens', finalCacheReadTokens)
pipeline.hincrby(modelMonthly, 'allTokens', totalTokens)
pipeline.hincrby(modelMonthly, 'requests', 1)
// 详细缓存类型统计
pipeline.hincrby(modelMonthly, 'ephemeral5mTokens', ephemeral5mTokens)
pipeline.hincrby(modelMonthly, 'ephemeral1hTokens', ephemeral1hTokens)
// API Key级别的模型统计 - 每日
pipeline.hincrby(keyModelDaily, 'inputTokens', finalInputTokens)
@@ -1136,6 +1142,9 @@ class RedisClient {
pipeline.hincrby(keyModelAlltime, 'cacheCreateTokens', finalCacheCreateTokens)
pipeline.hincrby(keyModelAlltime, 'cacheReadTokens', finalCacheReadTokens)
pipeline.hincrby(keyModelAlltime, 'requests', 1)
// 详细缓存类型统计
pipeline.hincrby(keyModelAlltime, 'ephemeral5mTokens', ephemeral5mTokens)
pipeline.hincrby(keyModelAlltime, 'ephemeral1hTokens', ephemeral1hTokens)
// 费用统计
if (realCost > 0) {
pipeline.hincrby(keyModelAlltime, 'realCostMicro', Math.round(realCost * 1000000))
@@ -1152,6 +1161,9 @@ class RedisClient {
pipeline.hincrby(hourly, 'cacheReadTokens', finalCacheReadTokens)
pipeline.hincrby(hourly, 'allTokens', totalTokens)
pipeline.hincrby(hourly, 'requests', 1)
// 详细缓存类型统计
pipeline.hincrby(hourly, 'ephemeral5mTokens', ephemeral5mTokens)
pipeline.hincrby(hourly, 'ephemeral1hTokens', ephemeral1hTokens)
// 按模型统计 - 每小时
pipeline.hincrby(modelHourly, 'inputTokens', finalInputTokens)
@@ -1160,6 +1172,9 @@ class RedisClient {
pipeline.hincrby(modelHourly, 'cacheReadTokens', finalCacheReadTokens)
pipeline.hincrby(modelHourly, 'allTokens', totalTokens)
pipeline.hincrby(modelHourly, 'requests', 1)
// 详细缓存类型统计
pipeline.hincrby(modelHourly, 'ephemeral5mTokens', ephemeral5mTokens)
pipeline.hincrby(modelHourly, 'ephemeral1hTokens', ephemeral1hTokens)
// API Key级别的模型统计 - 每小时
pipeline.hincrby(keyModelHourly, 'inputTokens', finalInputTokens)
@@ -1168,6 +1183,9 @@ class RedisClient {
pipeline.hincrby(keyModelHourly, 'cacheReadTokens', finalCacheReadTokens)
pipeline.hincrby(keyModelHourly, 'allTokens', totalTokens)
pipeline.hincrby(keyModelHourly, 'requests', 1)
// 详细缓存类型统计
pipeline.hincrby(keyModelHourly, 'ephemeral5mTokens', ephemeral5mTokens)
pipeline.hincrby(keyModelHourly, 'ephemeral1hTokens', ephemeral1hTokens)
// 费用统计
if (realCost > 0) {
pipeline.hincrby(keyModelHourly, 'realCostMicro', Math.round(realCost * 1000000))
@@ -1235,18 +1253,24 @@ class RedisClient {
pipeline.hincrby('usage:global:total', 'cacheCreateTokens', finalCacheCreateTokens)
pipeline.hincrby('usage:global:total', 'cacheReadTokens', finalCacheReadTokens)
pipeline.hincrby('usage:global:total', 'allTokens', totalTokens)
pipeline.hincrby('usage:global:total', 'ephemeral5mTokens', ephemeral5mTokens)
pipeline.hincrby('usage:global:total', 'ephemeral1hTokens', ephemeral1hTokens)
pipeline.hincrby(globalDaily, 'requests', 1)
pipeline.hincrby(globalDaily, 'inputTokens', finalInputTokens)
pipeline.hincrby(globalDaily, 'outputTokens', finalOutputTokens)
pipeline.hincrby(globalDaily, 'cacheCreateTokens', finalCacheCreateTokens)
pipeline.hincrby(globalDaily, 'cacheReadTokens', finalCacheReadTokens)
pipeline.hincrby(globalDaily, 'allTokens', totalTokens)
pipeline.hincrby(globalDaily, 'ephemeral5mTokens', ephemeral5mTokens)
pipeline.hincrby(globalDaily, 'ephemeral1hTokens', ephemeral1hTokens)
pipeline.hincrby(globalMonthly, 'requests', 1)
pipeline.hincrby(globalMonthly, 'inputTokens', finalInputTokens)
pipeline.hincrby(globalMonthly, 'outputTokens', finalOutputTokens)
pipeline.hincrby(globalMonthly, 'cacheCreateTokens', finalCacheCreateTokens)
pipeline.hincrby(globalMonthly, 'cacheReadTokens', finalCacheReadTokens)
pipeline.hincrby(globalMonthly, 'allTokens', totalTokens)
pipeline.hincrby(globalMonthly, 'ephemeral5mTokens', ephemeral5mTokens)
pipeline.hincrby(globalMonthly, 'ephemeral1hTokens', ephemeral1hTokens)
pipeline.expire(globalDaily, 86400 * 32)
pipeline.expire(globalMonthly, 86400 * 365)

View File

@@ -1289,6 +1289,8 @@ async function calculateKeyStats(keyId, timeRange, startDate, endDate) {
outputTokens: 0,
cacheCreateTokens: 0,
cacheReadTokens: 0,
ephemeral5mTokens: 0,
ephemeral1hTokens: 0,
requests: 0
})
}
@@ -1300,6 +1302,10 @@ async function calculateKeyStats(keyId, timeRange, startDate, endDate) {
parseInt(data.totalCacheCreateTokens) || parseInt(data.cacheCreateTokens) || 0
stats.cacheReadTokens +=
parseInt(data.totalCacheReadTokens) || parseInt(data.cacheReadTokens) || 0
stats.ephemeral5mTokens +=
parseInt(data.totalEphemeral5mTokens) || parseInt(data.ephemeral5mTokens) || 0
stats.ephemeral1hTokens +=
parseInt(data.totalEphemeral1hTokens) || parseInt(data.ephemeral1hTokens) || 0
stats.requests += parseInt(data.totalRequests) || parseInt(data.requests) || 0
totalRequests += parseInt(data.totalRequests) || parseInt(data.requests) || 0
@@ -1318,15 +1324,22 @@ async function calculateKeyStats(keyId, timeRange, startDate, endDate) {
cacheCreateTokens += stats.cacheCreateTokens
cacheReadTokens += stats.cacheReadTokens
const costResult = CostCalculator.calculateCost(
{
input_tokens: stats.inputTokens,
output_tokens: stats.outputTokens,
cache_creation_input_tokens: stats.cacheCreateTokens,
cache_read_input_tokens: stats.cacheReadTokens
},
model
)
const costUsage = {
input_tokens: stats.inputTokens,
output_tokens: stats.outputTokens,
cache_creation_input_tokens: stats.cacheCreateTokens,
cache_read_input_tokens: stats.cacheReadTokens
}
// 如果有 ephemeral 5m/1h 拆分数据,添加 cache_creation 子对象以实现精确计费
if (stats.ephemeral5mTokens > 0 || stats.ephemeral1hTokens > 0) {
costUsage.cache_creation = {
ephemeral_5m_input_tokens: stats.ephemeral5mTokens,
ephemeral_1h_input_tokens: stats.ephemeral1hTokens
}
}
const costResult = CostCalculator.calculateCost(costUsage, model)
totalCost += costResult.costs.total
}

View File

@@ -472,7 +472,9 @@ router.get('/model-stats', authenticateAdmin, async (req, res) => {
outputTokens: 0,
cacheCreateTokens: 0,
cacheReadTokens: 0,
allTokens: 0
allTokens: 0,
ephemeral5mTokens: 0,
ephemeral1hTokens: 0
}
stats.requests += parseInt(data.requests) || 0
@@ -481,6 +483,8 @@ router.get('/model-stats', authenticateAdmin, async (req, res) => {
stats.cacheCreateTokens += parseInt(data.cacheCreateTokens) || 0
stats.cacheReadTokens += parseInt(data.cacheReadTokens) || 0
stats.allTokens += parseInt(data.allTokens) || 0
stats.ephemeral5mTokens += parseInt(data.ephemeral5mTokens) || 0
stats.ephemeral1hTokens += parseInt(data.ephemeral1hTokens) || 0
modelStatsMap.set(normalizedModel, stats)
}
@@ -497,6 +501,14 @@ router.get('/model-stats', authenticateAdmin, async (req, res) => {
cache_read_input_tokens: stats.cacheReadTokens
}
// 如果有 ephemeral 5m/1h 拆分数据,添加 cache_creation 子对象以实现精确计费
if (stats.ephemeral5mTokens > 0 || stats.ephemeral1hTokens > 0) {
usage.cache_creation = {
ephemeral_5m_input_tokens: stats.ephemeral5mTokens,
ephemeral_1h_input_tokens: stats.ephemeral1hTokens
}
}
// 计算费用
const costData = CostCalculator.calculateCost(usage, model)

View File

@@ -786,6 +786,8 @@ router.get('/usage-trend', authenticateAdmin, async (req, res) => {
const modelOutputTokens = parseInt(data.outputTokens) || 0
const modelCacheCreateTokens = parseInt(data.cacheCreateTokens) || 0
const modelCacheReadTokens = parseInt(data.cacheReadTokens) || 0
const modelEphemeral5mTokens = parseInt(data.ephemeral5mTokens) || 0
const modelEphemeral1hTokens = parseInt(data.ephemeral1hTokens) || 0
const modelRequests = parseInt(data.requests) || 0
dayInputTokens += modelInputTokens
@@ -800,6 +802,15 @@ router.get('/usage-trend', authenticateAdmin, async (req, res) => {
cache_creation_input_tokens: modelCacheCreateTokens,
cache_read_input_tokens: modelCacheReadTokens
}
// 如果有 ephemeral 5m/1h 拆分数据,添加 cache_creation 子对象以实现精确计费
if (modelEphemeral5mTokens > 0 || modelEphemeral1hTokens > 0) {
modelUsage.cache_creation = {
ephemeral_5m_input_tokens: modelEphemeral5mTokens,
ephemeral_1h_input_tokens: modelEphemeral1hTokens
}
}
const modelCostResult = CostCalculator.calculateCost(modelUsage, model)
dayCost += modelCostResult.costs.total
}
@@ -948,6 +959,8 @@ router.get('/api-keys/:keyId/model-stats', authenticateAdmin, async (req, res) =
outputTokens: 0,
cacheCreateTokens: 0,
cacheReadTokens: 0,
ephemeral5mTokens: 0,
ephemeral1hTokens: 0,
allTokens: 0
})
}
@@ -957,6 +970,8 @@ router.get('/api-keys/:keyId/model-stats', authenticateAdmin, async (req, res) =
stats.outputTokens += parseInt(data.outputTokens) || 0
stats.cacheCreateTokens += parseInt(data.cacheCreateTokens) || 0
stats.cacheReadTokens += parseInt(data.cacheReadTokens) || 0
stats.ephemeral5mTokens += parseInt(data.ephemeral5mTokens) || 0
stats.ephemeral1hTokens += parseInt(data.ephemeral1hTokens) || 0
stats.allTokens += parseInt(data.allTokens) || 0
}
}
@@ -992,6 +1007,8 @@ router.get('/api-keys/:keyId/model-stats', authenticateAdmin, async (req, res) =
outputTokens: 0,
cacheCreateTokens: 0,
cacheReadTokens: 0,
ephemeral5mTokens: 0,
ephemeral1hTokens: 0,
allTokens: 0
})
}
@@ -1001,6 +1018,8 @@ router.get('/api-keys/:keyId/model-stats', authenticateAdmin, async (req, res) =
stats.outputTokens += parseInt(data.outputTokens) || 0
stats.cacheCreateTokens += parseInt(data.cacheCreateTokens) || 0
stats.cacheReadTokens += parseInt(data.cacheReadTokens) || 0
stats.ephemeral5mTokens += parseInt(data.ephemeral5mTokens) || 0
stats.ephemeral1hTokens += parseInt(data.ephemeral1hTokens) || 0
stats.allTokens += parseInt(data.allTokens) || 0
}
}
@@ -1016,6 +1035,14 @@ router.get('/api-keys/:keyId/model-stats', authenticateAdmin, async (req, res) =
cache_read_input_tokens: stats.cacheReadTokens
}
// 如果有 ephemeral 5m/1h 拆分数据,添加 cache_creation 子对象以实现精确计费
if (stats.ephemeral5mTokens > 0 || stats.ephemeral1hTokens > 0) {
usage.cache_creation = {
ephemeral_5m_input_tokens: stats.ephemeral5mTokens,
ephemeral_1h_input_tokens: stats.ephemeral1hTokens
}
}
// 使用CostCalculator计算费用
const costData = CostCalculator.calculateCost(usage, model)
@@ -1424,6 +1451,16 @@ router.get('/account-usage-trend', authenticateAdmin, async (req, res) => {
cache_read_input_tokens: parseInt(modelData.cacheReadTokens) || 0
}
// 如果有 ephemeral 5m/1h 拆分数据,添加 cache_creation 子对象以实现精确计费
const eph5m = parseInt(modelData.ephemeral5mTokens) || 0
const eph1h = parseInt(modelData.ephemeral1hTokens) || 0
if (eph5m > 0 || eph1h > 0) {
usage.cache_creation = {
ephemeral_5m_input_tokens: eph5m,
ephemeral_1h_input_tokens: eph1h
}
}
const costResult = CostCalculator.calculateCost(usage, modelName)
cost += costResult.costs.total
}
@@ -1582,6 +1619,16 @@ router.get('/account-usage-trend', authenticateAdmin, async (req, res) => {
cache_read_input_tokens: parseInt(modelData.cacheReadTokens) || 0
}
// 如果有 ephemeral 5m/1h 拆分数据,添加 cache_creation 子对象以实现精确计费
const eph5m = parseInt(modelData.ephemeral5mTokens) || 0
const eph1h = parseInt(modelData.ephemeral1hTokens) || 0
if (eph5m > 0 || eph1h > 0) {
usage.cache_creation = {
ephemeral_5m_input_tokens: eph5m,
ephemeral_1h_input_tokens: eph1h
}
}
const costResult = CostCalculator.calculateCost(usage, modelName)
cost += costResult.costs.total
}

View File

@@ -270,7 +270,9 @@ router.post('/api/user-stats', async (req, res) => {
inputTokens: 0,
outputTokens: 0,
cacheCreateTokens: 0,
cacheReadTokens: 0
cacheReadTokens: 0,
ephemeral5mTokens: 0,
ephemeral1hTokens: 0
})
}
@@ -279,6 +281,8 @@ router.post('/api/user-stats', async (req, res) => {
modelUsage.outputTokens += parseInt(data.outputTokens) || 0
modelUsage.cacheCreateTokens += parseInt(data.cacheCreateTokens) || 0
modelUsage.cacheReadTokens += parseInt(data.cacheReadTokens) || 0
modelUsage.ephemeral5mTokens += parseInt(data.ephemeral5mTokens) || 0
modelUsage.ephemeral1hTokens += parseInt(data.ephemeral1hTokens) || 0
}
}
@@ -291,6 +295,14 @@ router.post('/api/user-stats', async (req, res) => {
cache_read_input_tokens: usage.cacheReadTokens
}
// 如果有 ephemeral 5m/1h 拆分数据,添加 cache_creation 子对象以实现精确计费
if (usage.ephemeral5mTokens > 0 || usage.ephemeral1hTokens > 0) {
usageData.cache_creation = {
ephemeral_5m_input_tokens: usage.ephemeral5mTokens,
ephemeral_1h_input_tokens: usage.ephemeral1hTokens
}
}
const costResult = CostCalculator.calculateCost(usageData, model)
totalCost += costResult.costs.total
}

View File

@@ -607,6 +607,16 @@ class AccountBalanceService {
cache_read_input_tokens: parseInt(data.cacheReadTokens || 0)
}
// 如果有 ephemeral 5m/1h 拆分数据,添加 cache_creation 子对象以实现精确计费
const eph5m = parseInt(data.ephemeral5mTokens || 0)
const eph1h = parseInt(data.ephemeral1hTokens || 0)
if (eph5m > 0 || eph1h > 0) {
usage.cache_creation = {
ephemeral_5m_input_tokens: eph5m,
ephemeral_1h_input_tokens: eph1h
}
}
const costResult = CostCalculator.calculateCost(usage, model)
totalCost += costResult.costs.total || 0
}