Delen via


Wat is een invoegtoepassing?

Invoegtoepassingen zijn een belangrijk onderdeel van Semantische kernel. Als u al invoegtoepassingen van ChatGPT- of Copilot-extensies in Microsoft 365 hebt gebruikt, bent u er al bekend mee. Met invoegtoepassingen kunt u uw bestaande API's inkapselen in een verzameling die kan worden gebruikt door een AI. Hierdoor kunt u uw AI de mogelijkheid geven om acties uit te voeren die anders niet kunnen worden uitgevoerd.

Achter de schermen maakt Semantische kernel gebruik van functie-aanroepen, een systeemeigen functie van de meeste van de nieuwste LLM's om LLM's toe te staan, planning uit te voeren en uw API's aan te roepen. Met functieoproepen kunnen LLM's een bepaalde functie aanvragen (bijvoorbeeld aanroepen). Semantische kernel geeft vervolgens de aanvraag door aan de juiste functie in uw codebasis en retourneert de resultaten terug naar de LLM, zodat de LLM een definitief antwoord kan genereren.

Semantische kernelinvoegtoepassing

Niet alle AI SDK's hebben een analoog concept voor plug-ins (de meeste hebben alleen functies of hulpprogramma's). In bedrijfsscenario's zijn invoegtoepassingen echter waardevol omdat ze een set functionaliteit inkapselen die weerspiegelt hoe zakelijke ontwikkelaars al services en API's ontwikkelen. Plug-ins spelen ook mooi met afhankelijkheidsinjectie. Binnen de constructor van een invoegtoepassing kunt u services injecteren die nodig zijn om het werk van de invoegtoepassing uit te voeren (bijvoorbeeld databaseverbindingen, HTTP-clients, enzovoort). Dit is moeilijk te bereiken met andere SDK's die geen invoegtoepassingen hebben.

Anatomie van een invoegtoepassing

Op hoog niveau is een invoegtoepassing een groep functies die kunnen worden blootgesteld aan AI-apps en -services. De functies binnen invoegtoepassingen kunnen vervolgens worden ingedeeld door een AI-toepassing om gebruikersaanvragen uit te voeren. Binnen Semantische kernel kunt u deze functies automatisch aanroepen met functie-aanroepen.

Notitie

In andere platforms worden functies vaak aangeduid als 'hulpprogramma's' of 'acties'. In Semantische kernel gebruiken we de term 'functies' omdat ze doorgaans worden gedefinieerd als systeemeigen functies in uw codebasis.

Het bieden van functies is echter niet voldoende om een invoegtoepassing te maken. Om automatische indeling met functie-aanroepen mogelijk te maken, moeten plug-ins ook details opgeven die semantisch beschrijven hoe ze zich gedragen. Alles van de invoer, uitvoer en bijwerkingen van de functie moet worden beschreven op een manier die de AI kan begrijpen, anders roept de AI de functie niet correct aan.

De voorbeeldinvoegtoepassing WriterPlugin aan de rechterkant heeft bijvoorbeeld functies met semantische beschrijvingen die beschrijven wat elke functie doet. Een LLM kan deze beschrijvingen vervolgens gebruiken om de beste functies te kiezen die moeten worden aangeroepen om te voldoen aan het verzoek van een gebruiker.

In de afbeelding aan de rechterkant zou een LLM waarschijnlijk de ShortPoem en StoryGen functies aanroepen om te voldoen aan de vragen van de gebruikers aan de hand van de opgegeven semantische beschrijvingen.

Semantische beschrijving in de WriterPlugin-invoegtoepassing

Verschillende typen invoegtoepassingen importeren

Er zijn twee primaire manieren om invoegtoepassingen te importeren in Semantische kernel: systeemeigen code gebruiken of een OpenAPI-specificatie gebruiken. Met het vorige programma kunt u invoegtoepassingen maken in uw bestaande codebasis die gebruikmaken van afhankelijkheden en services die u al hebt. Met deze laatste kunt u invoegtoepassingen importeren uit een OpenAPI-specificatie, die kan worden gedeeld in verschillende programmeertalen en platforms.

Hieronder geven we een eenvoudig voorbeeld van het importeren en gebruiken van een systeemeigen invoegtoepassing. Raadpleeg de volgende artikelen voor meer informatie over het importeren van deze verschillende typen invoegtoepassingen:

Tip

Wanneer u aan de slag gaat, raden we u aan systeemeigen code-invoegtoepassingen te gebruiken. Naarmate uw toepassing verder wordt ontwikkeld en als u in meerdere platformoverschrijdende teams werkt, kunt u overwegen om OpenAPI-specificaties te gebruiken om invoegtoepassingen te delen in verschillende programmeertalen en platforms.

De verschillende typen invoegtoepassingsfuncties

Binnen een invoegtoepassing hebt u doorgaans twee verschillende typen functies, die gegevens ophalen voor het ophalen van augmented generation (RAG) en die taken automatiseren. Hoewel elk type functioneel hetzelfde is, worden ze meestal anders gebruikt binnen toepassingen die gebruikmaken van Semantische kernel.

Met ophaalfuncties kunt u bijvoorbeeld strategieën gebruiken om de prestaties te verbeteren (bijvoorbeeld caching en het gebruik van goedkopere tussenliggende modellen voor samenvatting). Terwijl u met taakautomatiseringsfuncties waarschijnlijk human-in-the-loop goedkeuringsprocessen wilt implementeren om ervoor te zorgen dat taken correct worden uitgevoerd.

Raadpleeg de volgende artikelen voor meer informatie over de verschillende typen invoegtoepassingsfuncties:

Aan de slag met invoegtoepassingen

Het gebruik van invoegtoepassingen in Semantic Kernel is altijd een proces van drie stappen:

  1. Uw invoegtoepassing definiëren
  2. De invoegtoepassing toevoegen aan uw kernel
  3. En roep vervolgens de functies van de invoegtoepassing aan in een prompt met functie-aanroepen

Hieronder vindt u een voorbeeld op hoog niveau van het gebruik van een invoegtoepassing in Semantic Kernel. Raadpleeg de bovenstaande koppelingen voor meer gedetailleerde informatie over het maken en gebruiken van invoegtoepassingen.

1) Definieer uw invoegtoepassing

De eenvoudigste manier om een invoegtoepassing te maken, is door een klasse te definiëren en aantekeningen te maken bij de methoden met het KernelFunction kenmerk. Dit laat semantische kernel weten dat dit een functie is die kan worden aangeroepen door een AI of waarnaar wordt verwezen in een prompt.

U kunt ook invoegtoepassingen importeren uit een OpenAPI-specificatie.

Hieronder maken we een plugin die de status van lichten kan ophalen en de status ervan kan wijzigen.

Tip

Omdat de meeste LLM zijn getraind met Python voor functie-aanroepen, is het raadzaam om snake case te gebruiken voor functienamen en eigenschapsnamen, zelfs als u de C# of Java SDK gebruikt.

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

U ziet dat we beschrijvingen bieden voor de functie, retourwaarde en parameters. Dit is belangrijk voor de AI om te begrijpen wat de functie doet en hoe u deze kunt gebruiken.

Tip

Wees niet bang om gedetailleerde beschrijvingen voor uw functies te bieden als een AI problemen ondervindt bij het aanroepen ervan. Enkele voorbeelden, aanbevelingen voor het gebruik van de functie (en niet) en richtlijnen voor het ophalen van vereiste parameters kunnen allemaal nuttig zijn.

2) Voeg de invoegtoepassing toe aan uw kernel

Zodra u uw invoegtoepassing hebt gedefinieerd, kunt u deze toevoegen aan uw kernel door een nieuw exemplaar van de invoegtoepassing te maken en deze toe te voegen aan de verzameling van de invoegtoepassing van de kernel.

In dit voorbeeld ziet u de eenvoudigste manier om een klasse als invoegtoepassing toe te voegen met de AddFromType methode. Raadpleeg het artikel over het toevoegen van systeemeigen invoegtoepassingen voor meer informatie over andere manieren om invoegtoepassingen toe te voegen.

var builder = new KernelBuilder();
builder.Plugins.AddFromType<LightsPlugin>("Lights")
Kernel kernel = builder.Build();
kernel = Kernel()
kernel.add_plugin(
   LightsPlugin(),
   plugin_name="Lights",
)

3) Roep de functies van de invoegtoepassing aan

Ten slotte kunt u de AI de functies van uw invoegtoepassing laten aanroepen met behulp van functie-aanroepen. Hieronder ziet u een voorbeeld waarin wordt gedemonstreert hoe u de AI kunt gebruiken om de get_lights functie aan te roepen vanuit de Lights invoegtoepassing voordat u de change_state functie aanroept om een licht in te schakelen.

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())

Met de bovenstaande code krijgt u een antwoord dat er als volgt uitziet:

Role Bericht
🔵Gebruiker Schakel de lamp in
🔴Assistent (functieoproep) 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 (functieoproep) Lights.change_state(1, { "isOn": true })
🟢Tool { "id": 1, "name": "Table Lamp", "isOn": true, "brightness": 100, "hex": "FF0000" }
🔴Assistent De lamp is nu ingeschakeld

Tip

Hoewel u een invoegtoepassingsfunctie rechtstreeks kunt aanroepen, wordt dit niet aanbevolen omdat de AI degene moet zijn die bepaalt welke functies moeten worden aangeroepen. Als u expliciete controle nodig hebt over welke functies worden aangeroepen, kunt u overwegen om standaardmethoden in uw codebase te gebruiken in plaats van invoegtoepassingen.