前言
本章讲一下在Semantic Kernel
中使用DependencyInject
(依赖注入),在之前的章节我们都是通过手动创建Kernel
对象来完成框架的初始化工作,今天我们用依赖注入的方式来实现。
实战
定义Native Plugins
我们用官网的LightPlugins
插件来演示依赖注入在SK
中的使用
c#
public class LightPlugin
{
public bool IsOn { get; set; } = false;
#pragma warning disable CA1024 // Use properties where appropriate
[KernelFunction]
[Description("Gets the state of the light.")]
public string GetState() => IsOn ? "on" : "off";
#pragma warning restore CA1024 // Use properties where appropriate
[KernelFunction]
[Description("Changes the state of the light.'")]
public string ChangeState(bool newState)
{
this.IsOn = newState;
var state = GetState();
// Print the state to the console
Console.WriteLine($"[Light is now {state}]");
return state;
}
}
这个插件有两个方法一个是获取当前灯的状态,第二个是改变灯的状态
创建kernel
对象
在之前我们的演示中都是通过Kernel对象提供的CreateBuilder
方法来创建Kernel
对象。
c#
var kernel = Kernel.CreateBuilder().
AddOpenAIChatCompletion(modelId: config.ModelId, apiKey: config.ApiKey)
.Build();
在api项目的开发中,依靠依赖注入的方式更容易管理依赖项,以及对象的复用
依赖注入注入Kernel依赖
有两种方式可以用依赖注入创建Kernel
对象,第一种是借助于KernelServiceCollectionExtensions
累提供的AddKernel
扩展方法,第二种就是自己Kernel kernel = new(services.BuildServiceProvider());
或者services.AddTransient<Kernel>();
AddKernel源码
c#
/// </returns>
/// <remarks>
/// Both services are registered as transient, as both objects are mutable.
/// </remarks>
public static IKernelBuilder AddKernel(this IServiceCollection services)
{
Verify.NotNull(services);
// Register a KernelPluginCollection to be populated with any IKernelPlugins that have been
// directly registered in DI. It's transient because the Kernel will store the collection
// directly, and we don't want two Kernel instances to hold on to the same mutable collection.
services.AddTransient<KernelPluginCollection>();
// Register the Kernel as transient. It's mutable and expected to be mutated by consumers,
// such as via adding event handlers, adding plugins, storing state in its Data collection, etc.
services.AddTransient<Kernel>();
// Create and return a builder that can be used for adding services and plugins
// to the IServiceCollection.
return new KernelBuilder(services);
}
通过源码我们可以看出来,这两种方式基本上没区别,第二种AddKernel
实际上是简化了我们第二种的步骤,我们就用第一种举例演示
c#
//依赖注入
{
IServiceCollection services = new ServiceCollection();
//会话服务注册到IOC容器
services.AddKernel().AddOpenAIChatCompletion(modelId: config.ModelId, apiKey: config.ApiKey, httpClient: client);
services.AddSingleton<KernelPlugin>(sp => KernelPluginFactory.CreateFromType<LightPlugin>(serviceProvider: sp));
var kernel = services.BuildServiceProvider().GetRequiredService<Kernel>();
这就是在依赖注入中注册Kernel
对象和插件
的步骤,依赖项都会被注册到IServiceCollection
中
Semantic Kernel
使用的服务 和插件 通常作为Singleton
单例注册到依赖注入容器中,以便它们可以在各种Kernel
之间重用/共享。Kernel
通常注册为Transient
瞬态,以便每个实例不受处理其他任务的Kernel
所做更改的影响。
在项目中使用时,我们可以通过在构造函数中获取Kernel
对象的实例,用Kernel
对象来获取服务实例
c#
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
IChatCompletionService
实例也可以通过IServiceProvider
来获取,您可以灵活地使用更适合您要求的方法。
实战
我们用依赖注入跑一下LightPlugin
插件
c#
// Create chat history
var history = new ChatHistory();
// Get chat completion service
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
// Start the conversation
Console.Write("User > ");
string? userInput;
while ((userInput = Console.ReadLine()) is not null)
{
// Add user input
history.AddUserMessage(userInput);
// Enable auto function calling
OpenAIPromptExecutionSettings openAIPromptExecutionSettings = new()
{
ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions
};
// Get the response from the AI
var result = await chatCompletionService.GetChatMessageContentAsync(
history,
executionSettings: openAIPromptExecutionSettings,
kernel: kernel);
// Print the results
Console.WriteLine("Assistant > " + result);
// Add the message from the agent to the chat history
history.AddMessage(result.Role, result.Content ?? string.Empty);
// Get user input again
Console.Write("User > ");
}
输出:
text
User > 当前灯光的状态
Assistant > 当前灯光的状态是关闭的。
User > 帮我开个灯
[Light is now on]
Assistant > 已经成功为您点亮了灯。
最后
本文Demo
用的大模型月之暗面的moonshot-v1-8k
json
"Endpoint": "https://api.moonshot.cn",
"ModelId": "moonshot-v1-8k",
原则上任何支持OpenAI function calling
格式的都可以使用。
通过本章的学习,我们深入了解了在Semantic Kernel
中利用依赖注入的方式来管理Kernel
对象和插件,使得项目开发更加灵活和高效。
参考文献
Using Semantic Kernel with Dependency Injection
示例代码