Semantic Kernel 内置的 IChatCompletionService 实现只支持 OpenAI 与 Azure OpenAI,而我却打算结合 DashScope(阿里云模型服务灵积) 学习 Semantic Kernel。

于是决定自己动手实现一个支持 DashScope 的 Semantic Kernel Connector —— DashScopeChatCompletionService,实现的过程也是学习 Semantic Kernel 源码的过程,

而且借助 Sdcb.DashScope,实现变得更容易了,详见前一篇博文 借助 .NET 开源库 Sdcb.DashScope 调用阿里云灵积通义千问 API

这里只实现用于调用 chat completion 服务的 connector,所以只需实现 IChatCompletionService 接口,该接口继承了 IAIService 接口,一共需要实现2个方法+1个属性。

public sealed class DashScopeChatCompletionService : IChatCompletionService
{
public IReadOnlyDictionary<string, object?> Attributes { get; } public Task<IReadOnlyList<ChatMessageContent>> GetChatMessageContentsAsync(ChatHistory chatHistory, PromptExecutionSettings? executionSettings = null, Kernel? kernel = null, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
} public IAsyncEnumerable<StreamingChatMessageContent> GetStreamingChatMessageContentsAsync(ChatHistory chatHistory, PromptExecutionSettings? executionSettings = null, Kernel? kernel = null, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
}

先实现 GetChatMessageContentsAsync 方法,调用 Kernel.InvokePromptAsync 方法时会用到这个方法。

实现起来比较简单,就是转手买卖:

  • 把 Semantic Kernel 的 ChatHistory 转换为 Sdcb.DashScope 的 IReadOnlyList<ChatMessage>
  • 把 Semantic Kernel 的 PromptExecutionSettings 转换为 Sdcb.DashScope 的 ChatParameters
  • 把 Sdcb.DashScope 的 ResponseWrapper<ChatOutput, ChatTokenUsage> 转换为 Semantic Kernel 的 IReadOnlyList<ChatMessageContent>

实现代码如下:

public async Task<IReadOnlyList<ChatMessageContent>> GetChatMessageContentsAsync(ChatHistory chatHistory, PromptExecutionSettings? executionSettings = null, Kernel? kernel = null, CancellationToken cancellationToken = default)
{
var chatMessages = chatHistory
.Where(x => !string.IsNullOrEmpty(x.Content))
.Select(x => new ChatMessage(x.Role.ToString(), x.Content!)).
ToList(); ChatParameters? chatParameters = null;
if (executionSettings?.ExtensionData?.Count > 0)
{
var json = JsonSerializer.Serialize(executionSettings.ExtensionData);
chatParameters = JsonSerializer.Deserialize<ChatParameters>(
json,
new JsonSerializerOptions { NumberHandling = JsonNumberHandling.AllowReadingFromString });
} var response = await _dashScopeClient.TextGeneration.Chat(_modelId, chatMessages, chatParameters, cancellationToken); return [new ChatMessageContent(new AuthorRole(chatMessages.First().Role), response.Output.Text)];
}

接下来实现 GetStreamingChatMessageContentsAsync,调用 Kernel.InvokePromptStreamingAsync 时会用到它,同样也是转手买卖。

ChatHistoryPromptExecutionSettings 参数的转换与 GetChatMessageContentsAsync 一样,所以引入2个扩展方法 ChatHistory.ToChatMessagesPromptExecutionSettings.ToChatParameters 减少重复代码,另外需要将 ChatParameters.IncrementalOutput 设置为 true

不同之处是返回值类型,需要将 Sdcb.DashScope 的 IAsyncEnumerable<ResponseWrapper<ChatOutput, ChatTokenUsage>> 转换为 IAsyncEnumerable<StreamingChatMessageContent>

实现代码如下:

public async IAsyncEnumerable<StreamingChatMessageContent> GetStreamingChatMessageContentsAsync(
ChatHistory chatHistory,
PromptExecutionSettings? executionSettings = null,
Kernel? kernel = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var chatMessages = chatHistory.ToChatMessages();
var chatParameters = executionSettings?.ToChatParameters() ?? new ChatParameters();
chatParameters.IncrementalOutput = true; var responses = _dashScopeClient.TextGeneration.ChatStreamed(_modelId, chatMessages, chatParameters, cancellationToken); await foreach (var response in responses)
{
yield return new StreamingChatMessageContent(new AuthorRole(chatMessages[0].Role), response.Output.Text);
}
}

到这里2个方法就实现好了,还剩下很容易实现的1个属性,轻松搞定

public sealed class DashScopeChatCompletionService : IChatCompletionService
{
private readonly DashScopeClient _dashScopeClient;
private readonly string _modelId;
private readonly Dictionary<string, object?> _attribues = []; public DashScopeChatCompletionService(
IOptions<DashScopeClientOptions> options,
HttpClient httpClient)
{
_dashScopeClient = new(options.Value.ApiKey, httpClient);
_modelId = options.Value.ModelId;
_attribues.Add(AIServiceExtensions.ModelIdKey, _modelId);
} public IReadOnlyDictionary<string, object?> Attributes => _attribues;
}

到此,DashScopeChatCompletionService 的实现就完成了。

接下来,实现一个扩展方法,将 DashScopeChatCompletionService 注册到依赖注入容器

public static class DashScopeServiceCollectionExtensions
{
public static IKernelBuilder AddDashScopeChatCompletion(
this IKernelBuilder builder,
string? serviceId = null,
Action<HttpClient>? configureClient = null,
string configSectionPath = "dashscope")
{
Func<IServiceProvider, object?, DashScopeChatCompletionService> factory = (serviceProvider, _) =>
serviceProvider.GetRequiredService<DashScopeChatCompletionService>(); if (configureClient == null)
{
builder.Services.AddHttpClient<DashScopeChatCompletionService>();
}
else
{
builder.Services.AddHttpClient<DashScopeChatCompletionService>(configureClient);
} builder.Services.AddOptions<DashScopeClientOptions>().BindConfiguration(configSectionPath);
builder.Services.AddKeyedSingleton<IChatCompletionService>(serviceId, factory);
return builder;
}
}

为了方便通过配置文件配置 ModelId 与 ApiKey,引入了 DashScopeClientOptions

public class DashScopeClientOptions : IOptions<DashScopeClientOptions>
{
public string ModelId { get; set; } = string.Empty; public string ApiKey { get; set; } = string.Empty; public DashScopeClientOptions Value => this;
}

最后就是写测试代码验证实现是否成功,为了减少代码块的长度,下面的代码片段只列出其中一个测试用例

public class DashScopeChatCompletionTests
{
[Fact]
public async Task ChatCompletion_InvokePromptAsync_WorksCorrectly()
{
// Arrange
var builder = Kernel.CreateBuilder();
builder.Services.AddSingleton(GetConfiguration());
builder.AddDashScopeChatCompletion();
var kernel = builder.Build(); var prompt = @"<message role=""user"">博客园是什么网站</message>";
PromptExecutionSettings settings = new()
{
ExtensionData = new Dictionary<string, object>()
{
{ "temperature", "0.8" }
}
};
KernelArguments kernelArguments = new(settings); // Act
var result = await kernel.InvokePromptAsync(prompt, kernelArguments); // Assert
Assert.Contains("博客园", result.ToString());
Trace.WriteLine(result.ToString());
} private static IConfiguration GetConfiguration()
{
return new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.AddUserSecrets<DashScopeChatCompletionTests>()
.Build();
}
}

最后的最后就是运行测试,在 appsettings.json 中添加模型Id

{
"dashscope": {
"modelId": "qwen-max"
}
}

注:qwen-max 是通义千问千亿级大模型

通过 user-secrets 添加 api key

dotnet user-secrets set "dashscope:apiKey" "sk-xxx"

dotnet test 命令运行测试

A total of 1 test files matched the specified pattern.
博客园是一个专注于提供信息技术(IT)领域知识分享和技术交流的中文博客平台,创建于2004年。博客园主要由软件开发人员、系统管理员以及对IT技术有深厚兴趣的人群使用,用户可以在该网站上撰写和发布自己的博客文章,内容涵盖编程、软件开发、云计算、人工智能等多个领域。同时,博客园也提供了丰富的技术文档、教程资源和社区互动功能,旨在促进IT专业人士之间的交流与学习。 Passed! - Failed: 0, Passed: 1, Skipped: 0, Total: 1, Duration: < 1 ms - SemanticKernel.DashScope.IntegrationTest.dll (net8.0)

测试通过!连接 DashScope 的 Semantic Kernel Connector 初步实现完成。

完整实现代码放在 github 上,详见 https://github.com/cnblogs/semantic-kernel-dashscope/tree/v0.1.0

实现阿里云模型服务灵积 DashScope 的 Semantic Kernel Connector的更多相关文章

  1. 阿里云容器服务中国最佳,进入 Forrester 报告强劲表现者象限

    近日,全球知名市场调研机构 Forrester 发布首个企业级公共云容器平台报告. 报告显示:阿里云容器服务创造了中国企业最好成绩,与谷歌云位于同一水平线,进入强劲表现者象限. 究其原因,分析师认为: ...

  2. 利用阿里云容器服务打通TensorFlow持续训练链路

    本系列将利用Docker和阿里云容器服务,帮助您上手TensorFlow的机器学习方案 第一篇:打造TensorFlow的实验环境 第二篇:轻松搭建TensorFlow Serving集群 第三篇:打 ...

  3. 用StackExchange.Redis客户端连接阿里云Redis服务遇到的问题

    阿里云推荐的Redis服务.NET客户端是ServiceStack.Redis,但ServiceStack.Redis不支持异步,不支持.NET Core,于是尝试使用StackExchange.Re ...

  4. 基于阿里云容器服务用docker容器运行ASP.NET 5示例程序

    小试阿里云容器服务 之后,接下来有一个挡不住的小试冲动--用docker容器运行程序.首先想到的程序是 ASP.NET 5示例程序,于是参考msdn博客中的这篇博文 Running ASP.NET 5 ...

  5. 阿里云 Redis 服务遇到的问题

    ERR unknown command eval 说明: 执行当前 Web 请求期间,出现未经处理的异常.请检查堆栈跟踪信息,以了解有关该错误以及代码中导致错误的出处的详细信息. 异常详细信息: St ...

  6. 阿里云容器服务--配置自定义路由服务应对DDOS攻击

    阿里云容器服务--配置自定义路由服务应对DDOS攻击 摘要: 容器服务中,除了slb之外,自定义路由服务(基于HAProxy)也可以作为DDOS攻击的一道防线,本文阐述了几种方法来应对普通规模的DDO ...

  7. 15分钟在阿里云Kubernetes服务上快速建立Jenkins X Platform并运用GitOps管理应用发布

    本文主要介绍如何在阿里云容器服务Kubernetes上快速安装部署Jenkins X Platform并结合demo实践演示GitOps的操作流程. 注意:本文中使用的jx工具.cloud-envir ...

  8. 自建k8s集群日志采集到阿里云日志服务

    自建k8s集群 的master 节点安装 logtail 采集工具 wget http://logtail-release-cn-hangzhou.oss-cn-hangzhou.aliyuncs.c ...

  9. 阿里云日志服务采集自建Kubernetes日志(标准输出日志)

    日志服务支持通过Logtail采集Kubernetes集群日志,并支持CRD(CustomResourceDefinition)进行采集配置管理.本文主要介绍如何安装并使用Logtail采集Kuber ...

  10. 阿里云容器服务与ASP.NET Core部署:用 docker secrets 保存 appsettings.Production.json

    这是我们使用阿里云容器服务基于 docker 容器部署 asp.net core 应用遇到的另一个问题 —— 如果将包含敏感信息的应用配置文件 appsettings.Production.json ...

随机推荐

  1. SD Host控制器微架构设计-02

    SD_clk 测试模式下,选择hclk,将扫描链中的时钟保持一致 clk_en表示可以通过软硬件关闭时钟 sd_if模块 模块中设置一些寄存器,我们可以对寄存器进行读写或者对于寄存器中的某些域段进行读 ...

  2. 神经网络优化篇:详解调试处理(Tuning process)

    调试处理 关于训练深度最难的事情之一是要处理的参数的数量,从学习速率\(a\)到Momentum(动量梯度下降法)的参数\(\beta\).如果使用Momentum或Adam优化算法的参数,\(\be ...

  3. C++开发PHP扩展

    前端时间用C开发PHP扩展,用C实现字符串和简单的cache不友好,因而有了用C++开发的想法. 相关环境初始化配置准备 1.用php源码提供的脚手架生成扩展名 php ext/ext_skel.ph ...

  4. [转帖]Linux命令之——rsync

    文章目录 1 rsync是干什么用的 2 rsync和scp有什么区别 3 rsync简单用法介绍 rsync四种工作方式 1. 本地文件系统上实现同步 2. 本地主机使用远程shell和远程主机通信 ...

  5. [转帖]ASH、AWR、ADDM区别联系

    ==================================================================================================== ...

  6. [转帖]Shell 脚本实现应用服务日志入库 Mysql

    今天给大家分享一个 shell 脚本工具,通过 shell 脚本与 mysql 的结合,将某个具体服务的错误输出日志入库到指定的 mysql 表中,以便于进行错误问题的定位与分析. 日常工作中,经常需 ...

  7. [转帖]利用Python调用outlook自动发送邮件

    ↓↓↓欢迎关注我的公众号,在这里有数据相关技术经验的优质原创文章↓↓↓ 使用Python发送邮件有两种方式,一种是使用smtp调用邮箱的smtp服务器,另一种是直接调用程序直接发送邮件.而在outlo ...

  8. [转帖]优化命令之iotop命令

    文章目录 引言 一.iotop简介 1.iotop安装 2.iotop语法 3.iotop参数 二.I/O的常用快捷键 三.交互模式 四.iotop示例 1.只显示正在产生I/O的进程 2.显示指定P ...

  9. Oracle AWR学习之二-利用ChatGPT编写一键获取AWR报告的脚本

    Oracle AWR学习之二-ChatGPT提升效率之n 背景 之前生成awr报告比较麻烦, 想着能够一键生成. 再辅以部分shell或者是python处理就可以进行细致的分析. 这一块其实还是比较简 ...

  10. 给无网络的CentOS服务器下载rpm包的一个解决办法

    很多公司的服务器为了安全都在内网, 是无法直接连接互联网的, 无法连接互联网就无法使用yum等的包管理器安装rpm包等. 有时候一些rpm包还是能很好的提高性能的, 所以可以使用多种方式获取rpm包进 ...