O que é um Plugin?

Plugins são um componente-chave do Kernel Semântico. Se você já usou plug-ins das extensões ChatGPT ou Copilot no Microsoft 365, já está familiarizado com eles. Com plugins, você pode encapsular suas APIs existentes em uma coleção que pode ser usada por uma IA. Isso permite que você dê à sua IA a capacidade de executar ações que ela não seria capaz de fazer de outra forma.

Nos bastidores, o Kernel Semântico aproveita a chamada de função, um recurso nativo da maioria dos LLMs mais recentes para permitir que LLMs realizem planejamento e invoquem suas APIs. Com a chamada de função, os LLMs podem solicitar (ou seja, chamar) uma função específica. O Kernel Semântico então marshals a solicitação para a função apropriada em sua base de código e retorna os resultados de volta para o LLM para que o LLM possa gerar uma resposta final.

Plugin do Kernel Semântico

Nem todos os SDKs de IA têm um conceito análogo aos plugins (a maioria só tem funções ou ferramentas). Em cenários corporativos, no entanto, os plug-ins são valiosos porque encapsulam um conjunto de funcionalidades que espelha como os desenvolvedores corporativos já desenvolvem serviços e APIs. Os plugins também funcionam bem com a injeção de dependência. Dentro do construtor de um plugin, você pode injetar serviços que são necessários para executar o trabalho do plugin (por exemplo, conexões de banco de dados, clientes HTTP, etc.). Isso é difícil de realizar com outros SDKs que não possuem plugins.

Anatomia de um plugin

Em um alto nível, um plugin é um grupo de funções que podem ser expostas a aplicativos e serviços de IA. As funções dentro dos plugins podem então ser orquestradas por um aplicativo de IA para realizar as solicitações do usuário. Dentro do Kernel Semântico, você pode invocar essas funções automaticamente com a chamada de função.

Observação

Em outras plataformas, as funções são frequentemente referidas como "ferramentas" ou "ações". No Kernel Semântico, usamos o termo "funções", uma vez que elas são tipicamente definidas como funções nativas em sua base de código.

Apenas fornecer funções, no entanto, não é suficiente para fazer um plugin. Para alimentar a orquestração automática com chamadas de função, os plugins também precisam fornecer detalhes que descrevam semanticamente como eles se comportam. Tudo, desde a entrada, saída e efeitos colaterais da função, precisa ser descrito de uma forma que a IA possa entender, caso contrário, a IA não chamará corretamente a função.

Por exemplo, o plugin de exemplo WriterPlugin à direita tem funções com descrições semânticas que descrevem o que cada função faz. Um LLM pode então usar essas descrições para escolher as melhores funções a serem chamadas para atender à solicitação de um usuário.

Na imagem à direita, um LLM provavelmente chamaria as ShortPoem funções e StoryGen para satisfazer as perguntas dos usuários graças às descrições semânticas fornecidas.

Descrição semântica dentro do plugin WriterPlugin

Importando diferentes tipos de plugins

Existem duas maneiras principais de importar plugins para o Kernel Semântico: usando código nativo ou usando uma especificação OpenAPI. O primeiro permite que você crie plug-ins em sua base de código existente que podem aproveitar dependências e serviços que você já tem. Este último permite importar plugins de uma especificação OpenAPI, que pode ser compartilhada entre diferentes linguagens de programação e plataformas.

Abaixo fornecemos um exemplo simples de importação e uso de um plugin nativo. Para saber mais sobre como importar esses diferentes tipos de plug-ins, consulte os seguintes artigos:

Dica

Ao começar, recomendamos o uso de plug-ins de código nativo. À medida que seu aplicativo amadurece e trabalha em equipes multiplataforma, convém considerar o uso de especificações OpenAPI para compartilhar plug-ins em diferentes linguagens de programação e plataformas.

Os diferentes tipos de funções do plugin

Dentro de um plugin, você normalmente terá dois tipos diferentes de funções, aquelas que recuperam dados para recuperação de geração aumentada (RAG) e aquelas que automatizam tarefas. Embora cada tipo seja funcionalmente o mesmo, eles normalmente são usados de forma diferente dentro de aplicativos que usam o Kernel Semântico.

Por exemplo, com funções de recuperação, convém usar estratégias para melhorar o desempenho (por exemplo, armazenar em cache e usar modelos intermediários mais baratos para resumo). Considerando que com as funções de automação de tarefas, você provavelmente desejará implementar processos de aprovação human-in-the-loop para garantir que as tarefas sejam concluídas corretamente.

Para saber mais sobre os diferentes tipos de funções de plug-in, consulte os seguintes artigos:

Introdução aos plug-ins

Usar plugins dentro do Kernel Semântico é sempre um processo de três etapas:

  1. Defina seu plugin
  2. Adicione o plugin ao seu kernel
  3. E, em seguida, invoque as funções do plugin em um prompt com chamada de função

Abaixo forneceremos um exemplo de alto nível de como usar um plugin dentro do Semantic Kernel. Consulte os links acima para obter informações mais detalhadas sobre como criar e usar plugins.

1) Defina seu plugin

A maneira mais fácil de criar um plugin é definindo uma classe e anotando seus métodos com o KernelFunction atributo. Isso permite que o Kernel Semântico saiba que esta é uma função que pode ser chamada por uma IA ou referenciada em um prompt.

Você também pode importar plugins de uma especificação OpenAPI.

Abaixo, vamos criar um plugin que pode recuperar o estado das luzes e alterar o seu estado.

Dica

Como a maioria dos LLM foi treinada com Python para chamada de função, é recomendado usar maiúsculas e minúsculas para nomes de função e nomes de propriedade, mesmo se você estiver usando o C# ou 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);
  }
}

Observe que fornecemos descrições para a função, o valor de retorno e os parâmetros. Isso é importante para que a IA entenda o que a função faz e como usá-la.

Dica

Não tenha medo de fornecer descrições detalhadas para suas funções se uma IA estiver tendo problemas para chamá-las. Exemplos de poucas fotos, recomendações de quando usar (e não usar) a função e orientações sobre onde obter os parâmetros necessários podem ser úteis.

2) Adicione o plugin ao seu kernel

Depois de definir seu plugin, você pode adicioná-lo ao seu kernel criando uma nova instância do plugin e adicionando-o à coleção de plug-ins do kernel.

Este exemplo demonstra a maneira mais fácil de adicionar uma classe como um plug-in com o AddFromType método. Para saber mais sobre outras maneiras de adicionar plug-ins, consulte o artigo adicionando plug-ins nativos.

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) Invoque as funções do plugin

Finalmente, você pode fazer com que a IA invoque as funções do seu plugin usando a chamada de função. Abaixo está um exemplo que demonstra como persuadir a IA a chamar a get_lights função do plugin antes de Lights chamar a change_state função para acender uma luz.

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

Com o código acima, você deve obter uma resposta semelhante à seguinte:

Função Mensagem
🔵Usuário Por favor, ligue a lâmpada
🔴Assistente (chamada de função) Lights.get_lights()
🟢Ferramenta [{ "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" }]
🔴Assistente (chamada de função) Lights.change_state(1, { "isOn": verdadeiro })
🟢Ferramenta { "id": 1, "name": "Table Lamp", "isOn": true, "brightness": 100, "hex": "FF0000" }
🔴Assistente A lâmpada está acesa

Dica

Embora você possa invocar uma função de plugin diretamente, isso não é aconselhável, porque a IA deve ser quem decide quais funções chamar. Se você precisar de controle explícito sobre quais funções são chamadas, considere usar métodos padrão em sua base de código em vez de plug-ins.