Semantic Kernel入门系列:通过依赖注入管理对象和插件

前言

本章讲一下在Semantic Kernel中使用DependencyInject(依赖注入),在之前的章节我们都是通过手动创建Kernel对象来完成框架的初始化工作,今天我们用依赖注入的方式来实现。

实战

定义Native Plugins

我们用官网的LightPlugins插件来演示依赖注入在SK中的使用

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对象。

    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源码

    /// </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实际上是简化了我们第二种的步骤,我们就用第一种举例演示

//依赖注入 {     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对象来获取服务实例

var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>(); 

IChatCompletionService 实例也可以通过 IServiceProvider 来获取,您可以灵活地使用更适合您要求的方法。

实战

我们用依赖注入跑一下LightPlugin插件

    // 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 > ");     }  

输出:

User > 当前灯光的状态 Assistant > 当前灯光的状态是关闭的。 User > 帮我开个灯 [Light is now on] Assistant > 已经成功为您点亮了灯。 

最后

本文Demo用的大模型月之暗面的moonshot-v1-8k

  "Endpoint": "https://api.moonshot.cn",   "ModelId": "moonshot-v1-8k", 

原则上任何支持OpenAI function calling 格式的都可以使用。

通过本章的学习,我们深入了解了在Semantic Kernel中利用依赖注入的方式来管理Kernel对象和插件,使得项目开发更加灵活和高效。

参考文献

Using Semantic Kernel with Dependency Injection

示例代码

本文源代码

发表评论

评论已关闭。

相关文章