fix: 修复 Anthropic TokenUsage 计算与流式响应的用量统计

This commit is contained in:
chenchun
2026-01-05 19:34:48 +08:00
parent 537104037b
commit 5157eac35c
2 changed files with 43 additions and 79 deletions

View File

@@ -18,39 +18,7 @@ public class AnthropicStreamDto
[JsonPropertyName("usage")] public AnthropicCompletionDtoUsage? Usage { get; set; }
[JsonPropertyName("error")] public AnthropicStreamErrorDto? Error { get; set; }
[JsonIgnore]
public ThorUsageResponse TokenUsage => new ThorUsageResponse
{
PromptTokens = Usage?.InputTokens??0 + Usage?.CacheCreationInputTokens??0 + Usage?.CacheReadInputTokens??0,
InputTokens = Usage?.InputTokens??0 + Usage?.CacheCreationInputTokens??0 + Usage?.CacheReadInputTokens??0,
OutputTokens = Usage?.OutputTokens??0,
InputTokensDetails = null,
CompletionTokens = Usage?.OutputTokens??0,
TotalTokens = Usage?.InputTokens??0 + Usage?.CacheCreationInputTokens??0 + Usage?.CacheReadInputTokens??0 +
Usage?.OutputTokens??0,
PromptTokensDetails = null,
CompletionTokensDetails = null
};
public void SupplementalMultiplier(decimal multiplier)
{
if (this.Usage is not null)
{
this.Usage.CacheCreationInputTokens =
(int)Math.Round((this.Usage.CacheCreationInputTokens ?? 0) * multiplier);
this.Usage.CacheReadInputTokens =
(int)Math.Round((this.Usage.CacheReadInputTokens ?? 0) * multiplier);
this.Usage.InputTokens =
(int)Math.Round((this.Usage.InputTokens ?? 0) * multiplier);
this.Usage.OutputTokens =
(int)Math.Round((this.Usage.OutputTokens ?? 0) * multiplier);
}
}
}
public class AnthropicStreamErrorDto
@@ -115,38 +83,7 @@ public class AnthropicChatCompletionDto
public object stop_sequence { get; set; }
public AnthropicCompletionDtoUsage? Usage { get; set; }
[JsonIgnore]
public ThorUsageResponse TokenUsage => new ThorUsageResponse
{
PromptTokens = Usage?.InputTokens??0 + Usage?.CacheCreationInputTokens??0 + Usage?.CacheReadInputTokens??0,
InputTokens = Usage?.InputTokens??0 + Usage?.CacheCreationInputTokens??0 + Usage?.CacheReadInputTokens??0,
OutputTokens = Usage?.OutputTokens??0,
InputTokensDetails = null,
CompletionTokens = Usage?.OutputTokens??0,
TotalTokens = Usage?.InputTokens??0 + Usage?.CacheCreationInputTokens??0 + Usage?.CacheReadInputTokens??0 +
Usage?.OutputTokens??0,
PromptTokensDetails = null,
CompletionTokensDetails = null
};
public void SupplementalMultiplier(decimal multiplier)
{
if (this.Usage is not null)
{
this.Usage.CacheCreationInputTokens =
(int)Math.Round((this.Usage?.CacheCreationInputTokens ?? 0) * multiplier);
this.Usage.CacheReadInputTokens =
(int)Math.Round((this.Usage?.CacheReadInputTokens ?? 0) * multiplier);
this.Usage.InputTokens =
(int)Math.Round((this.Usage?.InputTokens ?? 0) * multiplier);
this.Usage.OutputTokens =
(int)Math.Round((this.Usage?.OutputTokens ?? 0) * multiplier);
}
}
}
public class AnthropicChatCompletionDtoContent

View File

@@ -549,7 +549,16 @@ public class AiGateWayManager : DomainService
LazyServiceProvider.GetRequiredKeyedService<IAnthropicChatCompletionService>(modelDescribe.HandlerName);
var data = await chatService.ChatCompletionsAsync(modelDescribe, request, cancellationToken);
data.SupplementalMultiplier(modelDescribe.Multiplier);
var currentUsage = data.Usage;
ThorUsageResponse tokenUsage = new ThorUsageResponse
{
InputTokens = (currentUsage?.InputTokens??0) + (currentUsage?.CacheCreationInputTokens??0)+ (currentUsage?.CacheReadInputTokens??0),
OutputTokens = (currentUsage?.OutputTokens??0),
TotalTokens = (currentUsage?.InputTokens??0) + (currentUsage?.CacheCreationInputTokens??0)+ (currentUsage?.CacheReadInputTokens??0)+(currentUsage?.OutputTokens??0)
};
tokenUsage.SetSupplementalMultiplier(modelDescribe.Multiplier);
if (userId is not null)
{
@@ -558,7 +567,7 @@ public class AiGateWayManager : DomainService
{
Content = "不予存储",
ModelId = sourceModelId,
TokenUsage = data.TokenUsage,
TokenUsage = tokenUsage,
}, tokenId);
await _aiMessageManager.CreateSystemMessageAsync(userId.Value, sessionId,
@@ -566,13 +575,13 @@ public class AiGateWayManager : DomainService
{
Content = "不予存储",
ModelId = sourceModelId,
TokenUsage = data.TokenUsage
TokenUsage = tokenUsage
}, tokenId);
await _usageStatisticsManager.SetUsageAsync(userId.Value, sourceModelId, data.TokenUsage, tokenId);
await _usageStatisticsManager.SetUsageAsync(userId.Value, sourceModelId, tokenUsage, tokenId);
// 直接扣减尊享token包用量
var totalTokens = data.TokenUsage.TotalTokens ?? 0;
var totalTokens = tokenUsage.TotalTokens ?? 0;
if (totalTokens > 0)
{
await PremiumPackageManager.TryConsumeTokensAsync(userId.Value, totalTokens);
@@ -620,24 +629,41 @@ public class AiGateWayManager : DomainService
}
var completeChatResponse = chatService.StreamChatCompletionsAsync(modelDescribe, request, cancellationToken);
ThorUsageResponse? tokenUsage = null;
StringBuilder backupSystemContent = new StringBuilder();
ThorUsageResponse? tokenUsage = new ThorUsageResponse();
try
{
await foreach (var responseResult in completeChatResponse)
{
responseResult.Item2.SupplementalMultiplier(modelDescribe.Multiplier);
//message_start是为了保底机制
if (responseResult.Item1.Contains("message_delta") || responseResult.Item1.Contains("message_start")||responseResult.Item1.Contains("message_stop"))
//部分供应商message_start放一部分
if (responseResult.Item1.Contains("message_start"))
{
if (responseResult.Item2?.TokenUsage is not null&&responseResult.Item2?.TokenUsage.TotalTokens>0)
var currentTokenUsage = responseResult.Item2.Message.Usage;
if ((currentTokenUsage.InputTokens ?? 0) != 0)
{
tokenUsage = responseResult.Item2?.TokenUsage;
tokenUsage.InputTokens = (currentTokenUsage?.InputTokens??0) + (currentTokenUsage?.CacheCreationInputTokens??0)+ (currentTokenUsage?.CacheReadInputTokens??0);
}
if ((currentTokenUsage.OutputTokens ?? 0) != 0)
{
tokenUsage.OutputTokens = currentTokenUsage.OutputTokens;
}
}
backupSystemContent.Append(responseResult.Item2?.Delta?.Text);
//message_delta又放一部分
if (responseResult.Item1.Contains("message_delta"))
{
var currentTokenUsage = responseResult.Item2.Usage;
if ((currentTokenUsage.InputTokens ?? 0) != 0)
{
tokenUsage.InputTokens = (currentTokenUsage?.InputTokens??0) + (currentTokenUsage?.CacheCreationInputTokens??0)+ (currentTokenUsage?.CacheReadInputTokens??0);;
}
if ((currentTokenUsage.OutputTokens ?? 0) != 0)
{
tokenUsage.OutputTokens = currentTokenUsage.OutputTokens;
}
}
await WriteAsEventStreamDataAsync(httpContext, responseResult.Item1, responseResult.Item2,
cancellationToken);
}
@@ -648,7 +674,8 @@ public class AiGateWayManager : DomainService
var errorContent = $"对话Ai异常异常信息\n当前Ai模型{sourceModelId}\n异常信息{e.Message}\n异常堆栈{e}";
throw new UserFriendlyException(errorContent);
}
tokenUsage.TotalTokens = (tokenUsage.InputTokens ?? 0) + (tokenUsage.OutputTokens ?? 0);
tokenUsage.SetSupplementalMultiplier(modelDescribe.Multiplier);
await _aiMessageManager.CreateUserMessageAsync(userId, sessionId,
new MessageInputDto
{