Vad är ett plugin-program?
Plugin-program är en viktig komponent i semantisk kernel. Om du redan har använt plugin-program från ChatGPT- eller Copilot-tillägg i Microsoft 365 är du redan bekant med dem. Med plugin-program kan du kapsla in dina befintliga API:er i en samling som kan användas av en AI. På så sätt kan du ge din AI möjlighet att utföra åtgärder som den inte skulle kunna göra annars.
I bakgrunden använder Semantic Kernel funktionsanrop, en inbyggd funktion i de flesta av de senaste LLM:erna för att tillåta LLM:er, för att utföra planering och anropa dina API:er. Med funktionsanrop kan LLM:er begära (d.v.s. anropa) en viss funktion. Semantisk kernel konverterar sedan begäran till lämplig funktion i din kodbas och returnerar resultatet tillbaka till LLM så att LLM kan generera ett slutligt svar.
Alla AI-SDK:er har inte ett liknande begrepp som plugin-program (de flesta har bara funktioner eller verktyg). I företagsscenarier är dock plugin-program värdefulla eftersom de kapslar in en uppsättning funktioner som speglar hur företagsutvecklare redan utvecklar tjänster och API:er. Plugin-program spelar också bra med beroendeinmatning. I en plugin-konstruktor kan du mata in tjänster som är nödvändiga för att utföra plugin-programmets arbete (t.ex. databasanslutningar, HTTP-klienter osv.). Detta är svårt att åstadkomma med andra SDK:er som saknar plugin-program.
Anatomi för ett plugin-program
På hög nivå är ett plugin-program en grupp funktioner som kan exponeras för AI-appar och -tjänster. Funktionerna i plugin-program kan sedan orkestreras av ett AI-program för att utföra användarbegäranden. I semantisk kernel kan du anropa dessa funktioner automatiskt med funktionsanrop.
Kommentar
På andra plattformar kallas funktioner ofta för "verktyg" eller "åtgärder". I Semantic Kernel använder vi termen "functions" eftersom de vanligtvis definieras som inbyggda funktioner i din kodbas.
Att bara tillhandahålla funktioner räcker dock inte för att skapa ett plugin-program. För att kunna utföra automatisk orkestrering med funktionsanrop måste plugin-program också tillhandahålla information som semantiskt beskriver hur de beter sig. Allt från funktionens indata, utdata och biverkningar måste beskrivas på ett sätt som AI:n kan förstå, annars anropar AI:n inte funktionen korrekt.
Exempel-plugin-programmet WriterPlugin
till höger har till exempel funktioner med semantiska beskrivningar som beskriver vad varje funktion gör. En LLM kan sedan använda dessa beskrivningar för att välja de bästa funktionerna att anropa för att uppfylla en användares fråga.
På bilden till höger skulle en LLM sannolikt anropa ShortPoem
funktionerna och StoryGen
för att tillfredsställa användarnas fråga tack vare de angivna semantiska beskrivningarna.
Importera olika typer av plugin-program
Det finns två huvudsakliga sätt att importera plugin-program till semantisk kernel: använda inbyggd kod eller använda en OpenAPI-specifikation. Med det förra kan du skapa plugin-program i din befintliga kodbas som kan utnyttja beroenden och tjänster som du redan har. Med det senare kan du importera plugin-program från en OpenAPI-specifikation som kan delas mellan olika programmeringsspråk och plattformar.
Nedan visas ett enkelt exempel på hur du importerar och använder ett inbyggt plugin-program. Mer information om hur du importerar dessa olika typer av plugin-program finns i följande artiklar:
Dricks
När du kommer igång rekommenderar vi att du använder inbyggda kod-plugin-program. Allt eftersom ditt program mognar, och när du arbetar mellan plattformsoberoende team, kanske du vill överväga att använda OpenAPI-specifikationer för att dela plugin-program mellan olika programmeringsspråk och plattformar.
De olika typerna av plugin-funktioner
I ett plugin-program har du vanligtvis två olika typer av funktioner, de som hämtar data för hämtning av utökad generering (RAG) och de som automatiserar uppgifter. Även om varje typ fungerar likadant används de vanligtvis på olika sätt i program som använder semantisk kernel.
Med hämtningsfunktioner kanske du till exempel vill använda strategier för att förbättra prestanda (t.ex. cachelagring och användning av billigare mellanliggande modeller för sammanfattning). Med funktioner för uppgiftsautomatisering vill du förmodligen implementera processerna för godkännande av människor i loopen för att säkerställa att uppgifterna slutförs korrekt.
Mer information om de olika typerna av plugin-funktioner finns i följande artiklar:
Komma igång med plugin-program
Att använda plugin-program i semantisk kernel är alltid en process i tre steg:
- Definiera plugin-programmet
- Lägg till plugin-programmet i kerneln
- Och anropa sedan antingen plugin-programmets funktioner i antingen en prompt med funktionsanrop
Nedan visas ett exempel på hur du använder ett plugin-program i semantisk kernel. Mer detaljerad information om hur du skapar och använder plugin-program finns i länkarna ovan.
1) Definiera plugin-programmet
Det enklaste sättet att skapa ett plugin-program är att definiera en klass och kommentera dess metoder med attributet KernelFunction
. Nu ska vi semantisk kernel veta att det här är en funktion som kan anropas av en AI eller refereras i en prompt.
Du kan också importera plugin-program från en OpenAPI-specifikation.
Nedan skapar vi ett plugin-program som kan hämta lampornas tillstånd och ändra dess tillstånd.
Dricks
Eftersom de flesta LLM har tränats med Python för funktionsanrop rekommenderar vi att du använder ormfall för funktionsnamn och egenskapsnamn även om du använder C# eller 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);
}
}
Observera att vi tillhandahåller beskrivningar för funktionen, returvärdet och parametrarna. Det här är viktigt för AI:n att förstå vad funktionen gör och hur den ska användas.
Dricks
Var inte rädd för att ange detaljerade beskrivningar för dina funktioner om en AI har problem med att anropa dem. Få exempel, rekommendationer för när du ska använda (och inte använda) funktionen och vägledning om var du kan hämta obligatoriska parametrar kan vara till hjälp.
2) Lägg till plugin-programmet i din kernel
När du har definierat plugin-programmet kan du lägga till det i kerneln genom att skapa en ny instans av plugin-programmet och lägga till det i kernelns plugin-samling.
Det här exemplet visar det enklaste sättet att lägga till en klass som ett plugin-program med AddFromType
metoden. Mer information om andra sätt att lägga till plugin-program finns i artikeln om att lägga till inbyggda plugin-program .
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) Anropa plugin-programmets funktioner
Slutligen kan du låta AI:n anropa plugin-programmets funktioner med hjälp av funktionsanrop. Nedan visas ett exempel som visar hur du koaxar AI:n för att anropa get_lights
funktionen från Lights
plugin-programmet innan funktionen change_state
anropas för att aktivera en lampa.
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));
Med koden ovan bör du få ett svar som ser ut så här:
Roll | Meddelande |
---|---|
🔵Användare | Slå på lampan |
🔴Assistent (funktionsanrop) | Lights.get_lights() |
🟢Verktyg | [{ "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 (funktionsanrop) | Lights.change_state(1, { "isOn": true }) |
🟢Verktyg | { "id": 1, "name": "Table Lamp", "isOn": true, "brightness": 100, "hex": "FF0000" } |
🔴Assistent | Lampan är nu på |
Dricks
Du kan anropa en plugin-funktion direkt, men det rekommenderas inte eftersom AI:n bör vara den som bestämmer vilka funktioner som ska anropas. Om du behöver explicit kontroll över vilka funktioner som anropas bör du överväga att använda standardmetoder i din kodbas i stället för plugin-program.