Semantic Kernel 入門系列:💬Semantic Function

2023-04-11 06:00:38

如果把提示詞也算作一種程式碼的話,那麼語意技能所帶來的將會是全新程式設計方式,自然語言程式設計。

通常情況下一段prompt就可以構成一個Semantic Function,如此這般簡單,如果我們提前可以組織好一段段prompt的管理方式,甚至可以不需要寫任何的程式碼,就可以構造出足夠多的技能來。

使用資料夾管理Semantic Function

Semantic Kernel恰好就提供了這樣一種組織方式,僅需使用文字檔案和資料夾就可以管理Semantic Function。資料夾的大致結構如下:

TestSkill  #<- Skill
│
└─── SloganMaker  #<- Function
|    |
│    └─── skprompt.txt
│    └─── [config.json]
│   
└─── SummarizeBlurb  #<- Function 
     |
     └─── skprompt.txt
     └─── [config.json]

和自己手動定義的一樣,每一個Function 都包含了一個 skprompt.txt 檔案,裡面就是對應的prompt,還有一個可選檔案config.json 用作設定。如果有多個Skill的話,可以再往上建立一層資料夾將所有的Skill都放在裡面。

然後我們在程式碼中僅需要將這個技能的資料夾匯入到Kernel中即可。

// 這裡將所有的Skill都放在了 SkillCollection 這個資料夾下
var textSkill = kernel.ImportSemanticSkillFromDirectory("./SkillCollection","TextSkill");

然後還是和往常一樣正常呼叫即可,只不過這裡匯入得到的是Skill層級的,所以執行的時候需要從Skill中獲取對應的Function,Function的名字和對應的資料夾名一致。

var input = 
"""
Congratulations! You have imagined a delicious ASK for SK to run to completion. This ASK can be given to the Planner to get decomposed into steps. Although to make the Planner work reliably, you'll need to use the most advanced model available to you. So let's start from writing basic prompts to begin with.
""";

var resultContext = await kernel.RunAsync(input,textSkill["SummarizeBlurb"]);

resultContext.Result.Dump();
// output:
// You have imagined an ASK for SK that can be given to the Planner to be decomposed into steps. To make the Planner work reliably, you need to use the most advanced model available.

擴充套件自己的Semantic Function管理方式

除了官方提供的方式之外,也可以自行實現一些個性化的方便的管理方式,例如存放在檔案資料庫上,或者物件儲存服務上,甚至使用Git、FTP等方式也不是不可以。

所需要做的只不過是將prompt和設定從遠端方式獲取到本地,然後通過原生的SemanticFunction註冊介面註冊進去就行了。

一個基本的註冊方式如下:

var prompt = "A powerful Prompt"; // 對應skprompt.txt檔案
var promptConfig = new PromptTemplateConfig(); //對應config.json 設定

var promptTemplate= new PromptTemplate(prompt,promptConfig,kernel);
var functionConfig = new SemanticFunctionConfig(promptConfig,promptTemplate);

var skillName = "SkillName";  // skill名稱
var functionName = "FunctionName"; // function名稱

var function = kernel.RegisterSemanticFunction(skillName,functionName,functionConfig);

其中的SkillName 並不是必須的,如果沒有話,那預設會註冊到一個名為 _GLOBAL_FUNCTIONS_ 全域性技能下面,從kernel.Skills中取用的時候,如果不指定SkillName,也會從這個全域性技能下獲取。

只需要根據自己的喜好,處理好當前技能的管理方式,就可以打造出各種各樣的個性場景了。

例如為每一個使用者分配一個技能池,使用者可以自行微調每個技能的相關的引數。

結合後面會提及到的Prompt Template 語法,也可以創造出更多豐富的場景。

官方Github倉庫中有一個樣例,就是從雲端載入技能,可以大致參考一下https://github.com/microsoft/semantic-kernel/blob/main/samples/dotnet/kernel-extension-load-prompts-from-cloud/SampleExtension.cs

Semantic Function的引數設定

除了skprompt.txt ,另外一個需要注意的就是config.json檔案,也就對應著 PromptTemplateConfig 這個設定類。

一個典型的組態檔類似這樣:

{
  "schema": 1,
  "type": "completion",
  "description": "a function that generates marketing slogans",
  "completion": {
    "max_tokens": 1000,
    "temperature": 0.0,
    "top_p": 0.0,
    "presence_penalty": 0.0,
    "frequency_penalty": 0.0
  },
  "default_services": [
    "text-davinci-003"
  ]
}

其中 schema 目前沒啥用, description 提供了Function的功能說明, type 指定了當前Function的所使用的模型型別,"completion", "embeddings」之類,預設為」completion」, default_services 指定預設使用的模型名稱(官方檔案中還是default_backend,應該是還沒來得及更新)。然後就是我們作為常見的 completion設定了。直接參考官方檔案即可。

更為強大的模板語法

如果僅僅是將OpenAI的介面做了一層封裝的話,其實和市面上大多數的OpenAI的sdk差不了多少,

而Semantic Kernel所能提供自然會有更多,其中就Semantic Function部分,SK就提供了一套強大的Prompt Template 語法。

變數

前面已經用到過一個最簡單 {{$INPUT}} 就是SK提供的變數語法,所有的變數放在 {{ }} 中, $INPUT 就是預設的輸入引數,除此之外,還可以自行定義引數。

例如:

Write me a marketing slogan for my {{$INPUT}} in {{$CITY}} with 
a focus on {{$SPECIALTY}} we are without sacrificing quality.

這裡的引數不區分大小寫,所以有時會看到$INPUT,有時候會看到$input,都是可以的。

有了引數自然就需要能夠傳遞多個引數進去,需要使用的是ContextVariables進行管理的。

var myContext = new ContextVariables(); 
myContext.Set("BUSINESS", "Basketweaving Service"); 
myContext.Set("CITY", "Seattle"); 
myContext.Set("SPECIALTY","ribbons"); 

var myResult = await myKernel.RunAsync(myContext,mySkill["SloganMakerFlex"]);

相比較之前直接給input執行,這裡將所有引數都放在了一個ContextVariables中,打包塞進了Kernel。

函數呼叫

除了多個引數之外,SK還提供了類似函數呼叫的方式,可以在prompt中實現多種技能的組合,而且並不限制是Semantic Function 還是 Native Function。

例如有一個 weather.getForecast 的Native Function可以獲取指定 city 的天氣,還有一個 time.Date 可以獲取今天的日期。

需要根據使用者的所在城市,以及相關行程資訊撰寫一篇旅行日記。就可以這樣寫prompt:

The weather today is {{weather.getForecast $city}}.
The date is {{time.Date}}.
My itinerary for today is as follows:
===
{{ $itinerary }}
===
Generate a travel diary based on the above content.

除此之外,模板語法的還有一些符號跳脫的注意事項,可以具體參考Github中的檔案https://github.com/microsoft/semantic-kernel/blob/main/docs/PROMPT_TEMPLATE_LANGUAGE.md

至此,Semantic Function的基本設定和使用的掌握的差不多了。


參考資料:

  1. https://learn.microsoft.com/en-us/semantic-kernel/howto/semanticfunctions
  2. https://github.com/microsoft/semantic-kernel/tree/main/samples/dotnet/kernel-extension-load-prompts-from-cloud
  3. https://learn.microsoft.com/en-us/semantic-kernel/howto/configuringfunctions
  4. https://github.com/microsoft/semantic-kernel/blob/main/dotnet/src/SemanticKernel/SemanticFunctions/PromptTemplateConfig.cs
  5. https://github.com/microsoft/semantic-kernel/blob/main/docs/PROMPT_TEMPLATE_LANGUAGE.md