feat: 完成ai-hub第一期功能
This commit is contained in:
@@ -5,7 +5,7 @@ public class ModelGetListOutput
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 模型ID
|
/// 模型ID
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public long Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 模型分类
|
/// 模型分类
|
||||||
@@ -20,7 +20,7 @@ public class ModelGetListOutput
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 模型描述
|
/// 模型描述
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ModelDescribe { get; set; }
|
public string? ModelDescribe { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 模型价格
|
/// 模型价格
|
||||||
@@ -55,5 +55,5 @@ public class ModelGetListOutput
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 备注信息
|
/// 备注信息
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Remark { get; set; }
|
public string? Remark { get; set; }
|
||||||
}
|
}
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
namespace Yi.Framework.AiHub.Application.Contracts.Options;
|
|
||||||
|
|
||||||
public class AiGateWayOptions
|
|
||||||
{
|
|
||||||
public AiChatOptionDic Chats { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AiChatOptionDic : Dictionary<string, AiChatModelOptions>
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AiChatModelOptions
|
|
||||||
{
|
|
||||||
public List<string> ModelIds { get; set; }
|
|
||||||
public string Endpoint { get; set; }
|
|
||||||
public string ApiKey { get; set; }
|
|
||||||
}
|
|
||||||
@@ -5,17 +5,17 @@ using Microsoft.AspNetCore.Http;
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Microsoft.Identity.Client;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Serialization;
|
using Newtonsoft.Json.Serialization;
|
||||||
using OpenAI.Chat;
|
using OpenAI.Chat;
|
||||||
using Volo.Abp.Application.Services;
|
using Volo.Abp.Application.Services;
|
||||||
using Volo.Abp.Users;
|
using Volo.Abp.Users;
|
||||||
using Yi.Framework.AiHub.Application.Contracts.Dtos;
|
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.AiHub.Domain.Managers;
|
||||||
using Yi.Framework.Rbac.Application.Contracts.IServices;
|
using Yi.Framework.Rbac.Application.Contracts.IServices;
|
||||||
using Yi.Framework.Rbac.Domain.Shared.Dtos;
|
using Yi.Framework.Rbac.Domain.Shared.Dtos;
|
||||||
|
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||||
|
|
||||||
namespace Yi.Framework.AiHub.Application.Services;
|
namespace Yi.Framework.AiHub.Application.Services;
|
||||||
|
|
||||||
@@ -24,16 +24,17 @@ namespace Yi.Framework.AiHub.Application.Services;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class AiChatService : ApplicationService
|
public class AiChatService : ApplicationService
|
||||||
{
|
{
|
||||||
private readonly AiGateWayOptions _options;
|
|
||||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||||
private readonly AiMessageManager _aiMessageManager;
|
private readonly AiMessageManager _aiMessageManager;
|
||||||
|
private readonly ISqlSugarRepository<AiModelEntity> _aiModelRepository;
|
||||||
|
private readonly AiBlacklistManager _aiBlacklistManager;
|
||||||
|
|
||||||
public AiChatService(IOptions<AiGateWayOptions> options, IHttpContextAccessor httpContextAccessor,
|
public AiChatService(IHttpContextAccessor httpContextAccessor,
|
||||||
AiMessageManager aiMessageManager)
|
AiMessageManager aiMessageManager, AiBlacklistManager aiBlacklistManager)
|
||||||
{
|
{
|
||||||
_options = options.Value;
|
|
||||||
this._httpContextAccessor = httpContextAccessor;
|
this._httpContextAccessor = httpContextAccessor;
|
||||||
_aiMessageManager = aiMessageManager;
|
_aiMessageManager = aiMessageManager;
|
||||||
|
_aiBlacklistManager = aiBlacklistManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -56,18 +57,20 @@ public class AiChatService : ApplicationService
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<List<ModelGetListOutput>> GetModelAsync()
|
public async Task<List<ModelGetListOutput>> GetModelAsync()
|
||||||
{
|
{
|
||||||
var output = _options.Chats.SelectMany(x => x.Value.ModelIds)
|
var output = await _aiModelRepository._DbQueryable.Select(x => new ModelGetListOutput
|
||||||
.Select(x => new ModelGetListOutput()
|
{
|
||||||
{
|
Id = x.Id,
|
||||||
Id = 001,
|
Category = "chat",
|
||||||
Category = "chat",
|
ModelName = x.Name,
|
||||||
ModelName = x,
|
ModelDescribe = x.Description,
|
||||||
ModelDescribe = "这是一个直连模型",
|
ModelPrice = 0,
|
||||||
ModelPrice = 4,
|
ModelType = "1",
|
||||||
ModelType = "1",
|
ModelShow = "0",
|
||||||
ModelShow = "0",
|
SystemPrompt = null,
|
||||||
Remark = "直连模型"
|
ApiHost = null,
|
||||||
}).ToList();
|
ApiKey = null,
|
||||||
|
Remark = x.Description
|
||||||
|
}).ToListAsync();
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,6 +82,25 @@ public class AiChatService : ApplicationService
|
|||||||
/// <param name="cancellationToken"></param>
|
/// <param name="cancellationToken"></param>
|
||||||
public async Task PostSendAsync(SendMessageInput input, CancellationToken cancellationToken)
|
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 httpContext = this._httpContextAccessor.HttpContext;
|
||||||
var response = httpContext.Response;
|
var response = httpContext.Response;
|
||||||
// 设置响应头,声明是 SSE 流
|
// 设置响应头,声明是 SSE 流
|
||||||
@@ -116,7 +138,7 @@ public class AiChatService : ApplicationService
|
|||||||
// 启动一个后台任务来消费队列
|
// 启动一个后台任务来消费队列
|
||||||
var outputTask = Task.Run(async () =>
|
var outputTask = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
while (!(isComplete&&messageQueue.IsEmpty))
|
while (!(isComplete && messageQueue.IsEmpty))
|
||||||
{
|
{
|
||||||
if (messageQueue.TryDequeue(out var message))
|
if (messageQueue.TryDequeue(out var message))
|
||||||
{
|
{
|
||||||
@@ -153,16 +175,15 @@ public class AiChatService : ApplicationService
|
|||||||
await outputTask;
|
await outputTask;
|
||||||
if (CurrentUser.IsAuthenticated && input.SessionId.HasValue)
|
if (CurrentUser.IsAuthenticated && input.SessionId.HasValue)
|
||||||
{
|
{
|
||||||
// 等待接入token
|
await _aiMessageManager.CreateMessageAsync(CurrentUser.GetId(), input.SessionId.Value, new MessageInputDto
|
||||||
// await _aiMessageManager.CreateMessageAsync(CurrentUser.GetId(), input.SessionId.Value, new MessageInputDto
|
{
|
||||||
// {
|
Content = input.Messages.LastOrDefault().Content,
|
||||||
// Content = null,
|
Role = input.Messages.LastOrDefault().Role,
|
||||||
// Role = null,
|
DeductCost = 0,
|
||||||
// DeductCost = 0,
|
TotalTokens = 0,
|
||||||
// TotalTokens = 0,
|
ModelId = input.Model,
|
||||||
// ModelId = null,
|
Remark = null
|
||||||
// Remark = null
|
});
|
||||||
// });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
namespace Yi.Framework.AiHub.Domain.Shared.Dtos;
|
||||||
|
|
||||||
|
public class AiModelDescribe
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 应用id
|
||||||
|
/// </summary>
|
||||||
|
public Guid AppId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 应用名称
|
||||||
|
/// </summary>
|
||||||
|
public string AppName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 应用终结点
|
||||||
|
/// </summary>
|
||||||
|
public string Endpoint { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 应用key
|
||||||
|
/// </summary>
|
||||||
|
public string ApiKey { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 排序
|
||||||
|
/// </summary>
|
||||||
|
public int OrderNum { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 处理名
|
||||||
|
/// </summary>
|
||||||
|
public string HandlerName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 模型id
|
||||||
|
/// </summary>
|
||||||
|
public string ModelId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 模型名称
|
||||||
|
/// </summary>
|
||||||
|
public string ModelName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 模型描述
|
||||||
|
/// </summary>
|
||||||
|
public string? Description { get; set; }
|
||||||
|
}
|
||||||
@@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Consts\" />
|
<Folder Include="Consts\" />
|
||||||
<Folder Include="Dtos\" />
|
|
||||||
<Folder Include="Enums\" />
|
<Folder Include="Enums\" />
|
||||||
<Folder Include="Etos\" />
|
<Folder Include="Etos\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
using OpenAI.Chat;
|
using OpenAI.Chat;
|
||||||
|
using Yi.Framework.AiHub.Domain.Shared.Dtos;
|
||||||
|
|
||||||
namespace Yi.Framework.AiHub.Domain.AiChat;
|
namespace Yi.Framework.AiHub.Domain.AiChat;
|
||||||
|
|
||||||
public interface IChatService
|
public interface IChatService
|
||||||
{
|
{
|
||||||
public IAsyncEnumerable<string> CompleteChatAsync(string modelId, List<ChatMessage> messages,CancellationToken cancellationToken);
|
public IAsyncEnumerable<string> CompleteChatAsync(AiModelDescribe aiModelDescribe, List<ChatMessage> messages,
|
||||||
|
CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
@@ -1,28 +1,24 @@
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using Azure;
|
using Azure;
|
||||||
using Azure.AI.OpenAI;
|
using Azure.AI.OpenAI;
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using OpenAI.Chat;
|
using OpenAI.Chat;
|
||||||
using Yi.Framework.AiHub.Application.Contracts.Options;
|
using Yi.Framework.AiHub.Domain.Shared.Dtos;
|
||||||
|
|
||||||
namespace Yi.Framework.AiHub.Domain.AiChat.Impl;
|
namespace Yi.Framework.AiHub.Domain.AiChat.Impl;
|
||||||
|
|
||||||
public class AzureChatService : IChatService
|
public class AzureChatService : IChatService
|
||||||
{
|
{
|
||||||
private readonly AiChatModelOptions _options;
|
public AzureChatService()
|
||||||
|
|
||||||
public AzureChatService(IOptions<AiGateWayOptions> options)
|
|
||||||
{
|
{
|
||||||
this._options = options.Value.Chats[nameof(AzureChatService)];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async IAsyncEnumerable<string> CompleteChatAsync(string modelId, List<ChatMessage> messages,
|
public async IAsyncEnumerable<string> CompleteChatAsync(AiModelDescribe aiModelDescribe, List<ChatMessage> messages,
|
||||||
[EnumeratorCancellation] CancellationToken cancellationToken)
|
[EnumeratorCancellation] CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var endpoint = new Uri(_options.Endpoint);
|
var endpoint = new Uri(aiModelDescribe.Endpoint);
|
||||||
|
|
||||||
var deploymentName = modelId;
|
var deploymentName = aiModelDescribe.ModelId;
|
||||||
var apiKey = _options.ApiKey;
|
var apiKey = aiModelDescribe.ApiKey;
|
||||||
|
|
||||||
AzureOpenAIClient azureClient = new(
|
AzureOpenAIClient azureClient = new(
|
||||||
endpoint,
|
endpoint,
|
||||||
|
|||||||
@@ -1,28 +1,24 @@
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using OpenAI.Chat;
|
using OpenAI.Chat;
|
||||||
using Yi.Framework.AiHub.Application.Contracts.Options;
|
|
||||||
using Yi.Framework.AiHub.Domain.Extensions;
|
using Yi.Framework.AiHub.Domain.Extensions;
|
||||||
|
using Yi.Framework.AiHub.Domain.Shared.Dtos;
|
||||||
|
|
||||||
namespace Yi.Framework.AiHub.Domain.AiChat.Impl;
|
namespace Yi.Framework.AiHub.Domain.AiChat.Impl;
|
||||||
|
|
||||||
public class AzureRestChatService : IChatService
|
public class AzureRestChatService : IChatService
|
||||||
{
|
{
|
||||||
private readonly AiChatModelOptions _options;
|
public AzureRestChatService()
|
||||||
|
|
||||||
public AzureRestChatService(IOptions<AiGateWayOptions> options)
|
|
||||||
{
|
{
|
||||||
this._options = options.Value.Chats[nameof(AzureRestChatService)];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async IAsyncEnumerable<string> CompleteChatAsync(string modelId, List<ChatMessage> messages,
|
public async IAsyncEnumerable<string> CompleteChatAsync(AiModelDescribe aiModelDescribe, List<ChatMessage> messages,
|
||||||
[EnumeratorCancellation] CancellationToken cancellationToken)
|
[EnumeratorCancellation] CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 设置API URL
|
// 设置API URL
|
||||||
var apiUrl = $"{_options.Endpoint}models/chat/completions";
|
var apiUrl = $"{aiModelDescribe.Endpoint}models/chat/completions";
|
||||||
|
|
||||||
|
|
||||||
var ss = messages.Select(x => new
|
var ss = messages.Select(x => new
|
||||||
@@ -45,7 +41,7 @@ public class AzureRestChatService : IChatService
|
|||||||
top_p = 0.1,
|
top_p = 0.1,
|
||||||
presence_penalty = 0,
|
presence_penalty = 0,
|
||||||
frequency_penalty = 0,
|
frequency_penalty = 0,
|
||||||
model = modelId
|
model = aiModelDescribe.ModelId
|
||||||
};
|
};
|
||||||
|
|
||||||
// 序列化请求内容为JSON
|
// 序列化请求内容为JSON
|
||||||
@@ -53,24 +49,25 @@ public class AzureRestChatService : IChatService
|
|||||||
|
|
||||||
using var httpClient = new HttpClient();
|
using var httpClient = new HttpClient();
|
||||||
// 设置请求头
|
// 设置请求头
|
||||||
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_options.ApiKey}");
|
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {aiModelDescribe.ApiKey}");
|
||||||
// 其他头信息如Content-Type在StringContent中设置
|
// 其他头信息如Content-Type在StringContent中设置
|
||||||
|
|
||||||
// 构造 POST 请求
|
// 构造 POST 请求
|
||||||
var request = new HttpRequestMessage(HttpMethod.Post, apiUrl);
|
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请求
|
// 发送POST请求
|
||||||
HttpResponseMessage response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
|
HttpResponseMessage response =
|
||||||
|
await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
|
||||||
// 确认响应成功
|
// 确认响应成功
|
||||||
response.EnsureSuccessStatusCode();
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
// 读取响应内容
|
// 读取响应内容
|
||||||
var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken);
|
var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken);
|
||||||
// 从流中读取数据并输出到控制台
|
// 从流中读取数据并输出到控制台
|
||||||
using var streamReader = new System.IO.StreamReader(responseStream);
|
using var streamReader = new StreamReader(responseStream);
|
||||||
string line;
|
string line;
|
||||||
while ((line = await streamReader.ReadLineAsync(cancellationToken)) != null)
|
while ((line = await streamReader.ReadLineAsync(cancellationToken)) != null)
|
||||||
{
|
{
|
||||||
@@ -117,7 +114,5 @@ public class AzureRestChatService : IChatService
|
|||||||
// 解析失败
|
// 解析失败
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using SqlSugar;
|
||||||
|
using Volo.Abp.Domain.Entities.Auditing;
|
||||||
|
|
||||||
|
namespace Yi.Framework.AiHub.Domain.Entities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ai黑名单
|
||||||
|
/// </summary>
|
||||||
|
[SugarTable("Ai_Blacklist")]
|
||||||
|
public class AiBlacklistAggregateRoot : FullAuditedAggregateRoot<Guid>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 用户
|
||||||
|
/// </summary>
|
||||||
|
public Guid UserId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 有效开始时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime StartTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 有效结束时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime EndTime { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
using SqlSugar;
|
||||||
|
using Volo.Abp.Domain.Entities.Auditing;
|
||||||
|
using Yi.Framework.Core.Data;
|
||||||
|
|
||||||
|
namespace Yi.Framework.AiHub.Domain.Entities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ai应用
|
||||||
|
/// </summary>
|
||||||
|
[SugarTable("Ai_App")]
|
||||||
|
public class AiAppAggregateRoot : FullAuditedAggregateRoot<Guid>, IOrderNum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 应用名称
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 应用终结点
|
||||||
|
/// </summary>
|
||||||
|
public string Endpoint { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 应用key
|
||||||
|
/// </summary>
|
||||||
|
public string ApiKey { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 排序
|
||||||
|
/// </summary>
|
||||||
|
public int OrderNum { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ai模型
|
||||||
|
/// </summary>
|
||||||
|
[Navigate(NavigateType.OneToMany, nameof(AiModelEntity.AiAppId))]
|
||||||
|
public List<AiModelEntity> AiModels { get; set; }
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ai模型定义
|
||||||
|
/// </summary>
|
||||||
|
[SugarTable("Ai_Model")]
|
||||||
|
public class AiModelEntity : Entity<Guid>, IOrderNum,ISoftDelete
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 处理名
|
||||||
|
/// </summary>
|
||||||
|
public string HandlerName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 模型id
|
||||||
|
/// </summary>
|
||||||
|
public string ModelId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 模型名称
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 模型描述
|
||||||
|
/// </summary>
|
||||||
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 排序
|
||||||
|
/// </summary>
|
||||||
|
public int OrderNum { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 软删除
|
||||||
|
/// </summary>
|
||||||
|
public bool IsDeleted { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ai应用id
|
||||||
|
/// </summary>
|
||||||
|
public Guid AiAppId { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using SqlSugar;
|
||||||
|
using Volo.Abp.Domain.Entities.Auditing;
|
||||||
|
|
||||||
|
namespace Yi.Framework.AiHub.Domain.Entities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用量统计
|
||||||
|
/// </summary>
|
||||||
|
[SugarTable("Ai_UsageStatistics")]
|
||||||
|
public class UsageStatisticsAggregateRoot : FullAuditedAggregateRoot<Guid>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 用户id
|
||||||
|
/// </summary>
|
||||||
|
public Guid UserId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 哪个模型
|
||||||
|
/// </summary>
|
||||||
|
public string ModelId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 总token使用
|
||||||
|
/// </summary>
|
||||||
|
public decimal TotalTokens { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 对话次数
|
||||||
|
/// </summary>
|
||||||
|
public int Number { get; set; }
|
||||||
|
}
|
||||||
@@ -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<AiBlacklistAggregateRoot> _aiBlacklistRepository;
|
||||||
|
|
||||||
|
public AiBlacklistManager(ISqlSugarRepository<AiBlacklistAggregateRoot> aiBlacklistRepository)
|
||||||
|
{
|
||||||
|
_aiBlacklistRepository = aiBlacklistRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 校验黑名单
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId"></param>
|
||||||
|
/// <exception cref="UserFriendlyException"></exception>
|
||||||
|
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("当前用户已被加入黑名单,请联系管理员处理");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,35 +1,70 @@
|
|||||||
using Azure;
|
using System.Runtime.CompilerServices;
|
||||||
using Azure.AI.OpenAI;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using OpenAI.Chat;
|
using OpenAI.Chat;
|
||||||
using Volo.Abp.Domain.Services;
|
using Volo.Abp.Domain.Services;
|
||||||
using Yi.Framework.AiHub.Application.Contracts.Options;
|
|
||||||
using Yi.Framework.AiHub.Domain.AiChat;
|
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;
|
namespace Yi.Framework.AiHub.Domain.Managers;
|
||||||
|
|
||||||
public class AiGateWayManager : DomainService
|
public class AiGateWayManager : DomainService
|
||||||
{
|
{
|
||||||
private readonly AiGateWayOptions _options;
|
private readonly ISqlSugarRepository<AiAppAggregateRoot> _aiAppRepository;
|
||||||
|
|
||||||
public AiGateWayManager(IOptions<AiGateWayOptions> options)
|
public AiGateWayManager(ISqlSugarRepository<AiAppAggregateRoot> aiAppRepository)
|
||||||
{
|
{
|
||||||
this._options = options.Value;
|
_aiAppRepository = aiAppRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IAsyncEnumerable<string> CompleteChatAsync(string modelId, List<ChatMessage> messages,
|
/// <summary>
|
||||||
CancellationToken cancellationToken)
|
/// 获取模型
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="modelId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task<AiModelDescribe> 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<IChatService>(chat.Key);
|
return new AiModelDescribe
|
||||||
return chatService.CompleteChatAsync(modelId, messages, cancellationToken);
|
{
|
||||||
|
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}模型当前版本不支持");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 聊天完成
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="modelId"></param>
|
||||||
|
/// <param name="messages"></param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async IAsyncEnumerable<string> CompleteChatAsync(string modelId, List<ChatMessage> messages,
|
||||||
|
[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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,5 @@
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using OpenAI.Chat;
|
|
||||||
using Volo.Abp.Caching;
|
|
||||||
using Volo.Abp.Domain;
|
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.AiHub.Domain.Shared;
|
||||||
using Yi.Framework.Mapster;
|
using Yi.Framework.Mapster;
|
||||||
|
|
||||||
@@ -23,11 +17,10 @@ namespace Yi.Framework.AiHub.Domain
|
|||||||
var configuration = context.Services.GetConfiguration();
|
var configuration = context.Services.GetConfiguration();
|
||||||
var services = context.Services;
|
var services = context.Services;
|
||||||
|
|
||||||
Configure<AiGateWayOptions>(configuration.GetSection("AiGateWay"));
|
// Configure<AiGateWayOptions>(configuration.GetSection("AiGateWay"));
|
||||||
|
//
|
||||||
|
// services.AddKeyedTransient<IChatService, AzureChatService>(nameof(AzureChatService));
|
||||||
services.AddKeyedTransient<IChatService, AzureChatService>(nameof(AzureChatService));
|
// services.AddKeyedTransient<IChatService, AzureRestChatService>(nameof(AzureRestChatService));
|
||||||
services.AddKeyedTransient<IChatService, AzureRestChatService>(nameof(AzureRestChatService));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
|
public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
|
||||||
|
|||||||
@@ -56,12 +56,22 @@
|
|||||||
<Content Update="wwwroot\stock\**">
|
<Content Update="wwwroot\stock\**">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
<Content Remove="logs\**" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Update="ip2region.db">
|
<None Update="ip2region.db">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
<None Remove="logs\**" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Remove="logs\**" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Remove="logs\**" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
Reference in New Issue
Block a user