Udostępnij za pośrednictwem


Co to jest wtyczka?

Wtyczki są kluczowym składnikiem jądra semantycznego. Jeśli używasz już wtyczek z rozszerzeń ChatGPT lub Copilot na platformie Microsoft 365, znasz je już. Dzięki wtyczkom można hermetyzować istniejące interfejsy API do kolekcji, która może być używana przez sztuczną inteligencję. Dzięki temu można nadać sztucznej inteligencji możliwość wykonywania akcji, których nie będzie można wykonać w inny sposób.

W tle semantyczne jądro korzysta z wywoływania funkcji, natywnej funkcji większości najnowszych maszyn LLM, aby umożliwić maszynom LLM planowanie i wywoływanie interfejsów API. W przypadku wywoływania funkcji maszyny LLM mogą żądać (tj. wywołania) określonej funkcji. Semantyczne jądro następnie marshaluje żądanie do odpowiedniej funkcji w bazie kodu i zwraca wyniki z powrotem do usługi LLM, aby usługa LLM mogła wygenerować ostateczną odpowiedź.

Wtyczka jądra semantycznego

Nie wszystkie zestawy SDK sztucznej inteligencji mają analogiczną koncepcję wtyczek (większość ma tylko funkcje lub narzędzia). Jednak w scenariuszach dla przedsiębiorstw wtyczki są cenne, ponieważ hermetyzują zestaw funkcji, które odzwierciedlają sposób, w jaki deweloperzy przedsiębiorstwa już opracowują usługi i interfejsy API. Wtyczki również dobrze grać z iniekcji zależności. W konstruktorze wtyczki można wstrzyknąć usługi, które są niezbędne do wykonania pracy wtyczki (np. połączeń z bazą danych, klientów HTTP itp.). Jest to trudne do osiągnięcia w przypadku innych zestawów SDK, które nie mają wtyczek.

Anatomia wtyczki

Na wysokim poziomie wtyczka to grupa funkcji, które mogą być widoczne dla aplikacji i usług sztucznej inteligencji. Funkcje w ramach wtyczek mogą być następnie orkiestrowane przez aplikację sztucznej inteligencji w celu realizacji żądań użytkowników. W ramach jądra semantycznego można automatycznie wywoływać te funkcje za pomocą wywoływania funkcji.

Uwaga

Na innych platformach funkcje są często określane jako "narzędzia" lub "akcje". W semantycznym jądrze używamy terminu "functions", ponieważ są one zwykle definiowane jako funkcje natywne w bazie kodu.

Po prostu dostarczanie funkcji nie wystarczy, aby wtyczka. Aby zapewnić automatyczną aranżację za pomocą wywoływania funkcji, wtyczki muszą również podać szczegółowe informacje, które semantycznie opisują sposób ich działania. Wszystko, od danych wejściowych, wyjściowych i skutków ubocznych funkcji, należy opisać w sposób, w jaki sztuczna inteligencja może zrozumieć, w przeciwnym razie sztuczna inteligencja nie będzie poprawnie wywoływać funkcji.

Na przykład przykładowa WriterPlugin wtyczka po prawej stronie zawiera funkcje z semantycznymi opisami, które opisują działanie poszczególnych funkcji. Usługa LLM może następnie użyć tych opisów, aby wybrać najlepsze funkcje do wywołania w celu spełnienia pytania użytkownika.

Na zdjęciu po prawej stronie llM prawdopodobnie wywoła funkcję ShortPoem i StoryGen , aby spełnić wymagania użytkowników, dzięki podanym semantycznym opisom.

Opis semantyczny w wtyczki WriterPlugin

Importowanie różnych typów wtyczek

Istnieją dwa podstawowe sposoby importowania wtyczek do jądra semantycznego: przy użyciu kodu natywnego lub specyfikacji interfejsu OpenAPI. Pierwsza umożliwia tworzenie wtyczek w istniejącej bazie kodu, które mogą korzystać z już posiadanych zależności i usług. Ten ostatni umożliwia importowanie wtyczek ze specyfikacji interfejsu OpenAPI, którą można udostępniać w różnych językach programowania i platformach.

Poniżej przedstawiono prosty przykład importowania i używania wtyczki natywnej. Aby dowiedzieć się więcej na temat importowania tych różnych typów wtyczek, zapoznaj się z następującymi artykułami:

Napiwek

Podczas rozpoczynania pracy zalecamy używanie wtyczek kodu natywnego. W miarę dojrzewania aplikacji i pracy w zespołach międzyplatformowych warto rozważyć użycie specyfikacji interfejsu OpenAPI do udostępniania wtyczek w różnych językach programowania i platformach.

Różne typy funkcji wtyczki

W ramach wtyczki zazwyczaj będziesz mieć dwa różne typy funkcji, które pobierają dane na potrzeby pobierania rozszerzonej generacji (RAG) i tych, które automatyzują zadania. Mimo że każdy typ jest funkcjonalnie taki sam, są one zwykle używane inaczej w aplikacjach korzystających z jądra semantycznego.

Na przykład w przypadku funkcji pobierania warto użyć strategii w celu zwiększenia wydajności (np. buforowania i używania tańszych modeli pośrednich na potrzeby podsumowania). Podczas gdy w przypadku funkcji automatyzacji zadań warto zaimplementować procesy zatwierdzania pętli człowieka w pętli, aby upewnić się, że zadania są wykonywane poprawnie.

Aby dowiedzieć się więcej o różnych typach funkcji wtyczek, zapoznaj się z następującymi artykułami:

Wprowadzenie do wtyczek

Używanie wtyczek w ramach jądra semantycznego jest zawsze procesem trzech kroków:

  1. Definiowanie wtyczki
  2. Dodawanie wtyczki do jądra
  3. Następnie wywołaj funkcje wtyczki w wierszu polecenia z wywołaniem funkcji

Poniżej przedstawimy ogólny przykład użycia wtyczki w ramach jądra semantycznego. Zapoznaj się z powyższymi linkami, aby uzyskać bardziej szczegółowe informacje na temat tworzenia wtyczek i korzystania z nich.

1) Definiowanie wtyczki

Najprostszym sposobem utworzenia wtyczki jest zdefiniowanie klasy i dodawanie adnotacji do jej metod za pomocą atrybutu KernelFunction . W tym celu semantyczne jądro wiemy, że jest to funkcja, która może być wywoływana przez sztuczną inteligencję lub przywoływała w wierszu polecenia.

Możesz również zaimportować wtyczki ze specyfikacji interfejsu OpenAPI.

Poniżej utworzymy wtyczkę, która może pobrać stan świateł i zmienić jego stan.

Napiwek

Ponieważ większość llM została wytrenowana z językiem Python na potrzeby wywoływania funkcji, zaleca się używanie wielkości liter węży dla nazw funkcji i nazw właściwości, nawet jeśli używasz języka C# lub zestawu 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);
  }
}

Zwróć uwagę, że udostępniamy opisy funkcji, wartości zwracanej i parametrów. Jest to ważne dla sztucznej inteligencji, aby zrozumieć, co robi funkcja i jak jej używać.

Napiwek

Nie bój się podawać szczegółowych opisów funkcji, jeśli sztuczna inteligencja ma problemy z ich wywoływaniem. Przykłady kilku strzałów, zalecenia dotyczące tego, kiedy należy użyć (i nie używać) funkcji, i wskazówki dotyczące tego, gdzie uzyskać wymagane parametry, mogą być przydatne.

2) Dodawanie wtyczki do jądra

Po zdefiniowaniu wtyczki możesz dodać ją do jądra, tworząc nowe wystąpienie wtyczki i dodając ją do kolekcji wtyczek jądra.

W tym przykładzie pokazano najprostszy sposób dodawania klasy jako wtyczki do AddFromType metody . Aby dowiedzieć się więcej o innych sposobach dodawania wtyczek, zapoznaj się z artykułem dodawanie wtyczek natywnych.

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) Wywoływanie funkcji wtyczki

Na koniec możesz wywołać funkcje wtyczki za pomocą wywołania funkcji. Poniżej przedstawiono przykład, który pokazuje, jak połączyć sztuczną inteligencję w celu wywołania funkcji z Lights wtyczki przed wywołaniem get_lights change_state funkcji w celu włączenia światła.

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

Po wykonaniu powyższego kodu powinna zostać wyświetlona odpowiedź podobna do następującej:

Rola Komunikat
🔵Użytkownik Włącz lampę
🔴Asystent (wywołanie funkcji) Lights.get_lights()
🟢Tool (Narzędzie dostępu do centrum danych) [{ "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" }]
🔴Asystent (wywołanie funkcji) Lights.change_state(1, { "isOn": true })
🟢Tool (Narzędzie dostępu do centrum danych) { "id": 1, "name": "Table Lamp", "isOn": true, "brightness": 100, "hex": "FF0000" }
🔴Asystent Lampa jest teraz włączona

Napiwek

Chociaż można bezpośrednio wywołać funkcję wtyczki, nie jest to zalecane, ponieważ sztuczna inteligencja powinna być jedyną decyzją o tym, które funkcje mają być wywoływane. Jeśli potrzebujesz jawnej kontroli nad wywoływanymi funkcjami, rozważ użycie standardowych metod w bazie kodu zamiast wtyczek.