• 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();

    AddKernel源码

        /// 
        /// 
        /// Both services are registered as transient, as both objects are mutable.
        /// 
        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();
    
            // 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();
    
            // 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(sp => KernelPluginFactory.CreateFromType(serviceProvider: sp));
        var kernel = services.BuildServiceProvider().GetRequiredService();
    

    这就是在依赖注入中注册Kernel对象和插件的步骤,依赖项都会被注册到IServiceCollection

    Semantic Kernel使用的服务插件通常作为Singleton单例注册到依赖注入容器中,以便它们可以在各种Kernel之间重用/共享。Kernel通常注册为Transient瞬态,以便每个实例不受处理其他任务的Kernel所做更改的影响。

    在项目中使用时,我们可以通过在构造函数中获取Kernel对象的实例,用Kernel对象来获取服务实例

    var chatCompletionService = kernel.GetRequiredService();
    

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

    实战

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

        // Create chat history
        var history = new ChatHistory();
    
        // Get chat completion service
        var chatCompletionService = kernel.GetRequiredService();
    
        // 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

    示例代码

    本文源代码

  • 相关阅读:
    【学习笔记】数据结构算法文档(类C语言)
    java毕业设计颜如玉图书销售网站的设计与实现Mybatis+系统+数据库+调试部署
    表哥月薪22k+,而我还在混日子……
    Spring之控制反转(IoC)
    [附源码]计算机毕业设计springboot文具商城购物系统
    SpringBoot+Vue实现前后端分离的餐饮点餐系统
    MFC入门基础(十一)控件编程示例
    C++【类型转换】
    CUDA指针数组Kernel函数
    前端Math属性方法汇总集锦
  • 原文地址:https://www.cnblogs.com/ruipeng/p/18241147