From 8eea510583b7b1d434afbc074e1c23c59ef66027 Mon Sep 17 00:00:00 2001 From: ccnetcore Date: Wed, 25 Jun 2025 00:05:00 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20ai=E5=AE=8C=E6=88=90=E6=8E=A5=E5=85=A5d?= =?UTF-8?q?eepseek?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AiChat/Impl/AzureRestChatService.cs | 118 ++++++++++++++++++ .../Extensions/ChatMessageExtensions.cs | 21 ++++ .../YiFrameworkAiHubDomainModule.cs | 1 + .../YiAbpApplicationModule.cs | 14 +-- 4 files changed, 145 insertions(+), 9 deletions(-) create mode 100644 Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiChat/Impl/AzureRestChatService.cs create mode 100644 Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Extensions/ChatMessageExtensions.cs 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 new file mode 100644 index 00000000..1d6328ae --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiChat/Impl/AzureRestChatService.cs @@ -0,0 +1,118 @@ +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; + +namespace Yi.Framework.AiHub.Domain.AiChat.Impl; + +public class AzureRestChatService : IChatService +{ + private readonly AiChatModelOptions _options; + + public AzureRestChatService(IOptions options) + { + this._options = options.Value.Chats[nameof(AzureRestChatService)]; + } + + public async IAsyncEnumerable CompleteChatAsync(string modelId, List messages, + [EnumeratorCancellation] CancellationToken cancellationToken) + { + // 设置API URL + var apiUrl = $"{_options.Endpoint}models/chat/completions"; + + + var ss = messages.Select(x => new + { + role = x.GetRoleAsString(), + content = x.Content.FirstOrDefault()?.Text + }).ToList(); + + // 准备请求内容 + var requestBody = new + { + messages = messages.Select(x => new + { + role = x.GetRoleAsString(), + content = x.Content.FirstOrDefault()?.Text + }).ToList(), + stream = true, + max_tokens = 2048, + temperature = 0.8, + top_p = 0.1, + presence_penalty = 0, + frequency_penalty = 0, + model = modelId + }; + + // 序列化请求内容为JSON + string jsonBody = JsonConvert.SerializeObject(requestBody); + + using var httpClient = new HttpClient(); + // 设置请求头 + httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_options.ApiKey}"); + // 其他头信息如Content-Type在StringContent中设置 + + var content = new StringContent(jsonBody, Encoding.UTF8, "application/json"); + // 发送POST请求 + HttpResponseMessage response = await httpClient.PostAsync(apiUrl, content, cancellationToken); + // 确认响应成功 + response.EnsureSuccessStatusCode(); + + // 读取响应内容 + var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken); + // 从流中读取数据并输出到控制台 + using var streamReader = new System.IO.StreamReader(responseStream); + string line; + while ((line = await streamReader.ReadLineAsync(cancellationToken)) != null) + { + var result = GetContent(line); + if (result is not null) + { + yield return result; + } + } + } + + private string? GetContent(string line) + { + if (string.IsNullOrWhiteSpace(line)) + return null; + string prefix = "data: "; + line = line.Substring(prefix.Length); + + + try + { + // 解析为JObject + var jsonObj = JObject.Parse(line); + var content = jsonObj["choices"][0]["delta"]["content"].ToString(); + return content; + // // 判断choices是否存在且是数组,并且有元素 + // if (jsonObj.TryGetValue("choices", out var choicesToken) && choicesToken is JArray choicesArray && + // choicesArray.Count > 0) + // { + // var firstChoice = choicesArray[0] as JObject; + // // 判断delta字段是否存在 + // if (firstChoice.TryGetValue("delta", out var deltaToken)) + // { + // // 获取content字段 + // if (deltaToken.Type == JTokenType.Object && ((JObject)deltaToken).TryGetValue("content", out var contentToken)) + // { + // return contentToken.ToString(); + // } + // } + // } + } + catch (Exception) + { + // 解析失败 + return null; + } + + return null; + } +} \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Extensions/ChatMessageExtensions.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Extensions/ChatMessageExtensions.cs new file mode 100644 index 00000000..07b150da --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Extensions/ChatMessageExtensions.cs @@ -0,0 +1,21 @@ +using System.Reflection; +using OpenAI.Chat; + +namespace Yi.Framework.AiHub.Domain.Extensions; + +public static class ChatMessageExtensions +{ + public static string GetRoleAsString(this ChatMessage message) + { + var type = message.GetType(); + var propertyInfo = type.GetProperty("Role", BindingFlags.NonPublic | BindingFlags.Instance); + + if (propertyInfo != null) + { + var value = propertyInfo.GetValue(message) as ChatMessageRole?; + return value.ToString().ToLower(); + } + + return string.Empty; + } +} \ 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 b5a60c2a..71822c88 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 @@ -27,6 +27,7 @@ namespace Yi.Framework.AiHub.Domain services.AddKeyedTransient(nameof(AzureChatService)); + services.AddKeyedTransient(nameof(AzureRestChatService)); } public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context) diff --git a/Yi.Abp.Net8/src/Yi.Abp.Application/YiAbpApplicationModule.cs b/Yi.Abp.Net8/src/Yi.Abp.Application/YiAbpApplicationModule.cs index cd971d65..309b94aa 100644 --- a/Yi.Abp.Net8/src/Yi.Abp.Application/YiAbpApplicationModule.cs +++ b/Yi.Abp.Net8/src/Yi.Abp.Application/YiAbpApplicationModule.cs @@ -17,22 +17,18 @@ namespace Yi.Abp.Application [DependsOn( typeof(YiAbpApplicationContractsModule), typeof(YiAbpDomainModule), - - typeof(YiFrameworkRbacApplicationModule), - typeof(YiFrameworkBbsApplicationModule), + typeof(YiFrameworkBbsApplicationModule), typeof(YiFrameworkDigitalCollectiblesApplicationModule), - typeof(YiFrameworkChatHubApplicationModule), + typeof(YiFrameworkChatHubApplicationModule), typeof(YiFrameworkStockApplicationModule), typeof(YiFrameworkAiHubApplicationModule), - typeof(YiFrameworkTenantManagementApplicationModule), typeof(YiFrameworkCodeGenApplicationModule), - typeof (YiFrameworkSettingManagementApplicationModule), - + typeof(YiFrameworkSettingManagementApplicationModule), typeof(YiFrameworkDddApplicationModule) - )] + )] public class YiAbpApplicationModule : AbpModule { } -} +} \ No newline at end of file