Semantic Kernel 102— loading kernel

以不同方式來載入kernel (Demo & Deep-Dive)

Mars Wang
18 min readMay 31, 2023
Photo by Windows on Unsplash

注意

[update] 2023.6.6
此篇文章適用SK version: 0.14.X version,0.15.X 版本的說明並不適用,會在未來文章更新。

上一篇文章當中,我們提及了Semantic Kernel (以下簡稱SK)的基礎用法,以及串接Azure Open AI Model的實作,若還不了解Semantic Kernel的同學們可以先參考上篇文章的內容,做configuration設定後,再來此篇做探討。

WIIFM (What’s in it for me?)

此篇我們會透過在同一個Github Repo資料夾(samples/notebooks/dotnet)當中的notebook來深入說明載入Kernel時,不同的使用方式。

文章後半部我會deep dive into source code,粗略的查看內部的實踐邏輯(有興趣的可以一起參與討論)

Let’s Get Started

此次探討在Kernel實例化(instantiate)實踐的方式。那麼首先先了解其中兩個classes的內容:
<補充> 以下為0.14.X版本的Semantic Kernel SDK,後續版本請參照未來文章。

  • KernelBuilder (建議):A builder for Semantic Kernel
    → 專門用來做Kernel instance的建立以及組態設定。
    → 將Functions抽象化出來,複雜程度低,函數名稱簡白易懂。
    → KernelBuilder就是用作建立Kernel class本身。
  • Kernel:A Semantic Kernel class
    → Kernel本體。
    → 須先了解constructor裡面每個parameter的使用方式,才能有效的建立起Kernel class,要建立較為複雜。
    → 文檔建議使用KernelBuilder或是透過Kernel.Builder()建立起KernelBuilder會更為簡單,並且減少錯誤發生。
本次說明的notebook: 01-basic-loading-the-kernel.ipynb

<Reminder>
首先我們直接利用notebook當中的舉例,跟大家說明有哪幾種建立kernel的方式。若想要知道原始碼內怎麼寫的話,可以直接跳到後面Deep-Dive的部分。

Way 1: KernelBuilder建立Kernel

直接利用KernelBuilder.Create()建立起IKernel,並透過Config這個property的方式,把Azure Open AI or Open AI上部署好的模型做組態設定。
→ 白話文:讓Kernel可以套用好設定,串接上雲端上的AI模型

以下面的程式為例,.AddAzureTextCompletionService() 接上的是(Azure) Open AI當中的text completion模型(text-davinci)。
→ 若想要一次性讓kernel接上/移除多個models的話,則多次使用kernel.Config.AddxxxxService()即可。

  • AddChatCompletionService(): 針對chat completion. e.g. OpenAI ChatGPT
  • AddTextEmbeddingGenerationService(): 針對text embedding generation. e.g. Azure OpenAI Text Embedding
  • AddImageGenerationService(): 針對image generation. e.g. OpenAI DallE
using Microsoft.SemanticKernel;

// Simple instance
IKernel kernel = KernelBuilder.Create();

kernel.Config.AddAzureTextCompletionService(
"my-finetuned-Curie", // Azure OpenAI *Deployment ID* --> 這邊實際上是填入Deployment Name
"https://contoso.openai.azure.com/", // Azure OpenAI *Endpoint*
"...your Azure OpenAI Key...", // Azure OpenAI *Key*
"Azure_curie" // alias used in the prompt templates' config.json --> 像是別名,設定完後可以直接取來用
);

kernel.Config.AddOpenAITextCompletionService(
"text-davinci-003", // OpenAI Model Name
"...your OpenAI API Key...", // OpenAI API key
"...your OpenAI Org ID...", // *optional* OpenAI Organization ID
"OpenAI_davinci" // alias used in the prompt templates' config.json
);


// 使用到多個backends或是multiple models時:
// 若prompt configuration並沒有指定要在哪個AI bakcend底下運行,
// 或是prompt configuration不想在kernel當中寫出需要哪個AI backend的情況下,
// 可以設定以下的deafult service
// ------0.14.X version------
kernel.Config.SetDefaultTextCompletionService("Azure_curie");

// ############## 設定多個AI Backend模型 ##############
var kernel_multiple = new KernelBuilder()
.Configure(c =>
{
c.AddOpenAIChatCompletionService("gpt-3.5-turbo", apiKey, orgId);
c.AddOpenAIImageGenerationService(apiKey, orgId);
})
.Build();

// Get AI service instance used to generate images
var dallE = kernel.GetService<IImageGeneration>();

// Get AI service instance used to manage the user chat
var chatGPT = kernel_multiple.GetService<IChatCompletion>();

Way 2: Kernel呼叫Builder()建立

Kernel類當中,有一個用來建立Kernel的方法(method)叫做:Builder。
實際上是會直接return一個KernelBuilder類,後續若要對這個Kernel.Builder()做組態設定或是想加入logger,其實就可以參照跟Way 1的方式一樣實作即可。

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;

// Inject your logger
// see Microsoft.Extensions.Logging.ILogger @ https://learn.microsoft.com/dotnet/core/extensions/logging
ILogger myLogger = NullLogger.Instance;

// Inject a custom configuration
var config = new KernelConfig();

config.AddAzureTextCompletionService(
"my-finetuned-Curie", // Azure OpenAI *Deployment ID* --> 這邊實際上是填入Deployment Name
"https://contoso.openai.azure.com/", // Azure OpenAI *Endpoint*
"...your Azure OpenAI Key...", // Azure OpenAI *Key*
"Azure_curie" // alias used in the prompt templates' config.json --> 像是別名,設定完後可以直接取來用
);

IKernel kernel = Kernel.Builder
.WithConfiguration(config)
.WithLogger(myLogger)
.Build();

Way 3 (不建議, 可直接跳過): Kernel類直接new instance

與此處的方式與Way 2不同。雖都是在Kernel這個class當中,但Way 2是利用呼叫function方式new KernelBuilder類進行操作。而此處則是直接利用new Kernel方式進行。

不推薦使用的理由(缺點):

  • 所有parameters都必須手動初始化。
  • 所有parameters初始化後並沒有經過Verification。
  • 後期維護程式碼很雜亂,並且容易出錯,code review不易。
// using ......

public KernelConfig config = new();
public ISemanticTextMemory memory = NullMemory.Instance;
public ILogger logger = NullLogger.Instance;
public IMemoryStore? memoryStorage = null;
public IDelegatingHandlerFactory? httpHandlerFactory = null;
public IPromptTemplateEngine? promptTemplateEngine;
public readonly AIServiceCollection aiServices = new();
public ITrustService? trustService = null;

var kernel = new Kernel(config, memory, logger, memoryStorage, httpHandlerFactory, promptTemplateEngine, aiServices, trustService);

// ......

以上為建立Kernel的三種用法,接下來將deep dive into source code,查看這兩個classes當中的實作原理。

Deep Dive into Source Code

上面已經描述KernelBuilder以及Kernel兩個類的使用,但原始碼當中到底長什麼樣子?就讓我們一起來深入查看實例化 kernel的過程。若大家有不同想法,也歡迎留言分享討論:)

KernelBuilder.Create()

以上面Way 1的demo範例為例,可見KernelBuilder呼叫了Create()這個method做建立Kernel:

using Microsoft.SemanticKernel;

// Simple instance
IKernel kernel = KernelBuilder.Create();

kernel.Config.AddAzureTextCompletionService(
"my-finetuned-Curie", // Azure OpenAI *Deployment ID* --> 這邊實際上是填入Deployment Name
"https://contoso.openai.azure.com/", // Azure OpenAI *Endpoint*
"...your Azure OpenAI Key...", // Azure OpenAI *Key*
"Azure_curie" // alias used in the prompt templates' config.json --> 像是別名,設定完後可以直接取來用
);

source code可見KernelBuilder所建立的workflow為:

KernelBuilder.Create() → new KernelBuilder instance → 利用實例化的KernelBuilder呼叫.Build() → 實例化Kernel,同時加入預設或更改過的初始化變數,並返回IKernel類型。

// .......

public static IKernel Create()
{
var builder = new KernelBuilder();
return builder.Build();
}

public IKernel Build()
{
if (this._httpHandlerFactory != null)
{
this._config.SetHttpRetryHandlerFactory(this._httpHandlerFactory);
}

var instance = new Kernel(
new SkillCollection(this._logger),
this._aiServices.Build(),
this._promptTemplateEngine ?? new PromptTemplateEngine(this._logger),
this._memory,
this._config,
this._logger,
this._trustService
);

if (this._memoryStorage != null)
{
instance.UseMemory(this._memoryStorage);
}

return instance;
}

// .......

分析:

  • KernelBuilder.Create(): 一個static method,也就是說這個function並不需有一個KernelBuilder instance也可以被呼叫。
    [Assumption]
    設定成static method應該是為了簡化整個new Kernel instance的過程。因為在透過簡單KernelBuilder.Create(),就可以完成建立起KernelBuilder,並自動呼叫當中的KernelBuilder.Build()來進行Kernel的實例化。整個過程使用者可以不用知道自己要做以下步驟:
    new KernelBuilder → call .Build() function
  • KernelBuilder.Build(): 返回IKernel類型的Kernel instance,當中new Kernel instance時加入了原先初始化的private fields,會隨著Kernel instance的建立加入。若當中有需要調整這些初始化的變數,則需要呼叫KernelBuilder當中的其他public methods,例如WithPromptTemplate(…), WithLogger(…) etc. 來修改private fields。
    => KernelBuilder.Build 返回一個 IKernel object,而不是Kernel,此處是一個多型(Polymorphism)標準的應用:為了保護Kernel class當中的兩個private methods不被誤用: ImportSkill(…) 以及.CreateSemanticFunction(…),兩個都是用做在其他public methods當中verify的功能用,因此實際上使用者不應該會真實操作到這兩個functions。
  • Kernel class: 為Semantic Kernel當中最主要部分。constructor當中主要就是做變數的初始化。若arguments沒有在new Kernel instance的時候就傳入的話,也不會有defalut value,會導致建立失敗(Way 3建立方式)。
    取前述demo(Way 1)中的kernel.Config.xxx這條statement,可以了解是針對現有的Kernel instance當中Config這個類型為KernelConfig的field做更改,因此透過KernelConfig這個class裡的public methods做組態的設定,並加載回Kernel當中。

Kernel.Builder()

換到另一種建立Kernel的方式為Kernel.Builder(),為上面demo Way 2的舉例。實際上作法與KernelBuilder.Create()走的workflow是一樣的:

Kernel.Builder() → 建立KernelBuilder instance → 再透過這個instance呼叫 WithConfiguration(config) 以及 .WithLogger(…)做稍後Kernel constructor要初始化變數的傳入 → .Build() 建立Kernel instance並return IKernel類型

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;

// Inject your logger
// see Microsoft.Extensions.Logging.ILogger @ https://learn.microsoft.com/dotnet/core/extensions/logging
ILogger myLogger = NullLogger.Instance;

// Inject a custom configuration
var config = new KernelConfig();

config.AddAzureTextCompletionService(
"my-finetuned-Curie", // Azure OpenAI *Deployment ID* --> 這邊實際上是填入Deployment Name
"https://contoso.openai.azure.com/", // Azure OpenAI *Endpoint*
"...your Azure OpenAI Key...", // Azure OpenAI *Key*
"Azure_curie" // alias used in the prompt templates' config.json --> 像是別名,設定完後可以直接取來用
);

IKernel kernel = Kernel.Builder
.WithConfiguration(config)
.WithLogger(myLogger)
.Build();

與KernelBuilder.Create()建立Kernel的差異主要在:先後順序不同

  • KernelBuilder.Create(): 是先建立起預設arguments的kernel,後續再透過kernel.Config.xxx來做更改。
    先Build Kernel,才做調整
  • Kernel.Builder(): 是建立一個KernelBuilder instance,透過KernelBuilder instance當中的public methods設定初始值,直接傳入Kernel constructor當中做new Kernel instance。
    先做調整,才做Build

分析:

  • Kernel.Builder(): 與KernelBuilder.Create()一樣是static method,因此兩個methods用法相似。
  • Kernel會實作兩個抽象類(interface):
    - IKernel: interface for Semantic Kernel
    - IDisposable: 管理釋放資源的interface → 在Kernel當中主要implement釋放memory以及skillCollection這兩個資源。

[備註]
Kernel, KernelBuilder以及KernelConfig這3個classes都是sealed class,也就是這兩個classes 或是 methods是不能被繼承(inheritance)或是重寫(override),因此如果想要針對以上三個classes做繼承改寫,compiler就會報錯。

以上為此次針對notebooks 01 當中loading kernel的實踐方式所做的demo以及deep dive,後續還會有其他的說明,Stay Tune!

如果喜歡我的文章,也可以登入後giving me claps, leaving a comment, 並且follow我的profile。

如果有更多需要聯絡我的方式,請歡迎透過以下資訊聯繫:

My Github: MarsWangyang (Mars Wang) (github.com)

My LinkedIn: Meng-Yang (Mars) Wang | LinkedIn

--

--