Files
Yi.Admin/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/AiGateWayManager.cs

286 lines
10 KiB
C#
Raw Normal View History

2025-07-05 15:11:56 +08:00
using System.Collections.Concurrent;
2025-06-25 17:12:09 +08:00
using System.Runtime.CompilerServices;
2025-07-05 15:11:56 +08:00
using System.Text;
using Microsoft.AspNetCore.Http;
2025-06-21 01:08:14 +08:00
using Microsoft.Extensions.DependencyInjection;
2025-07-05 15:11:56 +08:00
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
2025-06-21 01:08:14 +08:00
using OpenAI.Chat;
using Volo.Abp.Domain.Services;
2025-07-05 15:11:56 +08:00
using Yi.Framework.AiHub.Application.Contracts.Dtos;
2025-06-21 01:08:14 +08:00
using Yi.Framework.AiHub.Domain.AiChat;
2025-06-25 17:12:09 +08:00
using Yi.Framework.AiHub.Domain.Entities;
2025-06-27 22:13:26 +08:00
using Yi.Framework.AiHub.Domain.Entities.Model;
2025-06-25 17:12:09 +08:00
using Yi.Framework.AiHub.Domain.Shared.Dtos;
using Yi.Framework.SqlSugarCore.Abstractions;
2025-06-21 01:08:14 +08:00
namespace Yi.Framework.AiHub.Domain.Managers;
public class AiGateWayManager : DomainService
{
2025-06-25 17:12:09 +08:00
private readonly ISqlSugarRepository<AiAppAggregateRoot> _aiAppRepository;
2025-07-05 15:11:56 +08:00
private readonly ILogger<AiGateWayManager> _logger;
private readonly AiMessageManager _aiMessageManager;
private readonly UsageStatisticsManager _usageStatisticsManager;
2025-06-21 01:08:14 +08:00
2025-07-05 15:11:56 +08:00
public AiGateWayManager(ISqlSugarRepository<AiAppAggregateRoot> aiAppRepository, ILogger<AiGateWayManager> logger,
AiMessageManager aiMessageManager, UsageStatisticsManager usageStatisticsManager)
2025-06-21 01:08:14 +08:00
{
2025-06-25 17:12:09 +08:00
_aiAppRepository = aiAppRepository;
2025-07-05 15:11:56 +08:00
_logger = logger;
_aiMessageManager = aiMessageManager;
_usageStatisticsManager = usageStatisticsManager;
2025-06-21 01:08:14 +08:00
}
2025-06-25 17:12:09 +08:00
/// <summary>
/// 获取模型
/// </summary>
/// <param name="modelId"></param>
/// <returns></returns>
private async Task<AiModelDescribe> GetModelAsync(string modelId)
2025-06-21 01:08:14 +08:00
{
2025-06-25 17:12:09 +08:00
var allApp = await _aiAppRepository._DbQueryable.Includes(x => x.AiModels).ToListAsync();
foreach (var app in allApp)
2025-06-21 01:08:14 +08:00
{
2025-06-25 17:12:09 +08:00
var model = app.AiModels.FirstOrDefault(x => x.ModelId == modelId);
if (model is not null)
2025-06-21 01:08:14 +08:00
{
2025-06-25 17:12:09 +08:00
return new AiModelDescribe
{
AppId = app.Id,
AppName = app.Name,
Endpoint = app.Endpoint,
ApiKey = app.ApiKey,
OrderNum = model.OrderNum,
HandlerName = model.HandlerName,
ModelId = model.ModelId,
ModelName = model.Name,
Description = model.Description
};
2025-06-21 01:08:14 +08:00
}
}
2025-06-21 01:41:05 +08:00
2025-06-25 17:12:09 +08:00
throw new UserFriendlyException($"{modelId}模型当前版本不支持");
}
/// <summary>
/// 聊天完成
/// </summary>
/// <param name="modelId"></param>
/// <param name="messages"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
2025-06-27 22:13:26 +08:00
public async IAsyncEnumerable<CompleteChatResponse> CompleteChatAsync(string modelId, List<ChatMessage> messages,
2025-06-25 17:12:09 +08:00
[EnumeratorCancellation] CancellationToken cancellationToken)
{
var modelDescribe = await GetModelAsync(modelId);
var chatService = LazyServiceProvider.GetRequiredKeyedService<IChatService>(modelDescribe.HandlerName);
await foreach (var result in chatService.CompleteChatAsync(modelDescribe, messages, cancellationToken))
{
yield return result;
}
2025-06-21 01:08:14 +08:00
}
2025-07-05 15:11:56 +08:00
/// <summary>
/// 聊天完成-缓存处理
/// </summary>
/// <param name="httpContext"></param>
/// <param name="modelId"></param>
/// <param name="messages"></param>
/// <param name="sessionId"></param>
/// <param name="cancellationToken"></param>
/// <param name="userId"></param>
/// <returns></returns>
public async Task CompleteChatForHttpContextAsync(
HttpContext httpContext,
string modelId,
List<ChatMessage> messages,
Guid? userId = null,
Guid? sessionId = null,
CancellationToken cancellationToken = default)
{
var response = httpContext.Response;
// 设置响应头,声明是 SSE 流
response.ContentType = "text/event-stream";
response.Headers.Append("Cache-Control", "no-cache");
response.Headers.Append("Connection", "keep-alive");
var gateWay = LazyServiceProvider.GetRequiredService<AiGateWayManager>();
var completeChatResponse = gateWay.CompleteChatAsync(modelId, messages, cancellationToken);
var tokenUsage = new TokenUsage();
await using var writer = new StreamWriter(response.Body, Encoding.UTF8, leaveOpen: true);
//缓存队列算法
// 创建一个队列来缓存消息
var messageQueue = new ConcurrentQueue<string>();
StringBuilder backupSystemContent = new StringBuilder();
// 设置输出速率例如每50毫秒输出一次
2025-07-08 18:24:21 +08:00
var outputInterval = TimeSpan.FromMilliseconds(75);
2025-07-05 15:11:56 +08:00
// 标记是否完成接收
var isComplete = false;
// 启动一个后台任务来消费队列
var outputTask = Task.Run(async () =>
{
while (!(isComplete && messageQueue.IsEmpty))
{
if (messageQueue.TryDequeue(out var message))
{
await writer.WriteLineAsync(message);
await writer.FlushAsync(cancellationToken);
}
if (!isComplete)
{
// 如果没有完成,才等待,已完成,全部输出
await Task.Delay(outputInterval, cancellationToken);
}
}
}, cancellationToken);
//IAsyncEnumerable 只能在最外层捕获异常(如果你有其他办法的话...
try
{
await foreach (var data in completeChatResponse)
{
if (data.IsFinish)
{
tokenUsage = data.TokenUsage;
}
var model = MapToMessage(modelId, data.Content);
var message = JsonConvert.SerializeObject(model, new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
backupSystemContent.Append(data.Content);
// 将消息加入队列而不是直接写入
messageQueue.Enqueue($"data: {message}\n");
}
}
catch (Exception e)
{
_logger.LogError(e, $"Ai对话异常");
var errorContent = $"Ai对话异常异常信息\n{e.Message}";
var model = MapToMessage(modelId, errorContent);
var message = JsonConvert.SerializeObject(model, new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
backupSystemContent.Append(errorContent);
messageQueue.Enqueue($"data: {message}\n");
}
//断开连接
messageQueue.Enqueue("data: [DONE]\n");
// 标记完成并发送结束标记
isComplete = true;
await outputTask;
if (userId is not null)
{
await _aiMessageManager.CreateUserMessageAsync(userId.Value, sessionId,
new MessageInputDto
{
Content = messages.LastOrDefault().Content.FirstOrDefault()?.Text ?? string.Empty,
ModelId = modelId,
TokenUsage = tokenUsage,
});
await _aiMessageManager.CreateSystemMessageAsync(userId.Value, sessionId,
new MessageInputDto
{
Content = backupSystemContent.ToString(),
ModelId = modelId,
TokenUsage = tokenUsage
});
await _usageStatisticsManager.SetUsageAsync(userId.Value, modelId, tokenUsage.InputTokenCount,
tokenUsage.OutputTokenCount);
}
}
private SendMessageOutputDto MapToMessage(string modelId, string content)
{
var output = new SendMessageOutputDto
{
Id = "chatcmpl-BotYP3BlN5T4g9YPnW0fBSBvKzXdd",
Object = "chat.completion.chunk",
Created = 1750336171,
Model = modelId,
Choices = new()
{
new Choice
{
Index = 0,
Delta = new Delta
{
Content = content,
Role = "assistant"
},
FinishReason = null,
ContentFilterResults = new()
{
Hate = new()
{
Filtered = false,
Detected = null
},
SelfHarm = new()
{
Filtered = false,
Detected = null
},
Sexual = new()
{
Filtered = false,
Detected = null
},
Violence = new()
{
Filtered = false,
Detected = null
},
Jailbreak = new()
{
Filtered = false,
Detected = false
},
Profanity = new()
{
Filtered = false,
Detected = false
},
}
}
},
SystemFingerprint = "",
Usage = new Usage
{
PromptTokens = 0,
CompletionTokens = 0,
TotalTokens = 0,
PromptTokensDetails = new()
{
AudioTokens = 0,
CachedTokens = 0
},
CompletionTokensDetails = new()
{
AudioTokens = 0,
ReasoningTokens = 0,
AcceptedPredictionTokens = 0,
RejectedPredictionTokens = 0
}
}
};
return output;
}
2025-06-21 01:08:14 +08:00
}