fix: 修复claude转发usage统计问题

This commit is contained in:
shaw
2025-08-17 00:03:17 +08:00
parent 043e3768ab
commit 4643e471ee

View File

@@ -269,17 +269,39 @@ class ClaudeRelayService {
}
}
// 记录成功的API调用
const inputTokens = requestBody.messages
? requestBody.messages.reduce((sum, msg) => sum + (msg.content?.length || 0), 0) / 4
: 0 // 粗略估算
const outputTokens = response.content
? response.content.reduce((sum, content) => sum + (content.text?.length || 0), 0) / 4
: 0
// 记录成功的API调用并打印详细的usage数据
let responseBody = null
try {
responseBody = typeof response.body === 'string' ? JSON.parse(response.body) : response.body
} catch (e) {
logger.debug('Failed to parse response body for usage logging')
}
logger.info(
`✅ API request completed - Key: ${apiKeyData.name}, Account: ${accountId}, Model: ${requestBody.model}, Input: ~${Math.round(inputTokens)} tokens, Output: ~${Math.round(outputTokens)} tokens`
)
if (responseBody && responseBody.usage) {
const { usage } = responseBody
logger.info('📊 === Non-Stream Request Usage Summary ===', {
request_model: requestBody.model,
response_model: responseBody.model || requestBody.model,
input_tokens: usage.input_tokens || 0,
output_tokens: usage.output_tokens || 0,
cache_creation_tokens: usage.cache_creation_input_tokens || 0,
cache_read_tokens: usage.cache_read_input_tokens || 0,
api_key: apiKeyData.name,
account_id: accountId
})
} else {
// 如果没有usage数据使用估算值
const inputTokens = requestBody.messages
? requestBody.messages.reduce((sum, msg) => sum + (msg.content?.length || 0), 0) / 4
: 0
const outputTokens = response.content
? response.content.reduce((sum, content) => sum + (content.text?.length || 0), 0) / 4
: 0
logger.info(
`✅ API request completed - Key: ${apiKeyData.name}, Account: ${accountId}, Model: ${requestBody.model}, Input: ~${Math.round(inputTokens)} tokens (estimated), Output: ~${Math.round(outputTokens)} tokens (estimated)`
)
}
// 在响应中添加accountId以便调用方记录账户级别统计
response.accountId = accountId
@@ -893,8 +915,8 @@ class ClaudeRelayService {
}
let buffer = ''
let finalUsageReported = false // 防止重复统计的标志
const collectedUsageData = {} // 收集来自不同事件的usage数据
const allUsageData = [] // 收集所有的usage事件
let currentUsageData = {} // 当前正在收集的usage数据
let rateLimitDetected = false // 限流检测标志
// 监听数据块解析SSE并寻找usage信息
@@ -931,34 +953,43 @@ class ClaudeRelayService {
// 收集来自不同事件的usage数据
if (data.type === 'message_start' && data.message && data.message.usage) {
// 新的消息开始,如果之前有数据,先保存
if (
currentUsageData.input_tokens !== undefined &&
currentUsageData.output_tokens !== undefined
) {
allUsageData.push({ ...currentUsageData })
currentUsageData = {}
}
// message_start包含input tokens、cache tokens和模型信息
collectedUsageData.input_tokens = data.message.usage.input_tokens || 0
collectedUsageData.cache_creation_input_tokens =
currentUsageData.input_tokens = data.message.usage.input_tokens || 0
currentUsageData.cache_creation_input_tokens =
data.message.usage.cache_creation_input_tokens || 0
collectedUsageData.cache_read_input_tokens =
currentUsageData.cache_read_input_tokens =
data.message.usage.cache_read_input_tokens || 0
collectedUsageData.model = data.message.model
currentUsageData.model = data.message.model
// 检查是否有详细的 cache_creation 对象
if (
data.message.usage.cache_creation &&
typeof data.message.usage.cache_creation === 'object'
) {
collectedUsageData.cache_creation = {
currentUsageData.cache_creation = {
ephemeral_5m_input_tokens:
data.message.usage.cache_creation.ephemeral_5m_input_tokens || 0,
ephemeral_1h_input_tokens:
data.message.usage.cache_creation.ephemeral_1h_input_tokens || 0
}
logger.info(
logger.debug(
'📊 Collected detailed cache creation data:',
JSON.stringify(collectedUsageData.cache_creation)
JSON.stringify(currentUsageData.cache_creation)
)
}
logger.info(
logger.debug(
'📊 Collected input/cache data from message_start:',
JSON.stringify(collectedUsageData)
JSON.stringify(currentUsageData)
)
}
@@ -968,18 +999,25 @@ class ClaudeRelayService {
data.usage &&
data.usage.output_tokens !== undefined
) {
collectedUsageData.output_tokens = data.usage.output_tokens || 0
currentUsageData.output_tokens = data.usage.output_tokens || 0
logger.info(
logger.debug(
'📊 Collected output data from message_delta:',
JSON.stringify(collectedUsageData)
JSON.stringify(currentUsageData)
)
// 如果已经收集到了input数据,现在有了output数据可以统计了
if (collectedUsageData.input_tokens !== undefined && !finalUsageReported) {
logger.info('🎯 Complete usage data collected, triggering callback')
usageCallback(collectedUsageData)
finalUsageReported = true
// 如果已经收集到了input数据output数据这是一个完整的usage
if (currentUsageData.input_tokens !== undefined) {
logger.info(
'🎯 Complete usage data collected for model:',
currentUsageData.model
)
// 触发回调记录这个usage
usageCallback(currentUsageData)
// 保存到列表中
allUsageData.push({ ...currentUsageData })
// 重置当前数据,准备接收下一个
currentUsageData = {}
}
}
@@ -1037,11 +1075,45 @@ class ClaudeRelayService {
logger.error('❌ Error processing stream end:', error)
}
// 如果还有未完成的usage数据尝试保存
if (currentUsageData.input_tokens !== undefined) {
if (currentUsageData.output_tokens === undefined) {
currentUsageData.output_tokens = 0 // 如果没有output设为0
}
usageCallback(currentUsageData)
allUsageData.push(currentUsageData)
}
// 检查是否捕获到usage数据
if (!finalUsageReported) {
if (allUsageData.length === 0) {
logger.warn(
'⚠️ Stream completed but no usage data was captured! This indicates a problem with SSE parsing or Claude API response format.'
)
} else {
// 打印此次请求的所有usage数据汇总
const totalUsage = allUsageData.reduce(
(acc, usage) => ({
input_tokens: (acc.input_tokens || 0) + (usage.input_tokens || 0),
output_tokens: (acc.output_tokens || 0) + (usage.output_tokens || 0),
cache_creation_input_tokens:
(acc.cache_creation_input_tokens || 0) + (usage.cache_creation_input_tokens || 0),
cache_read_input_tokens:
(acc.cache_read_input_tokens || 0) + (usage.cache_read_input_tokens || 0),
models: [...(acc.models || []), usage.model].filter(Boolean)
}),
{}
)
logger.info('📊 === Stream Request Usage Summary ===', {
request_body_model: body.model,
total_input_tokens: totalUsage.input_tokens,
total_output_tokens: totalUsage.output_tokens,
total_cache_creation: totalUsage.cache_creation_input_tokens,
total_cache_read: totalUsage.cache_read_input_tokens,
models_used: [...new Set(totalUsage.models)],
usage_events_count: allUsageData.length,
detailed_usage: allUsageData
})
}
// 处理限流状态