什麼是外掛程式?
外掛程式是語意核心的重要元件。 如果您已在 Microsoft 365 中使用來自 ChatGPT 或 Copilot 延伸模組的外掛程式,您已經熟悉它們。 透過外掛程式,您可以將現有的 API 封裝到 AI 可以使用的集合中。 這可讓您讓 AI 能夠執行無法執行的動作。
在幕後,Semantic Kernel 會 利用函式呼叫,這是大部分最新 LLM 的原生功能,可讓 LLM 執行 規劃和 叫用 API。 使用函式呼叫,LLM 可以要求特定函式(亦即呼叫)。 語意核心接著會將要求封送處理至程式代碼基底中的適當函式,並將結果傳回 LLM,以便 LLM 產生最終回應。
並非所有 AI SDK 都有與外掛程式類似的概念(大部分只有函式或工具)。 不過,在企業案例中,外掛程式是有價值的,因為它們封裝了一組功能,以反映企業開發人員如何開發服務和 API。 外掛程式也會很好地搭配相依性插入播放。 在外掛程式的建構函式內,您可以插入執行外掛程式工作所需的服務(例如資料庫連線、HTTP 用戶端等)。 這很難與其他缺少外掛程式的 SDK 一起完成。
外掛程式的結構
概括而言,外掛程式是一組 可公開給 AI 應用程式和服務的函 式。 然後,AI 應用程式可以協調外掛程式內的函式,以完成使用者要求。 在 Semantic Kernel 中,您可以使用函數呼叫自動叫用這些函式。
注意
在其他平臺上,函式通常稱為「工具」或「動作」。 在 Semantic Kernel 中,我們使用「函式」一詞,因為它們通常定義為程式代碼基底中的原生函式。
不過,只要提供函式就不足以製作外掛程式。 若要使用函式呼叫來提供自動協調流程,外掛程式也必須提供語意描述其行為的詳細數據。 從函式的輸入、輸出和副作用中,必須以 AI 可以理解的方式描述所有項目,否則 AI 將無法正確呼叫函式。
例如,右側的範例 WriterPlugin
外掛程式具有語意描述的函式,可描述每個函式的功能。 然後,LLM 可以使用這些描述來選擇要呼叫的最佳函式,以履行使用者的要求。
在右側的圖片中,LLM 可能會呼叫 ShortPoem
和 StoryGen
函式,以滿足使用者要求感謝提供的語意描述。
匯入不同類型的外掛程式
將外掛程式匯入語意核心有兩種主要方式:使用 機器碼 或使用 OpenAPI 規格。 前者可讓您在現有的程式代碼基底中撰寫外掛程式,以利用您已經擁有的相依性和服務。 後者可讓您從 OpenAPI 規格匯入外掛程式,此規格可以跨不同的程式設計語言和平台共用。
以下提供匯入和使用原生外掛程式的簡單範例。 若要深入瞭解如何匯入這些不同類型的外掛程式,請參閱下列文章:
提示
開始使用時,建議您使用原生程式碼外掛程式。 隨著應用程式成熟,而且當您跨跨平臺小組工作時,您可能想要考慮使用OpenAPI規格,跨不同的程式設計語言和平臺共用外掛程式。
不同類型的外掛程式函式
在外掛程式內,您通常會有兩種不同類型的函式,這些函式會擷取用於擷取增強產生的數據(RAG),以及自動執行工作的函式。 雖然每個類型在功能上都相同,但通常會在使用 Semantic Kernel 的應用程式內以不同的方式使用它們。
例如,使用擷取函式時,您可能想要使用策略來改善效能(例如快取和使用更便宜的中繼模型進行摘要)。 使用工作自動化功能時,您可能會想要實作人工迴圈核准程式,以確保工作已正確完成。
若要深入瞭解不同類型的外掛程式函式,請參閱下列文章:
開始使用外掛程式
在 Semantic Kernel 中使用外掛程式一律是三個步驟的程式:
下面我們將提供如何在語意核心中使用外掛程式的高階範例。 如需如何建立和使用外掛程式的詳細資訊,請參閱上述連結。
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 TypedDict, Annotated
class LightModel(TypedDict):
id: int
name: str
is_on: bool | None
brightness: int | None
hex: str | None
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
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
async def get_state(
self,
id: Annotated[int, "The ID of the light"]
) -> Annotated[LightModel | None], "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
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
public class LightsPlugin {
// Mock data for the lights
private final Map<Integer, LightModel> lights = new HashMap<>();
public LightsPlugin() {
lights.put(1, new LightModel(1, "Table Lamp", false));
lights.put(2, new LightModel(2, "Porch light", false));
lights.put(3, new LightModel(3, "Chandelier", true));
}
@DefineKernelFunction(name = "get_lights", description = "Gets a list of lights and their current state")
public List<LightModel> getLights() {
System.out.println("Getting lights");
return new ArrayList<>(lights.values());
}
@DefineKernelFunction(name = "change_state", description = "Changes the state of the light")
public LightModel changeState(
@KernelFunctionParameter(name = "id", description = "The ID of the light to change") int id,
@KernelFunctionParameter(name = "isOn", description = "The new state of the light") boolean isOn) {
System.out.println("Changing light " + id + " " + isOn);
if (!lights.containsKey(id)) {
throw new IllegalArgumentException("Light not found");
}
lights.get(id).setIsOn(isOn);
return lights.get(id);
}
}
請注意,我們提供函式、傳回值和參數的描述。 對於 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",
)
// Import the LightsPlugin
KernelPlugin lightPlugin = KernelPluginFactory.createFromObject(new LightsPlugin(),
"LightsPlugin");
// Create a kernel with Azure OpenAI chat completion and plugin
Kernel kernel = Kernel.builder()
.withAIService(ChatCompletionService.class, chatCompletionService)
.withPlugin(lightPlugin)
.build();
3) 叫用外掛程式的函式
最後,您可以使用函式呼叫,讓 AI 叫用外掛程式的函式。 以下範例示範如何在呼叫函式以開啟燈光之前,先將 AI 同軸處理以從Lights
外掛程式呼叫get_lights
change_state
函式。
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()
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};
// 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
chat_completion = AzureChatCompletion(
deployment_name="your_models_deployment_name",
api_key="your_api_key",
base_url="your_base_url",
)
kernel.add_service(chat_completion)
# Add a plugin (the LightsPlugin class is defined below)
kernel.add_plugin(
LightsPlugin(),
plugin_name="Lights",
)
# 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_content(
chat_history=history,
settings=execution_settings,
kernel=kernel,
)
# 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())
// Enable planning
InvocationContext invocationContext = new InvocationContext.Builder()
.withReturnMode(InvocationReturnMode.LAST_MESSAGE_ONLY)
.withToolCallBehavior(ToolCallBehavior.allowAllKernelFunctions(true))
.build();
// Create a history to store the conversation
ChatHistory history = new ChatHistory();
history.addUserMessage("Turn on light 2");
List<ChatMessageContent<?>> results = chatCompletionService
.getChatMessageContentsAsync(history, kernel, invocationContext)
.block();
System.out.println("Assistant > " + results.get(0));
使用上述程式代碼,您應該會收到如下所示的回應:
角色 | 訊息 |
---|---|
🔵使用者 | 請開啟燈 |
🔴小幫手(函數調用) | 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 應該是決定要呼叫哪些函式的函式。 如果您需要明確控制呼叫哪些函式,請考慮在程式代碼基底中使用標準方法,而不是外掛程式。