feat: 全站优化
This commit is contained in:
@@ -1,289 +0,0 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Yi.Framework.AiHub.Domain.Shared.Dtos;
|
||||
using Yi.Framework.AiHub.Domain.Shared.Dtos.Anthropic;
|
||||
|
||||
namespace Yi.Framework.AiHub.Domain.AiGateWay.Impl.ThorCustomOpenAI.Chats;
|
||||
|
||||
/// <summary>
|
||||
/// OpenAI到Claude适配器服务
|
||||
/// 将Claude格式的请求转换为OpenAI格式,然后将OpenAI的响应转换为Claude格式
|
||||
/// </summary>
|
||||
public class CustomOpenAIAnthropicChatCompletionsService(
|
||||
IAbpLazyServiceProvider serviceProvider,
|
||||
ILogger<CustomOpenAIAnthropicChatCompletionsService> logger)
|
||||
: IAnthropicChatCompletionService
|
||||
{
|
||||
private IChatCompletionService GetChatCompletionService()
|
||||
{
|
||||
return serviceProvider.GetRequiredKeyedService<IChatCompletionService>(nameof(OpenAiChatCompletionsService));
|
||||
}
|
||||
|
||||
public async Task<AnthropicChatCompletionDto> ChatCompletionsAsync(AiModelDescribe aiModelDescribe,
|
||||
AnthropicInput request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 转换请求格式:Claude -> OpenAI
|
||||
var openAIRequest = AnthropicToOpenAi.ConvertAnthropicToOpenAi(request);
|
||||
|
||||
// 调用OpenAI服务
|
||||
var openAIResponse =
|
||||
await GetChatCompletionService().CompleteChatAsync(aiModelDescribe,openAIRequest, cancellationToken);
|
||||
|
||||
// 转换响应格式:OpenAI -> Claude
|
||||
var claudeResponse = AnthropicToOpenAi.ConvertOpenAIToClaude(openAIResponse, request);
|
||||
|
||||
return claudeResponse;
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<(string, AnthropicStreamDto?)> StreamChatCompletionsAsync(AiModelDescribe aiModelDescribe,
|
||||
AnthropicInput request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var openAIRequest = AnthropicToOpenAi.ConvertAnthropicToOpenAi(request);
|
||||
openAIRequest.Stream = true;
|
||||
|
||||
var messageId = Guid.NewGuid().ToString();
|
||||
var hasStarted = false;
|
||||
var hasTextContentBlockStarted = false;
|
||||
var hasThinkingContentBlockStarted = false;
|
||||
var toolBlocksStarted = new Dictionary<int, bool>(); // 使用索引而不是ID
|
||||
var toolCallIds = new Dictionary<int, string>(); // 存储每个索引对应的ID
|
||||
var toolCallIndexToBlockIndex = new Dictionary<int, int>(); // 工具调用索引到块索引的映射
|
||||
var accumulatedUsage = new AnthropicCompletionDtoUsage();
|
||||
var isFinished = false;
|
||||
var currentContentBlockType = ""; // 跟踪当前内容块类型
|
||||
var currentBlockIndex = 0; // 跟踪当前块索引
|
||||
var lastContentBlockType = ""; // 跟踪最后一个内容块类型,用于确定停止原因
|
||||
|
||||
await foreach (var openAIResponse in GetChatCompletionService().CompleteChatStreamAsync(aiModelDescribe,openAIRequest,
|
||||
cancellationToken))
|
||||
{
|
||||
// 发送message_start事件
|
||||
if (!hasStarted && openAIResponse.Choices?.Count > 0 &&
|
||||
openAIResponse.Choices.Any(x => x.Delta.ToolCalls?.Count > 0) == false)
|
||||
{
|
||||
hasStarted = true;
|
||||
var messageStartEvent = AnthropicToOpenAi.CreateMessageStartEvent(messageId, request.Model);
|
||||
yield return ("message_start", messageStartEvent);
|
||||
}
|
||||
|
||||
// 更新使用情况统计
|
||||
if (openAIResponse.Usage != null)
|
||||
{
|
||||
// 使用最新的token计数(OpenAI通常在最后的响应中提供完整的统计)
|
||||
if (openAIResponse.Usage.PromptTokens.HasValue)
|
||||
{
|
||||
accumulatedUsage.InputTokens = openAIResponse.Usage.PromptTokens.Value;
|
||||
}
|
||||
|
||||
if (openAIResponse.Usage.CompletionTokens.HasValue)
|
||||
{
|
||||
accumulatedUsage.OutputTokens = (int)openAIResponse.Usage.CompletionTokens.Value;
|
||||
}
|
||||
|
||||
if (openAIResponse.Usage.PromptTokensDetails?.CachedTokens.HasValue == true)
|
||||
{
|
||||
accumulatedUsage.CacheReadInputTokens =
|
||||
openAIResponse.Usage.PromptTokensDetails.CachedTokens.Value;
|
||||
}
|
||||
|
||||
// 记录调试信息
|
||||
logger.LogDebug("OpenAI Usage更新: Input={InputTokens}, Output={OutputTokens}, CacheRead={CacheRead}",
|
||||
accumulatedUsage.InputTokens, accumulatedUsage.OutputTokens,
|
||||
accumulatedUsage.CacheReadInputTokens);
|
||||
}
|
||||
|
||||
if (openAIResponse.Choices is { Count: > 0 })
|
||||
{
|
||||
var choice = openAIResponse.Choices.First();
|
||||
|
||||
// 处理内容
|
||||
if (!string.IsNullOrEmpty(choice.Delta?.Content))
|
||||
{
|
||||
// 如果当前有其他类型的内容块在运行,先结束它们
|
||||
if (currentContentBlockType != "text" && !string.IsNullOrEmpty(currentContentBlockType))
|
||||
{
|
||||
var stopEvent = AnthropicToOpenAi.CreateContentBlockStopEvent();
|
||||
stopEvent.Index = currentBlockIndex;
|
||||
yield return ("content_block_stop", stopEvent);
|
||||
currentBlockIndex++; // 切换内容块时增加索引
|
||||
currentContentBlockType = "";
|
||||
}
|
||||
|
||||
// 发送content_block_start事件(仅第一次)
|
||||
if (!hasTextContentBlockStarted || currentContentBlockType != "text")
|
||||
{
|
||||
hasTextContentBlockStarted = true;
|
||||
currentContentBlockType = "text";
|
||||
lastContentBlockType = "text";
|
||||
var contentBlockStartEvent = AnthropicToOpenAi.CreateContentBlockStartEvent();
|
||||
contentBlockStartEvent.Index = currentBlockIndex;
|
||||
yield return ("content_block_start",
|
||||
contentBlockStartEvent);
|
||||
}
|
||||
|
||||
// 发送content_block_delta事件
|
||||
var contentDeltaEvent = AnthropicToOpenAi.CreateContentBlockDeltaEvent(choice.Delta.Content);
|
||||
contentDeltaEvent.Index = currentBlockIndex;
|
||||
yield return ("content_block_delta",
|
||||
contentDeltaEvent);
|
||||
}
|
||||
|
||||
// 处理工具调用
|
||||
if (choice.Delta?.ToolCalls is { Count: > 0 })
|
||||
{
|
||||
foreach (var toolCall in choice.Delta.ToolCalls)
|
||||
{
|
||||
var toolCallIndex = toolCall.Index; // 使用索引来标识工具调用
|
||||
|
||||
// 发送tool_use content_block_start事件
|
||||
if (toolBlocksStarted.TryAdd(toolCallIndex, true))
|
||||
{
|
||||
// 如果当前有文本或thinking内容块在运行,先结束它们
|
||||
if (currentContentBlockType == "text" || currentContentBlockType == "thinking")
|
||||
{
|
||||
var stopEvent = AnthropicToOpenAi.CreateContentBlockStopEvent();
|
||||
stopEvent.Index = currentBlockIndex;
|
||||
yield return ("content_block_stop", stopEvent);
|
||||
currentBlockIndex++; // 增加块索引
|
||||
}
|
||||
// 如果当前有其他工具调用在运行,也需要结束它们
|
||||
else if (currentContentBlockType == "tool_use")
|
||||
{
|
||||
var stopEvent = AnthropicToOpenAi.CreateContentBlockStopEvent();
|
||||
stopEvent.Index = currentBlockIndex;
|
||||
yield return ("content_block_stop", stopEvent);
|
||||
currentBlockIndex++; // 增加块索引
|
||||
}
|
||||
|
||||
currentContentBlockType = "tool_use";
|
||||
lastContentBlockType = "tool_use";
|
||||
|
||||
// 为此工具调用分配一个新的块索引
|
||||
toolCallIndexToBlockIndex[toolCallIndex] = currentBlockIndex;
|
||||
|
||||
// 保存工具调用的ID(如果有的话)
|
||||
if (!string.IsNullOrEmpty(toolCall.Id))
|
||||
{
|
||||
toolCallIds[toolCallIndex] = toolCall.Id;
|
||||
}
|
||||
else if (!toolCallIds.ContainsKey(toolCallIndex))
|
||||
{
|
||||
// 如果没有ID且之前也没有保存过,生成一个新的ID
|
||||
toolCallIds[toolCallIndex] = Guid.NewGuid().ToString();
|
||||
}
|
||||
|
||||
var toolBlockStartEvent = AnthropicToOpenAi.CreateToolBlockStartEvent(
|
||||
toolCallIds[toolCallIndex],
|
||||
toolCall.Function?.Name);
|
||||
toolBlockStartEvent.Index = currentBlockIndex;
|
||||
yield return ("content_block_start",
|
||||
toolBlockStartEvent);
|
||||
}
|
||||
|
||||
// 如果有增量的参数,发送content_block_delta事件
|
||||
if (!string.IsNullOrEmpty(toolCall.Function?.Arguments))
|
||||
{
|
||||
var toolDeltaEvent =
|
||||
AnthropicToOpenAi.CreateToolBlockDeltaEvent(toolCall.Function.Arguments);
|
||||
// 使用该工具调用对应的块索引
|
||||
toolDeltaEvent.Index = toolCallIndexToBlockIndex[toolCallIndex];
|
||||
yield return ("content_block_delta",
|
||||
toolDeltaEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理推理内容
|
||||
if (!string.IsNullOrEmpty(choice.Delta?.ReasoningContent))
|
||||
{
|
||||
// 如果当前有其他类型的内容块在运行,先结束它们
|
||||
if (currentContentBlockType != "thinking" && !string.IsNullOrEmpty(currentContentBlockType))
|
||||
{
|
||||
var stopEvent = AnthropicToOpenAi.CreateContentBlockStopEvent();
|
||||
stopEvent.Index = currentBlockIndex;
|
||||
yield return ("content_block_stop", stopEvent);
|
||||
currentBlockIndex++; // 增加块索引
|
||||
currentContentBlockType = "";
|
||||
}
|
||||
|
||||
// 对于推理内容,也需要发送对应的事件
|
||||
if (!hasThinkingContentBlockStarted || currentContentBlockType != "thinking")
|
||||
{
|
||||
hasThinkingContentBlockStarted = true;
|
||||
currentContentBlockType = "thinking";
|
||||
lastContentBlockType = "thinking";
|
||||
var thinkingBlockStartEvent = AnthropicToOpenAi.CreateThinkingBlockStartEvent();
|
||||
thinkingBlockStartEvent.Index = currentBlockIndex;
|
||||
yield return ("content_block_start",
|
||||
thinkingBlockStartEvent);
|
||||
}
|
||||
|
||||
var thinkingDeltaEvent =
|
||||
AnthropicToOpenAi.CreateThinkingBlockDeltaEvent(choice.Delta.ReasoningContent);
|
||||
thinkingDeltaEvent.Index = currentBlockIndex;
|
||||
yield return ("content_block_delta",
|
||||
thinkingDeltaEvent);
|
||||
}
|
||||
|
||||
// 处理结束
|
||||
if (!string.IsNullOrEmpty(choice.FinishReason) && !isFinished)
|
||||
{
|
||||
isFinished = true;
|
||||
|
||||
// 发送content_block_stop事件(如果有活跃的内容块)
|
||||
if (!string.IsNullOrEmpty(currentContentBlockType))
|
||||
{
|
||||
var contentBlockStopEvent = AnthropicToOpenAi.CreateContentBlockStopEvent();
|
||||
contentBlockStopEvent.Index = currentBlockIndex;
|
||||
yield return ("content_block_stop",
|
||||
contentBlockStopEvent);
|
||||
}
|
||||
|
||||
// 发送message_delta事件
|
||||
var messageDeltaEvent = AnthropicToOpenAi.CreateMessageDeltaEvent(
|
||||
AnthropicToOpenAi.GetStopReasonByLastContentType(choice.FinishReason, lastContentBlockType),
|
||||
accumulatedUsage);
|
||||
|
||||
// 记录最终Usage统计
|
||||
logger.LogDebug(
|
||||
"流式响应结束,最终Usage: Input={InputTokens}, Output={OutputTokens}, CacheRead={CacheRead}",
|
||||
accumulatedUsage.InputTokens, accumulatedUsage.OutputTokens,
|
||||
accumulatedUsage.CacheReadInputTokens);
|
||||
|
||||
yield return ("message_delta",
|
||||
messageDeltaEvent);
|
||||
|
||||
// 发送message_stop事件
|
||||
var messageStopEvent = AnthropicToOpenAi.CreateMessageStopEvent();
|
||||
yield return ("message_stop",
|
||||
messageStopEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 确保流正确结束
|
||||
if (!isFinished)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(currentContentBlockType))
|
||||
{
|
||||
var contentBlockStopEvent = AnthropicToOpenAi.CreateContentBlockStopEvent();
|
||||
contentBlockStopEvent.Index = currentBlockIndex;
|
||||
yield return ("content_block_stop",
|
||||
contentBlockStopEvent);
|
||||
}
|
||||
|
||||
var messageDeltaEvent =
|
||||
AnthropicToOpenAi.CreateMessageDeltaEvent(
|
||||
AnthropicToOpenAi.GetStopReasonByLastContentType("end_turn", lastContentBlockType),
|
||||
accumulatedUsage);
|
||||
yield return ("message_delta", messageDeltaEvent);
|
||||
|
||||
var messageStopEvent = AnthropicToOpenAi.CreateMessageStopEvent();
|
||||
yield return ("message_stop",
|
||||
messageStopEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -517,7 +517,7 @@ public class AiGateWayManager : DomainService
|
||||
await _aiMessageManager.CreateUserMessageAsync(userId.Value, sessionId,
|
||||
new MessageInputDto
|
||||
{
|
||||
Content = sessionId is null ? "不予存储" : request.Messages?.FirstOrDefault()?.Content ?? string.Empty,
|
||||
Content = "不予存储",
|
||||
ModelId = request.Model,
|
||||
TokenUsage = data.TokenUsage,
|
||||
}, tokenId);
|
||||
@@ -525,7 +525,7 @@ public class AiGateWayManager : DomainService
|
||||
await _aiMessageManager.CreateSystemMessageAsync(userId.Value, sessionId,
|
||||
new MessageInputDto
|
||||
{
|
||||
Content = sessionId is null ? "不予存储" : data.content?.FirstOrDefault()?.text,
|
||||
Content = "不予存储",
|
||||
ModelId = request.Model,
|
||||
TokenUsage = data.TokenUsage
|
||||
}, tokenId);
|
||||
@@ -602,7 +602,7 @@ public class AiGateWayManager : DomainService
|
||||
await _aiMessageManager.CreateUserMessageAsync(userId, sessionId,
|
||||
new MessageInputDto
|
||||
{
|
||||
Content = sessionId is null ? "不予存储" : request.Messages?.LastOrDefault()?.Content ?? string.Empty,
|
||||
Content = "不予存储",
|
||||
ModelId = request.Model,
|
||||
TokenUsage = tokenUsage,
|
||||
}, tokenId);
|
||||
@@ -610,7 +610,7 @@ public class AiGateWayManager : DomainService
|
||||
await _aiMessageManager.CreateSystemMessageAsync(userId, sessionId,
|
||||
new MessageInputDto
|
||||
{
|
||||
Content = sessionId is null ? "不予存储" : backupSystemContent.ToString(),
|
||||
Content = "不予存储",
|
||||
ModelId = request.Model,
|
||||
TokenUsage = tokenUsage
|
||||
}, tokenId);
|
||||
|
||||
@@ -48,9 +48,7 @@ namespace Yi.Framework.AiHub.Domain
|
||||
#endregion
|
||||
|
||||
#region Anthropic ChatCompletion
|
||||
|
||||
services.AddKeyedTransient<IAnthropicChatCompletionService, CustomOpenAIAnthropicChatCompletionsService>(
|
||||
nameof(CustomOpenAIAnthropicChatCompletionsService));
|
||||
|
||||
services.AddKeyedTransient<IAnthropicChatCompletionService, AnthropicChatCompletionsService>(
|
||||
nameof(AnthropicChatCompletionsService));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user