7、Semantic Kernel插件系统

7、Semantic Kernel插件系统

Semantic Kernel 强大的插件系统

SK Plugin

LLMs(大型语言模型)的数据存在时间限制,在增加实时内容和企业化知识方面有较大缺陷。OpenAI 通过插件将 ChatGPT 与第三方应用程序连接,这些插件使 ChatGPT 能与开发人员定义的 API 交互,增强了其功能并允许更广泛的操作,包括检索实时信息(如体育赛事比分、股票价格、最新新闻等)、检索知识库信息(如公司文档、个人笔记等)以及协助用户进行相关操作(如预订航班、订餐等)。

这种插件机制为语言模型的应用拓展提供了更多可能性,使它们能够更好地满足用户在不同场景下的需求。通过接入各种插件,语言模型可以获取实时信息和特定领域的知识,提高其回答的准确性和实用性。同时,开发人员也可以根据自己的需求定制插件,进一步增强语言模型的功能。

Semantic Kernel的Plugin(插件)是其关键组件之一,具有以下特点和功能:

  • 基本概念
    • 插件可封装现有API,为AI提供执行特定操作的能力,类似于ChatGPT或Copilot扩展中的插件。
    • 插件利用大多数最新LLM的函数调用功能,Semantic Kernel会自动描述插件的函数及其参数给AI,然后处理模型与代码之间的交互。
  • 插件结构:
    • 插件是一组函数的集合,这些函数可被AI应用和服务编排以完成用户请求。
    • 插件中的函数需要提供语义描述,包括函数名、输入输出和作用等信息,以便AI理解如何调用这些函数。

综上所述,Plugin是Semantic Kernel中实现AI功能的重要组成部分,它能够为AI提供具体的操作能力,并与内核和其他组件协同工作,以满足用户的需求。

Semantic Plugin vs Native Plugin

在继续介绍之前,有必要先行明确两个概念,就是Semantic Plugin 和 Native Plugin。二者的差异也很简单:

插件类型描述组成
Semantic Plugin(语义插件)通过自然语言编写的提示词插件Semantic Function(Prompt)组成
Native Plugin(本地插件)通过代码编写的函数插件Native Function组成

Native Function,即本地函数,使用C#或其他语言代码编写的函数。本地函数可以直接被SK调用,以便可以操作数据或执行其他操作。这样,本地函数就像AI应用程序的机械臂。它们可用于保存数据、检索数据和执行任何您可以在不适合LLM的代码中执行的其他操作(例如,执行计算)。

Your first Native Plugin

上面已经提及Native Plugin = Native Function + Native Function + ... ,因此编写Native Plugin的关键就是Native Function 的编写,在SK 中Native Function 使用[KernelFunction] 特性并配合[Description]特性标识即可,SK会根据标记的特性自动生成此对象的信息并且传递给 AI 代理,并将 AI 代理生成的参数列表映射到正确的对象。以下就是一个计算插件用于加减乘除运送。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#r "nuget:Microsoft.SemanticKernel"

using System.ComponentModel;
using Microsoft.SemanticKernel;
public class MathPlugin
{
[KernelFunction, Description("Take the square root of a number")]
public static double Sqrt(
[Description("The number to take a square root of")] double number1
)
{
Console.WriteLine("Taking the square root of {0}", number1);
return Math.Sqrt(number1);
}
[KernelFunction, Description("Add two numbers")]
public static double Add(
[Description("The first number to add")] double number1,
[Description("The second number to add")] double number2
)
{
Console.WriteLine("Adding {0} to {1}", number1, number2);
return number1 + number2;
}
[KernelFunction, Description("Subtract two numbers")]
public static double Subtract(
[Description("The first number to subtract from")] double number1,
[Description("The second number to subtract away")] double number2 )
{
Console.WriteLine("Subtracting {0} from {1}", number2, number1);
return number1 - number2;
}
[KernelFunction, Description("Multiply two numbers. When increasing by a percentage, don't forget to add 1 to the percentage.")]
public static double Multiply(
[Description("The first number to multiply")] double number1,
[Description("The second number to multiply")] double number2
)
{
Console.WriteLine("Multiplying {0} by {1}", number1, number2);
return number1 * number2;
}
[KernelFunction, Description("Divide two numbers")]
public static double Divide(
[Description("The first number to divide from")] double number1,
[Description("The second number to divide by")] double number2
)
{
Console.WriteLine("Dividing {0} by {1}", number1, number2);
return number1 / number2;
}
}

除了上面定义的插件方式之外,还可以使用KernelFunctionFactory.CreateFromMethod 来定义:

1
2
var builder = Kernel.CreateBuilder();
var kernel = builder.Build();
1
2
3
4
5
6
7
8
9
10
11
12
13
kernel.Plugins.AddFromFunctions("time_plugin",
[
KernelFunctionFactory.CreateFromMethod(
method: () => DateTime.Now,
functionName: "get_time",
description: "Get the current time"
),
KernelFunctionFactory.CreateFromMethod(
method: (DateTime start, DateTime end) => (end - start).TotalSeconds,
functionName: "diff_time",
description: "Get the difference between two times in seconds"
)
]);

主动调用

主动调用也很简单,分两步:

  1. 添加插件:通过AddFromTypeAddFromObjectAddFromFunctions添加
  2. 调用插件:
    • 传递插件名称
    • 传递函数名称
    • 传递函数参数
1
2
3
4
5
6
7
8
var time = await kernel.InvokeAsync<DateTime>("time_plugin", "get_time", new());
time.Display();

kernel.Plugins.Clear();
kernel.Plugins.AddFromType<MathPlugin>();
// Test the math plugin
double answer = await kernel.InvokeAsync<double>( "MathPlugin", "Sqrt", new() { { "number1", 9 } } );
Console.WriteLine($"The square root of 9 is {answer}.");

2025-02-17 16:28:13Z

Taking the square root of 9
The square root of 9 is 3.

被动调用

即允许LLMs自动去选择已注册到内核的的最佳函数来满足用户的要求。首先必不可少的准备工作:

1
2
//创建kernel等事宜后
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();

其次,主要是要启用Function Calling,主要是设置OpenAIPromptExecutionSettingsToolCallBehavior 属性:

1
2
3
4
OpenAIPromptExecutionSettings openAIPromptExecutionSettings = new()
{
ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions
};

其中ToolCallBehavior有以下两种赋值,但二者都会将内核绑定的插件信息提供给模型,以供模型选择合适的函数进去调用,但区别在于是否自动完成函数调用:

  1. ToolCallBehavior.AutoInvokeKernelFunctions :自动调用内核函数
  2. ToolCallBehavior.EnableKernelFunctions :仅返回模型的函数调用请求,需要应用自行发起对函数的调用

自动调用函数

1
2
3
4
5
6
7
8
sequenceDiagram
User ->> App: 用户请求
App ->> LLMs: 用户请求 + Plugins(Function1 + Function2 + ...)
LLMs -->> App: 匹配的函数:Function1(param1, param2, ...)
App -->> App: 函数执行:Function1(param1, param2, ...)
App -->> LLMs: 函数执行结果
LLMs -->> LLMs: 整合结果
LLMs -->> User: 自然语言答复
1
2
3
4
5
6
7
8
9
10
11
// 注册插件
kernel.Plugins.Clear();
kernel.Plugins.AddFromType<MathPlugin>();
kernel.Plugins.AddFromFunctions("time_plugin",
[
KernelFunctionFactory.CreateFromMethod(
method: () => DateTime.Now,
functionName: "get_time",
description: "Get the current time"
)
]);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using Microsoft.SemanticKernel.Connectors.OpenAI;

var request = await PolyglotKernel.GetInputAsync("请输入你的请求:");
// Enable auto function calling
OpenAIPromptExecutionSettings openAIPromptExecutionSettings = new()
{
ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions
};
// Get the response from the AI
var result = await chatCompletionService.GetChatMessageContentAsync(
prompt: request,
executionSettings: openAIPromptExecutionSettings,
kernel: kernel);

result.Display();

现在是2025年2月17日下午4点30分。

仅返回匹配的函数调用请求

返回值中会包含ToolCalls 内容。

1
2
3
4
5
6
sequenceDiagram
User ->> App: Request
App ->> LLMs: Request + Plugins(Function1 + Function2 + ...)
LLMs -->> App: Matched Function:Function1(param1, param2, ...)
App -->> App: Do something with Function1(param1, param2, ...)
App -->> User: Response
image-20250321093651359
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using Microsoft.SemanticKernel.Connectors.OpenAI;

var request = await PolyglotKernel.GetInputAsync("请输入你的请求:");
// Enable auto function calling
OpenAIPromptExecutionSettings openAIPromptExecutionSettings = new()
{
ToolCallBehavior = ToolCallBehavior.EnableKernelFunctions
};

var chatHistory = new ChatHistory();
chatHistory.AddMessage(AuthorRole.User, request);

// Get the response from the AI
var result = await chatCompletionService.GetChatMessageContentAsync(
chatHistory: chatHistory,
executionSettings: openAIPromptExecutionSettings,
kernel: kernel);

// 返回函数调用请求描述
result.Display();

接下来可以使用以下方式进行手动调用:

1
2
3
4
5
6
7
8
9
// 获取LLMs返回的函数调用
var functionCalls = FunctionCallContent.GetFunctionCalls(result);

foreach (var functionCall in functionCalls)
{
FunctionResultContent resultContent = await functionCall.InvokeAsync(kernel);
resultContent.Display();
chatHistory.Add(resultContent.ToChatMessage());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//完全自己手动调用
using System.Text.Json;

// 获取LLMs返回的函数调用
var functionCalls = ((OpenAIChatMessageContent)result).GetOpenAIFunctionToolCalls();

foreach (var functionCall in functionCalls)
{
// 获取函数和参数
kernel.Plugins.TryGetFunctionAndArguments(
functionCall,
out KernelFunction pluginFunction,
out KernelArguments arguments
);

// 调用函数
var functionResult = await kernel.InvokeAsync(pluginFunction!, arguments!);
var jsonResponse = functionResult.GetValue<object>();
var json = JsonSerializer.Serialize(jsonResponse);

// 添加到聊天历史,注意角色是Tool
chatHistory.AddMessage(AuthorRole.Tool, json);
json.Display();
}

chatHistory.Display();

// 再次调用LLMs将手动调用的Function call结果传递回LLMs,以获取最终结果
var finnalResult = await chatCompletionService.GetChatMessageContentAsync(
chatHistory: chatHistory,
kernel: kernel);

finnalResult.Display();

"2025-02-17T16:33:00.202409+08:00"

SK 内置的Plugin

内置的Plugins

需要安装Microsoft.SemanticKernel.Plugins.Core NuGet包,当前包还处于Alpha版本,请谨慎使用。

1
2
3
4
5
6
7
8
9
10
11
#r "nuget:Microsoft.SemanticKernel.Plugins.Core,*-*"

// 由于该插件还在演进当中,不建议在正式环境中使用,包含了一些警告,我们需要禁用警告
#pragma warning disable SKEXP0050


using Microsoft.SemanticKernel.Plugins.Core;

var builder = Kernel.CreateBuilder();
builder.Plugins.AddFromType<TimePlugin>();
builder.Plugins.AddFromType<HttpPlugin>();
作者

步步为营

发布于

2025-03-21

更新于

2025-03-21

许可协议