插件是Semantic Kernel的核心组件。通过插件,你可以将现有的API封装成一个集合,供AI调用,从而赋予AI原本无法执行的操作能力。
在背后,Semantic Kernel利用了最新大型语言模型(LLM)中的原生功能——Function Calling,来帮助LLM进行规划并调用API。通过函数调用,LLM可以请求特定函数的执行。Semantic Kernel会将这个请求传递给代码中的对应函数,并将结果返回给LLM,供其生成最终响应。
并不是所有的AI SDK都有类似插件的概念(大多数只提供函数或工具)。但在企业应用场景中,插件非常有用,因为它们封装了一组功能,契合企业开发者常用的服务和API开发方式。插件还支持依赖注入。在插件的构造函数中,你可以注入执行任务所需的服务(例如数据库连接、HTTP客户端等)。而没有插件功能的SDK通常难以实现这一点。
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using System;
using System.ComponentModel;
using System.Globalization;
var chatModelId = "gpt-4o";
var key = File.ReadAllText(@"C:\GPT\key.txt");
var builder = Kernel.CreateBuilder();
builder.AddOpenAIChatCompletion(chatModelId, key);
var kernel = builder.Build();
kernel.ImportPluginFromFunctions("dataPlugin",
[
kernel.CreateFunctionFromMethod(GetJapaneseDate, , )
]);
[ ]
string GetJapaneseDate()
{
var japaneseFormat = new CultureInfo("ja-JP", false).DateTimeFormat;
japaneseFormat.Calendar = new JapaneseCalendar();
return DateTime.Now.ToString("gg yy年MM月dd日", japaneseFormat);
}
var settings = new OpenAIPromptExecutionSettings()
{
ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions
};
var result = await kernel.InvokePromptAsync("今天是几号?", new KernelArguments(settings));
Console.WriteLine(result);
结果:
二、用Type导入插件
……
kernel.ImportPluginFromType<CurrentDateTime>();
var settings = new OpenAIPromptExecutionSettings()
{
ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions
};
var result = await kernel.InvokePromptAsync("今天是几号?", new KernelArguments(settings));
Console.WriteLine(result);
//定义一个类型
public class CurrentDateTime
{
[KernelFunction, Description("获取当前日本历法的日期")]
public string GetJapaneseDate()
{
var japaneseFormat = new CultureInfo("ja-JP", false).DateTimeFormat;
japaneseFormat.Calendar = new JapaneseCalendar();
return DateTime.Now.ToString("gg yy年MM月dd日", japaneseFormat);
}
}
结果与前面相同。
三、把对象导成插件
……
kernel.ImportPluginFromObject(new CurrentDateTime());
var settings = new OpenAIPromptExecutionSettings()
{
ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions
};
var result = await kernel.InvokePromptAsync("今天是几号?", new KernelArguments(settings));
Console.WriteLine(result);
……
结果与前面相同。
四、语义函数组成插件
前面几个都是本地函数,看一个语义函数导入成插件的情况。语义函数可以通过定义一组文件夹和文件来定义,结构如下:
config.json
{
"schema": 1,
"description": "把给定的内容翻译成日文",
"execution_settings": {
"default": {
"max_tokens": 512,
"temperature": 0.0,
"top_p": 0.0,
"presence_penalty": 0.0,
"frequency_penalty": 0.0
}
},
"input_variables": [
{
"name": "input",
"description": "翻译内容",
"default": "",
"is_required": true
}
]
}
skprompt.txt
把{{$input}}翻译成日文
导入语义函数插件:
var translateDirectory = Path.Combine(
System.IO.Directory.GetCurrentDirectory(),
"plugins",
"TranslatePlugin");
kernel.ImportPluginFromPromptDirectory(translateDirectory);
//method 04
var result = await kernel.InvokeAsync("TranslatePlugin", "translate", new() {
{ "input", "今天是星期一" }
});
Console.WriteLine(result);
结果: