Semantic Kernel入門系列:透過依賴注入管理物件和外掛

董瑞鹏發表於2024-06-11

前言

本章講一下在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

示例程式碼

本文原始碼

相關文章