dotnet SemanticKernel 入門 自定義變數和技能

2023-09-02 12:00:40

本文將告訴大家如何在 SemanticKernel 框架內定義自定義的變數和如何開發自定義的技能

本文屬於 SemanticKernel 入門系列部落格,更多部落格內容請參閱我的 部落格導航

自定義變數是一個非常有用的技能,自定義變數可以讓煉丹師和程式設計師進行並行工作。由煉丹師對 AI 模型進行訓練,從而找到對某項問題比較好的解決方案,煉丹師此時不需要關注具體所解決的問題,可以大量使用預留位置代替使用者的實際輸入。而程式設計師則可以用到煉丹師的成果進而替換預留位置為具體的使用者輸入,從而實現功能。下面舉一個具體的例子

比如說對 GPT 進行 prompt 煉丹,最後煉出一個分類器,這個分類器可以根據使用者的輸入內容進行分類。但是在煉丹的過程煉丹師是不會也不可能拿到所有使用者可能的輸入的,使用者可能的輸入是在煉丹師完成煉丹工作之後,程式設計師開發人機互動應用最後才能拿到使用者的實際輸入內容。這個時候自定義變數的功能相信大家就能知道了,通過自定義變數的功能,讓煉丹師可以方便插入預留位置,從而程式設計師進行對接

更進一步,自定義變數還可以更加方便技能的匯入,由於許多技能都可以輸入不止一個的輸入內容,在有自定義變數的輔助之下,即可完成更加複雜的管道邏輯。比如說在經過某個技能之後,可以進行多變數輸入和多變數輸出,於是就可以傳遞更多豐富的資訊給到後續的步驟。比如說 GPT 的分類功能,可以在分類之後對不同類別輸出不同的輸入要求,從而滿足對接後續的技能。舉一個具體的例子

比如說訓練 GPT 可以輸出使用者的輸入內容,將使用者的輸入型別分類為 1. 總結文字內容 2. 根據文字生成圖片 等,通過對接自定義變數功能時。可以讓煉丹師不需要關注特定的輸入,而是統一採用 input 變數當成使用者的輸入,然後在完成之後,注入使用者輸入型別作為 type 變數。如果使用者是總結文字內容的需求,則再新增 text 變數。如果是根據文字生成圖片,則注入 request 變數和 size 尺寸變數。於是程式設計師可以程式設計開發,給 SemanticKernel 框架先設定 input 變數,這個 input 變數就是使用者輸入的文字內容。接著執行 GPT 的智慧邏輯,再讀取自定義變數 type 拿到使用者期望執行的型別,分別呼叫不同的技能函數。比如說總結文字技能就需要用到 text 變數作為輸入,而根據文字生成圖片則需要 request 變數和 size 尺寸變數,這些都可以在一個順序之中完成

看到這裡,大家是不是想要試試看 SemanticKernel 框架賦予 AI 的強大能力了?放心,本文現在還不涉及到任何 AI 相關的邏輯,依然還在探索 SemanticKernel 框架的過程

先和大家介紹一下如何在 SemanticKernel 框架裡面注入自定義變數。在 SemanticKernel 框架裡面預設將所有的輸入當成了 input 變數,也就是後續介紹到的 SemanticKernel 的 SK 函數裡面,如果只有一個預設引數,那麼這個引數將會被同時賦給 input 變數

在 SemanticKernel 注入自定義變數的方法可以是先新建 ContextVariables 物件,通過變數上下文物件進行類似字典的方式賦值。預設給到 ContextVariables 建構函式的就是後續會被當成 input 的變數,如以下程式碼。下面程式碼將演示在沒有任何 AI 參與的情況下,輸出今天的日期

var variables = new ContextVariables("今天是: ");
variables.Set("day", DateTime.Now.ToString(CultureInfo.CurrentCulture));

如以上的程式碼就定義了 input 是 "今天是: " 而 day 變數就是通過 Set 方法設定為 DateTime.Now.ToString(CultureInfo.CurrentCulture) 的返回值

也就是說如果有邏輯能夠將 input 和 day 拼接在一起,就能夠完成一句話。當然,更多的時候變數是用來提供給到 AI 使用的。現在咱還不想使用魔法,先看看如果是純寫傳統程式碼的情況下,如何完成這個功能

在聊到自定義變數的時候,就肯定會聊到自定義技能功能了。在之前的部落格里面,大家也看到了呼叫框架自帶的技能的方法,接下來我將和大家介紹如何自定義技能

自定義技能是 SemanticKernel 框架所強大的地方,通過自定義技能即可將 AI 和傳統程式設計聯絡在一起,下面讓咱編寫一個技能,這個技能的用途是將日期追加到輸入字串裡面

自定義技能的做法是建立一個方法且標記 SKFunction 特性,可選的加上描述資訊。這個描述資訊現在也只是給程式設計師看的,據說後面微軟將準備出一個 GUI 設計器,這時候對技能的描述就可以更加方便給非程式設計的工程師進行開發 AI 功能

class StaticTextSkill
{
    [SKFunction, Description("追加 day 變數到字串")]
    public static string AppendDay
    (
        [Description("準備被追加的文字")] string input,
        [Description("追加到文字後面的字串")]
        string day
    )
        => input + day;
}

以上的 AppendDay 方法裡面的引數將會被 SemanticKernel 框架使用反射的方式進行注入,注入的就是引數名對應的變數。簡單來說就是從 ContextVariables 裡面嘗試通過引數名獲取到引數。如果沒能從 ContextVariables 裡讀取到和引數名對應的變數,則會記錄一個錯誤資訊,例如當技能函數裡面丟失了一個名為 xx 的變數時的輸出如下

Missing value for parameter 'xx'

在一個型別裡面可以定義許多個技能函數,此時就可以通過匯出技能類然後使用技能類裡面的多個技能方法,如下面程式碼定義一個非靜態的技能方法在 StaticTextSkill 型別裡面

class StaticTextSkill
{
    [SKFunction, Description("將所有的文字字串修改為大寫")]
    public string Uppercase([Description("準備修改為大寫的文字")] string input) =>
        input.ToUpperInvariant();

    [SKFunction, Description("追加 day 變數到字串")]
    public static string AppendDay
    (
        [Description("準備被追加的文字")] string input,
        [Description("追加到文字後面的字串")]
        string day
    )
        => input + day;
}

接下來需要做的就是將 ContextVariables 放入到 SemanticKernel 框架,通過管道方式呼叫 StaticTextSkill 技能,如以下程式碼

IKernel kernel = new KernelBuilder().Build();
var text = kernel.ImportSkill(new StaticTextSkill(), "text");

var variables = new ContextVariables("今天是: ");
variables.Set("day", DateTime.Now.ToString(CultureInfo.CurrentCulture));

SKContext result = await kernel.RunAsync(variables,
    text["AppendDay"],
    text["Uppercase"]);

Console.WriteLine(result);

執行以上程式碼,即可看到輸出了今天的時間

本文的程式碼放在githubgitee 歡迎存取

可以通過如下方式獲取本文的原始碼,先建立一個空資料夾,接著使用命令列 cd 命令進入此空資料夾,在命令列裡面輸入以下程式碼,即可獲取到本文的程式碼

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 84c1e073be77bee177607596b5e03cabb0c0a719

以上使用的是 gitee 的源,如果 gitee 不能存取,請替換為 github 的源。請在命令列繼續輸入以下程式碼

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 84c1e073be77bee177607596b5e03cabb0c0a719

獲取程式碼之後,進入 SemanticKernelSamples\Example03_Variables 資料夾