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

@@ -19,38 +19,6 @@ public class AnthropicStreamDto
[JsonPropertyName("error")] public AnthropicStreamErrorDto? Error { 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 public class AnthropicStreamErrorDto
@@ -116,37 +84,6 @@ public class AnthropicChatCompletionDto
public AnthropicCompletionDtoUsage? Usage { 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 public class AnthropicChatCompletionDtoContent

View File

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