From 695aaedfba241d126afc077f12cebd090eff9a74 Mon Sep 17 00:00:00 2001 From: chenchun Date: Wed, 25 Jun 2025 17:12:09 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90ai-hub=E7=AC=AC?= =?UTF-8?q?=E4=B8=80=E6=9C=9F=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Dtos/ModelGetListOutput.cs | 6 +- .../Options/AiGateWayOptions.cs | 17 ---- .../Services/AiChatService.cs | 79 ++++++++++++------- .../Dtos/AiModelDescribe.cs | 49 ++++++++++++ .../Yi.Framework.AiHub.Domain.Shared.csproj | 1 - .../AiChat/IChatService.cs | 4 +- .../AiChat/Impl/AzureChatService.cs | 16 ++-- .../AiChat/Impl/AzureRestChatService.cs | 29 +++---- .../Entities/AiBlacklistAggregateRoot.cs | 26 ++++++ .../{ => Chat}/MessageAggregateRoot.cs | 0 .../{ => Chat}/SessionAggregateRoot.cs | 0 .../Entities/Model/AiAppAggregateRoot.cs | 38 +++++++++ .../Entities/Model/AiModelEntity.cs | 48 +++++++++++ .../Entities/UsageStatisticsAggregateRoot.cs | 31 ++++++++ .../Managers/AiBlacklistManager.cs | 31 ++++++++ .../Managers/AiGateWayManager.cs | 63 +++++++++++---- .../YiFrameworkAiHubDomainModule.cs | 15 +--- Yi.Abp.Net8/src/Yi.Abp.Web/Yi.Abp.Web.csproj | 10 +++ 18 files changed, 360 insertions(+), 103 deletions(-) delete mode 100644 Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Options/AiGateWayOptions.cs create mode 100644 Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Dtos/AiModelDescribe.cs create mode 100644 Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/AiBlacklistAggregateRoot.cs rename Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/{ => Chat}/MessageAggregateRoot.cs (100%) rename Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/{ => Chat}/SessionAggregateRoot.cs (100%) create mode 100644 Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/Model/AiAppAggregateRoot.cs create mode 100644 Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/Model/AiModelEntity.cs create mode 100644 Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/UsageStatisticsAggregateRoot.cs create mode 100644 Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/AiBlacklistManager.cs diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/ModelGetListOutput.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/ModelGetListOutput.cs index 556599c0..786c8d98 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/ModelGetListOutput.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/ModelGetListOutput.cs @@ -5,7 +5,7 @@ public class ModelGetListOutput /// /// 模型ID /// - public long Id { get; set; } + public Guid Id { get; set; } /// /// 模型分类 @@ -20,7 +20,7 @@ public class ModelGetListOutput /// /// 模型描述 /// - public string ModelDescribe { get; set; } + public string? ModelDescribe { get; set; } /// /// 模型价格 @@ -55,5 +55,5 @@ public class ModelGetListOutput /// /// 备注信息 /// - public string Remark { get; set; } + public string? Remark { get; set; } } \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Options/AiGateWayOptions.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Options/AiGateWayOptions.cs deleted file mode 100644 index 914b8a6a..00000000 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Options/AiGateWayOptions.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Yi.Framework.AiHub.Application.Contracts.Options; - -public class AiGateWayOptions -{ - public AiChatOptionDic Chats { get; set; } -} - -public class AiChatOptionDic : Dictionary -{ -} - -public class AiChatModelOptions -{ - public List ModelIds { get; set; } - public string Endpoint { get; set; } - public string ApiKey { get; set; } -} \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/AiChatService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/AiChatService.cs index efb6a5d5..9b5e0808 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/AiChatService.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/AiChatService.cs @@ -5,17 +5,17 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -using Microsoft.Identity.Client; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using OpenAI.Chat; using Volo.Abp.Application.Services; using Volo.Abp.Users; using Yi.Framework.AiHub.Application.Contracts.Dtos; -using Yi.Framework.AiHub.Application.Contracts.Options; +using Yi.Framework.AiHub.Domain.Entities; using Yi.Framework.AiHub.Domain.Managers; using Yi.Framework.Rbac.Application.Contracts.IServices; using Yi.Framework.Rbac.Domain.Shared.Dtos; +using Yi.Framework.SqlSugarCore.Abstractions; namespace Yi.Framework.AiHub.Application.Services; @@ -24,16 +24,17 @@ namespace Yi.Framework.AiHub.Application.Services; /// public class AiChatService : ApplicationService { - private readonly AiGateWayOptions _options; private readonly IHttpContextAccessor _httpContextAccessor; private readonly AiMessageManager _aiMessageManager; + private readonly ISqlSugarRepository _aiModelRepository; + private readonly AiBlacklistManager _aiBlacklistManager; - public AiChatService(IOptions options, IHttpContextAccessor httpContextAccessor, - AiMessageManager aiMessageManager) + public AiChatService(IHttpContextAccessor httpContextAccessor, + AiMessageManager aiMessageManager, AiBlacklistManager aiBlacklistManager) { - _options = options.Value; this._httpContextAccessor = httpContextAccessor; _aiMessageManager = aiMessageManager; + _aiBlacklistManager = aiBlacklistManager; } @@ -56,18 +57,20 @@ public class AiChatService : ApplicationService /// public async Task> GetModelAsync() { - var output = _options.Chats.SelectMany(x => x.Value.ModelIds) - .Select(x => new ModelGetListOutput() - { - Id = 001, - Category = "chat", - ModelName = x, - ModelDescribe = "这是一个直连模型", - ModelPrice = 4, - ModelType = "1", - ModelShow = "0", - Remark = "直连模型" - }).ToList(); + var output = await _aiModelRepository._DbQueryable.Select(x => new ModelGetListOutput + { + Id = x.Id, + Category = "chat", + ModelName = x.Name, + ModelDescribe = x.Description, + ModelPrice = 0, + ModelType = "1", + ModelShow = "0", + SystemPrompt = null, + ApiHost = null, + ApiKey = null, + Remark = x.Description + }).ToListAsync(); return output; } @@ -79,6 +82,25 @@ public class AiChatService : ApplicationService /// public async Task PostSendAsync(SendMessageInput input, CancellationToken cancellationToken) { + //除了免费模型,其他的模型都要校验 + if (input.Model != "DeepSeek-R1-0528") + { + //有token,需要黑名单校验 + if (CurrentUser.IsAuthenticated) + { + await _aiBlacklistManager.VerifiyAiBlacklist(CurrentUser.GetId()); + if (!CurrentUser.Roles.Contains("YiXinAi-Vip")) + { + throw new UserFriendlyException("该模型需要VIP用户才能使用,请购买VIP后重新登录重试"); + } + } + else + { + throw new UserFriendlyException("未登录用户,只能使用未加速的DeepSeek-R1,请登录后重试"); + } + } + + //前面都是校验,后面才是真正的调用 var httpContext = this._httpContextAccessor.HttpContext; var response = httpContext.Response; // 设置响应头,声明是 SSE 流 @@ -116,7 +138,7 @@ public class AiChatService : ApplicationService // 启动一个后台任务来消费队列 var outputTask = Task.Run(async () => { - while (!(isComplete&&messageQueue.IsEmpty)) + while (!(isComplete && messageQueue.IsEmpty)) { if (messageQueue.TryDequeue(out var message)) { @@ -153,16 +175,15 @@ public class AiChatService : ApplicationService await outputTask; if (CurrentUser.IsAuthenticated && input.SessionId.HasValue) { - // 等待接入token - // await _aiMessageManager.CreateMessageAsync(CurrentUser.GetId(), input.SessionId.Value, new MessageInputDto - // { - // Content = null, - // Role = null, - // DeductCost = 0, - // TotalTokens = 0, - // ModelId = null, - // Remark = null - // }); + await _aiMessageManager.CreateMessageAsync(CurrentUser.GetId(), input.SessionId.Value, new MessageInputDto + { + Content = input.Messages.LastOrDefault().Content, + Role = input.Messages.LastOrDefault().Role, + DeductCost = 0, + TotalTokens = 0, + ModelId = input.Model, + Remark = null + }); } } diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Dtos/AiModelDescribe.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Dtos/AiModelDescribe.cs new file mode 100644 index 00000000..06572d65 --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Dtos/AiModelDescribe.cs @@ -0,0 +1,49 @@ +namespace Yi.Framework.AiHub.Domain.Shared.Dtos; + +public class AiModelDescribe +{ + /// + /// 应用id + /// + public Guid AppId { get; set; } + + /// + /// 应用名称 + /// + public string AppName { get; set; } + + /// + /// 应用终结点 + /// + public string Endpoint { get; set; } + + /// + /// 应用key + /// + public string ApiKey { get; set; } + + /// + /// 排序 + /// + public int OrderNum { get; set; } + + /// + /// 处理名 + /// + public string HandlerName { get; set; } + + /// + /// 模型id + /// + public string ModelId { get; set; } + + /// + /// 模型名称 + /// + public string ModelName { get; set; } + + /// + /// 模型描述 + /// + public string? Description { get; set; } +} \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Yi.Framework.AiHub.Domain.Shared.csproj b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Yi.Framework.AiHub.Domain.Shared.csproj index c5a91f0e..248e1dc2 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Yi.Framework.AiHub.Domain.Shared.csproj +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Yi.Framework.AiHub.Domain.Shared.csproj @@ -7,7 +7,6 @@ - diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiChat/IChatService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiChat/IChatService.cs index 76ca58c6..fdc56878 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiChat/IChatService.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiChat/IChatService.cs @@ -1,8 +1,10 @@ using OpenAI.Chat; +using Yi.Framework.AiHub.Domain.Shared.Dtos; namespace Yi.Framework.AiHub.Domain.AiChat; public interface IChatService { - public IAsyncEnumerable CompleteChatAsync(string modelId, List messages,CancellationToken cancellationToken); + public IAsyncEnumerable CompleteChatAsync(AiModelDescribe aiModelDescribe, List messages, + CancellationToken cancellationToken); } \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiChat/Impl/AzureChatService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiChat/Impl/AzureChatService.cs index ae9b8321..6b8a4c6b 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiChat/Impl/AzureChatService.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiChat/Impl/AzureChatService.cs @@ -1,28 +1,24 @@ using System.Runtime.CompilerServices; using Azure; using Azure.AI.OpenAI; -using Microsoft.Extensions.Options; using OpenAI.Chat; -using Yi.Framework.AiHub.Application.Contracts.Options; +using Yi.Framework.AiHub.Domain.Shared.Dtos; namespace Yi.Framework.AiHub.Domain.AiChat.Impl; public class AzureChatService : IChatService { - private readonly AiChatModelOptions _options; - - public AzureChatService(IOptions options) + public AzureChatService() { - this._options = options.Value.Chats[nameof(AzureChatService)]; } - public async IAsyncEnumerable CompleteChatAsync(string modelId, List messages, + public async IAsyncEnumerable CompleteChatAsync(AiModelDescribe aiModelDescribe, List messages, [EnumeratorCancellation] CancellationToken cancellationToken) { - var endpoint = new Uri(_options.Endpoint); + var endpoint = new Uri(aiModelDescribe.Endpoint); - var deploymentName = modelId; - var apiKey = _options.ApiKey; + var deploymentName = aiModelDescribe.ModelId; + var apiKey = aiModelDescribe.ApiKey; AzureOpenAIClient azureClient = new( endpoint, diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiChat/Impl/AzureRestChatService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiChat/Impl/AzureRestChatService.cs index 502eae48..c1419710 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiChat/Impl/AzureRestChatService.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiChat/Impl/AzureRestChatService.cs @@ -1,28 +1,24 @@ using System.Runtime.CompilerServices; using System.Text; -using Microsoft.Extensions.Options; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OpenAI.Chat; -using Yi.Framework.AiHub.Application.Contracts.Options; using Yi.Framework.AiHub.Domain.Extensions; +using Yi.Framework.AiHub.Domain.Shared.Dtos; namespace Yi.Framework.AiHub.Domain.AiChat.Impl; public class AzureRestChatService : IChatService { - private readonly AiChatModelOptions _options; - - public AzureRestChatService(IOptions options) + public AzureRestChatService() { - this._options = options.Value.Chats[nameof(AzureRestChatService)]; } - public async IAsyncEnumerable CompleteChatAsync(string modelId, List messages, + public async IAsyncEnumerable CompleteChatAsync(AiModelDescribe aiModelDescribe, List messages, [EnumeratorCancellation] CancellationToken cancellationToken) { // 设置API URL - var apiUrl = $"{_options.Endpoint}models/chat/completions"; + var apiUrl = $"{aiModelDescribe.Endpoint}models/chat/completions"; var ss = messages.Select(x => new @@ -45,7 +41,7 @@ public class AzureRestChatService : IChatService top_p = 0.1, presence_penalty = 0, frequency_penalty = 0, - model = modelId + model = aiModelDescribe.ModelId }; // 序列化请求内容为JSON @@ -53,24 +49,25 @@ public class AzureRestChatService : IChatService using var httpClient = new HttpClient(); // 设置请求头 - httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_options.ApiKey}"); + httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {aiModelDescribe.ApiKey}"); // 其他头信息如Content-Type在StringContent中设置 - + // 构造 POST 请求 var request = new HttpRequestMessage(HttpMethod.Post, apiUrl); // 设置请求内容(示例) - request.Content =new StringContent(jsonBody, Encoding.UTF8, "application/json"); - + request.Content = new StringContent(jsonBody, Encoding.UTF8, "application/json"); + // 发送POST请求 - HttpResponseMessage response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + HttpResponseMessage response = + await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); // 确认响应成功 response.EnsureSuccessStatusCode(); // 读取响应内容 var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken); // 从流中读取数据并输出到控制台 - using var streamReader = new System.IO.StreamReader(responseStream); + using var streamReader = new StreamReader(responseStream); string line; while ((line = await streamReader.ReadLineAsync(cancellationToken)) != null) { @@ -117,7 +114,5 @@ public class AzureRestChatService : IChatService // 解析失败 return null; } - - return null; } } \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/AiBlacklistAggregateRoot.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/AiBlacklistAggregateRoot.cs new file mode 100644 index 00000000..2063f6ee --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/AiBlacklistAggregateRoot.cs @@ -0,0 +1,26 @@ +using SqlSugar; +using Volo.Abp.Domain.Entities.Auditing; + +namespace Yi.Framework.AiHub.Domain.Entities; + +/// +/// ai黑名单 +/// +[SugarTable("Ai_Blacklist")] +public class AiBlacklistAggregateRoot : FullAuditedAggregateRoot +{ + /// + /// 用户 + /// + public Guid UserId { get; set; } + + /// + /// 有效开始时间 + /// + public DateTime StartTime { get; set; } + + /// + /// 有效结束时间 + /// + public DateTime EndTime { get; set; } +} \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/MessageAggregateRoot.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/Chat/MessageAggregateRoot.cs similarity index 100% rename from Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/MessageAggregateRoot.cs rename to Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/Chat/MessageAggregateRoot.cs diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/SessionAggregateRoot.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/Chat/SessionAggregateRoot.cs similarity index 100% rename from Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/SessionAggregateRoot.cs rename to Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/Chat/SessionAggregateRoot.cs diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/Model/AiAppAggregateRoot.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/Model/AiAppAggregateRoot.cs new file mode 100644 index 00000000..cd7eced2 --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/Model/AiAppAggregateRoot.cs @@ -0,0 +1,38 @@ +using SqlSugar; +using Volo.Abp.Domain.Entities.Auditing; +using Yi.Framework.Core.Data; + +namespace Yi.Framework.AiHub.Domain.Entities; + +/// +/// ai应用 +/// +[SugarTable("Ai_App")] +public class AiAppAggregateRoot : FullAuditedAggregateRoot, IOrderNum +{ + /// + /// 应用名称 + /// + public string Name { get; set; } + + /// + /// 应用终结点 + /// + public string Endpoint { get; set; } + + /// + /// 应用key + /// + public string ApiKey { get; set; } + + /// + /// 排序 + /// + public int OrderNum { get; set; } + + /// + /// ai模型 + /// + [Navigate(NavigateType.OneToMany, nameof(AiModelEntity.AiAppId))] + public List AiModels { get; set; } +} \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/Model/AiModelEntity.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/Model/AiModelEntity.cs new file mode 100644 index 00000000..d221d8eb --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/Model/AiModelEntity.cs @@ -0,0 +1,48 @@ +using SqlSugar; +using Volo.Abp.Domain.Entities; +using Volo.Abp.Domain.Entities.Auditing; +using Yi.Framework.Core.Data; + +namespace Yi.Framework.AiHub.Domain.Entities; + +/// +/// ai模型定义 +/// +[SugarTable("Ai_Model")] +public class AiModelEntity : Entity, IOrderNum,ISoftDelete +{ + /// + /// 处理名 + /// + public string HandlerName { get; set; } + + /// + /// 模型id + /// + public string ModelId { get; set; } + + /// + /// 模型名称 + /// + public string Name { get; set; } + + /// + /// 模型描述 + /// + public string? Description { get; set; } + + /// + /// 排序 + /// + public int OrderNum { get; set; } + + /// + /// 软删除 + /// + public bool IsDeleted { get; set; } + + /// + /// ai应用id + /// + public Guid AiAppId { get; set; } +} \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/UsageStatisticsAggregateRoot.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/UsageStatisticsAggregateRoot.cs new file mode 100644 index 00000000..be212d3e --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/UsageStatisticsAggregateRoot.cs @@ -0,0 +1,31 @@ +using SqlSugar; +using Volo.Abp.Domain.Entities.Auditing; + +namespace Yi.Framework.AiHub.Domain.Entities; + +/// +/// 用量统计 +/// +[SugarTable("Ai_UsageStatistics")] +public class UsageStatisticsAggregateRoot : FullAuditedAggregateRoot +{ + /// + /// 用户id + /// + public Guid UserId { get; set; } + + /// + /// 哪个模型 + /// + public string ModelId { get; set; } + + /// + /// 总token使用 + /// + public decimal TotalTokens { get; set; } + + /// + /// 对话次数 + /// + public int Number { get; set; } +} \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/AiBlacklistManager.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/AiBlacklistManager.cs new file mode 100644 index 00000000..a8191d3a --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/AiBlacklistManager.cs @@ -0,0 +1,31 @@ +using Volo.Abp.Domain.Services; +using Yi.Framework.AiHub.Domain.Entities; +using Yi.Framework.SqlSugarCore.Abstractions; + +namespace Yi.Framework.AiHub.Domain.Managers; + +public class AiBlacklistManager : DomainService +{ + private readonly ISqlSugarRepository _aiBlacklistRepository; + + public AiBlacklistManager(ISqlSugarRepository aiBlacklistRepository) + { + _aiBlacklistRepository = aiBlacklistRepository; + } + + /// + /// 校验黑名单 + /// + /// + /// + public async Task VerifiyAiBlacklist(Guid userId) + { + var now = DateTime.Now; + if (await _aiBlacklistRepository._DbQueryable + .Where(x => now >= x.StartTime && now <= x.EndTime) + .AnyAsync(x => x.UserId == userId)) + { + throw new UserFriendlyException("当前用户已被加入黑名单,请联系管理员处理"); + } + } +} \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/AiGateWayManager.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/AiGateWayManager.cs index 5d926496..6f4a5a57 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/AiGateWayManager.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/AiGateWayManager.cs @@ -1,35 +1,70 @@ -using Azure; -using Azure.AI.OpenAI; +using System.Runtime.CompilerServices; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; using OpenAI.Chat; using Volo.Abp.Domain.Services; -using Yi.Framework.AiHub.Application.Contracts.Options; using Yi.Framework.AiHub.Domain.AiChat; +using Yi.Framework.AiHub.Domain.Entities; +using Yi.Framework.AiHub.Domain.Shared.Dtos; +using Yi.Framework.SqlSugarCore.Abstractions; namespace Yi.Framework.AiHub.Domain.Managers; public class AiGateWayManager : DomainService { - private readonly AiGateWayOptions _options; + private readonly ISqlSugarRepository _aiAppRepository; - public AiGateWayManager(IOptions options) + public AiGateWayManager(ISqlSugarRepository aiAppRepository) { - this._options = options.Value; + _aiAppRepository = aiAppRepository; } - public IAsyncEnumerable CompleteChatAsync(string modelId, List messages, - CancellationToken cancellationToken) + /// + /// 获取模型 + /// + /// + /// + private async Task GetModelAsync(string modelId) { - foreach (var chat in _options.Chats) + var allApp = await _aiAppRepository._DbQueryable.Includes(x => x.AiModels).ToListAsync(); + foreach (var app in allApp) { - if (chat.Value.ModelIds.Contains(modelId)) + var model = app.AiModels.FirstOrDefault(x => x.ModelId == modelId); + if (model is not null) { - var chatService = LazyServiceProvider.GetRequiredKeyedService(chat.Key); - return chatService.CompleteChatAsync(modelId, messages, cancellationToken); + 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 + }; } } - throw new UserFriendlyException($"当前暂不支持该模型-【{modelId}】"); + throw new UserFriendlyException($"{modelId}模型当前版本不支持"); + } + + + /// + /// 聊天完成 + /// + /// + /// + /// + /// + public async IAsyncEnumerable CompleteChatAsync(string modelId, List messages, + [EnumeratorCancellation] CancellationToken cancellationToken) + { + var modelDescribe = await GetModelAsync(modelId); + var chatService = LazyServiceProvider.GetRequiredKeyedService(modelDescribe.HandlerName); + await foreach (var result in chatService.CompleteChatAsync(modelDescribe, messages, cancellationToken)) + { + yield return result; + } } } \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/YiFrameworkAiHubDomainModule.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/YiFrameworkAiHubDomainModule.cs index 71822c88..3c135ca5 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/YiFrameworkAiHubDomainModule.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/YiFrameworkAiHubDomainModule.cs @@ -1,11 +1,5 @@ using Microsoft.Extensions.DependencyInjection; -using OpenAI.Chat; -using Volo.Abp.Caching; using Volo.Abp.Domain; -using Yi.Framework.AiHub.Application.Contracts.Options; -using Yi.Framework.AiHub.Domain.AiChat; -using Yi.Framework.AiHub.Domain.AiChat.Impl; -using Yi.Framework.AiHub.Domain.Managers; using Yi.Framework.AiHub.Domain.Shared; using Yi.Framework.Mapster; @@ -23,11 +17,10 @@ namespace Yi.Framework.AiHub.Domain var configuration = context.Services.GetConfiguration(); var services = context.Services; - Configure(configuration.GetSection("AiGateWay")); - - - services.AddKeyedTransient(nameof(AzureChatService)); - services.AddKeyedTransient(nameof(AzureRestChatService)); + // Configure(configuration.GetSection("AiGateWay")); + // + // services.AddKeyedTransient(nameof(AzureChatService)); + // services.AddKeyedTransient(nameof(AzureRestChatService)); } public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context) diff --git a/Yi.Abp.Net8/src/Yi.Abp.Web/Yi.Abp.Web.csproj b/Yi.Abp.Net8/src/Yi.Abp.Web/Yi.Abp.Web.csproj index 83941d49..5d8907c6 100644 --- a/Yi.Abp.Net8/src/Yi.Abp.Web/Yi.Abp.Web.csproj +++ b/Yi.Abp.Net8/src/Yi.Abp.Web/Yi.Abp.Web.csproj @@ -56,12 +56,22 @@ Always + Always + + + + + + + + +