• 深入学习Semantic Kernel:创建和配置prompts functions


    引言

    上一章我们熟悉了一下 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 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,表示一个键值对集合,其中键为服务 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();
    kernel.GetRequiredService("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 为[""],则当模型生成文本时遇到""序列时,生成过程将停止。

    • 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 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

  • 相关阅读:
    【机器学习】01. 波士顿房价为例子学习线性回归(代码注释,思路推导)
    C++ 指针
    C/C++ 进程间通信system V IPC对象超详细讲解(系统性学习day9)
    React中hooks使用限制及保存函数组件状态
    ElasticSearch集群缩容
    《吐血整理》进阶系列教程-拿捏Fiddler抓包教程(11)-Fiddler设置安卓手机抓包,不会可是万万不行的!
    活动安排问题(贪心算法)
    和琪宝的深圳,香港之旅~
    SPASS-探索性分析
    docker.2-容器技术所涉及Linux内核关键技术
  • 原文地址:https://www.cnblogs.com/ruipeng/p/18203015