什么是插件?

插件是语义内核的关键组件。 如果已在 Microsoft 365 中使用了 ChatGPT 或 Copilot 扩展中的插件,则已熟悉它们。 使用插件,可以将现有 API 封装到 AI 可以使用的集合中。 这使你可以让你的 AI 能够执行其他操作。

在后台,语义内核利用 函数调用,这是大多数最新 LLM 的本机功能,允许 LLM 执行 规划和 调用 API。 使用函数调用,LLM 可以请求特定函数(即调用)。 然后,语义内核将请求封送至代码库中的相应函数,并将结果返回给 LLM,以便 LLM 可以生成最终响应。

语义内核插件

并非所有 AI SDK 都有类似于插件的概念(大多数只有函数或工具)。 但是,在企业方案中,插件很有价值,因为它们封装了一组功能,反映了企业开发人员已经开发服务和 API 的方式。 插件还很好地与依赖项注入一起播放。 在插件的构造函数中,可以注入执行插件工作所需的服务(例如,数据库连接、HTTP 客户端等)。 这很难通过缺少插件的其他 SDK 来实现。

插件剖析

概括而言,插件是一组 可向 AI 应用和服务公开的函数 。 然后,AI 应用程序可以协调插件内的函数来完成用户请求。 在语义内核中,可以使用函数调用自动调用这些函数。

注意

在其他平台上,函数通常称为“工具”或“操作”。 在语义内核中,我们使用术语“functions”,因为它们通常定义为代码库中的本机函数。

但是,只是提供函数不足以生成插件。 若要通过函数调用为自动业务流程提供支持,插件还需要提供以语义方式描述其行为的详细信息。 需要用 AI 可以理解的方式描述函数的输入、输出和副作用的所有内容,否则 AI 将无法正确调用函数。

例如,右侧的示例 WriterPlugin 插件具有包含语义说明的函数,用于描述每个函数的作用。 然后,LLM 可以使用这些说明选择要调用以满足用户请求的最佳函数。

在右侧的图片中,LLM 可能会调用 ShortPoemStoryGen 函数来满足用户的要求,这要归功于提供的语义说明。

WriterPlugin 插件中的语义说明

导入不同类型的插件

将插件导入语义内核有两种主要方法:使用 本机代码 或使用 OpenAPI 规范。 前者允许你在现有的代码库中创作插件,这些插件可以利用你已有的依赖项和服务。 后者允许从 OpenAPI 规范导入插件,该规范可以跨不同的编程语言和平台共享。

下面提供了一个简单的导入和使用本机插件的示例。 若要详细了解如何导入这些不同类型的插件,请参阅以下文章:

提示

入门时,建议使用本机代码插件。 随着应用程序成熟,并且跨跨平台团队工作,可能需要考虑使用 OpenAPI 规范跨不同的编程语言和平台共享插件。

不同类型的插件函数

在插件中,通常具有两种不同类型的函数:检索用于检索扩充生成的数据(RAG)和自动执行任务的函数。 虽然每种类型在功能上相同,但它们通常在使用语义内核的应用程序中使用不同。

例如,使用检索函数时,你可能想要使用策略来提高性能(例如缓存和使用更便宜的中间模型进行汇总)。 而使用任务自动化函数时,你可能希望实现人工循环审批流程,以确保任务正确完成。

若要详细了解不同类型的插件函数,请参阅以下文章:

插件入门

在语义内核中使用插件始终是三个步骤:

  1. 定义插件
  2. 将插件添加到内核
  3. 然后在调用函数的提示中调用插件的函数

下面我们将提供有关如何在语义内核中使用插件的高级示例。 有关如何创建和使用插件的更多详细信息,请参阅上面的链接。

1) 定义插件

创建插件的最简单方法是使用特性定义类并批注其方法 KernelFunction 。 这让语义内核知道,这是可由 AI 调用或在提示中引用的函数。

还可以从 OpenAPI 规范导入插件。

下面,我们将创建一个插件,该插件可以检索灯的状态并更改其状态。

提示

由于大多数 LLM 已使用 Python 进行函数调用训练,因此,即使使用的是 C# 或 Java SDK,也建议对函数名称和属性名称使用蛇大小写。

using System.ComponentModel;
using Microsoft.SemanticKernel;

public class LightsPlugin
{
   // Mock data for the lights
   private readonly List<LightModel> lights = new()
   {
      new LightModel { Id = 1, Name = "Table Lamp", IsOn = false, Brightness = 100, Hex = "FF0000" },
      new LightModel { Id = 2, Name = "Porch light", IsOn = false, Brightness = 50, Hex = "00FF00" },
      new LightModel { Id = 3, Name = "Chandelier", IsOn = true, Brightness = 75, Hex = "0000FF" }
   };

   [KernelFunction("get_lights")]
   [Description("Gets a list of lights and their current state")]
   [return: Description("An array of lights")]
   public async Task<List<LightModel>> GetLightsAsync()
   {
      return lights
   }

   [KernelFunction("get_state")]
   [Description("Gets the state of a particular light")]
   [return: Description("The state of the light")]
   public async Task<LightModel?> GetStateAsync([Description("The ID of the light")] int id)
   {
      // Get the state of the light with the specified ID
      return lights.FirstOrDefault(light => light.Id == id);
   }

   [KernelFunction("change_state")]
   [Description("Changes the state of the light")]
   [return: Description("The updated state of the light; will return null if the light does not exist")]
   public async Task<LightModel?> ChangeStateAsync(int id, LightModel LightModel)
   {
      var light = lights.FirstOrDefault(light => light.Id == id);

      if (light == null)
      {
         return null;
      }

      // Update the light with the new state
      light.IsOn = LightModel.IsOn;
      light.Brightness = LightModel.Brightness;
      light.Hex = LightModel.Hex;

      return light;
   }
}

public class LightModel
{
   [JsonPropertyName("id")]
   public int Id { get; set; }

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

   [JsonPropertyName("is_on")]
   public bool? IsOn { get; set; }

   [JsonPropertyName("brightness")]
   public byte? Brightness { get; set; }

   [JsonPropertyName("hex")]
   public string? Hex { get; set; }
}
from typing import List, Optional, TypedDict, Annotated

class LightModel(TypedDict):
   id: int
   name: str
   is_on: Optional[bool]
   brightness: Optional[int]
   hex: Optional[str]

class LightsPlugin:
   lights: List[LightModel] = [
      {"id": 1, "name": "Table Lamp", "is_on": False, "brightness": 100, "hex": "FF0000"},
      {"id": 2, "name": "Porch light", "is_on": False, "brightness": 50, "hex": "00FF00"},
      {"id": 3, "name": "Chandelier", "is_on": True, "brightness": 75, "hex": "0000FF"},
   ]

   @kernel_function(
      name="get_lights",
      description="Gets a list of lights and their current state",
   )
   async def get_lights(self) -> Annotated[List[LightModel], "An array of lights"]:
      """Gets a list of lights and their current state."""
      return self.lights

   @kernel_function(
      name="get_state",
      description="Gets the state of a particular light",
   )
   async def get_state(
      self,
      id: Annotated[int, "The ID of the light"]
   ) -> Annotated[Optional[LightModel], "The state of the light"]:
      """Gets the state of a particular light."""
      for light in self.lights:
         if light["id"] == id:
               return light
      return None

   @kernel_function(
      name="change_state",
      description="Changes the state of the light",
   )
   async def change_state(
      self,
      id: Annotated[int, "The ID of the light"],
      new_state: LightModel
   ) -> Annotated[Optional[LightModel], "The updated state of the light; will return null if the light does not exist"]:
      """Changes the state of the light."""
      for light in self.lights:
         if light["id"] == id:
               light["is_on"] = new_state.get("is_on", light["is_on"])
               light["brightness"] = new_state.get("brightness", light["brightness"])
               light["hex"] = new_state.get("hex", light["hex"])
               return light
      return None

请注意,我们提供了函数、返回值和参数的说明。 对于 AI 来说,了解函数的作用以及如何使用它,这一点非常重要。

提示

如果 AI 在调用函数时遇到问题,请不要害怕为函数提供详细说明。 很少的示例、何时使用(而不使用)函数的建议,以及获取所需参数的位置的指导都很有帮助。

2) 将插件添加到内核

定义插件后,可以通过创建插件的新实例并将其添加到内核的插件集合来将其添加到内核。

此示例演示了使用该方法将类添加为插件 AddFromType 的最简单方法。 若要了解添加插件的其他方法,请参阅 添加本机插件 文章。

var builder = new KernelBuilder();
builder.Plugins.AddFromType<LightsPlugin>("Lights")
Kernel kernel = builder.Build();
kernel = Kernel()
kernel.add_plugin(
   LightsPlugin(),
   plugin_name="Lights",
)

3) 调用插件函数

最后,可以使用函数调用让 AI 调用插件的函数。 下面是一个示例,演示如何在调用change_state函数以打开灯之前,让 AI 从Lights插件调用get_lights函数。

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;

// Create a kernel with Azure OpenAI chat completion
var builder = Kernel.CreateBuilder().AddAzureOpenAIChatCompletion(modelId, endpoint, apiKey);

// Build the kernel
Kernel kernel = builder.Build();
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();

// Add a plugin (the LightsPlugin class is defined below)
kernel.Plugins.AddFromType<LightsPlugin>("Lights");

// Enable planning
OpenAIPromptExecutionSettings openAIPromptExecutionSettings = new() 
{
    ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions
};

// Create a history store the conversation
var history = new ChatHistory();
history.AddUserMessage("Please turn on the lamp");

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

// Print the results
Console.WriteLine("Assistant > " + result);

// Add the message from the agent to the chat history
history.AddAssistantMessage(result);
import asyncio

from semantic_kernel import Kernel
from semantic_kernel.functions import kernel_function
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
from semantic_kernel.connectors.ai.function_call_behavior import FunctionCallBehavior
from semantic_kernel.connectors.ai.chat_completion_client_base import ChatCompletionClientBase
from semantic_kernel.contents.chat_history import ChatHistory
from semantic_kernel.functions.kernel_arguments import KernelArguments

from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.azure_chat_prompt_execution_settings import (
    AzureChatPromptExecutionSettings,
)

async def main():
   # Initialize the kernel
   kernel = Kernel()

   # Add Azure OpenAI chat completion
   kernel.add_service(AzureChatCompletion(
      deployment_name="your_models_deployment_name",
      api_key="your_api_key",
      base_url="your_base_url",
   ))

   # Add a plugin (the LightsPlugin class is defined below)
   kernel.add_plugin(
      LightsPlugin(),
      plugin_name="Lights",
   )

   chat_completion : AzureChatCompletion = kernel.get_service(type=ChatCompletionClientBase)

   # Enable planning
   execution_settings = AzureChatPromptExecutionSettings(tool_choice="auto")
   execution_settings.function_call_behavior = FunctionCallBehavior.EnableFunctions(auto_invoke=True, filters={})

   # Create a history of the conversation
   history = ChatHistory()
   history.add_message("Please turn on the lamp")

   # Get the response from the AI
   result = (await chat_completion.get_chat_message_contents(
      chat_history=history,
      settings=execution_settings,
      kernel=kernel,
      arguments=KernelArguments(),
   ))[0]

   # Print the results
   print("Assistant > " + str(result))

   # Add the message from the agent to the chat history
   history.add_message(result)

# Run the main function
if __name__ == "__main__":
    asyncio.run(main())

使用上述代码时,应获得如下所示的响应:

角色 消息
🔵用户 请打开灯
🔴助手(函数调用) Lights.get_lights()
🟢工具 [{ "id": 1, "name": "Table Lamp", "isOn": false, "brightness": 100, "hex": "FF0000" }, { "id": 2, "name": "Porch light", "isOn": false, "brightness": 50, "hex": "00FF00" }, { "id": 3, "name": "Chandelier", "isOn": true, "brightness": 75, "hex": "0000FF" }]
🔴助手(函数调用) Lights.change_state(1, { “isOn”: true })
🟢工具 { "id": 1, "name": "Table Lamp", "isOn": true, "brightness": 100, "hex": "FF0000" }
🔴助手 灯现已打开

提示

虽然可以直接调用插件函数,但不建议这样做,因为 AI 应该是决定调用哪些函数的函数。 如果需要显式控制调用哪些函数,请考虑在代码库中使用标准方法,而不是插件。