Apa itu Plugin?
Plugin adalah komponen kunci dari Semantic Kernel. Jika Anda telah menggunakan plugin dari ekstensi ChatGPT atau Copilot di Microsoft 365, Anda sudah terbiasa dengannya. Dengan plugin, Anda dapat merangkum API yang ada ke dalam koleksi yang dapat digunakan oleh AI. Ini memungkinkan Anda untuk memberi AI Anda kemampuan untuk melakukan tindakan yang tidak akan dapat dilakukan sebaliknya.
Di balik layar, Semantic Kernel memanfaatkan pemanggilan fungsi, fitur asli dari sebagian besar LLM terbaru untuk memungkinkan LLM, melakukan perencanaan dan memanggil API Anda. Dengan panggilan fungsi, LLM dapat meminta (yaitu, memanggil) fungsi tertentu. Semantic Kernel kemudian melakukan marsekal permintaan ke fungsi yang sesuai di basis kode Anda dan mengembalikan hasilnya kembali ke LLM sehingga LLM dapat menghasilkan respons akhir.
Tidak semua SDK AI memiliki konsep analog untuk plugin (sebagian besar hanya memiliki fungsi atau alat). Namun, dalam skenario perusahaan, plugin sangat berharga karena merangkum serangkaian fungsionalitas yang mencerminkan bagaimana pengembang perusahaan sudah mengembangkan layanan dan API. Plugin juga bermain dengan baik dengan injeksi dependensi. Dalam konstruktor plugin, Anda dapat menyuntikkan layanan yang diperlukan untuk melakukan pekerjaan plugin (misalnya, koneksi database, klien HTTP, dll.). Ini sulit dicapai dengan SDK lain yang tidak memiliki plugin.
Anatomi plugin
Pada tingkat tinggi, plugin adalah sekelompok fungsi yang dapat diekspos ke aplikasi dan layanan AI. Fungsi dalam plugin kemudian dapat diorkestrasi oleh aplikasi AI untuk mencapai permintaan pengguna. Dalam Semantic Kernel, Anda dapat memanggil fungsi-fungsi ini secara otomatis dengan panggilan fungsi.
Catatan
Di platform lain, fungsi sering disebut sebagai "alat" atau "tindakan". Di Kernel Semantik, kami menggunakan istilah "fungsi" karena biasanya didefinisikan sebagai fungsi asli di basis kode Anda.
Namun, hanya menyediakan fungsi, tidak cukup untuk membuat plugin. Untuk mendukung orkestrasi otomatis dengan panggilan fungsi, plugin juga perlu memberikan detail yang secara semantik menjelaskan bagaimana mereka berperilaku. Segala sesuatu dari input, output, dan efek samping fungsi perlu dijelaskan dengan cara yang dapat dipahami AI, jika tidak, AI tidak akan memanggil fungsi dengan benar.
Misalnya, plugin sampel WriterPlugin
di sebelah kanan memiliki fungsi dengan deskripsi semantik yang menjelaskan apa yang dilakukan setiap fungsi. LLM kemudian dapat menggunakan deskripsi ini untuk memilih fungsi terbaik yang akan dipanggil untuk memenuhi permintaan pengguna.
Dalam gambar di sebelah kanan, LLM kemungkinan akan memanggil ShortPoem
fungsi dan StoryGen
untuk memenuhi permintaan pengguna berkat deskripsi semantik yang disediakan.
Mengimpor berbagai jenis plugin
Ada dua cara utama untuk mengimpor plugin ke Kernel Semantik: menggunakan kode asli atau menggunakan spesifikasi OpenAPI. Yang pertama memungkinkan Anda untuk menulis plugin di basis kode yang ada yang dapat memanfaatkan dependensi dan layanan yang sudah Anda miliki. Yang terakhir memungkinkan Anda mengimpor plugin dari spesifikasi OpenAPI, yang dapat dibagikan di berbagai bahasa dan platform pemrograman.
Di bawah ini kami memberikan contoh sederhana mengimpor dan menggunakan plugin asli. Untuk mempelajari selengkapnya tentang cara mengimpor berbagai jenis plugin ini, lihat artikel berikut:
Tip
Saat memulai, sebaiknya gunakan plugin kode asli. Saat aplikasi Anda matang, dan saat Anda bekerja di seluruh tim lintas platform, Anda mungkin ingin mempertimbangkan untuk menggunakan spesifikasi OpenAPI untuk berbagi plugin di berbagai bahasa dan platform pemrograman.
Berbagai jenis fungsi plugin
Dalam plugin, Anda biasanya akan memiliki dua jenis fungsi yang berbeda, yang mengambil data untuk pengambilan augmented generation (RAG) dan yang mengotomatiskan tugas. Meskipun setiap jenis secara fungsional sama, mereka biasanya digunakan secara berbeda dalam aplikasi yang menggunakan Semantic Kernel.
Misalnya, dengan fungsi pengambilan, Anda mungkin ingin menggunakan strategi untuk meningkatkan performa (misalnya, penembolokan dan menggunakan model perantara yang lebih murah untuk ringkasan). Sedangkan dengan fungsi otomatisasi tugas, Anda mungkin ingin menerapkan proses persetujuan human-in-the-loop untuk memastikan bahwa tugas diselesaikan dengan benar.
Untuk mempelajari selengkapnya tentang berbagai jenis fungsi plugin, lihat artikel berikut:
Mulai menggunakan plugin
Menggunakan plugin dalam Semantic Kernel selalu merupakan proses tiga langkah:
- Tentukan plugin Anda
- Menambahkan plugin ke kernel Anda
- Dan kemudian panggil fungsi plugin dalam perintah dengan panggilan fungsi
Di bawah ini kami akan memberikan contoh tingkat tinggi tentang cara menggunakan plugin dalam Kernel Semantik. Lihat tautan di atas untuk informasi lebih rinci tentang cara membuat dan menggunakan plugin.
1) Tentukan plugin Anda
Cara term mudah untuk membuat plugin adalah dengan mendefinisikan kelas dan menganotasi metodenya dengan KernelFunction
atribut . Ini mari kita Semantic Kernel tahu bahwa ini adalah fungsi yang dapat dipanggil oleh AI atau direferensikan dalam perintah.
Anda juga dapat mengimpor plugin dari spesifikasi OpenAPI.
Di bawah ini, kita akan membuat plugin yang dapat mengambil status lampu dan mengubah keadaannya.
Tip
Karena sebagian besar LLM telah dilatih dengan Python untuk panggilan fungsi, disarankan untuk menggunakan kasus ular untuk nama fungsi dan nama properti bahkan jika Anda menggunakan C# atau 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);
}
}
Perhatikan bahwa kami memberikan deskripsi untuk fungsi, nilai pengembalian, dan parameter. Ini penting bagi AI untuk memahami apa yang dilakukan fungsi dan cara menggunakannya.
Tip
Jangan takut untuk memberikan deskripsi terperinci untuk fungsi Anda jika AI mengalami masalah saat memanggilnya. Beberapa contoh bidikan, rekomendasi kapan harus menggunakan (dan tidak menggunakan) fungsi, dan panduan tentang di mana mendapatkan parameter yang diperlukan semuanya dapat membantu.
2) Tambahkan plugin ke kernel Anda
Setelah menentukan plugin, Anda dapat menambahkannya ke kernel dengan membuat instans baru plugin dan menambahkannya ke koleksi plugin kernel.
Contoh ini menunjukkan cara termampukan untuk menambahkan kelas sebagai plugin dengan AddFromType
metode . Untuk mempelajari tentang cara lain menambahkan plugin, lihat artikel menambahkan plugin asli.
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) Memanggil fungsi plugin
Terakhir, Anda dapat meminta AI memanggil fungsi plugin Anda dengan menggunakan panggilan fungsi. Di bawah ini adalah contoh yang menunjukkan cara membujuk AI untuk memanggil get_lights
fungsi dari Lights
plugin sebelum memanggil change_state
fungsi untuk menyalakan lampu.
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));
Dengan kode di atas, Anda harus mendapatkan respons yang terlihat seperti berikut:
Peran | Pesan |
---|---|
🔵Pengguna | Nyalakan lampu |
🔴Asisten (panggilan fungsi) | Lights.get_lights() |
🟢Alat | [{ "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" }] |
🔴Asisten (panggilan fungsi) | Lights.change_state(1, { "isOn": true }) |
🟢Alat | { "id": 1, "name": "Table Lamp", "isOn": true, "brightness": 100, "hex": "FF0000" } |
🔴Asisten | Lampu sekarang menyala |
Tip
Meskipun Anda dapat memanggil fungsi plugin secara langsung, ini tidak disarankan karena AI harus menjadi yang memutuskan fungsi mana yang akan dipanggil. Jika Anda memerlukan kontrol eksplisit atas fungsi mana yang dipanggil, pertimbangkan untuk menggunakan metode standar di basis kode Anda alih-alih plugin.