从 OpenAPI 规范添加插件

通常,企业中已有一组执行实际工作的 API。 这些应用可由人类与之交互的其他自动化服务或电源前端应用程序使用。 在语义内核中,可以添加这些与插件完全相同的 API,以便代理也可以使用它们。

OpenAPI 规范示例

例如,允许更改灯泡状态的 API。 此 API 的 OpenAPI 规范(称为 Swagger 规范或仅 Swagger)可能如下所示:

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

此规范提供 AI 所需的所有内容来了解 API 以及如何与之交互。 API 包括两个终结点:一个用于获取所有灯光,另一个终结点用于更改光线的状态。 它还提供以下内容:

  • 终结点及其参数的语义说明
  • 参数的类型
  • 预期响应

由于 AI 代理可以理解此规范,因此可以将它作为插件添加到代理。

语义内核支持 OpenAPI 版本 2.0 和 3.0,旨在通过将版本降级到版本 3.0 来适应版本 3.1 规范。

提示

如果有现有的 OpenAPI 规范,可能需要进行更改,以便 AI 更容易理解它们。 例如,可能需要在说明中提供指导。 有关如何使 OpenAPI 规范 AI 友好的详细信息,请参阅 有关添加 OpenAPI 插件的提示和技巧。

添加 OpenAPI 插件

使用几行代码,可以将 OpenAPI 插件添加到代理。 以下代码片段演示如何从上面的 OpenAPI 规范中添加浅色插件:

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

使用语义内核,可以从各种源(例如 URL、文件或流)添加 OpenAPI 插件。 此外,可以创建一次插件,并在多个内核实例或代理之间重复使用。

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

之后,可以在代理中使用插件,就像它是本机插件一样。

处理 OpenAPI 插件参数

语义内核会自动提取元数据,例如 OpenAPI 文档中定义的所有参数的名称、说明、类型和架构。 此元数据存储在每个 OpenAPI 操作的 KernelFunction.Metadata.Parameters 属性中,并提供给 LLM,并提示为函数调用生成正确的参数。

默认情况下,原始参数名称提供给 LLM,语义内核使用该名称在 LLM 提供的参数列表中查找相应的参数。 但是,在某些情况下,OpenAPI 插件具有多个具有相同名称的参数。 向 LLM 提供此参数元数据可能会导致混淆,从而可能阻止 LLM 为函数调用生成正确的参数。

此外,由于为每个 OpenAPI 操作创建不允许使用非唯一参数名称的内核函数,因此添加此类插件可能会导致某些操作不可用。 具体而言,将跳过具有非唯一参数名称的操作,并记录相应的警告。 即使可以在内核函数中包含具有相同名称的多个参数,这可能会导致参数选择过程中存在歧义。

考虑到这一切,语义内核提供了一个解决方案,用于管理具有非唯一参数名称的插件。 如果更改 API 本身不可行,无论是第三方服务还是旧系统,此解决方案尤其有用。

以下代码片段演示如何在 OpenAPI 插件中处理非唯一参数名称。 如果change_light_state操作具有与现有“id”参数同名的其他参数-具体而言,除了表示光的 ID 的当前“id”以外,还可以处理会话 ID,如下所示:

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

此代码片段利用 OpenApiDocumentParser 类分析 OpenAPI 文档并访问表示文档的 RestApiSpecification 模型对象。 它将参数名称分配给参数,并将转换后的 OpenAPI 插件规范导入内核。 语义内核向 LLM 提供参数名称,而不是原始名称,并使用这些名称在 LLM 提供的列表中查找相应的参数。

请务必注意,参数名称在调用 OpenAPI 操作时不会代替原始名称。 在上面的示例中,路径中的“id”参数将替换为 LLM 为“lightId”参数返回的值。 这同样适用于“id”标头参数;LLM 为“sessionId”参数返回的值将用作名为“id”的标头的值。

处理 OpenAPI 插件载荷

OpenAPI 插件可以使用 POST、PUT 或 PATCH 操作修改系统的状态。 这些操作通常需要将有效负载包含在请求中。

Semantic Kernel 提供了一些选项用于管理 OpenAPI 插件的有效负载处理,这取决于您的具体情况和 API 要求。

动态有效负载构造

动态有效负载构造允许根据 LLM 提供的有效负载架构和参数动态创建 OpenAPI 操作的有效负载。 此功能默认启用,但可以通过在添加 OpenAPI 插件时将 EnableDynamicPayload 属性设置为 falseOpenApiFunctionExecutionParameters 对象中来禁用此功能。

例如,请考虑change_light_state操作,该操作需要按如下所示构建的有效负载:

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

若要更改光线的状态并获取有效负载属性的值,语义内核为 LLM 提供操作的元数据,以便它可以进行推理:

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

除了向 LLM 提供操作元数据外,语义内核还将执行以下步骤:

  1. 处理对 OpenAPI 操作的 LLM 调用,基于架构构建有效负载,并使用 LLM 的属性值来提供。
  2. 将携带载荷的 HTTP 请求发送到 API。

动态有效负载构造的限制

动态有效负载构造对于具有相对简单有效负载结构的 API 最有效。 对于具有以下特征的 API 有效负载,它可能工作不稳定或根本不起作用。

  • 无论属性的位置如何,具有非唯一属性名称的负载信息。 例如,名为 id的两个属性,一个用于发送方对象,另一个用于接收方对象 - json { "sender": { "id": ... }, "receiver": { "id": ... }}
  • 使用任何复合关键字的负载模式 oneOfanyOfallOf
  • 具有递归引用的有效负载架构。 例如,json { "parent": { "child": { "$ref": "#parent" } } }

若要处理具有非唯一属性名称的有效负载,请考虑以下替代方法:

  • 为每个非唯一属性提供唯一参数名称,方法是使用类似于 处理 OpenAPI 插件参数 节中所述的方法。
  • 使用命名空间来避免命名冲突,详情请参阅 \载荷命名空间 \的下一部分。
  • 禁用动态有效负载构造并允许 LLM 基于其架构创建有效负载,如 有效负载参数 部分中所述。

如果有效负载架构使用任何 oneOfanyOfallOf 复合关键字或递归引用,请考虑禁用动态有效负载构造并允许 LLM 根据其架构创建有效负载,如 有效负载参数 部分中所述。

有效负载命名空间

有效负载名称有助于防止由于 OpenAPI 插件有效负载中非唯一属性名称而导致的命名冲突。

开启名称空间功能后,语义内核为 LLM 提供包含扩展属性名称的 OpenAPI 操作的元数据。 这些扩展名称是通过在子属性名称前加上用点分隔的父属性名称作为前缀来创建的。

例如,如果 change_light_state 操作包含一个嵌套的 offTimer 对象,并且该对象具有 scheduledTime 属性:

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

语义内核将为 LLM 提供包含以下属性名称的操作元数据:

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

除了向 LLM 提供具有扩充属性名称的操作元数据外,语义内核还执行以下步骤:

  1. 处理对 OpenAPI 操作的大语言模型调用,并在 LLM 提供的参数中查找与有效负载中所有属性相对应的参数,使用增强的属性名称,并在必要时回退到原始属性名称。
  2. 使用原始属性名称作为键和解析的参数作为值构造数据载荷。
  3. 将构造的有效负载与 HTTP 请求一起发送到 API。

默认情况下,负载命名空间选项处于禁用状态。 可以通过在添加 OpenAPI 插件时将 EnablePayloadNamespacing 属性设置为 OpenApiFunctionExecutionParameters 对象中的 true 来启用它:

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

注意

EnablePayloadNamespace 选项仅在启用动态有效负载构造时生效;否则,它不起作用。

有效负载参数

语义内核可以通过有效负载参数来使用由 LLM 创建的有效负载。 当有效负载架构复杂且包含非唯一属性名称时,这非常有用,这使得语义内核无法动态构造有效负载。 在这种情况下,你将依赖于 LLM 理解模式并构建有效载荷的能力。 最近的模型(如 gpt-4o)在生成有效的 JSON 负载时表现良好。

若要启用有效负载参数,请在添加 OpenAPI 插件时将 EnableDynamicPayload 属性设置为 OpenApiFunctionExecutionParameters 对象中的 false

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

启用有效负载参数后,语义内核为 LLM 提供包含有效负载架构和content_type参数的操作的元数据,使 LLM 能够了解有效负载结构并相应地构造它:

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

除了向 LLM 提供包含有效负载和内容类型参数架构的操作元数据外,语义内核还执行以下步骤:

  1. 处理对 OpenAPI 操作的 LLM 调用,并使用 LLM 为有效负载和content_type参数提供的参数。
  2. 使用提供的有效负载和内容类型将 HTTP 请求发送到 API。

服务器基础URL

语义内核 OpenAPI 插件需要一个基 URL,该 URL 用于在发出 API 请求时追加终结点路径。 此基 URL 可以在 OpenAPI 文档中指定,可通过从 URL 加载文档来隐式获取,或者在将插件添加到内核时提供。

OpenAPI 文档中指定的 URL

OpenAPI v2 文档使用 schemeshostbasePath 字段定义服务器 URL:

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

语义内核将服务器 URL 构造为 https://example.com/v1

相比之下,OpenAPI v3 文档使用 servers 字段定义服务器 URL:

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

语义内核将使用文档中指定的第一个服务器 URL 作为基础 URL:https://example.com/v1

OpenAPI v3 还允许使用大括号指示的变量参数化服务器 URL:

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

在这种情况下,语义内核会将变量占位符替换为变量参数中提供的值。如果没有提供参数,则使用默认值。最终形成的 URL 为:https://prod.example.com/v1

如果 OpenAPI 文档未指定服务器 URL,则语义内核将使用从中加载 OpenAPI 文档的服务器的基本 URL:

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

基准URL将会是https://api-host.com

重写服务器 URL

在某些情况下,在 OpenAPI 文档或加载文档的服务器中指定的服务器 URL 可能不适合涉及 OpenAPI 插件的用例。

Semantic Kernel 允许您在将 OpenAPI 插件添加到内核时,通过提供自定义基 URL 来覆盖服务器 URL。

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

在此示例中,基 URL 将设置为 https://custom-server.com/v1,覆盖 OpenAPI 文档中指定的服务器 URL 以及加载文档时的服务器 URL。

认证

大多数 REST API 都需要身份验证才能访问其资源。 语义内核提供了一种机制,使你能够集成 OpenAPI 插件所需的各种身份验证方法。

此机制依赖于身份验证回调函数,该函数在每个 API 请求之前调用。 此回调函数有权访问 HttpRequestMessage 对象,表示将发送到 API 的 HTTP 请求。 可以使用此对象向请求添加身份验证凭据。 凭据可以添加为标头、查询参数或在请求正文中,具体取决于 API 使用的身份验证方法。

将 OpenAPI 插件添加到内核时,需要注册此回调函数。 以下代码片段演示如何注册它以对请求进行身份验证:

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

对于需要动态访问 API 支持的身份验证架构的详细信息的更复杂的身份验证方案,可以使用文档和操作元数据来获取此信息。 有关详细信息,请参阅 文档和操作元数据

响应内容的读取自定义

语义内核具有一种内置机制,用于从 OpenAPI 插件读取 HTTP 响应的内容,并将其转换为适当的 .NET 数据类型。 例如,可以将图像响应读取为字节数组,而 JSON 或 XML 响应可以读取为字符串。

但是,在某些情况下,内置机制不足以满足你的需求。 例如,当响应是需要作为流读取的大型 JSON 对象或图像时,才能作为输入提供给另一个 API。 在这种情况下,将响应内容读取为字符串或字节数组,然后将其转换回流可能会效率低下,并可能导致性能问题。 为了解决此问题,语义内核通过提供自定义内容读取器,允许自定义响应内容的读取。

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

在此示例中,当内容类型 application/json 或请求包含自定义标头 x-stream时,ReadHttpResponseContentAsync 方法将 HTTP 响应内容读取为流。 该方法为任何其他内容类型返回 null,指示应使用默认内容读取器。

文档和操作元数据

语义内核提取 OpenAPI 文档和操作元数据,包括 API 信息、安全架构、操作 ID、说明、参数元数据等。 它通过 KernelFunction.Metadata.AdditionalParameters 属性提供对此信息的访问权限。 在需要 API 或操作的其他信息(例如出于身份验证目的)的情况下,此元数据非常有用:

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

在此示例中,AuthenticateRequestAsyncCallbackAsync 方法从函数上下文中读取操作元数据,并提取操作的安全要求以确定身份验证方案。 然后,它会从应用标识提供者检索方案和范围的 API 密钥,并将其添加到请求标头或查询参数。

下表列出了 KernelFunction.Metadata.AdditionalParameters 字典中可用的元数据:

钥匙 类型 描述
信息 RestApiInfo API 信息,包括标题、说明和版本。
操作 RestApiOperation API 操作详细信息,例如 ID、说明、路径、方法等。
安全 IList<RestApiSecurityRequirement> API 安全要求 - 类型、名称、in 等。

添加 OpenAPI 插件的提示和技巧

由于 OpenAPI 规范通常专为人类设计,可能需要进行一些更改,以便 AI 更容易理解。 下面是帮助你执行此操作的一些提示和技巧:

建议 描述
版本控制 API 规范 请考虑将 Swagger 文件上传并进行版本控制,而不是指向实时 API 规格说明。 这样,AI 研究人员就可以测试 AI 代理使用的 API 规范(并更改),而不会影响实时 API,反之亦然。
限制终结点数 尝试限制 API 中的终结点数。 将类似的功能合并到具有可选参数的单个终结点中,以减少复杂性。
对终结点和参数使用描述性名称 确保终结点和参数的名称具有描述性和自我解释性。 这有助于 AI 了解其用途,而无需进行广泛的解释。
使用一致的命名约定 在整个 API 中保持一致的命名约定。 这样可以减少混淆,并帮助 AI 更轻松地学习和预测 API 的结构。
简化 API 规范 通常,OpenAPI 规范非常详细,并包含许多对 AI 代理协助用户而言不必要的信息。 API 越简单,描述它所需的令牌就越少,而 AI 发送请求所需的令牌也就越少。
避免字符串参数 如果可能,请避免在 API 中使用字符串参数。 请改用更具体的类型,例如整数、布尔值或枚举。 这将有助于 AI 更好地了解 API。
提供说明中的示例 当人类使用 Swagger 文件时,他们通常能够使用 Swagger UI(包括示例请求和响应)测试 API。 由于 AI 代理无法执行此操作,因此请考虑在参数的说明中提供示例。
在说明中引用其他终结点 通常,AIS 会混淆类似的终结点。 为了帮助 AI 区分终结点,请考虑在说明中引用其他终结点。 例如,你可以说“此终结点类似于 get_all_lights 终结点,但它只返回单个光。
提供有用的错误消息 虽然不在 OpenAPI 规范中,但请考虑提供有助于 AI 自我更正的错误消息。 例如,如果用户提供无效 ID,请考虑提供一条错误消息,提示 AI 代理从 get_all_lights 终结点获取正确的 ID。

后续步骤

了解如何创建插件后,现在可以了解如何将其与 AI 代理配合使用。 根据添加到插件的函数类型,应遵循不同的模式。 有关检索函数,请参阅在使用检索函数的文章 中的 。 有关任务自动化函数,请参阅 一文中使用任务自动化函数