Compartilhar via


Adicionar plug-ins de especificações do 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 humanos interagem. No Kernel Semântico, você pode adicionar essas mesmas APIs como plug-ins, permitindo que seus agentes as utilizem.

Uma especificação de OpenAPI de exemplo

Veja, por exemplo, uma API que permite alterar o estado das lâmpadas. A especificação OpenAPI, conhecida como Especificação do Swagger 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
         }
      }
   }
}

Essa especificação fornece tudo o que é necessário para a IA 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. Ele também fornece o seguinte:

  • Descrições semânticas para os pontos de extremidade 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 ao agente.

O Kernel Semântico dá suporte às versões 2.0 e 3.0 do OpenAPI e tem como objetivo acomodar as especificações da versão 3.1, fazendo o downgrade para a versão 3.0.

Dica

Se você tiver especificações de OpenAPI existentes, talvez seja necessário fazer alterações para torná-las mais fáceis para uma IA compreendê-las. Por exemplo, talvez seja necessário fornecer diretrizes nas descrições. Para obter mais dicas sobre como tornar suas especificações do OpenAPI amigáveis com a IA, consulte Dicas e truques para adicionar plug-ins OpenAPI.

Adicionando o plugin OpenAPI

Com algumas linhas de código, você pode adicionar o plug-in OpenAPI ao seu agente. O snippet de código a seguir mostra como adicionar o plug-in de luz 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 uma URL, um arquivo ou um fluxo. Além disso, os plug-ins 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();

Posteriormente, você pode usar o plug-in 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 Kernel Semântico para pesquisar o argumento correspondente na lista de argumentos fornecidos pelo LLM. No entanto, pode haver casos em que o plug-in OpenAPI tem vários parâmetros com o mesmo nome. Fornecer esses metadados de parâmetro para a LLM pode criar confusão, potencialmente impedindo que o LLM gere os argumentos corretos para chamadas de função.

Além disso, como uma função de kernel que não permite nomes de parâmetros não exclusivos é criada para cada operação OpenAPI, a adição desse plug-in pode fazer com que algumas operações fiquem indisponíveis para uso. Especificamente, as operações com nomes de parâmetro 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 à ambiguidade no processo de seleção de argumento.

Considerando tudo isso, o Kernel Semântico oferece uma solução para gerenciar plug-ins com nomes de parâmetro não exclusivos. Essa solução é particularmente útil ao alterar a API em si não é viável, seja por ser um serviço de terceiros ou por um sistema herdado.

O snippet de código a seguir demonstra como lidar com nomes de parâmetro não exclusivos em um plug-in OpenAPI. Se a operação change_light_state tiver um parâmetro adicional com o mesmo nome do parâmetro "id" existente , especificamente, para representar uma ID de sessão além da "id" atual que representa a ID da luz, ela poderá ser tratada 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 snippet 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 de plug-in OpenAPI transformada para o kernel. O Kernel Semântico fornece os nomes de argumento para a LLM em vez dos nomes originais e os usa para pesquisar os argumentos correspondentes na lista fornecida pela LLM.

É importante observar 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 do cabeçalho chamado 'id'.

Manipulando a carga útil de 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 seja incluída com a solicitação.

O Kernel Semântico oferece algumas opções para gerenciar o tratamento de conteúdo para plug-ins OpenAPI, dependendo de seu cenário específico e requisitos de API.

Construção de carga útil dinâmica

A construção de conteúdo dinâmico permite que os conteúdos das operações OpenAPI sejam criados dinamicamente com base no esquema de conteúdo e nos argumentos fornecidos pela LLM. Esse recurso é habilitado por padrão, mas pode ser desabilitado 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 estruturada da seguinte maneira:

{
   "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 conteúdo, o Kernel Semântico fornece à LLM metadados para a operação para que ela 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 a LLM, o Kernel Semântico executará as seguintes etapas:

  1. Gerencie a chamada LLM para a operação OpenAPI, construindo o payload com base no esquema e valores das propriedades LLM.
  2. Envie a solicitação HTTP com o conteúdo para a API.

Limitações da construção de payload dinâmico

A construção de conteúdo dinâmico é mais eficaz para APIs com estruturas de conteúdo relativamente simples. Pode não funcionar de forma confiável nem funcionar absolutamente para cargas de APIs que exibem as seguintes características:

  • Conteúdos com nomes de propriedade não exclusivos, independentemente do local das propriedades. Por exemplo, duas propriedades chamadas id, uma para objeto remetente e outra para objeto receptor - 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 conteúdos com nomes de propriedade não exclusivos, considere as seguintes alternativas:

Se quaisquer esquemas de carga útil usarem algum dos termos compostos oneOf, anyOf, allOf ou referências recursivas, considere desabilitar a construção dinâmica de carga útil e permitir que a LLM crie a carga útil com base em seu esquema, conforme explicado na seção parâmetro de carga.

Espaçamento de nomes de conteúdo

O espaçamento de nomes de conteúdo ajuda a evitar conflitos de nomenclatura que podem ocorrer devido a nomes de propriedade não exclusivos em cargas de plug-in OpenAPI.

Quando o espaçamento de nomes está habilitado, o Kernel Semântico fornece à LLM metadados de operação OpenAPI que incluem nomes de propriedades aumentadas. Esses nomes aumentados são criados ao adicionar o nome da propriedade pai como prefixo, separado por um ponto, aos nomes das propriedades filhas.

Por exemplo, se a operação de change_light_state tiver 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"
  }
}

Kernel semântico teria fornecido metadados à LLM 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 aumentadas para a LLM, o Kernel Semântico executa as seguintes etapas:

  1. Manipule a chamada LLM para a operação OpenAPI e procure os argumentos correspondentes entre aqueles fornecidos pela LLM para todas as propriedades no payload, usando os nomes de propriedade aumentados e recorrendo aos nomes de propriedade originais, se 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 o conteúdo construído para a API.

Por padrão, a opção de espaçamento de nomes de conteúdo está desabilitada. Ele pode ser habilitado 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
    });

Nota

A opção EnablePayloadNamespace só tem efeito quando a construção de conteúdo dinâmico também está habilitada; caso contrário, não terá efeito.

O parâmetro de carga útil

O Kernel Semântico pode trabalhar com payloads criadas pela LLM usando o parâmetro de payload. Isso é útil quando o esquema de conteúdo é complexo e contém nomes de propriedade não exclusivos, o que torna inviável que Kernel Semântico construa dinamicamente o conteúdo. Nesses casos, você dependerá da capacidade da LLM de entender o esquema e construir um payload válido. Modelos recentes, como gpt-4o, são eficazes na geração de cargas JSON válidas.

Para habilitar o parâmetro de conteúdo, defina a propriedade EnableDynamicPayload para 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á habilitado, o Kernel Semântico fornece à LLM metadados para a operação que inclui esquemas para os parâmetros payload e content_type, permitindo que a LLM entenda a estrutura do payload e a construa adequadamente:

{
    "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 de operação com o esquema para os parâmetros de carga útil e tipo de conteúdo para o LLM, o Kernel Semântico executa as seguintes etapas:

  1. Manipula a chamada LLM para a operação OpenAPI e utiliza argumentos fornecidos pela LLM para os parâmetros payload e content_type.
  2. Envie a solicitação HTTP para a API com o payload e o tipo de conteúdo fornecido.

URL base do servidor

Os plug-ins OpenAPI do Kernel Semântico exigem uma URL base, que é usada para preparar caminhos de ponto de extremidade ao fazer solicitações de API. Essa URL base pode ser especificada no documento OpenAPI, obtida implicitamente carregando o documento de uma URL ou fornecida ao adicionar o plug-in ao kernel.

Url especificada 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.

Por outro lado, 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.

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

{
   "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 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 Kernel Semântico usará a URL base do servidor do qual o documento OpenAPI foi carregado:

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

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

Substituindo a URL do servidor

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

O Kernel Semântico permite substituir 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 da qual o documento foi carregado.

Autenticação

A maioria das APIs REST exige 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 plug-ins 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 snippet 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 compatíveis com uma API, você pode usar metadados de documento e operação para obter essas informações. Para obter mais detalhes, consulte Documento e metadados de operação.

Personalização da leitura do conteúdo da resposta

O Kernel Semântico tem um mecanismo interno para ler o conteúdo de respostas HTTP de plug-ins OpenAPI e convertê-las nos 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 interno é insuficiente para suas necessidades. Por exemplo, quando a resposta é um objeto JSON grande ou uma 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 convertê-lo de volta em um fluxo pode ser ineficiente e pode levar a problemas de desempenho. Para resolver isso, o Kernel Semântico permite a personalização de 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 qualquer outro tipo de conteúdo, indicando que o leitor de conteúdo padrão deve ser usado.

Metadados de documento e operação

O Kernel Semântico extrai metadados de documento e operação do OpenAPI, incluindo informações de API, esquemas de segurança, ID da operação, descrição, metadados de parâmetro e muito mais. Ele fornece acesso a essas informações por meio 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 da operação para determinar o esquema de autenticação. Em seguida, recupera a chave de API, para o esquema e 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 de API, incluindo título, descrição e versão.
operação RestApiOperation Detalhes da operação da API, como id, descrição, caminho, método etc.
segurança IList<RestApiSecurityRequirement> Requisitos de segurança de API – tipo, nome, etc.

Dicas e truques para adicionar plug-ins OpenAPI

Como as especificações do OpenAPI são normalmente projetadas para humanos, talvez seja necessário 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 de suas especificações de API Em vez de apontar para uma especificação de API dinâmica, considere arquivar e versionar o arquivo Swagger. Isso permitirá que os pesquisadores de IA testem (e alterem) a especificação da API usada pelo agente de IA sem afetar a API dinâmica e vice-versa.
Limitar o número de pontos de extremidade Tente limitar o número de endpoints em sua API. Consolide funcionalidades semelhantes em pontos de extremidade únicos com parâmetros opcionais para reduzir a complexidade.
Usar nomes descritivos para pontos de extremidade e parâmetros Verifique se os nomes de seus pontos de extremidade e parâmetros são descritivos e autoexplicativos. Isso ajuda a IA a entender sua finalidade sem precisar de explicações abrangentes.
Usar 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 API com mais facilidade.
Simplifique suas especificações de API Muitas vezes, as especificações do 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 que a IA precisa para enviar solicitações a ela.
Evitar 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 enumerações. Isso ajudará a IA a entender melhor a API.
Fornecer exemplos em descrições Quando humanos usam arquivos Swagger, eles normalmente conseguem testar a API usando a interface do usuário 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.
Referenciar outros pontos de extremidade em descrições Muitas vezes, as IAs confundem endpoints semelhantes. Para ajudar a IA a diferenciar entre pontos de extremidade, considere referenciar outros pontos de extremidade nas descrições. Por exemplo, você poderia dizer "Este ponto de extremidade é semelhante ao ponto de extremidade get_all_lights, mas ele retorna apenas uma única luz".
Fornecer mensagens de erro úteis Embora não seja dentro da especificação OpenAPI, considere fornecer mensagens de erro que ajudam a IA a se auto-corrigir. 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óximas etapas

Agora que você sabe como criar um plug-in, agora você pode aprender a usá-los com seu agente de IA. Dependendo do tipo de funções que você adicionou aos plug-ins, há padrões diferentes que você deve seguir. Para recuperar funções, consulte o artigo sobre o uso de funções de recuperação referente ao . Para funções de automação de tarefas, consulte o usando funções de automação de tarefas artigo.