Condividi tramite


Aggiungere plug-in dalle specifiche OpenAPI

Spesso in un'azienda si dispone già di un set di API che eseguono un lavoro reale. Questi possono essere usati da altri servizi di automazione o da applicazioni front-end che interagiscono con gli esseri umani. Nel kernel semantico è possibile aggiungere queste API identiche ai plug-in in modo che gli agenti possano usarle anche.

Specifica OpenAPI di esempio

Si prenda ad esempio un'API che consente di modificare lo stato delle lampadine. La specifica OpenAPI, nota come specifica Swagger o solo Swagger, per questa API potrebbe essere simile alla seguente:

{
   "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
         }
      }
   }
}

Questa specifica fornisce tutto ciò che serve all'intelligenza artificiale per comprendere l'API e come interagire con esso. L'API include due endpoint: uno per ottenere tutte le luci e un altro per modificare lo stato di una luce. Fornisce anche quanto segue:

  • Descrizioni semantiche per gli endpoint e i relativi parametri
  • Tipi di parametri
  • Risposte previste

Poiché l'agente di intelligenza artificiale può comprendere questa specifica, è possibile aggiungerlo come plug-in all'agente.

Il kernel semantico supporta le versioni OpenAPI 2.0 e 3.0 e mira a soddisfare le specifiche della versione 3.1 eseguendo il downgrade alla versione 3.0.

Mancia

Se sono presenti specifiche OpenAPI, potrebbe essere necessario apportare modifiche per semplificarne la comprensione da parte di un'intelligenza artificiale. Ad esempio, potrebbe essere necessario fornire indicazioni nelle descrizioni. Per altri suggerimenti su come rendere le specifiche OpenAPI compatibili con l'intelligenza artificiale, vedere Suggerimenti e consigli per l'aggiunta di plug-in OpenAPI.

Aggiunta del plug-in OpenAPI

Con alcune righe di codice, è possibile aggiungere il plug-in OpenAPI all'agente. Il frammento di codice seguente illustra come aggiungere il plug-in light dalla specifica OpenAPI precedente:

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

Con il kernel semantico è possibile aggiungere plug-in OpenAPI da varie origini, ad esempio un URL, un file o un flusso. Inoltre, i plug-in possono essere creati una volta e riutilizzati in più istanze del kernel o agenti.

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

Successivamente, è possibile usare il plug-in nell'agente come se fosse un plug-in nativo.

Gestione dei parametri del plug-in OpenAPI

Il kernel semantico estrae automaticamente i metadati, ad esempio nome, descrizione, tipo e schema per tutti i parametri definiti nei documenti OpenAPI. Questi metadati vengono archiviati nella proprietà KernelFunction.Metadata.Parameters per ogni operazione OpenAPI e vengono forniti all'LLM insieme al prompt per generare gli argomenti corretti per le chiamate di funzione.

Per impostazione predefinita, il nome del parametro originale viene fornito all'LLM e viene usato dal kernel semantico per cercare l'argomento corrispondente nell'elenco di argomenti forniti da LLM. Tuttavia, in alcuni casi il plug-in OpenAPI ha più parametri con lo stesso nome. Se si specificano i metadati di questo parametro all'LLM, si potrebbe creare confusione, impedendo potenzialmente all'LLM di generare gli argomenti corretti per le chiamate di funzione.

Inoltre, poiché viene creata una funzione kernel che non consente nomi di parametri non univoci per ogni operazione OpenAPI, l'aggiunta di tale plug-in potrebbe comportare la mancata disponibilità di alcune operazioni per l'uso. In particolare, le operazioni con nomi di parametri non univoci verranno ignorate e verrà registrato un avviso corrispondente. Anche se fosse possibile includere più parametri con lo stesso nome nella funzione kernel, ciò potrebbe causare ambiguità nel processo di selezione dell'argomento.

Considerando tutto questo, Semantic Kernel offre una soluzione per la gestione dei plug-in con nomi di parametri non univoci. Questa soluzione è particolarmente utile quando modificare l'API stessa non è possibile, sia che si tratti di un servizio di terze parti o di un sistema legacy.

Il frammento di codice seguente illustra come gestire i nomi di parametri non univoci in un plug-in OpenAPI. Se l'operazione change_light_state ha un parametro aggiuntivo con lo stesso nome del parametro "id" esistente, in particolare per rappresentare un ID sessione oltre all'"id" corrente che rappresenta l'ID della luce, potrebbe essere gestito come illustrato di seguito:

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

Questo frammento di codice usa la classe OpenApiDocumentParser per analizzare il documento OpenAPI e accedere all'oggetto modello RestApiSpecification che rappresenta il documento. Assegna i nomi degli argomenti ai parametri e importa la specifica del plugin OpenAPI trasformata nel kernel. Il kernel semantico fornisce i nomi degli argomenti all'LLM anziché i nomi originali e li usa per cercare gli argomenti corrispondenti nell'elenco fornito dall'LLM.

È importante notare che i nomi dei parametri non vengono usati al posto dei nomi originali quando si chiama l'operazione OpenAPI. Nell'esempio precedente, il parametro 'id' nel percorso verrà sostituito da un valore restituito dall'LLM per l'argomento 'lightId'. Lo stesso vale per il parametro di intestazione 'id'; il valore restituito dall'LLM per l'argomento 'sessionId' verrà usato come valore per l'intestazione denominata 'id'.

Gestione del payload dei plug-in OpenAPI

I plug-in OpenAPI possono modificare lo stato del sistema usando operazioni POST, PUT o PATCH. Queste operazioni richiedono spesso un payload da includere nella richiesta.

Il kernel semantico offre alcune opzioni per la gestione del payload per i plug-in OpenAPI, a seconda dello scenario specifico e dei requisiti dell'API.

Costruzione dinamica del payload

La costruzione dinamica del payload consente di creare i payload delle operazioni OpenAPI in modo dinamico in base allo schema del payload e agli argomenti forniti dall'LLM. Questa funzionalità è abilitata per impostazione predefinita, ma può essere disabilitata impostando la proprietà EnableDynamicPayload su false nell'oggetto OpenApiFunctionExecutionParameters quando si aggiunge un plug-in OpenAPI.

Si consideri ad esempio l'operazione di change_light_state, che richiede un payload strutturato come segue:

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

Per modificare lo stato della luce e ottenere i valori per le proprietà del payload, il kernel semantico fornisce i metadati all'LLM per l'operazione in modo che possa ragionare su di essa.

{
    "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"}},
    ]
}

Oltre a fornire i metadati dell'operazione all'LLM, il kernel semantico eseguirà i passaggi seguenti:

  1. Gestire la chiamata LLM all'operazione OpenAPI, costruendo il payload in base allo schema e ai valori delle proprietà forniti dall'LLM.
  2. Inviare la richiesta HTTP con il payload all'API.

Limitazioni della costruzione dinamica del payload

La costruzione dinamica del payload è più efficace per le API con strutture di payload relativamente semplici. Potrebbe non funzionare in modo affidabile o non funzionare affatto, per i payload delle API che presentano le seguenti caratteristiche:

  • Payload con nomi di proprietà non univoci indipendentemente dalla posizione delle proprietà. Ad esempio, due proprietà denominate id, una per l'oggetto mittente e un'altra per l'oggetto ricevitore- json { "sender": { "id": ... }, "receiver": { "id": ... }}
  • Schemi di payload che usano una delle parole chiave composite oneOf, anyOf, allOf.
  • Schemi di payload con riferimenti ricorsivi. Ad esempio, json { "parent": { "child": { "$ref": "#parent" } } }

Per gestire i payload con nomi di proprietà non univoci, considerare le alternative seguenti:

  • Specificare un nome di argomento univoco per ogni proprietà non univoca, usando un metodo simile a quello descritto nella sezione Gestione dei parametri del plug-in OpenAPI.
  • Usare i namespace per evitare conflitti di denominazione, come indicato nella sezione successiva su Payload namespacing.
  • Disabilitare la costruzione dinamica del payload e consentire a LLM di creare il payload in base al relativo schema, come illustrato nella sezione Il parametro di payload.

Se gli schemi di payload usano uno dei oneOf, anyOf, allOf parole chiave composite o riferimenti ricorsivi, valutare la possibilità di disabilitare la costruzione dinamica del payload e consentire a LLM di creare il payload in base al relativo schema, come illustrato nella sezione Il parametro payload.

Spaziatura dei payload

Il payload namespacing consente di evitare conflitti di denominazione che possono verificarsi a causa di nomi di proprietà non univoci nei payload del plug-in OpenAPI.

Quando il namespacing è abilitato, Semantic Kernel fornisce LLM con i metadati dell'operazione OpenAPI che includono nomi di proprietà aumentati. Questi nomi aumentati vengono creati aggiungendo il nome della proprietà padre come prefisso, separato da un punto, ai nomi delle proprietà figlio.

Ad esempio, se l'operazione di change_light_state includeva un oggetto offTimer annidato con una proprietà scheduledTime:

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

Il Kernel Semantico avrebbe fornito all'LLM i metadati per l'operazione, includendo i seguenti nomi di proprietà:

{
    "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"}},
    ]
}

Oltre a fornire i metadati dell'operazione con nomi di proprietà aumentate a LLM, il Kernel Semantico esegue i passaggi seguenti:

  1. Gestisci la chiamata LLM all'operazione OpenAPI e individua gli argomenti corrispondenti tra quelli forniti dal LLM per tutte le proprietà del payload, usando i nomi delle proprietà aumentate e ritornando ai nomi delle proprietà originali, se necessario.
  2. Costruire il payload usando i nomi delle proprietà originali come chiavi e gli argomenti risolti come valori.
  3. Inviare la richiesta HTTP con il payload costruito all'API.

Per impostazione predefinita, l'opzione payload namespacing è disabilitata. Può essere abilitata impostando la proprietà EnablePayloadNamespacing su true nell'oggetto OpenApiFunctionExecutionParameters quando si aggiunge un 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

L'opzione EnablePayloadNamespace diventa effettiva solo quando è abilitata anche la costruzione dinamica del payload; in caso contrario, non ha alcun effetto.

Parametro di payload

Il kernel semantico può funzionare con i payload creati da LLM usando il parametro payload. Ciò è utile quando lo schema del payload è complesso e contiene nomi di proprietà non univoci, il che rende impossibile per il Semantic Kernel costruire dinamicamente il payload. In questi casi, si farà affidamento sulla capacità dell'LLM di comprendere lo schema e costruire un payload valido. I modelli recenti, ad esempio gpt-4o, sono efficaci per generare payload JSON validi.

Per abilitare il parametro payload, impostare la proprietà EnableDynamicPayload su false nell'oggetto OpenApiFunctionExecutionParameters quando si aggiunge un 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 il parametro di payload è abilitato, il kernel semantico fornisce all'LLM i metadati per l'operazione che include gli schemi per il payload e i parametri content_type, consentendo a LLM di comprendere la struttura del payload e di crearla di conseguenza:

{
    "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."
        }
    }
}

Oltre a fornire i metadati dell'operazione con lo schema per i parametri del payload e del tipo di contenuto per LLM, il kernel semantico esegue i passaggi seguenti:

  1. Gestire la chiamata dell'LLM all'operazione OpenAPI e utilizzare gli argomenti forniti dall'LLM per i parametri payload e content_type.
  2. Inviare la richiesta HTTP all'API con il payload e il tipo di contenuto forniti.

URL di base del server

I plug-in OpenAPI del kernel semantico richiedono un URL di base, usato per anteporre i percorsi degli endpoint durante l'esecuzione di richieste API. Questo URL di base può essere specificato nel documento OpenAPI, ottenuto in modo implicito caricando il documento da un URL o fornito quando si aggiunge il plug-in al kernel.

URL specificato nel documento OpenAPI

I documenti OpenAPI v2 definiscono l'URL del server usando i campi schemes, hoste basePath:

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

Il kernel semantico creerà l'URL del server come https://example.com/v1.

Al contrario, i documenti OpenAPI v3 definiscono l'URL del server usando il campo servers:

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

Il kernel semantico userà il primo URL del server specificato nel documento come URL di base: https://example.com/v1.

OpenAPI v3 consente anche URL dei server parametrizzati utilizzando variabili indicate da parentesi graffe.

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

In questo caso, il Kernel Semantico sostituirà il segnaposto della variabile con il valore fornito come argomento per la variabile o con il valore predefinito se non è fornito alcun argomento, che risulta in un URL: https://prod.example.com/v1.

Se il documento OpenAPI non specifica alcun URL del server, il kernel semantico userà l'URL di base del server da cui è stato caricato il documento OpenAPI:

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

L'URL di base verrà impostato su https://api-host.com.

Sovrascrittura dell'URL del server

In alcuni casi, l'URL del server specificato nel documento OpenAPI o il server da cui è stato caricato il documento potrebbe non essere adatto per i casi d'uso che coinvolgono il plug-in OpenAPI.

Il kernel semantico consente di eseguire l'override dell'URL del server fornendo un URL di base personalizzato quando si aggiunge il plug-in OpenAPI al 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")  
    });  

In questo esempio, l'URL di base verrà https://custom-server.com/v1, ignorando l'URL del server specificato nel documento OpenAPI e l'URL del server da cui è stato caricato il documento.

Autenticazione

La maggior parte delle API REST richiede l'autenticazione per accedere alle risorse. Il kernel semantico fornisce un meccanismo che consente di integrare un'ampia gamma di metodi di autenticazione richiesti dai plug-in OpenAPI.

Questo meccanismo si basa su una funzione di callback di autenticazione, richiamata prima di ogni richiesta API. Questa funzione di callback ha accesso all'oggetto HttpRequestMessage, che rappresenta la richiesta HTTP che verrà inviata all'API. È possibile usare questo oggetto per aggiungere le credenziali di autenticazione alla richiesta. Le credenziali possono essere aggiunte come intestazioni, parametri di query o nel corpo della richiesta, a seconda del metodo di autenticazione usato dall'API.

È necessario registrare questa funzione di callback quando si aggiunge il plug-in OpenAPI al kernel. Il frammento di codice seguente illustra come registrarlo per autenticare le richieste:

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

Per scenari di autenticazione più complessi che richiedono l'accesso dinamico ai dettagli degli schemi di autenticazione supportati da un'API, è possibile usare i metadati del documento e dell'operazione per ottenere queste informazioni. Per altre informazioni, vedere metadati di documenti e operazioni.

Personalizzazione della lettura del contenuto della risposta

Il kernel semantico include un meccanismo predefinito per leggere il contenuto delle risposte HTTP dai plug-in OpenAPI e convertirli nei tipi di dati .NET appropriati. Ad esempio, una risposta di immagine può essere letta come matrice di byte, mentre una risposta JSON o XML può essere letta come stringa.

Tuttavia, potrebbero esserci situazioni in cui il meccanismo predefinito non è sufficiente per le vostre esigenze. Ad esempio, quando la risposta è un oggetto JSON o un'immagine di grandi dimensioni che deve essere letta come flusso per poter essere fornita come input a un'altra API. In questi casi, leggere il contenuto di una risposta come stringa o come array di byte e poi convertirlo nuovamente in un flusso può risultare inefficiente e causare problemi di prestazioni. Per risolvere questo problema, Semantic Kernel consente la personalizzazione della lettura del contenuto della risposta fornendo un lettore di contenuto personalizzato:

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

In questo esempio, il metodo ReadHttpResponseContentAsync legge il contenuto della risposta HTTP come flusso quando il tipo di contenuto è application/json o quando la richiesta contiene un'intestazione personalizzata x-stream. Il metodo restituisce null per qualsiasi altro tipo di contenuto, a indicare che deve essere utilizzato il lettore di contenuto predefinito.

Metadati del documento e dell'operazione

Il Kernel Semantico estrae i metadati del documento OpenAPI e delle operazioni, incluse informazioni sulle API, schemi di sicurezza, ID operazione, descrizione, metadati dei parametri e molto altro. Fornisce l'accesso a queste informazioni tramite la proprietà KernelFunction.Metadata.AdditionalParameters. Questi metadati possono essere utili negli scenari in cui sono necessarie informazioni aggiuntive sull'API o sull'operazione, ad esempio a scopo di autenticazione:

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

In questo esempio, il metodo AuthenticateRequestAsyncCallbackAsync legge i metadati dell'operazione dal contesto della funzione ed estrae i requisiti di sicurezza per l'operazione per determinare lo schema di autenticazione. Recupera quindi la chiave API, per lo schema e gli ambiti, dal provider di identità dell'app e la aggiunge alle intestazioni della richiesta o ai parametri di query.

Nella tabella seguente sono elencati i metadati disponibili nel dizionario KernelFunction.Metadata.AdditionalParameters:

Chiave Digitare Descrizione
Informazioni RestApiInfo Informazioni sulle API, tra cui titolo, descrizione e versione.
operazione RestApiOperation Dettagli dell'operazione API, ad esempio ID, descrizione, percorso, metodo e così via.
sicurezza IList<RestApiSecurityRequirement> Requisiti di sicurezza api: tipo, nome, contenuto e così via.

Suggerimenti e consigli per l'aggiunta di plug-in OpenAPI

Poiché le specifiche OpenAPI sono in genere progettate per gli esseri umani, potrebbe essere necessario apportare alcune modifiche per semplificarne la comprensione da parte di un'intelligenza artificiale. Ecco alcuni suggerimenti e trucchi per aiutarti a farlo:

Raccomandazione Descrizione
Versionare le specifiche della tua API Invece di puntare a una specifica dell'API dinamica, considerare di caricare e gestire le versioni del file Swagger. Ciò consentirà ai ricercatori di intelligenza artificiale di testare (e modificare) la specifica dell'API usata dall'agente di intelligenza artificiale senza influire sull'API dinamica e viceversa.
Limitare il numero di endpoint Provare a limitare il numero di endpoint nell'API. Consolidare funzionalità simili in singoli endpoint con parametri facoltativi per ridurre la complessità.
Usare nomi descrittivi per endpoint e parametri Assicurarsi che i nomi degli endpoint e dei parametri siano descrittivi e autoesplicativi. In questo modo l'intelligenza artificiale comprende il proprio scopo senza dover fornire spiegazioni complete.
Usare convenzioni di denominazione coerenti Mantenere convenzioni di denominazione coerenti in tutta l'API. Ciò riduce la confusione e aiuta l'intelligenza artificiale a apprendere e prevedere più facilmente la struttura dell'API.
Semplificare le specifiche dell'API Spesso, le specifiche OpenAPI sono molto dettagliate e includono molte informazioni che non sono necessarie per l'agente di intelligenza artificiale per aiutare un utente. Più semplice è l'API, il minor numero di token che è necessario spendere per descriverlo e il minor numero di token necessari all'intelligenza artificiale per inviare richieste.
Evitare di usare parametri di tipo stringa Quando possibile, evitare di usare parametri stringa nell'API. Usare invece tipi più specifici, ad esempio numeri interi, valori booleani o enumerazioni. Ciò consentirà all'intelligenza artificiale di comprendere meglio l'API.
Fornire esempi nelle descrizioni Quando gli esseri umani usano file Swagger, in genere sono in grado di testare l'API usando l'interfaccia utente di Swagger, che include richieste di esempio e risposte. Poiché l'agente di intelligenza artificiale non può eseguire questa operazione, prendere in considerazione la possibilità di fornire esempi nelle descrizioni dei parametri.
Fare riferimento ad altri endpoint nelle descrizioni Spesso, le IA confonderanno endpoint simili. Per facilitare la differenziazione dell'intelligenza artificiale tra gli endpoint, è consigliabile fare riferimento ad altri endpoint nelle descrizioni. Ad esempio, è possibile pronunciare "Questo endpoint è simile all'endpoint get_all_lights, ma restituisce solo una singola luce".
Fornire messaggi di errore utili Anche se non rientra nella specifica OpenAPI, è consigliabile fornire messaggi di errore che consentono di correggere automaticamente l'intelligenza artificiale. Ad esempio, se un utente fornisce un ID non valido, è consigliabile specificare un messaggio di errore che suggerisce all'agente di intelligenza artificiale di ottenere l'ID corretto dall'endpoint get_all_lights.

Passaggi successivi

Ora che si sa come creare un plug-in, è ora possibile imparare a usarli con l'agente di intelligenza artificiale. A seconda del tipo di funzioni aggiunte ai plug-in, ci sono modelli diversi da seguire. Per le funzioni di recupero, vedere l'articolo uso delle funzioni di recupero. Per le funzioni di automazione delle attività, consultare l'articolo sull'uso delle funzioni di automazione delle attività.