mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
fix: 修复claude转发usage统计问题
This commit is contained in:
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
// 处理限流状态
|
||||
|
||||
Reference in New Issue
Block a user