深入學習Semantic Kernel:建立和配置prompts functions

董瑞鹏發表於2024-05-21

引言

上一章我們熟悉了一下 Semantic Kernel 的理論知識,Kernel 建立以及簡單的Sample熟悉了一下 SK 的基本使用。在Semantic Kernel中的 kernel functions由兩部分組成第一部分是prompts functions(提示函式),第二部分Native function(原生函式), kernel functions是構成外掛(Plugins)的核心,一個外掛代表一個或者多個的kernel functions,今天我們重點了解一下第一部分prompts functions(提示函式)。

kernel functions 基礎

我們知道跟大語言模型(LLM)互動靠的是提示(prompts),有效的提示設計對於使用 LLM AI 模型實現預期結果至關重要。提示工程,也稱為提示設計,是一個新興領域,需要創造力和對細節的關注。它涉及選擇正確的單詞、短語、符號和格式,以指導模型生成高質量和相關的文字。

提示工程的深入學習是用好大語言模型的關鍵。

建立提示函式

Semantic Kernel中提供了多種透過Prompts建立KernelFunction的擴充套件方法,底層本質上都是呼叫KernelFunctionFromPromptCreate方法來完成提示函式的建立。

    public static KernelFunction Create(
        IPromptTemplate promptTemplate,
        PromptTemplateConfig promptConfig,
        ILoggerFactory? loggerFactory = null)
    {
        Verify.NotNull(promptTemplate);
        Verify.NotNull(promptConfig);

        return new KernelFunctionFromPrompt(
            template: promptTemplate,
            promptConfig: promptConfig,
            logger: loggerFactory?.CreateLogger(typeof(KernelFunctionFactory)) ?? NullLogger.Instance);
    }

這裡面其實只有第二個引數PromptTemplateConfig是我們需要關心的,第一個引數promptTemplate是根據第二個引數 promptConfigTemplate屬性來構造的,接下來我們重點了解一下PromptTemplateConfig的配置。

PromptTemplateConfig的屬性

public sealed class PromptTemplateConfig
{
    private string? _templateFormat;
    private string _template = string.Empty;

    [JsonPropertyName("name")]
    public string? Name { get; set; }

    [JsonPropertyName("description")]
    public string? Description { get; set; }
    public static string SemanticKernelTemplateFormat => "semantic-kernel";

    [JsonPropertyName("template_format")]
    [AllowNull]
    public string TemplateFormat
    {
        get => this._templateFormat ?? SemanticKernelTemplateFormat;
        set => this._templateFormat = value;
    }

    [JsonPropertyName("template")]
    public string Template
    {
        get => this._template;
        set
        {
            Verify.NotNull(value);
            this._template = value;
        }
    }

    [JsonPropertyName("input_variables")]
    public List<InputVariable> InputVariables
    {
        get => this._inputVariables ??= [];
        set
        {
            Verify.NotNull(value);
            this._inputVariables = value;
        }
    }

    [JsonPropertyName("output_variable")]
    public OutputVariable? OutputVariable { get; set; }


    [JsonPropertyName("execution_settings")]
    public Dictionary<string, PromptExecutionSettings> ExecutionSettings
    {
        get => this._executionSettings ??= [];
        set
        {
            Verify.NotNull(value);
            this._executionSettings = value;
        }
    }

    [Experimental("SKEXP0001")]
    [JsonPropertyName("allow_unsafe_content")]
    public bool AllowUnsafeContent { get; set; } = false;
}

為了方便展示我們只保留PromptTemplateConfig的核心屬性,這個類非常的重要,包括我們要定義配置模版也是基於此類的欄位來配置。

下面我們對PromptTemplateConfig的屬性進行簡單的講解

我們可以把PromptTemplateConfig可以看做是對一個函式的表述,帶著這個理解來解讀這個配置類更容易理解,如用 C#定義一個函式

[Description("無參無返回值的靜態函式")]
static void SampleFunction()
{
    Console.Write("無參無返回值函式");
}

Name屬性

Name屬性是在PromptTemplateConfig中用來獲取或設定在使用此配置建立提示函式(Prompts functions)時使用的預設函式名稱。
型別可空,如果不設定建立函式時將動態生成一個隨機名稱。命名規則:利用 GUID 生成一個不含連字元的隨機字串,並將其格式化為以"func"為字首的函式名稱

    private static string CreateRandomFunctionName() => $"func{Guid.NewGuid():N}";

Name 類似與我們在 C#中的函式名

Description 屬性

Description屬性是用於表示一個函式的描述資訊,如果在建立prompts functions時候沒有顯示指定函式描述資訊,那會採用Description 屬性的描述。

結合我們定義的 C#自定義函式中Description來理解這個屬性,其實就是給方法配置一個描述資訊,提示方法也是一種特殊的方法。

TemplateFormat屬性

TemplateFormat屬性用於對prompts提示模板的格式配置,預設值為 "semantic-kernel"
對於prompts的模版格式化引擎 用的有兩種,第一種就是 Semantic Kernel 自帶的處理格式"semantic-kernel";第二種則是handlebars

Template屬性

Template 屬性用於儲存和管理用於定義prompts模板字串。在設定模板字串時,會進行空值驗證,以確保模板字串不為 null,從而保證在生成prompts提示時模板內容有有效可用。

InputVariables 屬性

InputVariables屬性用於prompts提示模板中使用的輸入變數集合。

InputVariable 物件包含的屬性:

  • Name:變數的名稱,用於標識輸入變數。
  • Description:變數的描述,提供關於輸入變數的說明。
  • Default:變數的預設值。
  • IsRequired:指示變數是否為必需的,預設為 true。
  • JsonSchema:描述變數的 JSON 模式。
  • AllowUnsafeContent:指示是否允許不安全內容,預設為 false。

OutputVariable 屬性

OutputVariable 屬性用於定義和管理prompts提示模板中的輸出變數。

參考我們c#定義函式 這個我理解的就是對我們函式返回值引數的一個描述

ExecutionSettings屬性

ExecutionSettings屬性用於獲取或設定提示模板使用的執行設定集合;型別為Dictionary<string, PromptExecutionSettings>,表示一個鍵值對集合,其中鍵為服務 ID,值為執行設定。

      ExecutionSettings =
      {
          {
            OpenAIPromptExecutionSettings.DefaultServiceId,
              new OpenAIPromptExecutionSettings()
              {
                  MaxTokens = 1000,
                  Temperature = 0
              }
          },
          {
              "gpt-3.5-turbo", new OpenAIPromptExecutionSettings()
              {
                  ModelId = "gpt-3.5-turbo-0613",
                  MaxTokens = 4000,
                  Temperature = 0.2
              }
          }
      }

執行的配置為 PromptExecutionSettings.DefaultServiceId預設值是"default",因為Semantic Kernel都是基於.Net 8 的鍵值依賴注入Keyed,所以 default 就是獲取的上面預設的執行配置,

kernel.GetRequiredService<ITextGenerationService>();
kernel.GetRequiredService<ITextGenerationService>("gpt-3.5-turbo");

從程式碼處理解就容易多了,可以透過ServiceKey去獲取不同大模型的例項。

OpenAIPromptExecutionSettings 配置

這個配置是大模型的進行請求時的引數配置,是PromptExecutionSettings提示執行設定的子類,OpenAI 的配置就是OpenAIPromptExecutionSettings,Google的大模型有自己的實現比如GeminiPromptExecutionSettings 核心引數其實都差不多,現在我們用OpenAI的提示詞執行設定熟悉下配置的引數。

  • MaxTokens:指定在生成文字或完成請求時允許生成的最大標記數,大多數模型的上下文長度為 2048 個標記(支援 4096 的 davinci-codex 除外)。
  • Temperature: 控制完成結果的隨機性。預設是 1.0,通常取值範圍在0-1.0之間。較高的溫度會增加生成文字的隨機性,使得生成的文字更加多樣化和創新性,而較低的溫度則會減少隨機性,使得生成的文字更加穩定和可預測。

    對於更具創意的應用程式,請嘗試 0.9,對於具有明確答案的應用程式,請嘗試 0(argmax 取樣)。

  • TopP: 用於控制完成結果的多樣性。預設是1.0。透過設定不同的值可以調整生成文字的多樣性程度。較高的 TopP 值會導致生成的文字更加多樣化,而較低的值則可能使生成的文字更加穩定和集中。

    使用溫度進行取樣的替代方法,稱為核取樣,其中模型考慮具有 top_p 機率質量的標記的結果。因此,0.1 表示僅考慮包含前 10% 機率質量的代幣。我們通常建議改變這個或溫度,但不要同時改變兩者。

  • PresencePenalty: 屬性接受介於-2.0和2.0之間的數字。預設是0。正值將根據新標記是否在文字中出現來對其進行懲罰,從而增加模型談論新主題的可能性。

    新標記:模型嘗試引入新的內容或概念,以增加生成文字的多樣性和創新性

  • FrequencyPenalty:屬性用於控制模型生成文字時對重複內容的處理方式。預設是0。它接受介於-2.0 和 2.0 之間的數字,其中正值表示根據標記在文字中的現有頻率對其進行懲罰,以降低模型直接重複相同內容的可能性。透過設定較高的 FrequencyPenalty 值,模型更有可能避免直接重複相同內容,從而降低生成文字中重複內容的頻率.

  • StopSequences:屬性用於指定一個字串列表,其中包含模型在生成文字時遇到指定序列時應停止生成進一步標記。

    例如,如果設定 StopSequences 為[""],則當模型生成文字時遇到"<END>"序列時,生成過程將停止。

  • ChatSystemPrompt:屬性用於指定在使用聊天模型生成文字時要使用的系統提示。預設值是:"Assistant is a large language model."。這個系統提示可以影響生成文字的方向和內容,幫助模型更好地理解生成任務的背景和要求。透過合理設定 ChatSystemPrompt 屬性,可以定製生成文字時使用的系統提示,以獲得符合預期的生成結果。

  • ToolCallBehavior:屬性用於獲取或設定如何處理工具呼叫的行為。

          // Enable auto function calling
        OpenAIPromptExecutionSettings openAIPromptExecutionSettings = new()
        {
            ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions
        };
    

    1.EnableKernelFunctions:會向模型提供核心的外掛函式資訊,但不會自動處理函式呼叫請求。模型需要顯式發起函式呼叫請求,並系統會傳播這些請求給適當的處理程式來執行。

     OpenAIPromptExecutionSettings settings = new() { ToolCallBehavior = ToolCallBehavior.EnableKernelFunctions };
     var chatHistory = new ChatHistory();
     ChatMessageContent result = await chat.GetChatMessageContentAsync(chatHistory, settings, kernel);
     //手動呼叫
     IEnumerable<FunctionCallContent> functionCalls = FunctionCallContent.GetFunctionCalls(result);
    

    EnableKernelFunctions:需要透過 FunctionCallContent 手動呼叫

    2.AutoInvokeKernelFunctions:除了向模型提供核心的外掛函式資訊外,還會嘗試自動處理任何函式呼叫請求。模型發起函式呼叫請求後,系統會自動執行相應的操作,並將結果返回給模型,而無需模型顯式處理函式呼叫的過程。

ResponseFormat屬性

ResponseFormat的屬性,用於獲取或設定用於完成操作的響應格式。可選值包括:"json_object""text",以及 ChatCompletionsResponseFormat 物件,可以選擇不同的值來指定響應的格式型別,例如使用 JSON 物件、純文字等不同的響應格式

ResultsPerPrompt屬性

ResultsPerPrompt 屬性用於確定每個提示生成的完成次數。預設值:預設為 1,即每個提示只生成一個完成結果。在自然語言處理中,一個提示(prompt)是輸入給模型的文字或問題,而完成(completion)是模型生成的對應輸出。透過設定 ResultsPerPrompt 屬性,您可以指定每個提示應該生成多少個完成結果。

Seed屬性

Seed屬性的作用是為了控制取樣的確定性,透過指定種子值,可以在一定程度上確保相同種子和引數下的重複請求返回相同的結果。然而,由於確定性並不是完全保證的,結果仍可能有一定程度的變化

User屬性

透過為每個終端使用者分配一個唯一的識別符號,OpenAI 可以更好地跟蹤和管理使用者的行為,同時也可以更有效地監控系統是否受到濫用。

prompts functions 實戰

Semantic Kernel 有幾個 Kernel 物件的擴充套件方法用於prompts提示詞模版來建立KernelFunction,總的來說可以有三類:

我們繼續用我們上一章的 OneApi 代理星火訊飛 V3.5 方式來對接 Semantic Kernel 具體配置可以找我上一篇文章

基於 String 字串建立 prompts functions

實戰

//基於String模版建立kernel functions
Console.WriteLine("====>基於String模版建立kernel functions<=====");
{
    string prompt = "What is the intent of this request? {{$request}}";
    var kernel = Kernel.CreateBuilder().AddOpenAIChatCompletion(modelId: config.ModelId,
        apiKey: config.ApiKey,
        httpClient: client).Build();
    var kernelFunction = kernel.CreateFunctionFromPrompt(prompt);

    string request = "I want to send an email to the marketing team celebrating their recent milestone.";

    // Create a kernel arguments object and add the  request
    var kernelArguments = new KernelArguments
            {
                { "request", request }
            };
    var functionResult = await kernelFunction.InvokeAsync(kernel, kernelArguments);

    Console.WriteLine(functionResult?.ToString() ?? string.Empty);
}

當然 SK 也提供了更加簡單的方法,直接傳prompts string 模版

    var functionResult = await kernel.InvokePromptAsync(prompt, kernelArguments);

這個方法內部實際上就是呼叫了CreateFunctionFromPrompt建立了kernel functions,目的是簡化提示函式建立的過程

基於PromptTemplateConfig物件建立 prompts functions

經過上面的介紹我們已經對PromptTemplateConfig的引數設定已經有了一個大致的認識,要實現這個要求需要藉助到我們的 kernel.CreateFunctionFromPrompt這個擴充套件方法,下面我們來實操一下:

實戰

string request = "I want to send an email to the marketing team celebrating their recent milestone.";
{
    var kernel = Kernel.CreateBuilder().AddOpenAIChatCompletion(modelId: config.ModelId,
    apiKey: config.ApiKey,
    httpClient: client).Build();

    var kernelFunctions = kernel.CreateFunctionFromPrompt(new PromptTemplateConfig()
    {
        Name = "intent",
        Description = "use assistant to understand user input intent.",
        TemplateFormat = PromptTemplateConfig.SemanticKernelTemplateFormat,//此處可以省略預設就是"semantic-kernel"
        Template = "What is the intent of this request? {{$request}}",
        InputVariables = [new() { Name = "request", Description = "The user's request.", IsRequired = true }],
        ExecutionSettings = new Dictionary<string, PromptExecutionSettings>() {
               {
                      OpenAIPromptExecutionSettings.DefaultServiceId ,//"default"
                        new OpenAIPromptExecutionSettings()
                        {
                            MaxTokens = 1024,
                            Temperature = 0
                        }
                    },
        }
    });
    var kernelArguments = new KernelArguments
    {
                { "request", request }
            };
    var functionResult = await kernelFunctions.InvokeAsync(kernel, kernelArguments);

    Console.WriteLine(functionResult?.ToString() ?? string.Empty);
}

執行效果

image

基於pluginDirectory從指定的外掛目錄中建立外掛

此方法是建立外掛的方法之一,之前有介紹過外掛就是一組kernel functions的集合,透過定義資料夾模版可以生成prompts functions,這部分內容等學習到Semantic KernelPlugins在著重講解吧。

最後

在本章中,我們深入探討了 Semantic Kernel 中的 kernel functions,重點關注了第一部分的 prompts functions(提示函式)。我們學習瞭如何基於不同方法建立這些提示函式,包括基於字串模板和 PromptTemplateConfig 物件的建立方式,以及如何從指定的外掛目錄中建立外掛。

透過詳細講解 PromptTemplateConfig 的屬性,我們理解了如何配置和管理提示模板,以及如何調整執行設定來影響提示函式的生成結果。我們還實際操作了建立 kernel functions 的過程,加深了對提示工程的實際運用。

最後,我們展望了未來的學習方向,即 Semantic KernelPlugins 部分,這將為我們提供更多關於外掛的建立和應用方法,進一步擴充套件我們的知識和應用領域。

透過本章的學習,相信您對 prompts functions 的建立和配置有了更深入的瞭解,為進一步探索和應用 Semantic Kernel 打下了堅實的基礎。如果您有任何疑問或需要進一步幫助,請隨時向我提問。感謝閱讀!🚀

參考資料

configure-prompts

本文示例原始碼

本文原始碼

😄歡迎關注筆者公眾號一起學習交流,獲取更多有用的知識~
image

相關文章