Was ist ein Plug-In?
Plug-Ins sind eine Schlüsselkomponente des semantischen Kernels. Wenn Sie bereits Plug-Ins von ChatGPT- oder Copilot-Erweiterungen in Microsoft 365 verwendet haben, sind Sie bereits damit vertraut. Mit Plug-Ins können Sie Ihre vorhandenen APIs in eine Sammlung kapseln, die von einer KI verwendet werden kann. Auf diese Weise können Sie Ihrer KI Die Möglichkeit geben, Aktionen auszuführen, die andernfalls nicht möglich wären.
Hinter den Kulissen nutzt der semantische Kernel Funktionsaufrufe, ein systemeigenes Feature der neuesten LLMs, um LLMs zu ermöglichen, die Planung durchzuführen und Ihre APIs aufzurufen. Mit Funktionsaufrufen können LLMs eine bestimmte Funktion anfordern (d. h. eine bestimmte Funktion aufrufen). Der semantische Kernel marshallt dann die Anforderung an die entsprechende Funktion in Ihrer Codebasis und gibt die Ergebnisse zurück an die LLM zurück, damit die LLM eine endgültige Antwort generieren kann.
Nicht alle KI-SDKs haben ein analoges Konzept zu Plug-Ins (die meisten haben nur Funktionen oder Tools). In Unternehmensszenarien sind Plug-Ins jedoch hilfreich, da sie eine Reihe von Funktionen kapseln, die spiegeln, wie Unternehmensentwickler bereits Dienste und APIs entwickeln. Plugins spielen auch schön mit Abhängigkeitsinjektion. Innerhalb des Konstruktors eines Plug-Ins können Sie Dienste einfügen, die zum Ausführen der Arbeit des Plug-Ins erforderlich sind (z. B. Datenbankverbindungen, HTTP-Clients usw.). Dies ist schwierig mit anderen SDKs zu erreichen, die keine Plug-Ins haben.
Anatomie eines Plug-Ins
Auf hoher Ebene ist ein Plug-In eine Gruppe von Funktionen , die KI-Apps und -Diensten zur Verfügung stehen können. Die Funktionen in Plug-Ins können dann von einer KI-Anwendung orchestriert werden, um Benutzeranforderungen auszuführen. Innerhalb des semantischen Kernels können Sie diese Funktionen automatisch mit Funktionsaufrufen aufrufen.
Hinweis
In anderen Plattformen werden Funktionen häufig als "Tools" oder "Aktionen" bezeichnet. Im semantischen Kernel verwenden wir den Begriff "Funktionen", da sie in der Regel als systemeigene Funktionen in Ihrer Codebasis definiert sind.
Die Bereitstellung von Funktionen reicht jedoch nicht aus, um ein Plug-In zu erstellen. Um die automatische Orchestrierung mit Funktionsaufrufen zu aktivieren, müssen Plug-Ins auch Details angeben, die semantisch beschreiben, wie sie sich verhalten. Alles aus der Eingabe, Ausgabe und Nebenwirkungen der Funktion muss so beschrieben werden, dass die KI verstehen kann, andernfalls ruft die KI die Funktion nicht ordnungsgemäß auf.
Beispielsweise verfügt das Beispiel-Plug-In WriterPlugin
auf der rechten Seite über Funktionen mit semantischen Beschreibungen, die beschreiben, was jede Funktion tut. Ein LLM kann dann diese Beschreibungen verwenden, um die besten Funktionen auszuwählen, die aufgerufen werden sollen, um die Anfrage eines Benutzers zu erfüllen.
Auf der rechten Seite würde ein LLM wahrscheinlich die ShortPoem
Funktionen StoryGen
aufrufen, um den Benutzern dank der bereitgestellten semantischen Beschreibungen zu entsprechen.
Importieren verschiedener Arten von Plug-Ins
Es gibt zwei primäre Methoden zum Importieren von Plug-Ins in semantischen Kernel: Verwenden von systemeigenem Code oder Verwenden einer OpenAPI-Spezifikation. Der frühere ermöglicht Es Ihnen, Plug-Ins in Ihrer vorhandenen Codebasis zu erstellen, die Abhängigkeiten und Dienste nutzen kann, die Sie bereits haben. Letzteres ermöglicht es Ihnen, Plug-Ins aus einer OpenAPI-Spezifikation zu importieren, die für verschiedene Programmiersprachen und Plattformen freigegeben werden kann.
Nachfolgend finden Sie ein einfaches Beispiel für das Importieren und Verwenden eines nativen Plug-Ins. Weitere Informationen zum Importieren dieser verschiedenen Arten von Plug-Ins finden Sie in den folgenden Artikeln:
Tipp
Bei den ersten Schritten empfehlen wir die Verwendung nativer Code-Plug-Ins. Wenn Ihre Anwendung reift und während Sie plattformübergreifend arbeiten, sollten Sie die Verwendung von OpenAPI-Spezifikationen in Betracht ziehen, um Plug-Ins in verschiedenen Programmiersprachen und Plattformen freizugeben.
Die verschiedenen Arten von Plug-In-Funktionen
Innerhalb eines Plug-Ins verfügen Sie in der Regel über zwei unterschiedliche Funktionstypen, die Daten für das Abrufen einer erweiterten Generation (RAG) und solche abrufen, die Aufgaben automatisieren. Obwohl jeder Typ funktional gleich ist, werden sie in der Regel in Anwendungen unterschiedlich verwendet, die semantischen Kernel verwenden.
Mit Abruffunktionen können Sie z. B. Strategien verwenden, um die Leistung zu verbessern (z. B. Zwischenspeichern und Verwenden günstigerer Zwischenmodelle für die Zusammenfassung). Während Sie mit Aufgabenautomatisierungs-Funktionen wahrscheinlich Die Genehmigungsprozesse von Menschen in der Schleife implementieren möchten, um sicherzustellen, dass Aufgaben ordnungsgemäß ausgeführt werden.
Weitere Informationen zu den verschiedenen Arten von Plug-In-Funktionen finden Sie in den folgenden Artikeln:
Erste Schritte mit Plug-Ins
Die Verwendung von Plug-Ins innerhalb des semantischen Kernels ist immer ein dreistufiger Prozess:
- Definieren Ihres Plug-Ins
- Hinzufügen des Plug-Ins zu Ihrem Kernel
- Und rufen Sie dann entweder die Funktionen des Plug-Ins in einer Eingabeaufforderung mit Funktionsaufrufen auf.
Nachfolgend finden Sie ein allgemeines Beispiel für die Verwendung eines Plug-Ins innerhalb des semantischen Kernels. Ausführlichere Informationen zum Erstellen und Verwenden von Plug-Ins finden Sie unter den oben genannten Links.
1) Definieren Ihres Plug-Ins
Die einfachste Möglichkeit zum Erstellen eines Plug-Ins besteht darin, eine Klasse zu definieren und die Methoden mit dem KernelFunction
Attribut zu kommentieren. Dieser semantische Kernel weiß, dass dies eine Funktion ist, die von einer KI aufgerufen oder in einer Eingabeaufforderung referenziert werden kann.
Sie können Plug-Ins auch aus einer OpenAPI-Spezifikation importieren.
Unten erstellen wir ein Plug-In, das den Zustand der Lichter abrufen und seinen Zustand ändern kann.
Tipp
Da die meisten LLM mit Python zum Aufrufen von Funktionen trainiert wurden, empfiehlt es sich, Schlangenfall für Funktionsnamen und Eigenschaftsnamen zu verwenden, auch wenn Sie das C#- oder Java SDK verwenden.
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);
}
}
Beachten Sie, dass wir Beschreibungen für die Funktion, den Rückgabewert und die Parameter bereitstellen. Dies ist wichtig für die KI, um zu verstehen, was die Funktion tut und wie sie verwendet wird.
Tipp
Scheuen Sie sich nicht, detaillierte Beschreibungen für Ihre Funktionen bereitzustellen, wenn eine KI Probleme beim Aufrufen hat. Einige Beispiele, Empfehlungen für die Verwendung der Funktion (und nicht die Verwendung) und Anleitungen zum Abrufen erforderlicher Parameter können hilfreich sein.
2) Hinzufügen des Plug-Ins zu Ihrem Kernel
Nachdem Sie Ihr Plug-In definiert haben, können Sie es ihrem Kernel hinzufügen, indem Sie eine neue Instanz des Plug-Ins erstellen und der Plug-In-Sammlung des Kernels hinzufügen.
In diesem Beispiel wird die einfachste Methode zum Hinzufügen einer Klasse als Plug-In mit der AddFromType
Methode veranschaulicht. Weitere Informationen zum Hinzufügen von Plug-Ins finden Sie im Artikel zum Hinzufügen nativer Plug-Ins .
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) Aufrufen der Funktionen des Plug-Ins
Schließlich können Sie mithilfe von Funktionsaufrufen die Funktionen Ihres Plug-Ins aufrufen lassen. Im Folgenden finden Sie ein Beispiel, das veranschaulicht, wie die KI die Funktion aus dem Lights
Plug-In aufruftget_lights
, bevor die change_state
Funktion aufgerufen wird, um ein Licht zu aktivieren.
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_choice_behavior import FunctionChoiceBehavior
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()
execution_settings.function_call_behavior = FunctionChoiceBehavior.Auto()
# 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));
Mit dem obigen Code sollten Sie eine Antwort erhalten, die wie folgt aussieht:
Role | `Message` |
---|---|
🔵Benutzer | Bitte schalten Sie die Lampe ein |
🔴Assistent (Funktionsaufruf) | Lights.get_lights() |
🟢Tool | [{ "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" }] |
🔴Assistent (Funktionsaufruf) | Lights.change_state(1, { "isOn": true }) |
🟢Tool | { "id": 1, "name": "Table Lamp", "isOn": true, "brightness": 100, "hex": "FF0000" } |
🔴Assistent | Die Lampe ist jetzt eingeschaltet |
Tipp
Zwar können Sie eine Plug-In-Funktion direkt aufrufen, dies wird jedoch nicht empfohlen, da die KI die entscheidung, welche Funktionen aufgerufen werden sollen. Wenn Sie explizit steuern müssen, welche Funktionen aufgerufen werden, sollten Sie die Verwendung von Standardmethoden in Ihrer Codebasis anstelle von Plug-Ins in Betracht ziehen.