Ajouter des plug-ins à partir de spécifications OpenAPI
Souvent dans une entreprise, vous disposez déjà d’un ensemble d’API qui effectuent un travail réel. Ceux-ci peuvent être utilisés par d’autres services d’automatisation ou par des applications front-end avec lesquelles les humains interagissent. Dans le noyau sémantique, vous pouvez ajouter ces mêmes API que les plug-ins afin que vos agents puissent également les utiliser.
Exemple de spécification OpenAPI
Prenons un exemple d’API qui vous permet de modifier l’état des ampoules. La spécification OpenAPI, appelée Spécification Swagger, ou simplement Swagger, peut ressembler à ceci :
{
"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
}
}
}
}
Cette spécification fournit tout ce dont l’IA a besoin pour comprendre l’API et comment interagir avec elle. L’API inclut deux points de terminaison : un pour obtenir toutes les lumières et une autre pour changer l’état d’une lumière. Il fournit également les éléments suivants :
- Descriptions sémantiques des points de terminaison et de leurs paramètres
- Types des paramètres
- Réponses attendues
Étant donné que l’agent IA peut comprendre cette spécification, vous pouvez l’ajouter en tant que plug-in à l’agent.
Le noyau sémantique prend en charge les versions 2.0 et 3.0 d’OpenAPI, et vise à prendre en charge les spécifications de la version 3.1 en la rétrogradant vers la version 3.0.
Pourboire
Si vous avez des spécifications OpenAPI existantes, vous devrez peut-être apporter des modifications pour faciliter leur compréhension d’une IA. Par exemple, vous devrez peut-être fournir des conseils dans les descriptions. Pour plus d’informations sur la façon de rendre vos spécifications OpenAPI conviviales pour l’IA, consultez conseils et astuces pour ajouter des plug-ins OpenAPI.
Ajout du plug-in OpenAPI
Avec quelques lignes de code, vous pouvez ajouter le plug-in OpenAPI à votre agent. L’extrait de code suivant montre comment ajouter le plug-in light à partir de la spécification OpenAPI ci-dessus :
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
}
);
Avec le noyau sémantique, vous pouvez ajouter des plug-ins OpenAPI à partir de différentes sources, telles qu’une URL, un fichier ou un flux. En outre, les plug-ins peuvent être créés une fois et réutilisés sur plusieurs instances ou agents de noyau.
// 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();
Ensuite, vous pouvez utiliser le plug-in dans votre agent comme s’il s’agissait d’un plug-in natif.
Gestion des paramètres du plug-in OpenAPI
Le noyau sémantique extrait automatiquement les métadonnées, telles que le nom, la description, le type et le schéma pour tous les paramètres définis dans les documents OpenAPI.
Ces métadonnées sont stockées dans la propriété KernelFunction.Metadata.Parameters
pour chaque opération OpenAPI et sont fournies au LLM avec l’invite pour générer les arguments appropriés pour les appels de fonction.
Par défaut, le nom du paramètre d’origine est fourni au LLM et est utilisé par le noyau sémantique pour rechercher l’argument correspondant dans la liste des arguments fournis par le LLM. Toutefois, il se peut que le plug-in OpenAPI ait plusieurs paramètres portant le même nom. Fournir ces métadonnées de paramètre au LLM peut créer une confusion, ce qui peut empêcher le LLM de générer les arguments appropriés pour les appels de fonction.
En outre, étant donné qu’une fonction de noyau qui n’autorise pas les noms de paramètres non uniques est créée pour chaque opération OpenAPI, l’ajout d’un tel plug-in peut entraîner l’indisponibilité de certaines opérations pour une utilisation. Plus précisément, les opérations avec des noms de paramètres non uniques sont ignorées et un avertissement correspondant est enregistré. Même s’il était possible d’inclure plusieurs paramètres portant le même nom dans la fonction noyau, cela pourrait entraîner une ambiguïté dans le processus de sélection d’arguments.
Compte tenu de tout cela, le noyau sémantique offre une solution pour la gestion des plug-ins avec des noms de paramètres non uniques. Cette solution est particulièrement utile lorsque la modification de l’API elle-même n’est pas réalisable, que ce soit en raison d’un service tiers ou d’un système hérité.
L’extrait de code suivant montre comment gérer les noms de paramètres non uniques dans un plug-in OpenAPI. Si l’opération de change_light_state avait un paramètre supplémentaire portant le même nom que le paramètre « id » existant , en particulier, pour représenter un ID de session en plus de l’ID actuel « id » qui représente l’ID de la lumière , il peut être géré comme indiqué ci-dessous :
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);
Cet extrait de code utilise la classe OpenApiDocumentParser
pour analyser le document OpenAPI et accéder à l’objet de modèle RestApiSpecification
qui représente le document. Il affecte des noms d’arguments aux paramètres et importe la spécification de plug-in OpenAPI transformée dans le noyau. Le noyau sémantique fournit les noms d’arguments au LLM au lieu des noms d’origine et les utilise pour rechercher les arguments correspondants dans la liste fournie par le LLM.
Il est important de noter que les noms d’arguments ne sont pas utilisés à la place des noms d’origine lors de l’appel de l’opération OpenAPI. Dans l'exemple ci-dessus, le paramètre 'id' dans le chemin sera remplacé par une valeur retournée par le LLM pour l'argument 'lightId'. La même chose s’applique au paramètre d’en-tête 'id' ; la valeur retournée par le LLM pour l’argument « sessionId » sera utilisée comme valeur de l’en-tête nommé « id ».
Gestion de la charge utile des plug-ins OpenAPI
Les plug-ins OpenAPI peuvent modifier l’état du système à l’aide des opérations POST, PUT ou PATCH. Ces opérations nécessitent souvent l’inclusion d’une charge utile dans la requête.
Le noyau sémantique offre quelques options pour gérer la gestion des charges utiles pour les plug-ins OpenAPI, en fonction de votre scénario spécifique et des exigences d’API.
Construction de charge utile dynamique
La construction de charge utile dynamique permet de créer dynamiquement les charges utiles des opérations OpenAPI en fonction du schéma de charge utile et des arguments fournis par le LLM.
Cette fonctionnalité est activée par défaut, mais peut être désactivée en définissant la propriété EnableDynamicPayload
sur false
dans l’objet OpenApiFunctionExecutionParameters
lors de l’ajout d’un plug-in OpenAPI.
Par exemple, considérez l’opération de change_light_state, qui nécessite une charge utile structurée comme suit :
{
"isOn": true,
"hexColor": "#FF0000",
"brightness": 100,
"fadeDurationInMilliseconds": 500,
"scheduledTime": "2023-07-12T12:00:00Z"
}
Pour modifier l’état de la lumière et obtenir des valeurs pour les propriétés de charge utile, le noyau sémantique fournit le LLM avec des métadonnées pour l’opération afin qu’il puisse raisonner :
{
"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"}},
]
}
En plus de fournir des métadonnées d’opération au LLM, le noyau sémantique effectue les étapes suivantes :
- Gérez l’appel LLM à l’opération OpenAPI, en construisant la charge utile sur la base du schéma et des valeurs de propriété fournies par le LLM.
- Envoyez la requête HTTP avec la charge utile à l’API.
Limitations de la construction de charge utile dynamique
La construction de charge utile dynamique est la plus efficace pour les API avec des structures de charge utile relativement simples. Il se peut qu'il ne fonctionne pas du tout, ou bien qu'il ne fonctionne pas de manière fiable, pour les charges utiles des API présentant les caractéristiques suivantes :
- Charges utiles avec des noms de propriétés non uniques, quel que soit l’emplacement des propriétés. Par exemple, deux propriétés nommées
id
, une pour l’objet expéditeur et une autre pour l’objet récepteur -json { "sender": { "id": ... }, "receiver": { "id": ... }}
- Schémas de charge utile qui utilisent l’un des mots clés composites
oneOf
,anyOf
,allOf
. - Schémas de charge utile avec références récursives. Par exemple,
json { "parent": { "child": { "$ref": "#parent" } } }
Pour gérer les charges utiles avec des noms de propriétés non uniques, tenez compte des alternatives suivantes :
- Fournissez un nom d’argument unique pour chaque propriété non unique, à l’aide d’une méthode similaire à celle décrite dans les paramètres de plug-in OpenAPI Gestion des paramètres de plug-in OpenAPI section.
- Utilisez des espaces de noms pour éviter les conflits de nommage, comme indiqué dans la section suivante sur Payload.
- Désactivez la construction de charge utile dynamique et autorisez le LLM à créer la charge utile en fonction de son schéma, comme expliqué dans la section Le paramètre de charge utile.
Si les schémas de charge utile utilisent l’une des oneOf
, anyOf
, allOf
mots clés composites ou références récursives, envisagez de désactiver la construction de charge utile dynamique et de permettre au LLM de créer la charge utile en fonction de son schéma, comme expliqué dans la section Le paramètre de charge utile section.
Espacement des noms de charge utile
L’espacement des noms de charge utile permet d’éviter les conflits d’affectation de noms qui peuvent se produire en raison de noms de propriétés non uniques dans les charges utiles du plug-in OpenAPI.
Lorsque l’espacement des noms est activé, le noyau sémantique fournit le LLM avec les métadonnées d’opération OpenAPI qui incluent des noms de propriétés augmentées. Ces noms augmentés sont créés en ajoutant le nom de propriété parent, en tant que préfixe, aux noms de propriétés enfants, séparés par un point.
Par exemple, si l’opération de change_light_state avait inclus un objet offTimer
imbriqué avec une propriété scheduledTime
:
{
"isOn": true,
"hexColor": "#FF0000",
"brightness": 100,
"fadeDurationInMilliseconds": 500,
"scheduledTime": "2023-07-12T12:00:00Z",
"offTimer": {
"scheduledTime": "2023-07-12T12:00:00Z"
}
}
Le noyau sémantique aurait fourni le LLM avec des métadonnées pour l’opération qui inclut les noms de propriétés suivants :
{
"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"}},
]
}
En plus de fournir des métadonnées d’opération avec des noms de propriétés augmentées au LLM, le noyau sémantique effectue les étapes suivantes :
- Gérez l’appel LLM à l’opération OpenAPI et recherchez les arguments correspondants entre ceux fournis par le LLM pour toutes les propriétés de la charge utile, en utilisant les noms de propriétés augmentées et en revenant aux noms de propriétés d’origine si nécessaire.
- Construisez la charge utile à l’aide des noms de propriétés d’origine en tant que clés et des arguments résolus en tant que valeurs.
- Envoyez la requête HTTP avec la charge utile construite à l’API.
Par défaut, l’option d’espacement des noms de charge utile est désactivée. Elle peut être activée en définissant la propriété EnablePayloadNamespacing
sur true
dans l’objet OpenApiFunctionExecutionParameters
lors de l’ajout d’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
});
Note
L’option EnablePayloadNamespace
prend effet uniquement lorsque la construction de charge utile dynamique est également activée ; sinon, il n’a aucun effet.
Paramètre de charge utile
Le noyau sémantique peut fonctionner avec des charges utiles créées par le LLM à l’aide du paramètre de charge utile. Cela est utile lorsque le schéma de charge utile est complexe et contient des noms de propriétés non uniques, ce qui le rend impossible pour que le noyau sémantique construise la charge utile de manière dynamique.
Dans ce cas, vous allez compter sur la capacité du LLM à comprendre le schéma et à construire une charge utile valide. Les modèles récents, tels que gpt-4o
, sont efficaces pour générer des charges utiles JSON valides.
Pour activer le paramètre de charge utile, définissez la propriété EnableDynamicPayload
sur false
dans l’objet OpenApiFunctionExecutionParameters
lors de l’ajout d’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
});
Lorsque le paramètre de charge utile est activé, le noyau sémantique fournit le LLM avec des métadonnées pour l’opération qui inclut des schémas pour la charge utile et des paramètres de content_type, ce qui permet au LLM de comprendre la structure de charge utile et de la construire en conséquence :
{
"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."
}
}
}
En plus de fournir les métadonnées d'opération avec le schéma pour les paramètres de charge et de type de contenu au LLM, le noyau sémantique effectue les étapes suivantes :
- Gérez l’appel LLM à l’opération OpenAPI et utilisez les arguments fournis par le LLM pour la charge utile et les paramètres content_type.
- Envoyez la requête HTTP à l’API avec une charge utile et un type de contenu fournis.
URL de base du serveur
Les plug-ins OpenAPI du noyau sémantique nécessitent une URL de base, qui est utilisée pour ajouter les chemins d'accès à l'API lors de requêtes API. Cette URL de base peut être spécifiée dans le document OpenAPI, obtenue implicitement en chargeant le document à partir d’une URL ou fournie lors de l’ajout du plug-in au noyau.
URL spécifiée dans le document OpenAPI
Les documents OpenAPI v2 définissent l’URL du serveur à l’aide des champs schemes
, host
et basePath
:
{
"swagger": "2.0",
"host": "example.com",
"basePath": "/v1",
"schemes": ["https"]
...
}
Le noyau sémantique construit l’URL du serveur en tant que https://example.com/v1
.
En revanche, les documents OpenAPI v3 définissent l’URL du serveur à l’aide du champ servers
:
{
"openapi": "3.0.1",
"servers": [
{
"url": "https://example.com/v1"
}
],
...
}
Le noyau sémantique utilise la première URL du serveur spécifiée dans le document comme URL de base : https://example.com/v1
.
OpenAPI v3 autorise également les URL de serveur paramétrables à l’aide de variables indiquées par des accolades :
{
"openapi": "3.0.1",
"servers": [
{
"url": "https://{environment}.example.com/v1",
"variables": {
"environment": {
"default": "prod"
}
}
}
],
...
}
Dans ce cas, le Noyau Sémantique remplace l’espace réservé de variable par la valeur fournie en tant qu’argument pour la variable ou la valeur par défaut si aucun argument n’est fourni, ce qui entraîne l’URL : https://prod.example.com/v1
.
Si le document OpenAPI ne spécifie aucune URL de serveur, le noyau sémantique utilise l’URL de base du serveur à partir duquel le document OpenAPI a été chargé :
await kernel.ImportPluginFromOpenApiAsync(pluginName: "lights", uri: new Uri("https://api-host.com/swagger.json"));
L’URL de base sera https://api-host.com
.
Remplacement de l’URL du serveur
Dans certains cas, l’URL du serveur spécifiée dans le document OpenAPI ou le serveur à partir duquel le document a été chargé peut ne pas convenir aux cas d’usage impliquant le plug-in OpenAPI.
Le noyau sémantique vous permet de remplacer l’URL du serveur en fournissant une URL de base personnalisée lors de l’ajout du plug-in OpenAPI au noyau :
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")
});
Dans cet exemple, l’URL de base sera https://custom-server.com/v1
, en remplaçant l’URL du serveur spécifiée dans le document OpenAPI et l’URL du serveur à partir de laquelle le document a été chargé.
Authentification
La plupart des API REST nécessitent l’authentification pour accéder à leurs ressources. Le noyau sémantique fournit un mécanisme qui vous permet d’intégrer diverses méthodes d’authentification requises par les plug-ins OpenAPI.
Ce mécanisme s’appuie sur une fonction de rappel d’authentification, appelée avant chaque demande d’API. Cette fonction de rappel a accès à l’objet HttpRequestMessage, représentant la requête HTTP qui sera envoyée à l’API. Vous pouvez utiliser cet objet pour ajouter des informations d’identification d’authentification à la demande. Les informations d’identification peuvent être ajoutées en tant qu’en-têtes, paramètres de requête ou dans le corps de la requête, selon la méthode d’authentification utilisée par l’API.
Vous devez inscrire cette fonction de rappel lors de l’ajout du plug-in OpenAPI au noyau. L’extrait de code suivant montre comment l’inscrire pour authentifier les demandes :
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
});
Pour les scénarios d’authentification plus complexes qui nécessitent un accès dynamique aux détails des schémas d’authentification pris en charge par une API, vous pouvez utiliser des métadonnées de document et d’opération pour obtenir ces informations. Pour plus d'informations, consultez les métadonnées des documents et des opérations .
Personnalisation de la lecture du contenu de réponse
Le noyau sémantique a un mécanisme intégré pour lire le contenu des réponses HTTP à partir de plug-ins OpenAPI et les convertir en types de données .NET appropriés. Par exemple, une réponse d’image peut être lue sous la forme d’un tableau d’octets, tandis qu’une réponse JSON ou XML peut être lue sous forme de chaîne.
Toutefois, il peut arriver que le mécanisme intégré soit insuffisant pour vos besoins. Par exemple, lorsque la réponse est un objet OU une image JSON volumineux qui doit être lu en tant que flux afin d’être fournie en tant qu’entrée à une autre API. Dans ce cas, la lecture du contenu de la réponse sous forme de chaîne ou de tableau d’octets, puis sa conversion en flux peut être inefficace et peut entraîner des problèmes de performances. Pour résoudre ce problème, le noyau sémantique permet la personnalisation de la lecture du contenu de réponse en fournissant un lecteur de contenu personnalisé :
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
});
Dans cet exemple, la méthode ReadHttpResponseContentAsync
lit le contenu de la réponse HTTP en tant que flux lorsque le type de contenu est application/json
ou lorsque la requête contient un en-tête personnalisé x-stream
. La méthode retourne null
pour tous les autres types de contenu, indiquant que le lecteur de contenu par défaut doit être utilisé.
Métadonnées de document et d’opération
Le noyau sémantique extrait les métadonnées de document et d’opération OpenAPI, notamment les informations d’API, les schémas de sécurité, l’ID d’opération, la description, les métadonnées de paramètre et bien plus encore.
Il fournit l’accès à ces informations via la propriété KernelFunction.Metadata.AdditionalParameters
. Ces métadonnées peuvent être utiles dans les scénarios où des informations supplémentaires sur l’API ou l’opération sont requises, comme à des fins d’authentification :
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");
Dans cet exemple, la méthode AuthenticateRequestAsyncCallbackAsync
lit les métadonnées de l’opération à partir du contexte de fonction et extrait les exigences de sécurité de l’opération pour déterminer le schéma d’authentification. Il récupère ensuite la clé API, pour le schéma et les étendues, à partir du fournisseur d’identité d’application et l’ajoute aux en-têtes de requête ou aux paramètres de requête.
Le tableau suivant répertorie les métadonnées disponibles dans le dictionnaire KernelFunction.Metadata.AdditionalParameters
:
Clé | Genre | Description |
---|---|---|
informations | RestApiInfo |
Informations sur l’API, notamment le titre, la description et la version. |
opération | RestApiOperation |
Détails de l’opération d’API, tels que l’ID, la description, le chemin d’accès, la méthode, etc. |
sécurité | IList<RestApiSecurityRequirement > |
Exigences de sécurité de l’API : type, nom, in, etc. |
Conseils et astuces pour l’ajout de plug-ins OpenAPI
Étant donné que les spécifications OpenAPI sont généralement conçues pour les humains, vous devrez peut-être apporter certaines modifications pour faciliter leur compréhension d’une IA. Voici quelques conseils et astuces pour vous aider à le faire :
Recommandation | Description |
---|---|
Contrôle de version de vos spécifications d’API | Au lieu de pointer vers une spécification d'API active, envisagez l'enregistrement et le versionnage de votre fichier Swagger. Cela permettra à vos chercheurs d’IA de tester (et de modifier) la spécification de l’API utilisée par l’agent IA sans affecter l’API active et vice versa. |
Limiter le nombre de points de terminaison | Essayez de limiter le nombre de points de terminaison dans votre API. Regroupez des fonctionnalités similaires dans des points de terminaison uniques avec des paramètres facultatifs pour réduire la complexité. |
Utiliser des noms descriptifs pour les points de terminaison et les paramètres | Vérifiez que les noms de vos points de terminaison et paramètres sont descriptifs et explicites. Cela aide l’IA à comprendre son objectif sans avoir besoin d’explications approfondies. |
Utiliser des conventions d’affectation de noms cohérentes | Conservez des conventions d’affectation de noms cohérentes tout au long de votre API. Cela réduit la confusion et aide l’IA à apprendre et à prédire plus facilement la structure de votre API. |
simplifier vos spécifications d’API | Souvent, les spécifications OpenAPI sont très détaillées et incluent beaucoup d’informations qui ne sont pas nécessaires pour que l’agent IA aide un utilisateur. Plus l’API est simple, le moins de jetons que vous devez dépenser pour le décrire et le moins de jetons dont l’IA a besoin pour envoyer des demandes. |
Éviter les paramètres de chaîne | Si possible, évitez d’utiliser des paramètres de chaîne dans votre API. Utilisez plutôt des types plus spécifiques tels que des entiers, des booléens ou des énumérations. Cela aidera l’IA à mieux comprendre l’API. |
Fournir des exemples dans les descriptions | Lorsque les humains utilisent des fichiers Swagger, ils peuvent généralement tester l’API à l’aide de l’interface utilisateur Swagger, qui inclut des exemples de requêtes et de réponses. Étant donné que l’agent IA ne peut pas le faire, envisagez de fournir des exemples dans les descriptions des paramètres. |
Référencer d’autres points de terminaison dans les descriptions | Souvent, les IA confondent les points de terminaison similaires. Pour aider l’IA à différencier les points de terminaison, envisagez de référencer d’autres points de terminaison dans les descriptions. Par exemple, vous pouvez dire « Ce point de terminaison est similaire au point de terminaison get_all_lights , mais il ne retourne qu’une seule lumière ». |
Fournir des messages d’erreur utiles | Bien qu’il ne se trouve pas dans la spécification OpenAPI, envisagez de fournir des messages d’erreur qui aident l’IA à corriger automatiquement. Par exemple, si un utilisateur fournit un ID non valide, envisagez de fournir un message d’erreur qui suggère que l’agent IA obtient l’ID correct à partir du point de terminaison get_all_lights . |
Étapes suivantes
Maintenant que vous savez comment créer un plug-in, vous pouvez maintenant apprendre à les utiliser avec votre agent IA. Selon le type de fonctions que vous avez ajoutées à vos plug-ins, vous devez suivre différents modèles. Pour les fonctions de récupération, reportez-vous à l'article utilisant les fonctions de récupération. Pour les fonctions d'automatisation des tâches, reportez-vous à l'article utilisant les fonctions d'automatisation des tâches.