Partilhar via


Adicionar plugins a partir das especificações OpenAPI

Muitas vezes, em uma empresa, você já tem um conjunto de APIs que executam um trabalho real. Eles podem ser usados por outros serviços de automação ou aplicativos front-end de energia com os quais os seres humanos interagem. No Kernel Semântico, você pode adicionar essas mesmas APIs como plug-ins para que seus agentes também possam usá-las.

Um exemplo de especificação OpenAPI

Tomemos como exemplo uma API que permite alterar o estado das lâmpadas. A especificação OpenAPI, conhecida como Swagger Specification, ou apenas Swagger, para esta API pode ter esta aparência:

{
   "openapi": "3.0.1",
   "info": {
      "title": "Light API",
      "version": "v1"
   },
   "paths": {
      "/Light": {
         "get": {
            "summary": "Retrieves all lights in the system.",
            "operationId": "get_all_lights",
            "responses": {
               "200": {
                  "description": "Returns a list of lights with their current state",
                  "application/json": {
                     "schema": {
                        "type": "array",
                        "items": {
                              "$ref": "#/components/schemas/LightStateModel"
                        }
                     }
                  }
               }
            }
         }
      },
      "/Light/{id}": {
         "post": {
               "summary": "Changes the state of a light.",
               "operationId": "change_light_state",
               "parameters": [
                  {
                     "name": "id",
                     "in": "path",
                     "description": "The ID of the light to change.",
                     "required": true,
                     "style": "simple",
                     "schema": {
                           "type": "string"
                     }
                  }
               ],
               "requestBody": {
                  "description": "The new state of the light and change parameters.",
                  "content": {
                     "application/json": {
                           "schema": {
                              "$ref": "#/components/schemas/ChangeStateRequest"
                           }
                     }
                  }
               },
               "responses": {
                  "200": {
                     "description": "Returns the updated light state",
                     "content": {
                           "application/json": {
                              "schema": {
                                 "$ref": "#/components/schemas/LightStateModel"
                              }
                           }
                     }
                  },
                  "404": {
                     "description": "If the light is not found"
                  }
               }
         }
      }
   },
   "components": {
      "schemas": {
         "ChangeStateRequest": {
               "type": "object",
               "properties": {
                  "isOn": {
                     "type": "boolean",
                     "description": "Specifies whether the light is turned on or off.",
                     "nullable": true
                  },
                  "hexColor": {
                     "type": "string",
                     "description": "The hex color code for the light.",
                     "nullable": true
                  },
                  "brightness": {
                     "type": "integer",
                     "description": "The brightness level of the light.",
                     "format": "int32",
                     "nullable": true
                  },
                  "fadeDurationInMilliseconds": {
                     "type": "integer",
                     "description": "Duration for the light to fade to the new state, in milliseconds.",
                     "format": "int32",
                     "nullable": true
                  },
                  "scheduledTime": {
                     "type": "string",
                     "description": "Use ScheduledTime to synchronize lights. It's recommended that you asynchronously create tasks for each light that's scheduled to avoid blocking the main thread.",
                     "format": "date-time",
                     "nullable": true
                  }
               },
               "additionalProperties": false,
               "description": "Represents a request to change the state of the light."
         },
         "LightStateModel": {
               "type": "object",
               "properties": {
                  "id": {
                     "type": "string",
                     "nullable": true
                  },
                  "name": {
                     "type": "string",
                     "nullable": true
                  },
                  "on": {
                     "type": "boolean",
                     "nullable": true
                  },
                  "brightness": {
                     "type": "integer",
                     "format": "int32",
                     "nullable": true
                  },
                  "hexColor": {
                     "type": "string",
                     "nullable": true
                  }
               },
               "additionalProperties": false
         }
      }
   }
}

Esta especificação fornece tudo o que a IA precisa para entender a API e como interagir com ela. A API inclui dois endpoints: um para obter todas as luzes e outro para alterar o estado de uma luz. Prevê igualmente o seguinte:

  • Descrições semânticas para os pontos finais e seus parâmetros
  • Os tipos dos parâmetros
  • As respostas esperadas

Como o agente de IA pode entender essa especificação, você pode adicioná-la como um plug-in para o agente.

O Semantic Kernel suporta OpenAPI versões 2.0 e 3.0, e visa acomodar as especificações da versão 3.1 fazendo o downgrade para a versão 3.0.

Dica

Se você tiver especificações OpenAPI existentes, talvez seja necessário fazer alterações para torná-las mais fáceis para uma IA entendê-las. Por exemplo, pode ser necessário fornecer orientação nas descrições. Para obter mais dicas sobre como tornar suas especificações OpenAPI amigáveis à IA, consulte Dicas e truques para adicionar plug-ins OpenAPI.

Adicionar o plugin OpenAPI

Com algumas linhas de código, você pode adicionar o plugin OpenAPI ao seu agente. O trecho de código a seguir mostra como adicionar o plug-in light da especificação OpenAPI acima:

await kernel.ImportPluginFromOpenApiAsync(
   pluginName: "lights",
   uri: new Uri("https://example.com/v1/swagger.json"),
   executionParameters: new OpenApiFunctionExecutionParameters()
   {
      // Determines whether payload parameter names are augmented with namespaces.
      // Namespaces prevent naming conflicts by adding the parent parameter name
      // as a prefix, separated by dots
      EnablePayloadNamespacing = true
   }
);

Com o Kernel Semântico, você pode adicionar plug-ins OpenAPI de várias fontes, como URL, arquivo ou fluxo. Além disso, os plugins podem ser criados uma vez e reutilizados em várias instâncias ou agentes do kernel.

// Create the OpenAPI plugin from a local file somewhere at the root of the application
KernelPlugin plugin = await OpenApiKernelPluginFactory.CreateFromOpenApiAsync(
    pluginName: "lights",
    filePath: "path/to/lights.json"
);

// Add the plugin to the kernel
Kernel kernel = new Kernel();
kernel.Plugins.Add(plugin);
await kernel.add_plugin_from_openapi(
   plugin_name="lights",
   openapi_document_path="https://example.com/v1/swagger.json",
   execution_settings=OpenAPIFunctionExecutionParameters(
         # Determines whether payload parameter names are augmented with namespaces.
         # Namespaces prevent naming conflicts by adding the parent parameter name
         # as a prefix, separated by dots
         enable_payload_namespacing=True,
   ),
)
String yaml = EmbeddedResourceLoader.readFile("petstore.yaml", ExamplePetstoreImporter.class);

KernelPlugin plugin = SemanticKernelOpenAPIImporter
   .builder()
   .withPluginName("petstore")
   .withSchema(yaml)
   .withServer("http://localhost:8090/api/v3")
   .build();

Kernel kernel = ExampleOpenAPIParent.kernelBuilder()
   .withPlugin(plugin)
   .build();

Depois, você pode usar o plugin em seu agente como se fosse um plug-in nativo.

Manipulando parâmetros de plug-in OpenAPI

O Kernel Semântico extrai automaticamente metadados - como nome, descrição, tipo e esquema para todos os parâmetros definidos em documentos OpenAPI. Esses metadados são armazenados na propriedade KernelFunction.Metadata.Parameters para cada operação OpenAPI e são fornecidos ao LLM juntamente com o prompt para gerar os argumentos corretos para chamadas de função.

Por padrão, o nome do parâmetro original é fornecido ao LLM e é usado pelo Semantic Kernel para procurar o argumento correspondente na lista de argumentos fornecida pelo LLM. No entanto, pode haver casos em que o plugin OpenAPI tem vários parâmetros com o mesmo nome. Fornecer esses metadados de parâmetro para o LLM poderia criar confusão, potencialmente impedindo que o LLM gerasse os argumentos corretos para chamadas de função.

Além disso, como uma função do kernel que não permite nomes de parâmetros não exclusivos é criada para cada operação OpenAPI, adicionar tal plugin pode resultar em algumas operações ficando indisponíveis para uso. Especificamente, as operações com nomes de parâmetros não exclusivos serão ignoradas e um aviso correspondente será registrado. Mesmo que fosse possível incluir vários parâmetros com o mesmo nome na função kernel, isso poderia levar a ambiguidade no processo de seleção de argumentos.

Considerando tudo isso, o Semantic Kernel oferece uma solução para gerenciar plugins com nomes de parâmetros não exclusivos. Esta solução é particularmente útil quando a alteração da API em si não é viável, seja por ser um serviço de terceiros ou um sistema legado.

O trecho de código a seguir demonstra como manipular nomes de parâmetros não exclusivos em um plug-in OpenAPI. Se a operação change_light_state tivesse um parâmetro adicional com o mesmo nome do parâmetro "id" existente - especificamente, para representar um ID de sessão além do "id" atual que representa o ID da luz - ele poderia ser tratado como mostrado abaixo:

OpenApiDocumentParser parser = new();

using FileStream stream = File.OpenRead("path/to/lights.json");

// Parse the OpenAPI document
RestApiSpecification specification = await parser.ParseAsync(stream);

// Get the change_light_state operation
RestApiOperation operation = specification.Operations.Single(o => o.Id == "change_light_state");

// Set the 'lightId' argument name to the 'id' path parameter that represents the ID of the light
RestApiParameter idPathParameter = operation.Parameters.Single(p => p.Location == RestApiParameterLocation.Path && p.Name == "id");
idPathParameter.ArgumentName = "lightId";

// Set the 'sessionId' argument name to the 'id' header parameter that represents the session ID
RestApiParameter idHeaderParameter = operation.Parameters.Single(p => p.Location == RestApiParameterLocation.Header && p.Name == "id");
idHeaderParameter.ArgumentName = "sessionId";

// Import the transformed OpenAPI plugin specification
kernel.ImportPluginFromOpenApi(pluginName: "lights", specification: specification);

Esse trecho de código utiliza a classe OpenApiDocumentParser para analisar o documento OpenAPI e acessar o objeto de modelo RestApiSpecification que representa o documento. Ele atribui nomes de argumento aos parâmetros e importa a especificação do plug-in OpenAPI transformado para o kernel. O Kernel Semântico fornece os nomes dos argumentos para o LLM em vez dos nomes originais e os usa para procurar os argumentos correspondentes na lista fornecida pelo LLM.

É importante notar que os nomes de argumento não são usados no lugar dos nomes originais ao chamar a operação OpenAPI. No exemplo acima, o parâmetro 'id' no caminho será substituído por um valor retornado pelo LLM para o argumento 'lightId'. O mesmo se aplica ao parâmetro de cabeçalho 'id'; o valor retornado pelo LLM para o argumento 'sessionId' será usado como o valor para o cabeçalho chamado 'id'.

Manipulando a carga útil dos plugins OpenAPI

Os plug-ins OpenAPI podem modificar o estado do sistema usando operações POST, PUT ou PATCH. Essas operações geralmente exigem que uma carga útil seja incluída com a solicitação.

O Semantic Kernel oferece algumas opções para gerenciar o manuseio de carga útil para plug-ins OpenAPI, dependendo do seu cenário específico e requisitos de API.

Construção dinâmica de carga útil

A construção dinâmica de carga útil permite que as cargas úteis das operações OpenAPI sejam criadas dinamicamente com base no esquema de carga útil e nos argumentos fornecidos pelo LLM. Esse recurso é ativado por padrão, mas pode ser desativado definindo a propriedade EnableDynamicPayload para false no objeto OpenApiFunctionExecutionParameters ao adicionar um plug-in OpenAPI.

Por exemplo, considere a operação change_light_state, que requer uma carga útil estruturada da seguinte forma:

{
   "isOn": true,
   "hexColor": "#FF0000",
   "brightness": 100,
   "fadeDurationInMilliseconds": 500,
   "scheduledTime": "2023-07-12T12:00:00Z"
}

Para alterar o estado da luz e obter valores para as propriedades de carga útil, o Semantic Kernel fornece ao LLM metadados para a operação para que ele possa raciocinar sobre ela:

{
    "name":"lights-change-light-state",
    "description": "Changes the state of a light.",
    "parameters":[
        { "name": "id", "schema": {"type":"string", "description": "The ID of the light to change.", "format":"uuid"}},
        { "name": "isOn", "schema": { "type": "boolean", "description": "Specifies whether the light is turned on or off."}},
        { "name": "hexColor", "schema": { "type": "string", "description": "Specifies whether the light is turned on or off."}},
        { "name": "brightness", "schema": { "type":"string", "description":"The brightness level of the light.", "enum":["Low","Medium","High"]}},
        { "name": "fadeDurationInMilliseconds", "schema": { "type":"integer", "description":"Duration for the light to fade to the new state, in milliseconds.", "format":"int32"}},
        { "name": "scheduledTime", "schema": {"type":"string", "description":"The time at which the change should occur.", "format":"date-time"}},
    ]
}

Além de fornecer metadados de operação para o LLM, o Semantic Kernel executará as seguintes etapas:

  1. Manipule a chamada LLM para a operação OpenAPI, construindo a carga útil com base no esquema e nos valores das propriedades fornecidas pelo LLM.
  2. Envie a solicitação HTTP com a carga para a API.

Limitações da construção de carga útil dinâmica

A construção dinâmica de carga útil é mais eficaz para APIs com estruturas de carga útil relativamente simples. Pode não funcionar de forma confiável ou simplesmente não funcionar, para cargas úteis de APIs que apresentam as seguintes características:

  • Cargas com nomes de propriedade que não são únicos, independentemente da localização das propriedades. Por exemplo, duas propriedades nomeadas id, uma para o objeto emissor e outra para o objeto recetor - json { "sender": { "id": ... }, "receiver": { "id": ... }}
  • Esquemas de carga útil que usam qualquer uma das palavras-chave compostas oneOf, anyOf, allOf.
  • Esquemas de carga útil com referências recursivas. Por exemplo, json { "parent": { "child": { "$ref": "#parent" } } }

Para lidar com cargas úteis com nomes de propriedade não exclusivos, considere as seguintes alternativas:

  • Forneça um nome de argumento exclusivo para cada propriedade não exclusiva, usando um método semelhante ao descrito na seção Manipulando parâmetros de plug-in OpenAPI.
  • Use namespaces para evitar conflitos de nomenclatura, conforme descrito na próxima seção sobre Nomes de carga útilpacing.
  • Desative a construção de carga útil dinâmica e permita que o LLM crie a carga útil com base em seu esquema, conforme explicado na seção O parâmetro de carga útil.

Se os esquemas de cargas úteis utilizarem quaisquer das palavras-chave de composição oneOf, anyOf, allOf ou referências recursivas, considere desativar a construção dinâmica da carga útil e permitir que o LLM crie a carga útil com base no seu esquema, conforme explicado na seção O parâmetro de carga útil.

Espaçamento de carga útil

O pacing de nomes de carga útil ajuda a evitar conflitos de nomenclatura que podem ocorrer devido a nomes de propriedades não exclusivos em cargas úteis de plug-in OpenAPI.

Quando o espaçamento de nomes está habilitado, o Semantic Kernel fornece ao LLM metadados de operação OpenAPI que incluem nomes de propriedades ampliados. Esses nomes aumentados são criados adicionando o nome da propriedade pai como um prefixo, separado por um ponto, aos nomes de propriedade filho.

Por exemplo, se a operação change_light_state tivesse incluído um objeto offTimer aninhado com uma propriedade scheduledTime:

{
  "isOn": true,
  "hexColor": "#FF0000",
  "brightness": 100,
  "fadeDurationInMilliseconds": 500,
  "scheduledTime": "2023-07-12T12:00:00Z",
  "offTimer": {
      "scheduledTime": "2023-07-12T12:00:00Z"
  }
}

O Semantic Kernel teria fornecido ao LLM metadados para a operação que inclui os seguintes nomes de propriedade:

{
    "name":"lights-change-light-state",
    "description": "Changes the state of a light.",
    "parameters":[
        { "name": "id", "schema": {"type":"string", "description": "The ID of the light to change.", "format":"uuid"}},
        { "name": "isOn", "schema": { "type": "boolean", "description": "Specifies whether the light is turned on or off."}},
        { "name": "hexColor", "schema": { "type": "string", "description": "Specifies whether the light is turned on or off."}},
        { "name": "brightness", "schema": { "type":"string", "description":"The brightness level of the light.", "enum":["Low","Medium","High"]}},
        { "name": "fadeDurationInMilliseconds", "schema": { "type":"integer", "description":"Duration for the light to fade to the new state, in milliseconds.", "format":"int32"}},
        { "name": "scheduledTime", "schema": {"type":"string", "description":"The time at which the change should occur.", "format":"date-time"}},
        { "name": "offTimer.scheduledTime", "schema": {"type":"string", "description":"The time at which the device will be turned off.", "format":"date-time"}},
    ]
}

Além de fornecer metadados de operação com nomes de propriedade aumentados para o LLM, o Semantic Kernel executa as seguintes etapas:

  1. Gerencie a chamada LLM para a operação OpenAPI e procure os argumentos correspondentes entre aqueles fornecidos pelo LLM para todas as propriedades no payload, usando os nomes de propriedade aumentados e revertendo aos nomes originais caso necessário.
  2. Construa a carga usando os nomes de propriedade originais como chaves e os argumentos resolvidos como valores.
  3. Envie a solicitação HTTP com a carga construída para a API.

Por padrão, a opção payload namespacing está desativada. Ele pode ser ativado definindo a propriedade EnablePayloadNamespacing para true no objeto OpenApiFunctionExecutionParameters ao adicionar um plug-in OpenAPI:

await kernel.ImportPluginFromOpenApiAsync(
    pluginName: "lights",
    uri: new Uri("https://example.com/v1/swagger.json"),
    executionParameters: new OpenApiFunctionExecutionParameters()
    {
        EnableDynamicPayload = true, // Enable dynamic payload construction. This is enabled by default.
        EnablePayloadNamespacing = true // Enable payload namespacing
    });

Observação

A opção EnablePayloadNamespace só entra em vigor quando a construção de carga útil dinâmica também está ativada; caso contrário, não produz efeitos.

O parâmetro de carga útil

O Kernel Semântico pode trabalhar com cargas úteis criadas pelo LLM usando o parâmetro payload. Isso é útil quando o esquema de carga útil é complexo e contém nomes de propriedade não exclusivos, o que torna inviável para o Semantic Kernel construir dinamicamente a carga útil. Nesses casos, você confiará na capacidade do LLM de entender o esquema e construir uma carga útil válida. Modelos recentes, como gpt-4o são eficazes na geração de cargas úteis JSON válidas.

Para habilitar o parâmetro payload, defina a propriedade EnableDynamicPayload como false no objeto OpenApiFunctionExecutionParameters ao adicionar um plug-in OpenAPI:

await kernel.ImportPluginFromOpenApiAsync(
    pluginName: "lights",
    uri: new Uri("https://example.com/v1/swagger.json"),
    executionParameters: new OpenApiFunctionExecutionParameters()
    {
        EnableDynamicPayload = false, // Disable dynamic payload construction
    });

Quando o parâmetro payload está ativado, o Semantic Kernel fornece ao LLM metadados para a operação que incluem esquemas para a carga útil e parâmetros content_type, permitindo que o LLM compreenda a estrutura da carga útil e a construa de acordo:

{
    "name": "payload",
    "schema":
    {
        "type": "object",
        "properties": {
            "isOn": {
                "type": "boolean",
                "description": "Specifies whether the light is turned on or off."
            },
            "hexColor": {
                "type": "string",
                "description": "The hex color code for the light.",
            },
            "brightness": {
                "enum": ["Low", "Medium", "High"],
                "type": "string",
                "description": "The brightness level of the light."
            },
            "fadeDurationInMilliseconds": {
                "type": "integer",
                "description": "Duration for the light to fade to the new state, in milliseconds.",
                "format": "int32"
            },
            "scheduledTime": {
                "type": "string",
                "description": "The time at which the change should occur.",
                "format": "date-time"
            }
        },
        "additionalProperties": false,
        "description": "Represents a request to change the state of the light."
    },
    {
        "name": "content_type",
        "schema":
        {
            "type": "string",
            "description": "Content type of REST API request body."
        }
    }
}

Além de fornecer os metadados da operação com o esquema para parâmetros de carga útil e tipo de conteúdo para o LLM, o Semantic Kernel executa as seguintes etapas:

  1. Gerencie a chamada LLM para a operação OpenAPI e utilize argumentos fornecidos pela LLM para os parâmetros de carga útil e content_type.
  2. Envie a solicitação HTTP para a API com a carga útil e o tipo de conteúdo fornecidos.

URL base do servidor

Os plugins OpenAPI do Kernel Semântico exigem uma URL base, que é usada para antepor caminhos de endpoints ao fazer solicitações de API. Esta URL base pode ser especificada no documento OpenAPI, obtida implicitamente carregando o documento de uma URL, ou fornecida ao adicionar o plugin ao kernel.

URL especificado no documento OpenAPI

Os documentos OpenAPI v2 definem a URL do servidor usando os campos schemes, hoste basePath:

{
   "swagger": "2.0",
   "host": "example.com",
   "basePath": "/v1",
   "schemes": ["https"]
   ...
}

O Kernel Semântico construirá a URL do servidor como https://example.com/v1.

Em contraste, os documentos OpenAPI v3 definem a URL do servidor usando o campo servers:

{
   "openapi": "3.0.1",
   "servers": [
      {
         "url": "https://example.com/v1"
      }
   ],
   ...
}

O Kernel Semântico usará a primeira URL do servidor especificada no documento como a URL base: https://example.com/v1.

OpenAPI v3 também permite URLs de servidor parametrizadas usando variáveis indicadas por chaves curvas:

{
   "openapi": "3.0.1",
   "servers": [
      {
         "url": "https://{environment}.example.com/v1",
         "variables": {
            "environment": {
               "default": "prod"
            }
         }
      }
   ],
   ...  
}

Nesse caso, o Kernel Semântico substituirá o espaço reservado da variável pelo valor fornecido como argumento para a variável ou pelo valor padrão se nenhum argumento for fornecido, resultando na URL: https://prod.example.com/v1.

Se o documento OpenAPI não especificar nenhuma URL do servidor, o Semantic Kernel usará a URL base do servidor a partir do qual o documento OpenAPI foi carregado:

await kernel.ImportPluginFromOpenApiAsync(pluginName: "lights", uri: new Uri("https://api-host.com/swagger.json"));

O URL base será https://api-host.com.

Substituindo a URL do servidor

Em alguns casos, a URL do servidor especificada no documento OpenAPI ou o servidor a partir do qual o documento foi carregado pode não ser adequada para casos de uso envolvendo o plug-in OpenAPI.

O Semantic Kernel permite que você substitua a URL do servidor fornecendo uma URL base personalizada ao adicionar o plug-in OpenAPI ao kernel:

await kernel.ImportPluginFromOpenApiAsync(  
    pluginName: "lights",  
    uri: new Uri("https://example.com/v1/swagger.json"),  
    executionParameters: new OpenApiFunctionExecutionParameters()  
    {  
        ServerUrlOverride = new Uri("https://custom-server.com/v1")  
    });  

Neste exemplo, a URL base será https://custom-server.com/v1, substituindo a URL do servidor especificada no documento OpenAPI e a URL do servidor a partir da qual o documento foi carregado.

Autenticação

A maioria das APIs REST requer autenticação para acessar seus recursos. O Kernel Semântico fornece um mecanismo que permite integrar uma variedade de métodos de autenticação exigidos pelos plugins OpenAPI.

Esse mecanismo depende de uma função de retorno de chamada de autenticação, que é invocada antes de cada solicitação de API. Essa função de retorno de chamada tem acesso ao objeto HttpRequestMessage, representando a solicitação HTTP que será enviada à API. Você pode usar esse objeto para adicionar credenciais de autenticação à solicitação. As credenciais podem ser adicionadas como cabeçalhos, parâmetros de consulta ou no corpo da solicitação, dependendo do método de autenticação usado pela API.

Você precisa registrar essa função de retorno de chamada ao adicionar o plug-in OpenAPI ao kernel. O trecho de código a seguir demonstra como registrá-lo para autenticar solicitações:

static Task AuthenticateRequestAsyncCallback(HttpRequestMessage request, CancellationToken cancellationToken = default)
{
    // Best Practices:  
    // * Store sensitive information securely, using environment variables or secure configuration management systems.  
    // * Avoid hardcoding sensitive information directly in your source code.  
    // * Regularly rotate tokens and API keys, and revoke any that are no longer in use.  
    // * Use HTTPS to encrypt the transmission of any sensitive information to prevent interception.  

    // Example of Bearer Token Authentication  
    // string token = "your_access_token";  
    // request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);  

    // Example of API Key Authentication  
    // string apiKey = "your_api_key";  
    // request.Headers.Add("X-API-Key", apiKey);    

    return Task.CompletedTask;  
}

await kernel.ImportPluginFromOpenApiAsync(  
    pluginName: "lights",  
    uri: new Uri("https://example.com/v1/swagger.json"),  
    executionParameters: new OpenApiFunctionExecutionParameters()  
    {  
        AuthCallback = AuthenticateRequestAsyncCallback
    });  

Para cenários de autenticação mais complexos que exigem acesso dinâmico aos detalhes dos esquemas de autenticação suportados por uma API, você pode usar metadados de documento e operação para obter essas informações. Para obter mais detalhes, consulte Metadados de documentos e operações.

Customização da leitura do conteúdo de resposta

O Semantic Kernel tem um mecanismo interno para ler o conteúdo das respostas HTTP dos plugins OpenAPI e convertê-las para os tipos de dados .NET apropriados. Por exemplo, uma resposta de imagem pode ser lida como uma matriz de bytes, enquanto uma resposta JSON ou XML pode ser lida como uma cadeia de caracteres.

No entanto, pode haver casos em que o mecanismo integrado é insuficiente para as suas necessidades. Por exemplo, quando a resposta é um grande objeto JSON ou imagem que precisa ser lida como um fluxo para ser fornecida como entrada para outra API. Nesses casos, ler o conteúdo da resposta como uma cadeia de caracteres ou matriz de bytes e, em seguida, convertê-lo de volta para um fluxo pode ser ineficiente e pode levar a problemas de desempenho. Para resolver isso, o Semantic Kernel permite a personalização da leitura de conteúdo de resposta fornecendo um leitor de conteúdo personalizado:

private static async Task<object?> ReadHttpResponseContentAsync(HttpResponseContentReaderContext context, CancellationToken cancellationToken)  
{  
    // Read JSON content as a stream instead of as a string, which is the default behavior.  
    if (context.Response.Content.Headers.ContentType?.MediaType == "application/json")  
    {  
        return await context.Response.Content.ReadAsStreamAsync(cancellationToken);  
    }  

    // HTTP request and response properties can be used to determine how to read the content.  
    if (context.Request.Headers.Contains("x-stream"))  
    {  
        return await context.Response.Content.ReadAsStreamAsync(cancellationToken);  
    }  

    // Return null to indicate that any other HTTP content not handled above should be read by the default reader.  
    return null;  
}  

await kernel.ImportPluginFromOpenApiAsync(  
    pluginName: "lights",  
    uri: new Uri("https://example.com/v1/swagger.json"),  
    executionParameters: new OpenApiFunctionExecutionParameters()  
    {  
        HttpResponseContentReader = ReadHttpResponseContentAsync  
    });  

Neste exemplo, o método ReadHttpResponseContentAsync lê o conteúdo da resposta HTTP como um fluxo quando o tipo de conteúdo é application/json ou quando a solicitação contém um cabeçalho personalizado x-stream. O método retorna null para quaisquer outros tipos de conteúdo, indicando que o leitor de conteúdo padrão deve ser usado.

Metadados de documentos e operações

O Semantic Kernel extrai documentos OpenAPI e metadados de operação, incluindo informações de API, esquemas de segurança, ID de operação, descrição, metadados de parâmetros e muito mais. Proporciona acesso a essas informações através da propriedade KernelFunction.Metadata.AdditionalParameters. Esses metadados podem ser úteis em cenários em que informações adicionais sobre a API ou a operação são necessárias, como para fins de autenticação:

static async Task AuthenticateRequestAsyncCallbackAsync(HttpRequestMessage request, CancellationToken cancellationToken = default)
{
    // Get the function context
    if (request.Options.TryGetValue(OpenApiKernelFunctionContext.KernelFunctionContextKey, out OpenApiKernelFunctionContext? functionContext))
    {
        // Get the operation metadata
        if (functionContext!.Function!.Metadata.AdditionalProperties["operation"] is RestApiOperation operation)
        {
            // Handle API key-based authentication
            IEnumerable<KeyValuePair<RestApiSecurityScheme, IList<string>>> apiKeySchemes = operation.SecurityRequirements.Select(requirement => requirement.FirstOrDefault(schema => schema.Key.SecuritySchemeType == "apiKey"));
            if (apiKeySchemes.Any())
            {
                (RestApiSecurityScheme scheme, IList<string> scopes) = apiKeySchemes.First();

                // Get the API key for the scheme and scopes from your app identity provider
                var apiKey = await this.identityPropvider.GetApiKeyAsync(scheme, scopes);

                // Add the API key to the request headers
                if (scheme.In == RestApiParameterLocation.Header)
                {
                    request.Headers.Add(scheme.Name, apiKey);
                }
                else if (scheme.In == RestApiParameterLocation.Query)
                {
                    request.RequestUri = new Uri($"{request.RequestUri}?{scheme.Name}={apiKey}");
                }
                else
                {
                    throw new NotSupportedException($"API key location '{scheme.In}' is not supported.");
                }
            }

            // Handle other authentication types like Basic, Bearer, OAuth2, etc. For more information, see https://swagger.io/docs/specification/v3_0/authentication/
        }
    }
}

// Import the transformed OpenAPI plugin specification
var plugin = kernel.ImportPluginFromOpenApi(
    pluginName: "lights",
    uri: new Uri("https://example.com/v1/swagger.json"),
    new OpenApiFunctionExecutionParameters()
    {
        AuthCallback = AuthenticateRequestAsyncCallbackAsync
    });

await kernel.InvokePromptAsync("Test");

Neste exemplo, o método AuthenticateRequestAsyncCallbackAsync lê os metadados da operação do contexto da função e extrai os requisitos de segurança para a operação determinar o esquema de autenticação. Em seguida, ele recupera a chave da API, para o esquema e os escopos, do provedor de identidade do aplicativo e a adiciona aos cabeçalhos de solicitação ou parâmetros de consulta.

A tabela a seguir lista os metadados disponíveis no dicionário KernelFunction.Metadata.AdditionalParameters:

Chave Tipo Descrição
informação RestApiInfo Informações da API, incluindo título, descrição e versão.
Funcionamento RestApiOperation Detalhes da operação da API, como id, descrição, caminho, método, etc.
Segurança IList<RestApiSecurityRequirement> Requisitos de segurança da API - tipo, nome, em, etc.

Dicas e truques para adicionar plugins OpenAPI

Como as especificações OpenAPI são normalmente projetadas para humanos, você pode precisar fazer algumas alterações para torná-las mais fáceis de entender para uma IA. Aqui estão algumas dicas e truques para ajudá-lo a fazer isso:

Recomendação Descrição
Controle de versão suas especificações de API Em vez de apontar para uma especificação de API ativa, considere fazer check-in e controle de versão do arquivo Swagger. Isso permitirá que seus pesquisadores de IA testem (e alterem) a especificação da API usada pelo agente de IA sem afetar a API ativa e vice-versa.
Limitar o número de pontos de extremidade Tente limitar o número de endpoints na sua API. Consolide funcionalidades semelhantes em terminais únicos com parâmetros opcionais para reduzir a complexidade.
Use nomes descritivos para pontos de extremidade e parâmetros Certifique-se de que os nomes de seus pontos de extremidade e parâmetros sejam descritivos e autoexplicativos. Isso ajuda a IA a entender seu propósito sem precisar de explicações extensas.
Use convenções de nomenclatura consistentes Mantenha convenções de nomenclatura consistentes em toda a API. Isso reduz a confusão e ajuda a IA a aprender e prever a estrutura da sua API mais facilmente.
Simplifique suas especificações de API Muitas vezes, as especificações OpenAPI são muito detalhadas e incluem muitas informações que não são necessárias para o agente de IA ajudar um usuário. Quanto mais simples a API, menos tokens você precisa gastar para descrevê-la e menos tokens a IA precisa enviar solicitações para ela.
Evite parâmetros de cadeia de caracteres Quando possível, evite usar parâmetros de cadeia de caracteres em sua API. Em vez disso, use tipos mais específicos, como inteiros, booleanos ou enums. Isso ajudará a IA a entender melhor a API.
Forneça exemplos nas descrições Quando os humanos usam arquivos Swagger, eles normalmente são capazes de testar a API usando a interface do usuário do Swagger, que inclui solicitações e respostas de exemplo. Como o agente de IA não pode fazer isso, considere fornecer exemplos nas descrições dos parâmetros.
Fazer referência a outros pontos finais nas descrições Muitas vezes, as IAs confundem pontos finais semelhantes. Para ajudar a IA a diferenciar entre pontos de extremidade, considere fazer referência a outros pontos de extremidade nas descrições. Por exemplo, você pode dizer "Este ponto de extremidade é semelhante ao ponto de extremidade get_all_lights, mas retorna apenas uma única luz."
Fornecer mensagens de erro úteis Embora não esteja dentro da especificação OpenAPI, considere fornecer mensagens de erro que ajudem a IA a se autocorrigir. Por exemplo, se um usuário fornecer uma ID inválida, considere fornecer uma mensagem de erro que sugira que o agente de IA obtenha a ID correta do ponto de extremidade get_all_lights.

Próximos passos

Agora que você sabe como criar um plugin, agora você pode aprender como usá-los com seu agente de IA. Dependendo do tipo de funções que você adicionou aos seus plugins, existem diferentes padrões que você deve seguir. Para as funções de recuperação, consulte o artigo usando funções de recuperação no . Para funções de automação de tarefas, consulte o artigo usando funções de automação de tarefas.