Entenda as funções nativas

Concluído

As funções nativas são funções gravadas em código nativo que podem ser chamadas pelo SDK do Kernel Semântico em seu aplicativo. Elas são úteis para realizar tarefas que LLM (modelos de linguagem grandes) não conseguem fazer sozinhos. Você pode pensar em uma função nativa como uma habilidade que seu aplicativo pode executar.

Em módulos posteriores, você aprenderá a usar o kernel semântico para invocar automaticamente funções nativas criadas e combinar sua lógica com prompts para o LLM. A combinação de serviços de conclusão de chat com habilidades funcionais permite que você crie um agente de IA capaz de executar uma ampla gama de tarefas. Mas, vamos nos concentrar na criação de funções nativas por enquanto.

As funções nativas têm um determinado formato e uma estrutura de arquivo recomendada para ser usada pelo kernel. As funções nativas devem usar o decorador KernelFunction em suas definições. Elas também usam um campo Description para parâmetros. Por exemplo:

[KernelFunction, Description("Convert an amount of currency to USD")]
public static string ConvertCurrency(
  [Description("The currency")] string currency, 
  [Description("The amount")] double amount)
{
  // Code to convert currency
}

Você pode importar as funções nativas para o kernel como um plug-in. Classes que contêm funções nativas devem ser colocadas em um diretório "Plugins". As funções relacionadas devem ser colocadas no mesmo arquivo para manter o código organizado. Você também pode utilizar subdiretórios no diretório "Plug-ins" para organizar ainda mais seu código.

Por exemplo, digamos que você tem um aplicativo de lista de tarefas pendentes. Um usuário quer concluir um item da sua lista de tarefas. O LLM não pode acessar a lista de tarefas do usuário diretamente, mas você pode escrever uma função nativa para acessar a lista e marcar um item como concluído. Por exemplo, o arquivo da lista de tarefas pode conter o seguinte:

{
  "todoList": [
    {
      "task": "Complete coding exercise",
      "completed": false
    },
    {
      "task": "Practice Mandarin",
      "completed": false
    },
    {
      "task": "Buy groceries",
      "completed": false
    }
  ]
}

Você pode criar um arquivo TodoListPlugin.cs no diretório "Plugins" com algum código para marcar a tarefa como concluída:

using System.ComponentModel;
using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.SemanticKernel;

public class TodoListPlugin
{
    [KernelFunction, Description("Mark a todo list item as complete")]
    public static string CompleteTask([Description("The task to complete")] string task)
    {
        // Read the JSON file
        string jsonFilePath = $"{Directory.GetCurrentDirectory()}/todo.txt";
        string jsonContent = File.ReadAllText(jsonFilePath);

        // Parse the JSON content
        JsonNode todoData = JsonNode.Parse(jsonContent);

        // Find the task and mark it as complete
        JsonArray todoList = (JsonArray) todoData["todoList"];
        foreach (JsonNode taskNode in todoList)
        {
            if (taskNode["task"].ToString() == task)
            {
                taskNode["completed"] = true;
                break;
            }
        }

        // Save the modified JSON back to the file
        File.WriteAllText(jsonFilePath, JsonSerializer.Serialize(todoData));
        return $"Task '{task}' marked as complete.";
    }
}

Observe o decorador KernelFunction na função CompleteTask. Esse decorador informa ao kernel que essa função pode ser acessada. O decorador Description informa ao kernel o que a função faz. A função aceita task como uma cadeia de caracteres. Variáveis de funções de kernel devem incluir uma descrição que explique o que é a variável. Essa função também retorna uma cadeia de caracteres permitindo que o usuário saiba que a tarefa foi marcada como concluída.

No arquivo Program.cs, você pode importar e chamar essa função nativa de forma semelhante a como chamaria um dos plug-ins internos. Por exemplo:

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Plugins.Core;

var builder = Kernel.CreateBuilder();
builder.AddAzureOpenAIChatCompletion(
  "your-deployment-name",
  "your-endpoint",
  "your-api-key",
  "deployment-model");
var kernel = builder.Build();
kernel.ImportPluginFromType<TodoListPlugin>();

var result = await kernel.InvokeAsync<string>(
  "TodoListPlugin", 
  "CompleteTask", 
  new() {{ "task", "Buy groceries" }}
);
Console.WriteLine(result);

Neste exemplo, kernel.InvokeAsync é chamado com o nome do plug-in, o nome da função e os argumentos. O argumento task é definido como "Comprar mantimentos". A função marcará a tarefa como concluída no arquivo da lista de tarefas e retornará uma mensagem ao usuário.

Agora seu agente de IA pode ajudar o usuário a concluir as tarefas da lista de tarefas pendentes. Opcionalmente, você pode optar por denotar o tipo de retorno da função na chamada InvokeAsync. Caso contrário, um objeto FunctionResult será retornado.

No próximo exercício, você praticará a criação dos próprios plug-ins com funções nativas.