【Semantic Kernel 】 2、Semantic Function

发布时间 2023-07-06 20:00:25作者: .Neterr

如果把提示词也算作一种代码的话,那么Semantic Function所带来的将会是全新编程方式,自然语言编程。
通常情况下一段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"]);
Console.WriteLine(resultContext.Result);

扩展自己的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。

更为强大的模板语法

  • 多个参数:
    前面已经用到过一个最简单 {{$INPUT}} 就是SK提供的变量语法,所有的变量放在 {{ }} 中, $INPUT 就是默认的输入参数,除此之外,还可以自行定义参数。如果传递多个参数,需要使用的是ContextVariables进行管理的。
  • 函数调用:
    SK还提供了类似函数调用的方式,可以在prompt中实现多种技能的组合,而且并不限制是Semantic Function 还是 Native Function。

案例:

//传递多个参数、调用函数
{
    var prompt =
"""
    The date is {{time.now}},add {{$days}} days,{{$years}} years,What is the date
    """;
    var func = kernel.CreateSemanticFunction(prompt, new PromptTemplateConfig());
    kernel.ImportSkill(new Time(), "time");
    var myContext = new ContextVariables();
    myContext.Set("years", "2");
    myContext.Set("days", "1");
    var resultContext = await kernel.RunAsync(myContext, func);
    //输出结果
    Console.WriteLine(resultContext.Result);//If you add 1 day to the date 2023/7/6, the new date would be 2023/7/7. If you add 2 years to that date, the new date would be 2025 / 7 / 7.
}
public class Time
{
    [SKFunction("today date")]
    public DateTime Now()
    {
        return DateTime.UtcNow;
    }
}

函数传递变量:{{weather.getForecast $city}}