プラグインとは
プラグインはセマンティック カーネルの重要なコンポーネントです。 Microsoft 365 で ChatGPT または Copilot 拡張機能のプラグインを既に使用している場合は、既に使い慣れていると思います。 プラグインを使用すると、AI で使用できるコレクションに既存の API をカプセル化できます。 これにより、それ以外では実行できないアクションを実行する機能を AI に提供できます。
セマンティック カーネルはバックグラウンドで、最新の LLM のほとんどのネイティブ機能である function 呼び出しを利用して、LLM を許可し、 計画を実行し API を呼び出します。 関数呼び出しでは、LLM は特定の関数を要求 (つまり呼び出し) できます。 その後、セマンティック カーネルは、コードベース内の適切な関数に要求をマーシャリングし、LLM が最終的な応答を生成できるように結果を LLM に返します。
すべての AI SDK がプラグインに似た概念を持っているわけではありません (ほとんどの場合、関数やツールを持っているだけです)。 ただし、エンタープライズ シナリオでは、プラグインは、エンタープライズ開発者が既にサービスと API を開発する方法を反映する一連の機能をカプセル化しているため、価値があります。 プラグインは依存関係の挿入でもうまく機能します。 プラグインのコンストラクター内では、プラグインの処理を実行するために必要なサービス (データベース接続、HTTP クライアントなど) を挿入できます。 これは、プラグインがない他の SDK では実現が困難です。
プラグインの構造
大まかに言えば、プラグインは、AI アプリやサービスに公開できる 機能 のグループです。 プラグイン内の関数は、AI アプリケーションによって調整され、ユーザー要求を実現できます。 セマンティック カーネル内では、関数呼び出しでこれらの関数を自動的に呼び出すことができます。
Note
他のプラットフォームでは、関数は多くの場合、"ツール" または "アクション" と呼ばれます。 セマンティック カーネルでは、通常はコードベースでネイティブ関数として定義されるため、"functions" という用語を使用します。
ただし、関数を提供するだけでは、プラグインを作成するだけでは不十分です。 関数呼び出しによる自動オーケストレーションを行うために、プラグインは、その動作を意味的に記述する詳細も提供する必要があります。 関数の入力、出力、および副作用のすべてが、AI が理解できる方法で記述する必要があります。そうしないと、AI は関数を正しく呼び出しません。
たとえば、右側のサンプル WriterPlugin
プラグインには、各関数の動作を記述するセマンティック記述を含む関数があります。 LLM では、これらの説明を使用して、ユーザーの質問を満たすために呼び出す最適な関数を選択できます。
右側の図では、LLM は、 ShortPoem
関数と StoryGen
関数を呼び出して、提供されたセマンティックの説明のおかげでユーザーの要求を満たす可能性があります。
さまざまな種類のプラグインをインポートする
セマンティック カーネルにプラグインをインポートする主な方法は 2 つあります。コードを使用するかOpenAPI 仕様を使用します。 前者では、既存のコードベースでプラグインを作成し、既に持っている依存関係とサービスを活用できます。 後者では、異なるプログラミング言語とプラットフォーム間で共有できる OpenAPI 仕様からプラグインをインポートできます。
次に、ネイティブ プラグインのインポートと使用の簡単な例を示します。 これらのさまざまな種類のプラグインをインポートする方法の詳細については、次の記事を参照してください。
ヒント
作業を開始するときは、ネイティブ コード プラグインを使用することをお勧めします。 アプリケーションが成熟し、クロスプラットフォーム チーム間で作業する際に、OpenAPI 仕様を使用して、さまざまなプログラミング言語とプラットフォーム間でプラグインを共有することを検討できます。
さまざまな種類のプラグイン関数
プラグイン内には、通常、拡張生成 (RAG) を取得するためのデータを取得する関数とタスクを自動化する関数の 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
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 でプラグインの関数を呼び出すことができます。 次の例は、change_state
関数を呼び出してライトをオンにする前に、Lights
プラグインから get_lights
関数を呼び出すように AI を同調する方法を示しています。
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())
// 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));
上記のコードでは、次のような応答が返されます。
ロール | メッセージ |
---|---|
🔵User | ランプをオンにしてください |
🔴アシスタント (関数呼び出し) | 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 である必要があるため、これはお勧めしません。 呼び出される関数を明示的に制御する必要がある場合は、プラグインではなくコードベースで標準メソッドを使用することを検討してください。