From cdb5f80604b1c8c5a63b6c333e818ccfcb81cf0a Mon Sep 17 00:00:00 2001 From: zhengxs <674934275@qq.com> Date: Tue, 10 Sep 2024 18:00:07 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E5=AE=9A=E4=BD=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Senaprc.AI.Samples.Agents.csproj | 4 - .../appsettings.json | 29 +++++- .../Senparc.AI.Samples.Consoles.csproj | 13 --- .../appsettings.json | 24 ++++- src/Senparc.AI.Kernel/Extensions.cs | 39 ++++++++ .../Helpers/SemanticKernelHelper.Config.cs | 17 ++++ .../Senparc.AI.Kernel.csproj | 4 +- .../SparkAI/SparkAIChatService.cs | 96 +++++++++++++++++++ src/Senparc.AI/Entities/Keys/SparkAIKeys.cs | 30 ++++++ .../Entities/SenparcAiSettingBase.cs | 26 +++++ src/Senparc.AI/Enums.cs | 1 + .../Interfaces/ISenparcAiSetting.cs | 23 ++++- 12 files changed, 278 insertions(+), 28 deletions(-) create mode 100644 src/Senparc.AI.Kernel/SparkAI/SparkAIChatService.cs create mode 100644 src/Senparc.AI/Entities/Keys/SparkAIKeys.cs diff --git a/Samples/Senaprc.AI.Samples.Agents/Senaprc.AI.Samples.Agents.csproj b/Samples/Senaprc.AI.Samples.Agents/Senaprc.AI.Samples.Agents.csproj index 2bb7d2a..82017fc 100644 --- a/Samples/Senaprc.AI.Samples.Agents/Senaprc.AI.Samples.Agents.csproj +++ b/Samples/Senaprc.AI.Samples.Agents/Senaprc.AI.Samples.Agents.csproj @@ -21,10 +21,6 @@ - - PreserveNewest - - PreserveNewest diff --git a/Samples/Senaprc.AI.Samples.Agents/appsettings.json b/Samples/Senaprc.AI.Samples.Agents/appsettings.json index ee6c3a5..84d6f86 100644 --- a/Samples/Senaprc.AI.Samples.Agents/appsettings.json +++ b/Samples/Senaprc.AI.Samples.Agents/appsettings.json @@ -17,12 +17,12 @@ //Senparc.AI 设置 "SenparcAiSetting": { "IsDebug": true, - "AiPlatform": "AzureOpenAI", //注意修改为自己平台对应的枚举值 + "AiPlatform": "SparkAI", //注意修改为自己平台对应的枚举值 "NeuCharAIKeys": { - "ApiKey": "", //在 https://www.neuchar.com/Developer/AiApp 申请 - "NeuCharEndpoint": "https://www.neuchar.com/", //查看 ApiKey 时可看到 DeveloperId + "ApiKey": "5b0b30df3fd44811bdbfed8fe07be3eb", //在 https://www.neuchar.com/Developer/AiApp 申请 + "NeuCharEndpoint": "https://www.neuchar.com/18673", //查看 ApiKey 时可看到 DeveloperId "ModelName": { - "Chat": "gpt-35-turbo" + "Chat": "gpt-4-32k" } }, "AzureOpenAIKeys": { @@ -46,6 +46,15 @@ "TextCompletion": "chatglm2" } }, + "SparkAIKeys": { + "ApiKey": "40a519641152f3c7caecbcc416065ae1", //TODO:加密 + "AppId": "a891b281", + "ApiSecret": "ODdhYjEzZTY3OGE4OWJkYTI0M2ZlNGIz", + "SparkAiEndpoint": "", + "ModelName": { + "Chat": "gpt-35-turbo" + } + }, "Items": { "AzureDalle3": { "AiPlatform": "AzureOpenAI", @@ -68,6 +77,18 @@ } } }, + "SparkAIKeys": { + "AiPlatform": "SparkAI", + "SparkAIKeys": { + "ApiKey": "40a519641152f3c7caecbcc416065ae1", //TODO:加密 + "AppId": "a891b281", + "ApiSecret": "ODdhYjEzZTY3OGE4OWJkYTI0M2ZlNGIz", + "SparkAiEndpoint": "https://www.neuchar.com//", + "ModelName": { + "Chat": "gpt-35-turbo" + } + } + }, "OtherModels": { "AiPlatform": "" //任意数量的 *Keys 配置 diff --git a/Samples/Senparc.AI.Samples.Consoles/Senparc.AI.Samples.Consoles.csproj b/Samples/Senparc.AI.Samples.Consoles/Senparc.AI.Samples.Consoles.csproj index c53d65c..394fa45 100644 --- a/Samples/Senparc.AI.Samples.Consoles/Senparc.AI.Samples.Consoles.csproj +++ b/Samples/Senparc.AI.Samples.Consoles/Senparc.AI.Samples.Consoles.csproj @@ -9,19 +9,6 @@ - - - - - - - PreserveNewest - - - PreserveNewest - - - diff --git a/Samples/Senparc.AI.Samples.Consoles/appsettings.json b/Samples/Senparc.AI.Samples.Consoles/appsettings.json index 84a351f..3ac36bc 100644 --- a/Samples/Senparc.AI.Samples.Consoles/appsettings.json +++ b/Samples/Senparc.AI.Samples.Consoles/appsettings.json @@ -13,7 +13,7 @@ //Senparc.AI 设置 "SenparcAiSetting": { "IsDebug": true, - "AiPlatform": "NeuCharAI", //注意修改为自己平台对应的枚举值 + "AiPlatform": "SparkAI", //注意修改为自己平台对应的枚举值 "NeuCharAIKeys": { "ApiKey": "xxxxxxx", //在 https://www.neuchar.com/Developer/AiApp 申请 "NeuCharEndpoint": "https://www.neuchar.com/", //查看 ApiKey 时可看到 DeveloperId,替换掉 @@ -54,6 +54,15 @@ "TextCompletion": "qwen:7b" } }, + "SparkAIKeys": { + "ApiKey": "", //TODO:加密 + "AppId": "", + "ApiSecret": "", + "SparkAiEndpoint": "", + "ModelName": { + "Chat": "gpt-35-turbo" + } + }, "Items": { "AzureDalle3": { "AiPlatform": "AzureOpenAI", @@ -76,6 +85,19 @@ } } }, + "SparkAIKeys": { + "AiPlatform": "SparkAI", + "SparkAIKeys": { + "ApiKey": "40a519641152f3c7caecbcc416065ae1", //TODO:加密 + "AppId": "a891b281", + "ApiSecret": "ODdhYjEzZTY3OGE4OWJkYTI0M2ZlNGIz", + "SparkAiEndpoint": "https://www.neuchar.com//", + "ModelName": { + "Chat": "gpt-35-turbo" + } + } + }, + "OtherModels": { "AiPlatform": "" //任意数量的 *Keys 配置 diff --git a/src/Senparc.AI.Kernel/Extensions.cs b/src/Senparc.AI.Kernel/Extensions.cs index 6b2d169..d136c38 100644 --- a/src/Senparc.AI.Kernel/Extensions.cs +++ b/src/Senparc.AI.Kernel/Extensions.cs @@ -8,6 +8,9 @@ using Microsoft.SemanticKernel.Connectors.OpenAI; using Microsoft.SemanticKernel.TextGeneration; using Microsoft.SemanticKernel; +using Sdcb.SparkDesk; +using Senparc.AI.Kernel.SparkAI; +using System.Net; namespace Senparc.AI.Kernel { @@ -36,7 +39,21 @@ public static IKernelBuilder AddFastAPIChatCompletion(this IKernelBuilder builde builder.Services.AddKeyedSingleton((object?)serviceId, (Func)implementationFactory); return builder; } + //public static IKernelBuilder AddFOllamaChatCompletion(this IKernelBuilder builder, string modelId, string endpoint, string serviceId = null) + //{ + // string modelId2 = modelId; + + // Func implementationFactory = + // (IServiceProvider serviceProvider, object? _) => + // new OpenAIChatCompletionService(modelId, new OpenAIClient(new Uri(endpoint), new Azure.AzureKeyCredential(apiKey)), serviceProvider.GetService()); + + // OllamaApiClientExtensions. + + // builder.Services.AddKeyedSingleton((object?)serviceId, (Func)implementationFactory); + // builder.Services.AddKeyedSingleton((object?)serviceId, (Func)implementationFactory); + // return builder; + //} #region Ollama public static IKernelBuilder AddFOllamaChatCompletion(this IKernelBuilder builder, string modelId, string endpoint, string serviceId = null) @@ -65,5 +82,27 @@ public static IKernelBuilder AddFOllamaTextCompletion(this IKernelBuilder builde } #endregion + + + /// + /// 添加 SparkAI 聊天服务 + /// + /// + /// + /// + /// + /// + /// + /// + public static IKernelBuilder AddSparkAIChatCompletion(this IKernelBuilder builder, string appId, string apiKey, string apiSecret) + { + + string apiKey2 = apiKey; + string appId2 = appId; + string apiSecret2 = apiSecret; + // Register SparkAIService as a singleton in the dependency injection container + builder.Services.AddSingleton(new SparkAIChatService(appId, apiKey, apiSecret)); + return builder; + } } } diff --git a/src/Senparc.AI.Kernel/Helpers/SemanticKernelHelper.Config.cs b/src/Senparc.AI.Kernel/Helpers/SemanticKernelHelper.Config.cs index 4bf7f78..ba4bf1c 100644 --- a/src/Senparc.AI.Kernel/Helpers/SemanticKernelHelper.Config.cs +++ b/src/Senparc.AI.Kernel/Helpers/SemanticKernelHelper.Config.cs @@ -93,6 +93,11 @@ public IKernelBuilder ConfigChat(string userId, string modelName = null, ISenpar endpoint: senparcAiSetting.OllamaEndpoint, serviceId: null ), + AiPlatform.SparkAI => kernelBuilder.AddSparkAIChatCompletion( + apiKey: senparcAiSetting.ApiKey, + appId: senparcAiSetting.SparkAIKeys.AppId, + apiSecret: senparcAiSetting.SparkAIKeys.ApiSecret + ), _ => throw new SenparcAiException($"没有处理当前 {nameof(AiPlatform)} 类型:{aiPlatForm}") }; @@ -163,6 +168,18 @@ public IKernelBuilder ConfigTextCompletion(string userId, string modelName = nul endpoint: senparcAiSetting.OllamaEndpoint, serviceId: null ), + AiPlatform.SparkAI => kernelBuilder.AddSparkAIChatCompletion( + appId:senparcAiSetting.SparkAIKeys.AppId, + apiKey:senparcAiSetting.SparkAIKeys.ApiKey, + apiSecret:senparcAiSetting.SparkAIKeys.ApiSecret + ), + //AiPlatform.Ollama => kernelBuilder.AddOpenAIChatCompletion( + // modelId: modelName, + // apiKey: senparcAiSetting.ApiKey, + // orgId: senparcAiSetting.OrganizationId, + // endpoint: senparcAiSetting.FastAPIEndpoint, + // serviceId: null + // ), _ => throw new SenparcAiException($"没有处理当前 {nameof(AiPlatform)} 类型:{aiPlatForm}") }; diff --git a/src/Senparc.AI.Kernel/Senparc.AI.Kernel.csproj b/src/Senparc.AI.Kernel/Senparc.AI.Kernel.csproj index 9478449..3d4ac9a 100644 --- a/src/Senparc.AI.Kernel/Senparc.AI.Kernel.csproj +++ b/src/Senparc.AI.Kernel/Senparc.AI.Kernel.csproj @@ -2,7 +2,7 @@ netstandard2.1 - 0.18.0 + 0.19.0 enable 10.0 Senparc.AI.Kernel @@ -67,10 +67,12 @@ + + diff --git a/src/Senparc.AI.Kernel/SparkAI/SparkAIChatService.cs b/src/Senparc.AI.Kernel/SparkAI/SparkAIChatService.cs new file mode 100644 index 0000000..eb86c0c --- /dev/null +++ b/src/Senparc.AI.Kernel/SparkAI/SparkAIChatService.cs @@ -0,0 +1,96 @@ +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.TextGeneration; +using Sdcb.SparkDesk; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using static Humanizer.On; + + +namespace Senparc.AI.Kernel.SparkAI +{ + /// + /// 讯飞星火api + /// + public class SparkAIChatService : ITextGenerationService + { + private readonly SparkDeskClient _client; + private readonly string _appId; + private readonly string _apiKey; + private readonly string _apiSecret; + + public SparkAIChatService(string appId, string apiKey, string apiSecret) + { + _appId = appId; + _apiKey = apiKey; + _apiSecret = apiSecret; + _client = new SparkDeskClient(appId, apiKey, apiSecret); + } + + public IReadOnlyDictionary Attributes => throw new NotImplementedException(); + + public async Task ChatAsync(string[] userMessages) + { + StringBuilder sb = new StringBuilder(); + List messages = new List(); + + foreach (var message in userMessages) + { + messages.Add(ChatMessage.FromUser(message)); + } + + // 假设这里的 ModelVersion.V2_0 是一个有效的版本指示。 + TokensUsage usage = await _client.ChatAsStreamAsync(ModelVersion.Max, messages.ToArray(), s => sb.Append(s), uid: "zhoujie"); + + return sb.ToString(); + } + + public async IAsyncEnumerable GetStreamingTextContentsAsync(string prompt, PromptExecutionSettings? executionSettings = null, Microsoft.SemanticKernel.Kernel? kernel = null, CancellationToken cancellationToken = default) + { + + + // 构建消息数组,这里使用 prompt 来构建 ChatMessage + var messages = new List + { + ChatMessage.FromUser(prompt) + }; + + // 用于接收来自 API 的文本数据的 StringBuilder + StringBuilder sb = new StringBuilder(); + + // 调用 ChatAsStreamAsync 方法,该方法异步接收聊天消息 + //TokensUsage usage = await _client.ChatAsStreamAsync(ModelVersion.V3, messages.ToArray(), s => sb.Append(s), uid: "zhoujie", cancellationToken: cancellationToken); + + //// 解析流式数据并生成 StreamingTextContent 实例 + //// 假设每次调用返回全部文本,我们可以直接生成一个 StreamingTextContent 对象 + //yield return new StreamingTextContent(sb.ToString(), encoding: Encoding.UTF8); + // Ensure ChatAsStreamAsync is an async streaming method + await foreach (var response in _client.ChatAsStreamAsync(ModelVersion.Max, messages.ToArray())) + { + yield return new StreamingTextContent(response.Text, encoding: Encoding.UTF8); + } + } + + public async Task> GetTextContentsAsync(string prompt, PromptExecutionSettings? executionSettings = null, Microsoft.SemanticKernel.Kernel? kernel = null, CancellationToken cancellationToken = default) + { + + + // 调用 ChatAsync 方法并获取响应 + ChatResponse response = await _client.ChatAsync(ModelVersion.Max, new ChatMessage[] + { + ChatMessage.FromUser(prompt) + }); + + // 创建并返回 TextContent 列表 + return new List + { + new TextContent { Text=response.Text } + }; + } + + + } + +} diff --git a/src/Senparc.AI/Entities/Keys/SparkAIKeys.cs b/src/Senparc.AI/Entities/Keys/SparkAIKeys.cs new file mode 100644 index 0000000..5ed83b7 --- /dev/null +++ b/src/Senparc.AI/Entities/Keys/SparkAIKeys.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Senparc.AI.Entities.Keys; + +namespace Senparc.AI +{ + /// + /// 科大讯飞 + /// + public class SparkAIKeys : BaseKeys + { + //public string ApiKey { get; set; } + //public string OrganizationId { get; set; } + + // 应用APPID(必须为webapi类型应用,并开通星火认知大模型授权) + public string AppId { get; set; } + + // 接口密钥(webapi类型应用开通星火认知大模型后,控制台--我的应用---星火认知大模型---相应服务的apikey) + public string ApiSecret { get; set; } + // 接口密钥(webapi类型应用开通星火认知大模型后,控制台--我的应用---星火认知大模型---相应服务的apisecret) + public string ApiKey { get; set; } + + public string SparkAIEndpoint { get; set; } + + + + + } +} diff --git a/src/Senparc.AI/Entities/SenparcAiSettingBase.cs b/src/Senparc.AI/Entities/SenparcAiSettingBase.cs index e18afbf..027b0f4 100644 --- a/src/Senparc.AI/Entities/SenparcAiSettingBase.cs +++ b/src/Senparc.AI/Entities/SenparcAiSettingBase.cs @@ -71,6 +71,7 @@ public class SenparcAiSettingBase : ISenparcAiSetting /// 是否使用 Ollama /// public virtual bool Ollama => AiPlatform == AiPlatform.Ollama; + public virtual bool UseSparkAI => AiPlatform == AiPlatform.SparkAI; /// /// AI 平台类型 @@ -87,6 +88,10 @@ public class SenparcAiSettingBase : ISenparcAiSetting public virtual FastAPIKeys FastAPIKeys { get; set; } public virtual OllamaKeys OllamaKeys { get; set; } + /// + /// 科大讯飞api + /// + public virtual SparkAIKeys SparkAIKeys { get; set; } /// /// Azure OpenAI 或 OpenAI API Key /// @@ -98,6 +103,7 @@ public class SenparcAiSettingBase : ISenparcAiSetting AiPlatform.HuggingFace => "", AiPlatform.FastAPI => FastAPIKeys.ApiKey, AiPlatform.Ollama => "", + AiPlatform.SparkAI => SparkAIKeys.ApiKey, _ => "" }; @@ -167,6 +173,12 @@ public class SenparcAiSettingBase : ISenparcAiSetting #endregion + #region SparkAI + + public virtual string SparkAIEndpoint => SparkAIKeys?.SparkAIEndpoint; + + #endregion + public virtual bool IsOpenAiKeysSetted => OpenAIKeys != null && !OpenAIKeys.ApiKey.IsNullOrEmpty(); @@ -253,5 +265,19 @@ public ISenparcAiSetting SetOtherPlatform() } #endregion + + /// + /// 设置 SparkAI + /// + /// + /// + public ISenparcAiSetting SetSparkAI(SparkAIKeys sparkAIKeys) + { + this.AiPlatform = AiPlatform.SparkAI; + this.SparkAIKeys = sparkAIKeys; + return this; + } + + } } \ No newline at end of file diff --git a/src/Senparc.AI/Enums.cs b/src/Senparc.AI/Enums.cs index 5d4316b..26eaf45 100644 --- a/src/Senparc.AI/Enums.cs +++ b/src/Senparc.AI/Enums.cs @@ -21,6 +21,7 @@ public enum AiPlatform //Oobabooga = 64,//未实现 FastAPI = 128, Ollama = 256, + SparkAI=512 } /// diff --git a/src/Senparc.AI/Interfaces/ISenparcAiSetting.cs b/src/Senparc.AI/Interfaces/ISenparcAiSetting.cs index a016962..0f71002 100644 --- a/src/Senparc.AI/Interfaces/ISenparcAiSetting.cs +++ b/src/Senparc.AI/Interfaces/ISenparcAiSetting.cs @@ -43,6 +43,7 @@ public interface ISenparcAiSetting AiPlatform.HuggingFace => HuggingFaceEndpoint, AiPlatform.FastAPI => FastAPIEndpoint, AiPlatform.Ollama => OllamaEndpoint, + AiPlatform.SparkAI=>SparkAIEndpoint, _ => throw new SenparcAiException($"未配置 {AiPlatform} 的 Endpoint 输出") }; @@ -55,6 +56,7 @@ public interface ISenparcAiSetting /// 是否使用 NeuChar OpenAI /// bool UseNeuCharAI => AiPlatform == AiPlatform.NeuCharAI; + bool UseSparkAI => AiPlatform == AiPlatform.SparkAI; /// /// AI 平台类型 /// @@ -66,6 +68,8 @@ public interface ISenparcAiSetting HuggingFaceKeys HuggingFaceKeys { get; set; } FastAPIKeys FastAPIKeys { get; set; } OllamaKeys OllamaKeys { get; set; } + + SparkAIKeys SparkAIKeys { get; set; } /// /// Neuchar OpenAI 或 Azure OpenAI 或 OpenAI API Key @@ -79,10 +83,7 @@ public interface ISenparcAiSetting #region OpenAI - /// - /// OpenAI Endpoint - /// - string OpenAIEndpoint { get; } + #endregion @@ -124,6 +125,14 @@ public interface ISenparcAiSetting #endregion + #region OpenAI + + /// + /// OpenAI Endpoint + /// + string OpenAIEndpoint { get; } + + #endregion #region FastAPI string FastAPIEndpoint { get; } @@ -135,7 +144,9 @@ public interface ISenparcAiSetting string OllamaEndpoint { get; } #endregion - + #region SparkAI + string SparkAIEndpoint { get; } + #endregion /// /// OpenAIKeys 是否已经设置 /// @@ -150,6 +161,7 @@ public interface ISenparcAiSetting AiPlatform.HuggingFace => HuggingFaceKeys.ModelName, AiPlatform.FastAPI => FastAPIKeys.ModelName, AiPlatform.Ollama => OllamaKeys.ModelName, + AiPlatform.SparkAI=>SparkAIKeys.ModelName, _ => throw new SenparcAiException($"100-未配置 {AiPlatform} 的 Endpoint 输出") }; @@ -162,6 +174,7 @@ public interface ISenparcAiSetting AiPlatform.HuggingFace => null, AiPlatform.FastAPI => null, AiPlatform.Ollama => null, + AiPlatform.SparkAI=>null, _ => throw new SenparcAiException($"未配置 {AiPlatform} 的 DeploymentName 输出") }; #pragma warning restore CS8603 // 可能返回 null 引用。 From 8d35290fb5d565e4190291294f3706fd3ef09735 Mon Sep 17 00:00:00 2001 From: zhengxs <674934275@qq.com> Date: Wed, 11 Sep 2024 17:47:03 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=AE=AF=E9=A3=9Eapi?= =?UTF-8?q?=E5=AF=B9=E6=8E=A5=E3=80=82=20=E4=B8=BA=E4=BA=86=E5=BF=AB?= =?UTF-8?q?=E9=80=9F=20=E9=87=87=E7=94=A8=E4=BA=86=E7=AC=AC=E4=B8=89?= =?UTF-8?q?=E6=96=B9=E7=9A=84=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Senparc.AI.Kernel/Extensions.cs | 12 +++----- .../Helpers/SemanticKernelHelper.Config.cs | 7 +++-- .../Senparc.AI.Kernel.csproj | 23 +++++++++----- .../SparkAI/SparkAIChatService.cs | 30 +++++++++++++++---- src/Senparc.AI/Entities/Keys/SparkAIKeys.cs | 6 ++-- src/Senparc.AI/Senparc.AI.csproj | 2 +- 6 files changed, 54 insertions(+), 26 deletions(-) diff --git a/src/Senparc.AI.Kernel/Extensions.cs b/src/Senparc.AI.Kernel/Extensions.cs index d136c38..50e050c 100644 --- a/src/Senparc.AI.Kernel/Extensions.cs +++ b/src/Senparc.AI.Kernel/Extensions.cs @@ -85,7 +85,7 @@ public static IKernelBuilder AddFOllamaTextCompletion(this IKernelBuilder builde /// - /// 添加 SparkAI 聊天服务 + /// 添加 SparkAI 聊天服务 现在只有简单聊天 /// /// /// @@ -94,14 +94,10 @@ public static IKernelBuilder AddFOllamaTextCompletion(this IKernelBuilder builde /// /// /// - public static IKernelBuilder AddSparkAIChatCompletion(this IKernelBuilder builder, string appId, string apiKey, string apiSecret) - { - - string apiKey2 = apiKey; - string appId2 = appId; - string apiSecret2 = apiSecret; + public static IKernelBuilder AddSparkAIChatCompletion(this IKernelBuilder builder, string appId, string apiKey, string apiSecret,string modelVersion) + { // Register SparkAIService as a singleton in the dependency injection container - builder.Services.AddSingleton(new SparkAIChatService(appId, apiKey, apiSecret)); + builder.Services.AddSingleton(new SparkAIChatService(appId, apiKey, apiSecret,modelVersion)); return builder; } } diff --git a/src/Senparc.AI.Kernel/Helpers/SemanticKernelHelper.Config.cs b/src/Senparc.AI.Kernel/Helpers/SemanticKernelHelper.Config.cs index ba4bf1c..5e00ff3 100644 --- a/src/Senparc.AI.Kernel/Helpers/SemanticKernelHelper.Config.cs +++ b/src/Senparc.AI.Kernel/Helpers/SemanticKernelHelper.Config.cs @@ -96,7 +96,9 @@ public IKernelBuilder ConfigChat(string userId, string modelName = null, ISenpar AiPlatform.SparkAI => kernelBuilder.AddSparkAIChatCompletion( apiKey: senparcAiSetting.ApiKey, appId: senparcAiSetting.SparkAIKeys.AppId, - apiSecret: senparcAiSetting.SparkAIKeys.ApiSecret + apiSecret: senparcAiSetting.SparkAIKeys.ApiSecret, + modelVersion: senparcAiSetting.SparkAIKeys.ModelVersion + ), _ => throw new SenparcAiException($"没有处理当前 {nameof(AiPlatform)} 类型:{aiPlatForm}") }; @@ -171,7 +173,8 @@ public IKernelBuilder ConfigTextCompletion(string userId, string modelName = nul AiPlatform.SparkAI => kernelBuilder.AddSparkAIChatCompletion( appId:senparcAiSetting.SparkAIKeys.AppId, apiKey:senparcAiSetting.SparkAIKeys.ApiKey, - apiSecret:senparcAiSetting.SparkAIKeys.ApiSecret + apiSecret:senparcAiSetting.SparkAIKeys.ApiSecret, + modelVersion:senparcAiSetting.SparkAIKeys.ModelVersion ), //AiPlatform.Ollama => kernelBuilder.AddOpenAIChatCompletion( // modelId: modelName, diff --git a/src/Senparc.AI.Kernel/Senparc.AI.Kernel.csproj b/src/Senparc.AI.Kernel/Senparc.AI.Kernel.csproj index 3d4ac9a..0e76e09 100644 --- a/src/Senparc.AI.Kernel/Senparc.AI.Kernel.csproj +++ b/src/Senparc.AI.Kernel/Senparc.AI.Kernel.csproj @@ -2,7 +2,7 @@ netstandard2.1 - 0.19.0 + 0.19.3 enable 10.0 Senparc.AI.Kernel @@ -73,11 +73,18 @@ - - - - - - - + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Senparc.AI.Kernel/SparkAI/SparkAIChatService.cs b/src/Senparc.AI.Kernel/SparkAI/SparkAIChatService.cs index eb86c0c..5d4b5a1 100644 --- a/src/Senparc.AI.Kernel/SparkAI/SparkAIChatService.cs +++ b/src/Senparc.AI.Kernel/SparkAI/SparkAIChatService.cs @@ -1,6 +1,7 @@ using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.TextGeneration; using Sdcb.SparkDesk; +using Senparc.AI.Entities.Keys; using System; using System.Collections.Generic; using System.Text; @@ -20,12 +21,31 @@ public class SparkAIChatService : ITextGenerationService private readonly string _appId; private readonly string _apiKey; private readonly string _apiSecret; - - public SparkAIChatService(string appId, string apiKey, string apiSecret) + private readonly ModelVersion _modelVersion; + public SparkAIChatService(string appId, string apiKey, string apiSecret,string modelVersion= "4_0_ultra") { + _appId = appId; _apiKey = apiKey; _apiSecret = apiSecret; + + switch (modelVersion?.ToLower()) { + case "lite": + _modelVersion = ModelVersion.Lite; + break; + case "pro": + _modelVersion = ModelVersion.Pro;break; + case "max": + _modelVersion = ModelVersion.Max; + break; + case "4_0_ultra": + _modelVersion = ModelVersion.V4_0_Ultra; + break; + default: + _modelVersion = ModelVersion.V4_0_Ultra; break; + + + } _client = new SparkDeskClient(appId, apiKey, apiSecret); } @@ -42,7 +62,7 @@ public async Task ChatAsync(string[] userMessages) } // 假设这里的 ModelVersion.V2_0 是一个有效的版本指示。 - TokensUsage usage = await _client.ChatAsStreamAsync(ModelVersion.Max, messages.ToArray(), s => sb.Append(s), uid: "zhoujie"); + TokensUsage usage = await _client.ChatAsStreamAsync(_modelVersion, messages.ToArray(), s => sb.Append(s), uid: "zhoujie"); return sb.ToString(); } @@ -67,7 +87,7 @@ public async IAsyncEnumerable GetStreamingTextContentsAsyn //// 假设每次调用返回全部文本,我们可以直接生成一个 StreamingTextContent 对象 //yield return new StreamingTextContent(sb.ToString(), encoding: Encoding.UTF8); // Ensure ChatAsStreamAsync is an async streaming method - await foreach (var response in _client.ChatAsStreamAsync(ModelVersion.Max, messages.ToArray())) + await foreach (var response in _client.ChatAsStreamAsync(_modelVersion, messages.ToArray())) { yield return new StreamingTextContent(response.Text, encoding: Encoding.UTF8); } @@ -78,7 +98,7 @@ public async Task> GetTextContentsAsync(string prompt // 调用 ChatAsync 方法并获取响应 - ChatResponse response = await _client.ChatAsync(ModelVersion.Max, new ChatMessage[] + ChatResponse response = await _client.ChatAsync(_modelVersion, new ChatMessage[] { ChatMessage.FromUser(prompt) }); diff --git a/src/Senparc.AI/Entities/Keys/SparkAIKeys.cs b/src/Senparc.AI/Entities/Keys/SparkAIKeys.cs index 5ed83b7..42b2a4d 100644 --- a/src/Senparc.AI/Entities/Keys/SparkAIKeys.cs +++ b/src/Senparc.AI/Entities/Keys/SparkAIKeys.cs @@ -22,8 +22,10 @@ public class SparkAIKeys : BaseKeys public string ApiKey { get; set; } public string SparkAIEndpoint { get; set; } - - + /// + /// 版本 + /// + public string ModelVersion { get; set; } } diff --git a/src/Senparc.AI/Senparc.AI.csproj b/src/Senparc.AI/Senparc.AI.csproj index 118134c..25379eb 100644 --- a/src/Senparc.AI/Senparc.AI.csproj +++ b/src/Senparc.AI/Senparc.AI.csproj @@ -2,7 +2,7 @@ netstandard2.1 - 0.16.5 + 0.16.7 enable 10.0 Senparc.AI From ccd5a48fa3d3cc6b6ebfb83a612b6d5f7efdbd39 Mon Sep 17 00:00:00 2001 From: zhengxs <674934275@qq.com> Date: Fri, 13 Sep 2024 11:20:04 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E7=AC=AC=E4=B8=89?= =?UTF-8?q?=E6=96=B9=E4=BE=9D=E8=B5=96=EF=BC=8C=20=E4=BD=BF=E7=94=A8Micros?= =?UTF-8?q?oft.SemanticKernel=E7=9A=84=20IKernelBuilder=20=E5=AF=B9?= =?UTF-8?q?=E6=8E=A5=E8=AE=AF=E9=A3=9E=E6=98=9F=E7=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + .../appsettings.json | 107 ------------------ 2 files changed, 2 insertions(+), 107 deletions(-) delete mode 100644 Samples/Senparc.AI.Samples.Consoles/appsettings.json diff --git a/.gitignore b/.gitignore index 03c9f61..6934a01 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ appsettings.Development.json /src/.idea/.idea.Senparc.AI/.idea /Samples/Senparc.AI.Samples.Consoles/Properties/PublishProfiles /Samples/Senparc.AI.Samples.Consoles/Senparc.AI.Samples.Consoles.csproj.user +/Samples/Senparc.AI.Samples.Consoles/appsettings.json +/src/ConsoleApp1/ConsoleApp1.csproj diff --git a/Samples/Senparc.AI.Samples.Consoles/appsettings.json b/Samples/Senparc.AI.Samples.Consoles/appsettings.json deleted file mode 100644 index 3ac36bc..0000000 --- a/Samples/Senparc.AI.Samples.Consoles/appsettings.json +++ /dev/null @@ -1,107 +0,0 @@ -{ - "Logging": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Warning" - } - }, - //CO2NET 设置 - "SenparcSetting": { - "IsDebug": true, - "DefaultCacheNamespace": "DefaultCacheTest" - }, - //Senparc.AI 设置 - "SenparcAiSetting": { - "IsDebug": true, - "AiPlatform": "SparkAI", //注意修改为自己平台对应的枚举值 - "NeuCharAIKeys": { - "ApiKey": "xxxxxxx", //在 https://www.neuchar.com/Developer/AiApp 申请 - "NeuCharEndpoint": "https://www.neuchar.com/", //查看 ApiKey 时可看到 DeveloperId,替换掉 - "NeuCharAIApiVersion": "2022-12-01", - "ModelName": { - "Chat": "gpt-35-turbo", - "Embedding": "text-embedding-ada-002", - "TextCompletion": "gpt-35-turbo-instruct" - } - }, - "AzureOpenAIKeys": { - "ApiKey": "", //TODO:加密 - "AzureEndpoint": "", //https://xxxx.openai.azure.com/ - "AzureOpenAIApiVersion": "2022-12-01", //调用限制请参考:https://learn.microsoft.com/en-us/azure/cognitive-services/openai/quotas-limits - "ModelName": { - "Chat": "gpt-35-turbo" - } - }, - "OpenAIKeys": { - "ApiKey": "", //TODO:加密 - "OrganizationId": "", - "OpenAIEndpoint": null, - "ModelName": { - "Chat": "gpt-35-turbo" - } - }, - "HuggingFaceKeys": { - "Endpoint": "", //HuggingFace 的 Endpoint - "ModelName": { - "TextCompletion": "chatglm2" - } - }, - "OllamaKeys": { - "Endpoint": "http://localhost:11434/", - "ModelName": { - "Chat": "qwen:7b", - "Embedding": "qwen:7b", - "TextCompletion": "qwen:7b" - } - }, - "SparkAIKeys": { - "ApiKey": "", //TODO:加密 - "AppId": "", - "ApiSecret": "", - "SparkAiEndpoint": "", - "ModelName": { - "Chat": "gpt-35-turbo" - } - }, - "Items": { - "AzureDalle3": { - "AiPlatform": "AzureOpenAI", - "AzureOpenAIKeys": { - "ApiKey": "", - "AzureEndpoint": "", - "AzureOpenAIApiVersion": "2022-12-01", - "ModelName": { - "TextToImage": "dall-e-3" - } - } - }, - "MyNeuCharAI": { - "AiPlatform": "NeuCharAI", - "NeuCharAIKeys": { - "ApiKey": "MyNeuCharAIKey", - "NeuCharEndpoint": "https://www.neuchar.com/", - "ModelName": { - "Chat": "gpt-35-turbo" - } - } - }, - "SparkAIKeys": { - "AiPlatform": "SparkAI", - "SparkAIKeys": { - "ApiKey": "40a519641152f3c7caecbcc416065ae1", //TODO:加密 - "AppId": "a891b281", - "ApiSecret": "ODdhYjEzZTY3OGE4OWJkYTI0M2ZlNGIz", - "SparkAiEndpoint": "https://www.neuchar.com//", - "ModelName": { - "Chat": "gpt-35-turbo" - } - } - }, - - "OtherModels": { - "AiPlatform": "" - //任意数量的 *Keys 配置 - } - } - } -} \ No newline at end of file From 2033acecd0cb559882c6557c6e0dcf0b6d8dc5f2 Mon Sep 17 00:00:00 2001 From: zhengxs <674934275@qq.com> Date: Fri, 13 Sep 2024 11:28:50 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E5=A4=9A=E4=BD=99?= =?UTF-8?q?=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Samples/ChatSample.cs | 30 +-- .../Senparc.AI.Samples.Consoles.csproj | 6 + src/ConsoleApp1/Program.cs | 10 + src/Senparc.AI.Kernel/Extensions.cs | 43 +++- .../Helpers/ExtensionHelper.cs | 33 ++++ .../Helpers/SemanticKernelHelper.Config.cs | 46 +++-- .../Senparc.AI.Kernel.csproj | 7 +- .../SparkAI/Model/ChatApiRequest.cs | 10 + .../SparkAI/Model/ChatRequestHeader.cs | 10 + .../SparkAI/OpenAIHttpClientHandler.cs | 41 ++++ .../SparkAI/SparkAIChatCompletionService.cs | 75 +++++++ .../SparkAI/SparkApiClient.cs | 186 ++++++++++++++++++ src/Senparc.AI.sln | 18 ++ src/Senparc.AI/Entities/Keys/SparkAIKeys.cs | 5 + .../Interfaces/ISenparcAiSetting.cs | 2 +- src/Senparc.AI/Senparc.AI.csproj | 2 +- src/Senparc.AI/appsettings.schema.json | 2 +- 17 files changed, 484 insertions(+), 42 deletions(-) create mode 100644 src/ConsoleApp1/Program.cs create mode 100644 src/Senparc.AI.Kernel/SparkAI/Model/ChatApiRequest.cs create mode 100644 src/Senparc.AI.Kernel/SparkAI/Model/ChatRequestHeader.cs create mode 100644 src/Senparc.AI.Kernel/SparkAI/OpenAIHttpClientHandler.cs create mode 100644 src/Senparc.AI.Kernel/SparkAI/SparkAIChatCompletionService.cs create mode 100644 src/Senparc.AI.Kernel/SparkAI/SparkApiClient.cs diff --git a/Samples/Senparc.AI.Samples.Consoles/Samples/ChatSample.cs b/Samples/Senparc.AI.Samples.Consoles/Samples/ChatSample.cs index 50cc9bd..a310a10 100644 --- a/Samples/Senparc.AI.Samples.Consoles/Samples/ChatSample.cs +++ b/Samples/Senparc.AI.Samples.Consoles/Samples/ChatSample.cs @@ -95,31 +95,33 @@ 输入 exit 退出。 var useMultiLine = false; //开始对话 var talkingRounds = 0; - while (true) - { - talkingRounds++; + //bool useMultiLine = false; // 确保变量被正确初始化 + //StringBuilder multiLineContent = new StringBuilder(); // 初始化多行内容存储 + while (true) { + talkingRounds++; await Console.Out.WriteLineAsync($"[{talkingRounds}] 人类:"); var input = Console.ReadLine() ?? ""; - if (input.ToUpper() == "[ML]") - { + // 修剪输入并转换为大写 + if (input.Trim().ToUpper() == "[ML]") { await Console.Out.WriteLineAsync("识别到多行模式,请继续输入"); + await Console.Out.FlushAsync(); // 强制刷新缓冲区,确保输出顺序一致 useMultiLine = true; } - while (useMultiLine) - { - if (input.ToUpper() == "[END]") - { + while (useMultiLine) { + input = Console.ReadLine(); + + // 检查是否结束多行模式 + if (input.Trim().ToUpper() == "[END]") { useMultiLine = false; input = multiLineContent.ToString(); - } - else - { + multiLineContent.Clear(); // 清空多行内容缓存 + } else { await Console.Out.WriteLineAsync("请继续输入,直到输入 [END] 停止..."); - input = Console.ReadLine(); - multiLineContent.Append(input); + await Console.Out.FlushAsync(); // 强制刷新缓冲区,确保输出顺序一致 + multiLineContent.AppendLine(input); // 添加多行内容到 StringBuilder 中 } } diff --git a/Samples/Senparc.AI.Samples.Consoles/Senparc.AI.Samples.Consoles.csproj b/Samples/Senparc.AI.Samples.Consoles/Senparc.AI.Samples.Consoles.csproj index 394fa45..4a71b52 100644 --- a/Samples/Senparc.AI.Samples.Consoles/Senparc.AI.Samples.Consoles.csproj +++ b/Samples/Senparc.AI.Samples.Consoles/Senparc.AI.Samples.Consoles.csproj @@ -21,4 +21,10 @@ + + + Always + + + \ No newline at end of file diff --git a/src/ConsoleApp1/Program.cs b/src/ConsoleApp1/Program.cs new file mode 100644 index 0000000..39fa642 --- /dev/null +++ b/src/ConsoleApp1/Program.cs @@ -0,0 +1,10 @@ +namespace ConsoleApp1 +{ + internal class Program + { + static void Main(string[] args) + { + Console.WriteLine("Hello, World!"); + } + } +} diff --git a/src/Senparc.AI.Kernel/Extensions.cs b/src/Senparc.AI.Kernel/Extensions.cs index 50e050c..61825e5 100644 --- a/src/Senparc.AI.Kernel/Extensions.cs +++ b/src/Senparc.AI.Kernel/Extensions.cs @@ -8,9 +8,13 @@ using Microsoft.SemanticKernel.Connectors.OpenAI; using Microsoft.SemanticKernel.TextGeneration; using Microsoft.SemanticKernel; -using Sdcb.SparkDesk; using Senparc.AI.Kernel.SparkAI; using System.Net; +using System.Net.Http.Headers; +using System.Net.Http; +using static Humanizer.On; +using System.IO; +using System.Threading; namespace Senparc.AI.Kernel { @@ -82,10 +86,8 @@ public static IKernelBuilder AddFOllamaTextCompletion(this IKernelBuilder builde } #endregion - - /// - /// 添加 SparkAI 聊天服务 现在只有简单聊天 + /// 添加讯飞聊天服务 /// /// /// @@ -94,11 +96,36 @@ public static IKernelBuilder AddFOllamaTextCompletion(this IKernelBuilder builde /// /// /// - public static IKernelBuilder AddSparkAIChatCompletion(this IKernelBuilder builder, string appId, string apiKey, string apiSecret,string modelVersion) - { - // Register SparkAIService as a singleton in the dependency injection container - builder.Services.AddSingleton(new SparkAIChatService(appId, apiKey, apiSecret,modelVersion)); + public static IKernelBuilder AddSparkAPIChatCompletion(this IKernelBuilder builder, string modelId, string apiKey, string endpoint, string? orgId = null, string? serviceId = null) + { + // 创建 HttpClient,用于与讯飞星火 API 交互 + var httpClient = new HttpClient(); + httpClient.BaseAddress = new Uri(endpoint); + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey); + Func implementationFactory = + (IServiceProvider serviceProvider, object? _) => + new OpenAIChatCompletionService(modelId, new Uri(endpoint), apiKey, null, httpClient); + builder.Services.AddKeyedSingleton((object?)serviceId, (Func)implementationFactory); + builder.Services.AddKeyedSingleton((object?)serviceId, (Func)implementationFactory); return builder; } + + ///// + ///// 添加 SparkAI 聊天服务 现在只有简单聊天 过时 + ///// + ///// + ///// + ///// + ///// + ///// + ///// + ///// + //public static IKernelBuilder AddSparkAIChatCompletion(this IKernelBuilder builder, string appId, string apiKey, string apiSecret,string modelVersion) + //{ + // // Register SparkAIService as a singleton in the dependency injection container SparkApiClient + // // builder.Services.AddSingleton(new SparkAIChatService(appId, apiKey, apiSecret,modelVersion)); + // builder.Services.AddSingleton(new SparkApiClient(apiKey, modelVersion)); + // return builder; + //} } } diff --git a/src/Senparc.AI.Kernel/Helpers/ExtensionHelper.cs b/src/Senparc.AI.Kernel/Helpers/ExtensionHelper.cs index 662a8d8..c86e1af 100644 --- a/src/Senparc.AI.Kernel/Helpers/ExtensionHelper.cs +++ b/src/Senparc.AI.Kernel/Helpers/ExtensionHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Security.Cryptography; using System.Text; using Microsoft.SemanticKernel; @@ -17,5 +18,37 @@ public static void Set(this KernelArguments kernelArguments, string key, object { kernelArguments[key] = value; } + + public static string GenerateApiPassword(string key, string secret) + { + // 拼接 key 和 secret + string combined = $"{key}:{secret}"; + + // 将拼接后的字符串进行 Base64 编码 + byte[] byteArray = Encoding.UTF8.GetBytes(combined); + string base64Encoded = Convert.ToBase64String(byteArray); + + return base64Encoded; + } + + public static string GetAuthorizationUrl(string apiKey, string apiSecret, string hostUrl) + { + var url = new Uri(hostUrl); + + string dateString = DateTime.UtcNow.ToString("r"); + + byte[] signatureBytes = Encoding.ASCII.GetBytes($"host: {url.Host}\ndate: {dateString}\nGET {url.AbsolutePath} HTTP/1.1"); + + using HMACSHA256 hmacsha256 = new(Encoding.ASCII.GetBytes(apiSecret)); + byte[] computedHash = hmacsha256.ComputeHash(signatureBytes); + string signature = Convert.ToBase64String(computedHash); + + string authorizationString = $"api_key=\"{apiKey}\",algorithm=\"hmac-sha256\",headers=\"host date request-line\",signature=\"{signature}\""; + string authorization = Convert.ToBase64String(Encoding.ASCII.GetBytes(authorizationString)); + + string query = $"authorization={authorization}&date={dateString}&host={url.Host}"; + + return new UriBuilder(url) { Scheme = url.Scheme, Query = query }.ToString(); + } } } diff --git a/src/Senparc.AI.Kernel/Helpers/SemanticKernelHelper.Config.cs b/src/Senparc.AI.Kernel/Helpers/SemanticKernelHelper.Config.cs index 5e00ff3..029470c 100644 --- a/src/Senparc.AI.Kernel/Helpers/SemanticKernelHelper.Config.cs +++ b/src/Senparc.AI.Kernel/Helpers/SemanticKernelHelper.Config.cs @@ -93,13 +93,20 @@ public IKernelBuilder ConfigChat(string userId, string modelName = null, ISenpar endpoint: senparcAiSetting.OllamaEndpoint, serviceId: null ), - AiPlatform.SparkAI => kernelBuilder.AddSparkAIChatCompletion( - apiKey: senparcAiSetting.ApiKey, - appId: senparcAiSetting.SparkAIKeys.AppId, - apiSecret: senparcAiSetting.SparkAIKeys.ApiSecret, - modelVersion: senparcAiSetting.SparkAIKeys.ModelVersion - - ), + AiPlatform.SparkAI => kernelBuilder.AddSparkAPIChatCompletion( + apiKey: senparcAiSetting.SparkAIKeys.ApiPassword, + endpoint: senparcAiSetting.Endpoint, + modelId: senparcAiSetting.SparkAIKeys.ModelName.Chat, + serviceId: null + + ), + // AiPlatform.SparkAI => kernelBuilder.AddSparkAIChatCompletion( + // apiKey: senparcAiSetting.ApiKey, + // appId: senparcAiSetting.SparkAIKeys.AppId, + // apiSecret: senparcAiSetting.SparkAIKeys.ApiSecret, + // modelVersion: senparcAiSetting.SparkAIKeys.ModelVersion + + //), _ => throw new SenparcAiException($"没有处理当前 {nameof(AiPlatform)} 类型:{aiPlatForm}") }; @@ -170,12 +177,18 @@ public IKernelBuilder ConfigTextCompletion(string userId, string modelName = nul endpoint: senparcAiSetting.OllamaEndpoint, serviceId: null ), - AiPlatform.SparkAI => kernelBuilder.AddSparkAIChatCompletion( - appId:senparcAiSetting.SparkAIKeys.AppId, - apiKey:senparcAiSetting.SparkAIKeys.ApiKey, - apiSecret:senparcAiSetting.SparkAIKeys.ApiSecret, - modelVersion:senparcAiSetting.SparkAIKeys.ModelVersion - ), + AiPlatform.SparkAI => kernelBuilder.AddSparkAPIChatCompletion( + apiKey: senparcAiSetting.SparkAIKeys.ApiPassword, + endpoint: senparcAiSetting.Endpoint, + modelId: senparcAiSetting.SparkAIKeys.ModelName.Chat, + serviceId: null + ), + //AiPlatform.SparkAI => kernelBuilder.AddSparkAIChatCompletion( + // appId: senparcAiSetting.SparkAIKeys.AppId, + // apiKey: senparcAiSetting.SparkAIKeys.ApiKey, + // apiSecret: senparcAiSetting.SparkAIKeys.ApiSecret, + // modelVersion: senparcAiSetting.SparkAIKeys.ModelVersion + //), //AiPlatform.Ollama => kernelBuilder.AddOpenAIChatCompletion( // modelId: modelName, // apiKey: senparcAiSetting.ApiKey, @@ -238,7 +251,12 @@ public IKernelBuilder ConfigTextEmbeddingGeneration(string userId, string modelN apiKey: senparcAiSetting.ApiKey, modelId: modelName, httpClient: _httpClient), - + // AiPlatform.SparkAI => kernelBuilder.AddSparkAPItext( + // deploymentName: deploymentName, + // endpoint: senparcAiSetting.Endpoint, + // apiKey: senparcAiSetting.ApiKey, + // modelId: modelName, + // httpClient: _httpClient), AiPlatform.HuggingFace => kernelBuilder.AddHuggingFaceTextEmbeddingGeneration( model: modelName, endpoint: new Uri(senparcAiSetting.Endpoint ?? throw new SenparcAiException("HuggingFace 必须提供 Endpoint")), diff --git a/src/Senparc.AI.Kernel/Senparc.AI.Kernel.csproj b/src/Senparc.AI.Kernel/Senparc.AI.Kernel.csproj index 0e76e09..dba5699 100644 --- a/src/Senparc.AI.Kernel/Senparc.AI.Kernel.csproj +++ b/src/Senparc.AI.Kernel/Senparc.AI.Kernel.csproj @@ -2,7 +2,7 @@ netstandard2.1 - 0.19.3 + 0.19.4 enable 10.0 Senparc.AI.Kernel @@ -61,6 +61,8 @@ + + @@ -72,14 +74,13 @@ - - + diff --git a/src/Senparc.AI.Kernel/SparkAI/Model/ChatApiRequest.cs b/src/Senparc.AI.Kernel/SparkAI/Model/ChatApiRequest.cs new file mode 100644 index 0000000..c475aeb --- /dev/null +++ b/src/Senparc.AI.Kernel/SparkAI/Model/ChatApiRequest.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Senparc.AI.Kernel.SparkAI.Model +{ + internal class ChatApiRequest + { + } +} diff --git a/src/Senparc.AI.Kernel/SparkAI/Model/ChatRequestHeader.cs b/src/Senparc.AI.Kernel/SparkAI/Model/ChatRequestHeader.cs new file mode 100644 index 0000000..6f54cb5 --- /dev/null +++ b/src/Senparc.AI.Kernel/SparkAI/Model/ChatRequestHeader.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Senparc.AI.Kernel.SparkAI.Model +{ + internal class ChatRequestHeader + { + } +} diff --git a/src/Senparc.AI.Kernel/SparkAI/OpenAIHttpClientHandler.cs b/src/Senparc.AI.Kernel/SparkAI/OpenAIHttpClientHandler.cs new file mode 100644 index 0000000..7734e22 --- /dev/null +++ b/src/Senparc.AI.Kernel/SparkAI/OpenAIHttpClientHandler.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using System.Threading; + +namespace Senparc.AI.Kernel.SparkAI +{ + public class OpenAIHttpClientHandler : HttpClientHandler + { + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + UriBuilder uriBuilder; + switch (request.RequestUri?.LocalPath) { + case "/v1/chat/completions": + uriBuilder = new UriBuilder(request.RequestUri) { + // 这里是你要修改的 URL + Scheme = "http", + Host = "你的ip地址", + Port = 3000, + Path = "v1/chat/completions", + }; + request.RequestUri = uriBuilder.Uri; + break; + } + + // 接着,调用基类的 SendAsync 方法将你的修改后的请求发出去 + HttpResponseMessage response = await base.SendAsync(request, cancellationToken); + + int n = 0; + while ((int)response.StatusCode == 500 && n < 10) { + response = await base.SendAsync(request, cancellationToken); + n++; + } + + return response; + } + } + +} diff --git a/src/Senparc.AI.Kernel/SparkAI/SparkAIChatCompletionService.cs b/src/Senparc.AI.Kernel/SparkAI/SparkAIChatCompletionService.cs new file mode 100644 index 0000000..d7d2e82 --- /dev/null +++ b/src/Senparc.AI.Kernel/SparkAI/SparkAIChatCompletionService.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.WebSockets; +using System.Runtime.CompilerServices; +using System.Security.Cryptography; +using System.Text; +using System.Text.Json.Serialization; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.ChatCompletion; +using Microsoft.SemanticKernel.Services; +using Microsoft.SemanticKernel.TextGeneration; +using Sdcb.SparkDesk; + +namespace Senparc.AI.Kernel.SparkAI +{ + public class SparkDeskClient + { + + private readonly string _appId; + private readonly string _apiKey; + private readonly string _apiSecret; + + private readonly static JsonSerializerOptions _defaultJsonEncodingOptions = new() { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }; + + /// + /// Initializes a new instance of the class with specified parameters. + /// + /// The app ID. + /// The API key. + /// The API Secret. + public SparkDeskClient(string appId, string apiKey, string apiSecret) + { + _appId = appId ?? throw new ArgumentNullException(nameof(appId)); + _apiKey = apiKey ?? throw new ArgumentNullException(nameof(apiKey)); + _apiSecret = apiSecret ?? throw new ArgumentNullException(nameof(apiSecret)); + } + + + + + + /// + /// Generates authorization URL for SparkDesk API. + /// + /// SparkDesk API key. + /// SparkDesk API secret. + /// Host URL. Optional, default is value from const field. + /// Authorization URL. + public static string GetAuthorizationUrl(string apiKey, string apiSecret, string hostUrl) + { + var url = new Uri(hostUrl); + + string dateString = DateTime.UtcNow.ToString("r"); + + byte[] signatureBytes = Encoding.ASCII.GetBytes($"host: {url.Host}\ndate: {dateString}\nGET {url.AbsolutePath} HTTP/1.1"); + + using HMACSHA256 hmacsha256 = new(Encoding.ASCII.GetBytes(apiSecret)); + byte[] computedHash = hmacsha256.ComputeHash(signatureBytes); + string signature = Convert.ToBase64String(computedHash); + + string authorizationString = $"api_key=\"{apiKey}\",algorithm=\"hmac-sha256\",headers=\"host date request-line\",signature=\"{signature}\""; + string authorization = Convert.ToBase64String(Encoding.ASCII.GetBytes(authorizationString)); + + string query = $"authorization={authorization}&date={dateString}&host={url.Host}"; + + return new UriBuilder(url) { Scheme = url.Scheme, Query = query }.ToString(); + } + } +} diff --git a/src/Senparc.AI.Kernel/SparkAI/SparkApiClient.cs b/src/Senparc.AI.Kernel/SparkAI/SparkApiClient.cs new file mode 100644 index 0000000..d05ec73 --- /dev/null +++ b/src/Senparc.AI.Kernel/SparkAI/SparkApiClient.cs @@ -0,0 +1,186 @@ + +using System; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; +using System.Threading; +using System.Text; +using Microsoft.SemanticKernel; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.IO; +using System.Text.Json; +using Microsoft.SemanticKernel.TextGeneration; +namespace Senparc.AI.Kernel +{ + public class SparkApiClient : ITextGenerationService + { + private readonly HttpClient _httpClient; + private readonly string _apiKey; + private readonly string _baseUrl; + private readonly string _modelVersion; + public class StreamingTextContent + { + public string Text { get; } + public Encoding Encoding { get; } + + public StreamingTextContent(string text, Encoding encoding) + { + Text = text; + Encoding = encoding; + } + } + public class ChatResponse + { + public string Text { get; set; } + // Add other properties as needed to match the API response + } + public IReadOnlyDictionary Attributes => throw new NotImplementedException(); + + public SparkApiClient(string apiKey, string modelVersion, string baseUrl = "https://spark-api-open.xf-yun.com/v1/chat/completions") + { + _httpClient = new HttpClient(); + _apiKey = apiKey ?? throw new ArgumentNullException(nameof(apiKey)); + _baseUrl = baseUrl ?? throw new ArgumentNullException(nameof(baseUrl)); + _modelVersion = modelVersion ?? throw new ArgumentNullException(nameof(modelVersion)); + } + + + //public async IAsyncEnumerable GetStreamingTextContentsAsync(string prompt, PromptExecutionSettings? executionSettings = null, Microsoft.SemanticKernel.Kernel? kernel = null, CancellationToken cancellationToken = default) + //{ + // var messages = new List + // { + // new { role = "user", content = prompt } + //}; + // _httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _apiKey); + + // var requestContent = new StringContent(System.Text.Json.JsonSerializer.Serialize(new { + // model = _modelVersion, + // messages, + // stream = true + // }), Encoding.UTF8, "application/json"); + + // var request = new HttpRequestMessage(HttpMethod.Post, _baseUrl) { + // Content = requestContent + // }; + + // using var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + // response.EnsureSuccessStatusCode(); + + // using var responseStream = await response.Content.ReadAsStreamAsync(); + // using var streamReader = new StreamReader(responseStream, Encoding.UTF8); + + // string line; + // while ((line = await streamReader.ReadLineAsync()) != null) { + // cancellationToken.ThrowIfCancellationRequested(); + + // yield return new StreamingTextContent(line, Encoding.UTF8); + // } + //} + + + public async Task> GetTextContentsAsync(string prompt, PromptExecutionSettings? executionSettings = null, Microsoft.SemanticKernel.Kernel? kernel = null, CancellationToken cancellationToken = default) + { + var messages = new List + { + new { role = "user", content = prompt } + }; + + _httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _apiKey); + + var requestBody = new { + model = _modelVersion, + messages, + stream = false // Ensure streaming is disabled for full response + }; + + var jsonString = JsonSerializer.Serialize(requestBody); + var requestContent = new StringContent(jsonString, Encoding.UTF8, "application/json"); + + using var response = await _httpClient.PostAsync(_baseUrl, requestContent, cancellationToken); + response.EnsureSuccessStatusCode(); + + // Check for cancellation before reading the response + cancellationToken.ThrowIfCancellationRequested(); + + var responseJson = await response.Content.ReadAsStringAsync(); + + // Check for cancellation after reading the response + cancellationToken.ThrowIfCancellationRequested(); + + var chatResponse = JsonSerializer.Deserialize(responseJson); + + return new List + { + new TextContent { Text = chatResponse.Text } + }; + } + + public async IAsyncEnumerable GetStreamingTextContentsAsync(string prompt, PromptExecutionSettings? executionSettings, Microsoft.SemanticKernel.Kernel? kernel, CancellationToken cancellationToken) + { + var messages = new List + { + new { role = "user", content = prompt } + }; + _httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _apiKey); + + var requestContent = new StringContent(System.Text.Json.JsonSerializer.Serialize(new { + model = _modelVersion, + messages, + stream = true + }), Encoding.UTF8, "application/json"); + + var request = new HttpRequestMessage(HttpMethod.Post, _baseUrl) { + Content = requestContent + }; + + using var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + response.EnsureSuccessStatusCode(); + + using var responseStream = await response.Content.ReadAsStreamAsync(); + using var streamReader = new StreamReader(responseStream, Encoding.UTF8); + + string line; + while ((line = await streamReader.ReadLineAsync()) != null) { + cancellationToken.ThrowIfCancellationRequested(); + + yield return new Microsoft.SemanticKernel.StreamingTextContent(line); + } + } + + + + //public async Task GetChatCompletionAsync(string model, string userMessage) + // { + // if (string.IsNullOrWhiteSpace(model)) + // throw new ArgumentException("Model cannot be null or empty.", nameof(model)); + // if (string.IsNullOrWhiteSpace(userMessage)) + // throw new ArgumentException("User message cannot be null or empty.", nameof(userMessage)); + + // var requestUrl = $"{_baseUrl}/chat/completions"; + // var requestBody = new { + // model, + // messages = new[] + // { + // new { role = "user", content = userMessage } + // } + // }; + // var requestJson = JsonConvert.SerializeObject(requestBody); + // using var requestContent = new StringContent(requestJson, Encoding.UTF8, "application/json"); + // _httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _apiKey); + // try { + // using var response = await _httpClient.PostAsync(requestUrl, requestContent); + // response.EnsureSuccessStatusCode(); + + // var responseJson = await response.Content.ReadAsStringAsync(); + // dynamic responseObject = JsonConvert.DeserializeObject(responseJson); + + // return responseObject.choices[0].message.content; + // } catch (HttpRequestException e) { + // throw new Exception("An error occurred while sending the request.", e); + // } catch (JsonException e) { + // throw new Exception("An error occurred while parsing the response.", e); + // } + // } + } +} diff --git a/src/Senparc.AI.sln b/src/Senparc.AI.sln index bd897e7..737d702 100644 --- a/src/Senparc.AI.sln +++ b/src/Senparc.AI.sln @@ -23,6 +23,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Senparc.AI.Agents", "Senpar EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "01 src", "01 src", "{85962C3A-3F83-41FD-98FC-B86F5178540A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleApp1", "ConsoleApp1\ConsoleApp1.csproj", "{6895ED7D-97EB-4914-BE94-AC6B46DEE52D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ceshidemo", "..\..\Spark_C#_demo\ceshidemo.csproj", "{A99F97AD-4A66-482B-A0EC-894C877FC7B0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -66,6 +70,18 @@ Global {60F51E40-9AA2-490C-9436-C0BB756D8BEB}.Release|Any CPU.Build.0 = Release|Any CPU {60F51E40-9AA2-490C-9436-C0BB756D8BEB}.Test|Any CPU.ActiveCfg = Debug|Any CPU {60F51E40-9AA2-490C-9436-C0BB756D8BEB}.Test|Any CPU.Build.0 = Debug|Any CPU + {6895ED7D-97EB-4914-BE94-AC6B46DEE52D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6895ED7D-97EB-4914-BE94-AC6B46DEE52D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6895ED7D-97EB-4914-BE94-AC6B46DEE52D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6895ED7D-97EB-4914-BE94-AC6B46DEE52D}.Release|Any CPU.Build.0 = Release|Any CPU + {6895ED7D-97EB-4914-BE94-AC6B46DEE52D}.Test|Any CPU.ActiveCfg = Debug|Any CPU + {6895ED7D-97EB-4914-BE94-AC6B46DEE52D}.Test|Any CPU.Build.0 = Debug|Any CPU + {A99F97AD-4A66-482B-A0EC-894C877FC7B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A99F97AD-4A66-482B-A0EC-894C877FC7B0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A99F97AD-4A66-482B-A0EC-894C877FC7B0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A99F97AD-4A66-482B-A0EC-894C877FC7B0}.Release|Any CPU.Build.0 = Release|Any CPU + {A99F97AD-4A66-482B-A0EC-894C877FC7B0}.Test|Any CPU.ActiveCfg = Debug|Any CPU + {A99F97AD-4A66-482B-A0EC-894C877FC7B0}.Test|Any CPU.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -78,6 +94,8 @@ Global {B90B7F0E-B7D4-43FF-B868-8C7758AB7FB7} = {758253C7-033C-4D73-B58A-BBB2EB57B873} {2802CC1C-39EF-4C11-8463-7934F7BD0E03} = {758253C7-033C-4D73-B58A-BBB2EB57B873} {60F51E40-9AA2-490C-9436-C0BB756D8BEB} = {85962C3A-3F83-41FD-98FC-B86F5178540A} + {6895ED7D-97EB-4914-BE94-AC6B46DEE52D} = {758253C7-033C-4D73-B58A-BBB2EB57B873} + {A99F97AD-4A66-482B-A0EC-894C877FC7B0} = {758253C7-033C-4D73-B58A-BBB2EB57B873} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E892F45B-7C63-4C1C-9F53-ABA5CBFA95C6} diff --git a/src/Senparc.AI/Entities/Keys/SparkAIKeys.cs b/src/Senparc.AI/Entities/Keys/SparkAIKeys.cs index 42b2a4d..7ba651c 100644 --- a/src/Senparc.AI/Entities/Keys/SparkAIKeys.cs +++ b/src/Senparc.AI/Entities/Keys/SparkAIKeys.cs @@ -8,6 +8,7 @@ namespace Senparc.AI /// /// 科大讯飞 /// + [Serializable] public class SparkAIKeys : BaseKeys { //public string ApiKey { get; set; } @@ -21,6 +22,10 @@ public class SparkAIKeys : BaseKeys // 接口密钥(webapi类型应用开通星火认知大模型后,控制台--我的应用---星火认知大模型---相应服务的apisecret) public string ApiKey { get; set; } + /// + /// 使用openai sdk调用的apipsd + /// + public string ApiPassword { get; set; } public string SparkAIEndpoint { get; set; } /// /// 版本 diff --git a/src/Senparc.AI/Interfaces/ISenparcAiSetting.cs b/src/Senparc.AI/Interfaces/ISenparcAiSetting.cs index 0f71002..10f00a0 100644 --- a/src/Senparc.AI/Interfaces/ISenparcAiSetting.cs +++ b/src/Senparc.AI/Interfaces/ISenparcAiSetting.cs @@ -56,7 +56,7 @@ public interface ISenparcAiSetting /// 是否使用 NeuChar OpenAI /// bool UseNeuCharAI => AiPlatform == AiPlatform.NeuCharAI; - bool UseSparkAI => AiPlatform == AiPlatform.SparkAI; + bool UseSparkAI => AiPlatform == AiPlatform.SparkAI; /// /// AI 平台类型 /// diff --git a/src/Senparc.AI/Senparc.AI.csproj b/src/Senparc.AI/Senparc.AI.csproj index 25379eb..4e5e9a4 100644 --- a/src/Senparc.AI/Senparc.AI.csproj +++ b/src/Senparc.AI/Senparc.AI.csproj @@ -2,7 +2,7 @@ netstandard2.1 - 0.16.7 + 0.16.8 enable 10.0 Senparc.AI diff --git a/src/Senparc.AI/appsettings.schema.json b/src/Senparc.AI/appsettings.schema.json index f044384..72f2d7a 100644 --- a/src/Senparc.AI/appsettings.schema.json +++ b/src/Senparc.AI/appsettings.schema.json @@ -7,7 +7,7 @@ "properties": { "AiPlatform": { "type": "string", - "enum": [ "OpenAI", "NeuCharAI", "AzureOpenAI", "HuggingFace","FastAPI","Other" ] + "enum": [ "OpenAI", "NeuCharAI", "AzureOpenAI", "HuggingFace", "FastAPI", "SparkAI", "Other" ] } } }