Megosztás a következőn keresztül:


Mi az a beépülő modul?

A beépülő modulok a Szemantic Kernel kulcsfontosságú összetevői. Ha már használt beépülő modulokat a ChatGPT vagy a Copilot bővítményekből a Microsoft 365-ben, már ismeri őket. Beépülő modulokkal a meglévő API-kat beágyazhatja egy AI által használható gyűjteménybe. Ez lehetővé teszi, hogy a mi-nek olyan műveleteket hajtson végre, amelyeket egyébként nem tudna elvégezni.

A háttérben a Szemantic Kernel függvényhívást használ, amely a legtöbb legújabb LLM natív funkciója, amely lehetővé teszi az LLM-eket, megtervezi és meghívja az API-kat. Függvényhívás esetén az LLM-ek kérhetnek (azaz meghívhatnak) egy adott függvényt. A Szemantic Kernel ezután a kódbázis megfelelő függvényének küldi el a kérést, és visszaadja az eredményeket az LLM-nek, hogy az LLM végleges választ generáljon.

Szemantikus kernel beépülő modul

Nem minden AI SDK-nak van hasonló fogalma a beépülő modulokhoz (legtöbben csak függvényekkel vagy eszközökkel rendelkeznek). A nagyvállalati forgatókönyvekben azonban a beépülő modulok értékesek, mivel olyan funkciókat foglalnak magukba, amelyek tükrözik, hogy a nagyvállalati fejlesztők hogyan fejlesztenek már szolgáltatásokat és API-kat. A beépülő modulok a függőséginjektálással is jól játszanak. A beépülő modul konstruktorán belül injektálhatja a beépülő modul működéséhez szükséges szolgáltatásokat (pl. adatbázis-kapcsolatok, HTTP-ügyfelek stb.). Ez nehezen valósítható meg más SDK-kkal, amelyek nem rendelkeznek beépülő modulokkal.

A beépülő modul anatómiája

Magas szinten a beépülő modul olyan függvények csoportja, amelyek ki lehetnek téve az AI-alkalmazásoknak és -szolgáltatásoknak. A beépülő modulokon belüli függvényeket ezután egy AI-alkalmazás vezényelheti a felhasználói kérések végrehajtásához. A Szemantic Kernelben ezeket a függvényeket automatikusan meghívhatja függvényhívással.

Feljegyzés

Más platformokon a függvényeket gyakran "eszközöknek" vagy "műveleteknek" is nevezik. A Szemantikus Kernelben a "függvények" kifejezést használjuk, mivel ezek általában natív függvényekként vannak definiálva a kódbázisban.

Csak a függvények biztosítása azonban nem elég egy beépülő modul létrehozásához. Az automatikus vezénylés funkcióhívással való használatához a beépülő moduloknak olyan részleteket is meg kell adniuk, amelyek szemantikailag írják le, hogyan viselkednek. A függvény bemenetéből, kimenetéből és mellékhatásaiból származó összes adatot úgy kell leírni, hogy az AI megértse, ellenkező esetben az AI nem fogja megfelelően meghívni a függvényt.

A jobb oldali minta WriterPlugin beépülő modul például szemantikai leírásokkal ellátott függvényekkel rendelkezik, amelyek leírják az egyes függvények működését. Az LLM ezután ezeket a leírásokat használva kiválaszthatja a legjobb függvényeket, amelyek meghívhatók a felhasználó kérésének teljesítéséhez.

A jobb oldali képen egy LLM valószínűleg meghívja a ShortPoem függvényeket, hogy StoryGen kielégítsék a felhasználók kérését a megadott szemantikai leírásoknak köszönhetően.

Szemantikai leírás a WriterPlugin beépülő modulban

Különböző típusú beépülő modulok importálása

A beépülő modulok szemantikus kernelbe való importálásának két elsődleges módja van: natív kód vagy OpenAPI-specifikáció használata. Az előbbi lehetővé teszi, hogy olyan beépülő modulokat szerkesszen a meglévő kódbázisban, amelyek kihasználhatják a már meglévő függőségeket és szolgáltatásokat. Ez utóbbi lehetővé teszi beépülő modulok importálását egy OpenAPI-specifikációból, amely különböző programozási nyelvek és platformok között osztható meg.

Az alábbiakban egy egyszerű példát mutatunk be egy natív beépülő modul importálására és használatára. Az ilyen típusú beépülő modulok importálásával kapcsolatos további információkért tekintse meg az alábbi cikkeket:

Tipp.

Első lépésként natív kód beépülő modulokat javasoljuk. Ahogy az alkalmazás kiforrott, és amikor platformfüggetlen csapatok között dolgozik, érdemes lehet megfontolni az OpenAPI-specifikációk használatát a beépülő modulok különböző programozási nyelvek és platformok közötti megosztásához.

A beépülő modulfüggvények különböző típusai

A beépülő modulon belül általában két különböző típusú függvényt fog használni, azokat, amelyek adatokat kérnek le a kiterjesztett generációs (RAG) lekéréséhez, valamint azokat, amelyek automatizálják a feladatokat. Bár az egyes típusok funkcionálisan azonosak, általában másként használják őket a Szemantikus Kernelt használó alkalmazásokban.

A lekérési függvényekkel például érdemes lehet stratégiákat használni a teljesítmény javítására (például gyorsítótárazás és olcsóbb köztes modellek használata az összegzéshez). A feladatautomatizálási funkciókkal azonban valószínűleg emberi jóváhagyási folyamatokat kell implementálnia, hogy a tevékenységek megfelelően befejeződjenek.

A beépülő modulfüggvények különböző típusairól az alábbi cikkekben olvashat bővebben:

Ismerkedés a beépülő modulokkal

A Beépülő modulok a Szemantic Kernelben való használata mindig három lépésből áll:

  1. A beépülő modul definiálása
  2. A beépülő modul hozzáadása a kernelhez
  3. Ezután vagy hívja meg a beépülő modul függvényeit egy függvényhívással rendelkező parancssorban

Az alábbiakban egy magas szintű példát mutatunk be arra, hogyan használhat beépülő modult a Szemantic Kernelben. A beépülő modulok létrehozásával és használatával kapcsolatos részletesebb információkért tekintse meg a fenti hivatkozásokat.

1) A beépülő modul definiálása

A beépülő modul létrehozásának legegyszerűbb módja egy osztály definiálása és a metódusainak az KernelFunction attribútummal való jegyzetelése. Ez a Szemantikus Kernel tudhatja, hogy ez egy olyan függvény, amelyet meghívhat egy MI, vagy hivatkozhat rá egy kérdésben.

Beépülő modulokat openAPI-specifikációból is importálhat.

Az alábbiakban létrehozunk egy beépülő modult, amely lekéri a fények állapotát, és módosíthatja annak állapotát.

Tipp.

Mivel a legtöbb LLM-et betanították a Pythonnal a függvényhíváshoz, ajánlott kígyós esetet használni a függvénynevekhez és a tulajdonságnevekhez még akkor is, ha a C# vagy a Java SDK-t használja.

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

Figyelje meg, hogy megadjuk a függvény leírását, a visszatérési értéket és a paramétereket. Ez fontos ahhoz, hogy az AI megértse a függvény működését és használatát.

Tipp.

Ne féljen részletes leírást adni a függvényekről, ha egy AI-nek problémái vannak a meghívásukkal. Néhány példa, a függvény használatának (és nem használatának) időpontjára vonatkozó javaslatok, valamint a szükséges paraméterek beszerzésére vonatkozó útmutatás mind hasznosak lehetnek.

2) Adja hozzá a beépülő modult a kernelhez

Miután definiálta a beépülő modult, hozzáadhatja a kernelhez úgy, hogy létrehoz egy új beépülő modulpéldányt, és hozzáadja a kernel beépülő modulgyűjteményéhez.

Ez a példa bemutatja az osztály beépülő modulként való hozzáadásának legegyszerűbb módját a AddFromType metódussal. A beépülő modulok hozzáadásának egyéb módjairól a natív beépülő modulok hozzáadásáról szóló cikkben olvashat.

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

3) A beépülő modul funkcióinak meghívása

Végül meghívhatja az AI-t a beépülő modul függvényeinek meghívásával. Az alábbi példa bemutatja, hogyan lehet a függvényt a beépülő modulból Lights meghívni get_lights az AI-t, mielőtt meghívja a függvényt, hogy bekapcsolja a change_state fényt.

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

A fenti kóddal a következőhöz hasonló választ kell kapnia:

Szerepkör Üzenet
🔵Felhasználó Kapcsolja be a lámpát
🔴Asszisztens (függvényhívás) Lights.get_lights()
🟢Eszköz [{ "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" }]
🔴Asszisztens (függvényhívás) Lights.change_state(1; { "isOn": true })
🟢Eszköz { "id": 1, "name": "Table Lamp", "isOn": true, "brightness": 100, "hex": "FF0000" }
🔴Asszisztens A lámpa be van kapcsolva

Tipp.

Bár közvetlenül is meghívhat beépülő modulfüggvényeket, ez nem ajánlott, mert az AI-nek kell eldöntenie, hogy melyik függvényt hívja meg. Ha explicit módon kell szabályoznia, hogy mely függvények legyenek meghívva, fontolja meg a standard metódusok használatát a kódbázisban beépülő modulok helyett.