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/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/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 c53d65c..4a71b52 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
-
-
-
@@ -34,4 +21,10 @@
+
+
+ Always
+
+
+
\ No newline at end of file
diff --git a/Samples/Senparc.AI.Samples.Consoles/appsettings.json b/Samples/Senparc.AI.Samples.Consoles/appsettings.json
deleted file mode 100644
index 84a351f..0000000
--- a/Samples/Senparc.AI.Samples.Consoles/appsettings.json
+++ /dev/null
@@ -1,85 +0,0 @@
-{
- "Logging": {
- "IncludeScopes": false,
- "LogLevel": {
- "Default": "Warning"
- }
- },
- //CO2NET 设置
- "SenparcSetting": {
- "IsDebug": true,
- "DefaultCacheNamespace": "DefaultCacheTest"
- },
- //Senparc.AI 设置
- "SenparcAiSetting": {
- "IsDebug": true,
- "AiPlatform": "NeuCharAI", //注意修改为自己平台对应的枚举值
- "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"
- }
- },
- "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"
- }
- }
- },
- "OtherModels": {
- "AiPlatform": ""
- //任意数量的 *Keys 配置
- }
- }
- }
-}
\ 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 6b2d169..61825e5 100644
--- a/src/Senparc.AI.Kernel/Extensions.cs
+++ b/src/Senparc.AI.Kernel/Extensions.cs
@@ -8,6 +8,13 @@
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.SemanticKernel.TextGeneration;
using Microsoft.SemanticKernel;
+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
{
@@ -36,7 +43,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 +86,46 @@ public static IKernelBuilder AddFOllamaTextCompletion(this IKernelBuilder builde
}
#endregion
+ ///
+ /// 添加讯飞聊天服务
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ 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 4bf7f78..029470c 100644
--- a/src/Senparc.AI.Kernel/Helpers/SemanticKernelHelper.Config.cs
+++ b/src/Senparc.AI.Kernel/Helpers/SemanticKernelHelper.Config.cs
@@ -93,6 +93,20 @@ public IKernelBuilder ConfigChat(string userId, string modelName = null, ISenpar
endpoint: senparcAiSetting.OllamaEndpoint,
serviceId: null
),
+ 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}")
};
@@ -163,6 +177,25 @@ public IKernelBuilder ConfigTextCompletion(string userId, string modelName = nul
endpoint: senparcAiSetting.OllamaEndpoint,
serviceId: null
),
+ 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,
+ // orgId: senparcAiSetting.OrganizationId,
+ // endpoint: senparcAiSetting.FastAPIEndpoint,
+ // serviceId: null
+ // ),
_ => throw new SenparcAiException($"没有处理当前 {nameof(AiPlatform)} 类型:{aiPlatForm}")
};
@@ -218,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 9478449..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.18.0
+ 0.19.4
enable
10.0
Senparc.AI.Kernel
@@ -61,21 +61,31 @@
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
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/SparkAIChatService.cs b/src/Senparc.AI.Kernel/SparkAI/SparkAIChatService.cs
new file mode 100644
index 0000000..5d4b5a1
--- /dev/null
+++ b/src/Senparc.AI.Kernel/SparkAI/SparkAIChatService.cs
@@ -0,0 +1,116 @@
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.TextGeneration;
+using Sdcb.SparkDesk;
+using Senparc.AI.Entities.Keys;
+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;
+ 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);
+ }
+
+ 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, 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, 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, new ChatMessage[]
+ {
+ ChatMessage.FromUser(prompt)
+ });
+
+ // 创建并返回 TextContent 列表
+ return new List
+ {
+ new TextContent { Text=response.Text }
+ };
+ }
+
+
+ }
+
+}
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