feat:完成ai网关搭建

This commit is contained in:
ccnetcore
2025-06-21 01:08:14 +08:00
parent 6abcc49ed4
commit 3b74dfd49a
10 changed files with 236 additions and 173 deletions

View File

@@ -0,0 +1,17 @@
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; }
}

View File

@@ -1,3 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using Yi.Framework.AiHub.Application.Contracts.Options;
using Yi.Framework.AiHub.Domain.Shared; using Yi.Framework.AiHub.Domain.Shared;
using Yi.Framework.Ddd.Application.Contracts; using Yi.Framework.Ddd.Application.Contracts;
@@ -5,11 +7,12 @@ namespace Yi.Framework.AiHub.Application.Contracts
{ {
[DependsOn( [DependsOn(
typeof(YiFrameworkAiHubDomainSharedModule), typeof(YiFrameworkAiHubDomainSharedModule),
typeof(YiFrameworkDddApplicationContractsModule))] typeof(YiFrameworkDddApplicationContractsModule))]
public class YiFrameworkAiHubApplicationContractsModule:AbpModule public class YiFrameworkAiHubApplicationContractsModule : AbpModule
{ {
public override void ConfigureServices(ServiceConfigurationContext context)
{
var build = context.Services.GetConfiguration();
}
} }
} }

View File

@@ -1,19 +1,28 @@
using Microsoft.AspNetCore.Http; using System.Text;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using OpenAI.Chat;
using Volo.Abp.Application.Services; using Volo.Abp.Application.Services;
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.Managers;
namespace Yi.Framework.AiHub.Application.Services; namespace Yi.Framework.AiHub.Application.Services;
public class AiService : ApplicationService public class AiService : ApplicationService
{ {
// private readonly SemanticKernelClient _skClient; private readonly AiGateWayOptions _options;
private IHttpContextAccessor httpContextAccessor; private readonly IHttpContextAccessor _httpContextAccessor;
public AiService(IOptions<AiGateWayOptions> options, IHttpContextAccessor httpContextAccessor)
{
_options = options.Value;
this._httpContextAccessor = httpContextAccessor;
}
// public AiService(SemanticKernelClient skClient, IHttpContextAccessor httpContextAccessor)
// {
// _skClient = skClient;
// this.httpContextAccessor = httpContextAccessor;
// }
/// <summary> /// <summary>
/// 获取模型列表 /// 获取模型列表
@@ -21,96 +30,68 @@ public class AiService : ApplicationService
/// <returns></returns> /// <returns></returns>
public async Task<List<ModelGetListOutput>> GetModelAsync() public async Task<List<ModelGetListOutput>> GetModelAsync()
{ {
return new List<ModelGetListOutput>() var output = _options.Chats.SelectMany(x => x.Value.ModelIds)
{ .Select(x => new ModelGetListOutput()
new ModelGetListOutput
{ {
Id = 001, Id = 001,
Category = "chat", Category = "chat",
ModelName = "gpt-4.1-mini", ModelName = x,
ModelDescribe = "gpt下的ai", ModelDescribe = "这是一个直连模型",
ModelPrice = 4, ModelPrice = 4,
ModelType = "1", ModelType = "1",
ModelShow = "0", ModelShow = "0",
SystemPrompt = "", Remark = "直连模型"
ApiHost = "", }).ToList();
ApiKey = "", return output;
Remark = "牛逼"
},
new ModelGetListOutput
{
Id = 002,
Category = "chat",
ModelName = "grok-3-mini",
ModelDescribe = "马斯克的ai",
ModelPrice = 5,
ModelType = "1",
ModelShow = "0",
SystemPrompt = "",
ApiHost = "",
ApiKey = "",
Remark = "牛逼啊"
}
};
} }
// /// <summary> /// <summary>
// /// 发送消息 /// 发送消息
// /// </summary> /// </summary>
// /// <param name="input"></param> /// <param name="input"></param>
// /// <param name="cancelToken"></param> /// <param name="cancelToken"></param>
// public async Task PostSendAsync(SendMessageInput input,CancellationToken cancelToken) public async Task PostSendAsync(SendMessageInput input, CancellationToken cancelToken)
// { {
// var httpContext = this.httpContextAccessor.HttpContext; var httpContext = this._httpContextAccessor.HttpContext;
// var response = httpContext.Response; var response = httpContext.Response;
// // 设置响应头,声明是 SSE 流 // 设置响应头,声明是 SSE 流
// response.ContentType = "text/event-stream"; response.ContentType = "text/event-stream";
// response.Headers.Append("Cache-Control", "no-cache"); response.Headers.Append("Cache-Control", "no-cache");
// response.Headers.Append("Connection", "keep-alive"); response.Headers.Append("Connection", "keep-alive");
//
//
// var chatCompletionService = this._skClient.Kernel.GetRequiredService<IChatCompletionService>(input.Model);
// var history = new ChatHistory();
// var openSettings = new AzureOpenAIPromptExecutionSettings()
// {
// MaxTokens = 3000
// };
// foreach (var aiChatContextDto in input.Messages)
// {
// if (aiChatContextDto.Role == "ai")
// {
// history.AddAssistantMessage(aiChatContextDto.Content);
// }
// else if (aiChatContextDto.Role == "user")
// {
// history.AddUserMessage(aiChatContextDto.Content);
// }
// }
//
// var results = chatCompletionService.GetStreamingChatMessageContentsAsync(
// chatHistory: history,
// executionSettings: openSettings,
// kernel: _skClient.Kernel,
// cancelToken);
//
//
// await using var writer = new StreamWriter(response.Body, Encoding.UTF8, leaveOpen: true);
// await foreach (var result in results)
// {
// var modle = GetMessage(input.Model, result.Content);
// var message = JsonConvert.SerializeObject(modle, new JsonSerializerSettings
// {
// ContractResolver = new CamelCasePropertyNamesContractResolver()
// });
//
// await writer.WriteLineAsync($"data: {message}\n");
// await writer.FlushAsync(cancelToken); // 确保立即推送数据
// }
// }
private SendMessageOutputDto GetMessage(string modelId, string content) var history = new List<ChatMessage>();
foreach (var aiChatContextDto in input.Messages)
{
if (aiChatContextDto.Role == "ai")
{
history.Add(ChatMessage.CreateAssistantMessage(aiChatContextDto.Content));
}
else if (aiChatContextDto.Role == "user")
{
history.Add(ChatMessage.CreateUserMessage(aiChatContextDto.Content));
}
}
var gateWay = LazyServiceProvider.GetRequiredService<AiGateWayManager>();
var completeChatResponse = gateWay.CompleteChatAsync(input.Model, history);
await using var writer = new StreamWriter(response.Body, Encoding.UTF8, leaveOpen: true);
await foreach (var data in completeChatResponse)
{
var model = MapToMessage(input.Model, data);
var message = JsonConvert.SerializeObject(model, new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
await writer.WriteLineAsync($"data: {message}\n");
await writer.FlushAsync(cancelToken); // 确保立即推送数据
}
}
private SendMessageOutputDto MapToMessage(string modelId, string content)
{ {
var output = new SendMessageOutputDto var output = new SendMessageOutputDto
{ {

View File

@@ -0,0 +1,8 @@
using OpenAI.Chat;
namespace Yi.Framework.AiHub.Domain.AiChat;
public interface IChatService
{
public IAsyncEnumerable<string> CompleteChatAsync(string modelId, List<ChatMessage> messages);
}

View File

@@ -0,0 +1,40 @@
using Azure;
using Azure.AI.OpenAI;
using Microsoft.Extensions.Options;
using OpenAI.Chat;
using Yi.Framework.AiHub.Application.Contracts.Options;
namespace Yi.Framework.AiHub.Domain.AiChat.Impl;
public class AzureChatService : IChatService
{
private readonly AiChatModelOptions _options;
public AzureChatService(IOptions<AiGateWayOptions> options)
{
this._options = options.Value.Chats[nameof(AzureChatService)];
}
public async IAsyncEnumerable<string> CompleteChatAsync(string modelId, List<ChatMessage> messages)
{
var endpoint = new Uri(_options.Endpoint);
var deploymentName = modelId;
var apiKey = _options.ApiKey;
AzureOpenAIClient azureClient = new(
endpoint,
new AzureKeyCredential(apiKey));
ChatClient chatClient = azureClient.GetChatClient(deploymentName);
var response = chatClient.CompleteChatStreamingAsync(messages);
await foreach (StreamingChatCompletionUpdate update in response)
{
foreach (ChatMessageContentPart updatePart in update.ContentUpdate)
{
yield return updatePart.Text;
}
}
}
}

View File

@@ -0,0 +1,33 @@
using Azure;
using Azure.AI.OpenAI;
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;
namespace Yi.Framework.AiHub.Domain.Managers;
public class AiGateWayManager : DomainService
{
private readonly AiGateWayOptions _options;
public AiGateWayManager(IOptions<AiGateWayOptions> options)
{
this._options = options.Value;
}
public IAsyncEnumerable<string> CompleteChatAsync(string modelId, List<ChatMessage> messages)
{
foreach (var chat in _options.Chats)
{
if (chat.Value.ModelIds.Contains(modelId))
{
var chatService = LazyServiceProvider.GetRequiredKeyedService<IChatService>(chat.Key);
return chatService.CompleteChatAsync(modelId, messages);
}
}
throw new UserFriendlyException($"当前暂不支持该模型-【{modelId}】");
}
}

View File

@@ -1,39 +0,0 @@
using Azure;
using Azure.AI.OpenAI;
using OpenAI.Chat;
using Volo.Abp.Domain.Services;
namespace Yi.Framework.AiHub.Domain.Managers;
public class OpenAiManager : DomainService
{
public static async Task TestAsync()
{
var endpoint = new Uri("https://japan-ccnetcore-resource.cognitiveservices.azure.com/");
// var deploymentName = "gpt-4.1-mini";
var deploymentName = "o4-mini";
var apiKey = "FaccnRh7Zvz25OCGH07kHPe2z1aCXMliLdr3esgWHgXQ2aivwFgDJQQJ99BFACi0881XJ3w3AAAAACOGAJ2G";
AzureOpenAIClient azureClient = new(
endpoint,
new AzureKeyCredential(apiKey));
ChatClient chatClient = azureClient.GetChatClient(deploymentName);
List<ChatMessage> messages = new List<ChatMessage>()
{
new UserChatMessage("使用c#写一个贪吃蛇代码"),
};
var response = chatClient.CompleteChatStreamingAsync(messages);
await foreach (StreamingChatCompletionUpdate update in response)
{
foreach (ChatMessageContentPart updatePart in update.ContentUpdate)
{
System.Console.Write(updatePart.Text);
}
}
System.Console.WriteLine("结束");
}
}

View File

@@ -9,6 +9,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\..\framework\Yi.Framework.Mapster\Yi.Framework.Mapster.csproj" /> <ProjectReference Include="..\..\..\framework\Yi.Framework.Mapster\Yi.Framework.Mapster.csproj" />
<ProjectReference Include="..\..\..\framework\Yi.Framework.SqlSugarCore.Abstractions\Yi.Framework.SqlSugarCore.Abstractions.csproj" /> <ProjectReference Include="..\..\..\framework\Yi.Framework.SqlSugarCore.Abstractions\Yi.Framework.SqlSugarCore.Abstractions.csproj" />
<ProjectReference Include="..\Yi.Framework.AiHub.Application.Contracts\Yi.Framework.AiHub.Application.Contracts.csproj" />
<ProjectReference Include="..\Yi.Framework.AiHub.Domain.Shared\Yi.Framework.AiHub.Domain.Shared.csproj" /> <ProjectReference Include="..\Yi.Framework.AiHub.Domain.Shared\Yi.Framework.AiHub.Domain.Shared.csproj" />
</ItemGroup> </ItemGroup>

View File

@@ -1,6 +1,11 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using OpenAI.Chat;
using Volo.Abp.Caching; 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;
@@ -17,11 +22,17 @@ 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"));
services.AddKeyedTransient<IChatService, AzureChatService>(nameof(AzureChatService));
} }
public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context) public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
{ {
await Yi.Framework.AiHub.Domain.Managers.OpenAiManager.Test2Async(); var service = context.ServiceProvider;
} }
} }
} }

View File

@@ -1,15 +1,15 @@
{ {
//多租户支持多库DbConnOptions会自动创建到默认租户,支持配置文件方式+数据库方式AbpDefaultTenantStoreOptions //多租户支持多库DbConnOptions会自动创建到默认租户,支持配置文件方式+数据库方式AbpDefaultTenantStoreOptions
// "Tenants": [ // "Tenants": [
// { // {
// "Id": "33333333-3d72-4339-9adc-845151f8ada0", // "Id": "33333333-3d72-4339-9adc-845151f8ada0",
// "Name": "Mes@MySql", // "Name": "Mes@MySql",
// "ConnectionStrings": { // "ConnectionStrings": {
// "Default": "DataSource=mes-dev.db" // "Default": "DataSource=mes-dev.db"
// }, // },
// "IsActive": false // "IsActive": false
// } // }
// ], // ],
"Logging": { "Logging": {
"LogLevel": { "LogLevel": {
@@ -27,36 +27,39 @@
"Settings": { "Settings": {
"Test": "hello" "Test": "hello"
}, },
//数据库类型列表 //数据库类型列表
"DbList": [ "Sqlite", "Mysql", "Sqlserver", "Oracle", "PostgreSQL" ], "DbList": [
"Sqlite",
"DbConnOptions": { "Mysql",
"Url": "DataSource=yi-abp-dev.db", "Sqlserver",
"DbType": "Sqlite", "Oracle",
"EnabledReadWrite": false, "PostgreSQL"
"EnabledCodeFirst": true, ],
"EnabledSqlLog": true, "DbConnOptions": {
"EnabledDbSeed": true, "Url": "DataSource=yi-abp-dev.db",
"EnableUnderLine": false, // 启用驼峰转下划线 "DbType": "Sqlite",
//SAAS多租户 "EnabledReadWrite": false,
"EnabledSaasMultiTenancy": true "EnabledCodeFirst": true,
//读写分离地址 "EnabledSqlLog": true,
//"ReadUrl": [ "EnabledDbSeed": true,
// "DataSource=[xxxx]", //Sqlite "EnableUnderLine": false,
// "server=[xxxx];port=3306;database=[xxxx];user id=[xxxx];password=[xxxx]", //Mysql // 启用驼峰转下划线
// "Data Source=[xxxx];Initial Catalog=[xxxx];User ID=[xxxx];password=[xxxx]" //Sqlserver //SAAS多租户
// "HOST=[xxxx];PORT=5432;DATABASE=[xxxx];USERID=[xxxx];PASSWORD=[xxxx]" //PostgreSQL "EnabledSaasMultiTenancy": true
//] //读写分离地址
}, //"ReadUrl": [
// "DataSource=[xxxx]", //Sqlite
// "server=[xxxx];port=3306;database=[xxxx];user id=[xxxx];password=[xxxx]", //Mysql
// "Data Source=[xxxx];Initial Catalog=[xxxx];User ID=[xxxx];password=[xxxx]" //Sqlserver
// "HOST=[xxxx];PORT=5432;DATABASE=[xxxx];USERID=[xxxx];PASSWORD=[xxxx]" //PostgreSQL
//]
},
//redis使用freeesql参数在“FreeSqlOptions的ConnectionStringBuilder中” //redis使用freeesql参数在“FreeSqlOptions的ConnectionStringBuilder中”
"Redis": { "Redis": {
"IsEnabled": false, "IsEnabled": false,
"Configuration": "127.0.0.1:6379,password=123,defaultDatabase=13", "Configuration": "127.0.0.1:6379,password=123,defaultDatabase=13",
"JobDb": 13 "JobDb": 13
}, },
//鉴权 //鉴权
"JwtOptions": { "JwtOptions": {
"Issuer": "https://ccnetcore.com", "Issuer": "https://ccnetcore.com",
@@ -71,8 +74,6 @@
"SecurityKey": "67ij4o6jo4i5j6io45j6i4j74p5k6i54ojoi5t9g8ergoj34ofgkrtbmreog894jbioemgropihj48rj4io5juopjgior", "SecurityKey": "67ij4o6jo4i5j6io45j6i4j74p5k6i54ojoi5t9g8ergoj34ofgkrtbmreog894jbioemgropihj48rj4io5juopjgior",
"ExpiresMinuteTime": 172800 "ExpiresMinuteTime": 172800
}, },
//第三方登录 //第三方登录
"OAuth": { "OAuth": {
//QQ //QQ
@@ -88,26 +89,33 @@
"RedirectUri": "" "RedirectUri": ""
} }
}, },
//Rbac模块 //Rbac模块
"RbacOptions": { "RbacOptions": {
//超级管理员种子数据默认密码 //超级管理员种子数据默认密码
"AdminPassword": "123456", "AdminPassword": "123456",
//是否开启验证码验证 //是否开启验证码验证
"EnableCaptcha": true, "EnableCaptcha": true,
//是否开启注册功能 //是否开启注册功能
"EnableRegister": false, "EnableRegister": false,
//开启定时数据库备份 //开启定时数据库备份
"EnableDataBaseBackup": false "EnableDataBaseBackup": false
}, },
//语义内核 //语义内核
"SemanticKernel": { "SemanticKernel": {
"ModelIds": ["gpt-4o"], "ModelIds": [
"gpt-4o"
],
"Endpoint": "https://xxx.com/v1", "Endpoint": "https://xxx.com/v1",
"ApiKey": "sk-xxxxxx" "ApiKey": "sk-xxxxxx"
},
//AI网关
"AiGateWay": {
"Chats": {
"AzureChatService": {
"ModelIds": ["gpt-4o"],
"Endpoint": "https://xxx.com/v1",
"ApiKey": "sk-xxxxxx"
}
}
} }
} }