Поделиться через


Добавить подключаемые модули из спецификаций OpenAPI

Часто в организации у вас уже есть набор API, выполняющих реальную работу. Они могут использоваться другими службами автоматизации или интерфейсными приложениями, с которыми взаимодействуют люди. В Semantic Kernel можно добавить те же самые API в качестве плагинов, чтобы агенты могли также их использовать.

Пример спецификации OpenAPI

Например, API, позволяющий изменить состояние лампочек. Спецификация OpenAPI, известная как спецификация Swagger или только Swagger, для этого API может выглядеть следующим образом:

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

Эта спецификация предоставляет все, что требуется для ИИ для понимания API и взаимодействия с ним. API включает две конечные точки: одну, чтобы получить все свет и другую, чтобы изменить состояние света. Он также предоставляет следующие возможности:

  • Семантические описания конечных точек и их параметров
  • Типы параметров
  • Ожидаемые ответы

Так как агент ИИ может понять эту спецификацию, его можно добавить в качестве подключаемого модуля к агенту.

Семантическое ядро поддерживает версии OpenAPI 2.0 и 3.0 и нацелено на поддержку спецификаций версии 3.1 путем их понижения до версии 3.0.

Совет

Если у вас есть спецификации OpenAPI, может потребоваться внести изменения, чтобы упростить их понимание ИИ. Например, может потребоваться предоставить рекомендации в описаниях. Дополнительные советы о том, как сделать спецификации OpenAPI более удобными для ИИ, см. в Советы и рекомендации по добавлению подключаемых модулей 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
   }
);

С помощью Semantic Kernel можно добавлять подключаемые модули OpenAPI из различных источников, таких как URL-адрес, файл или поток. Кроме того, подключаемые модули можно создавать один раз и повторно использовать в нескольких экземплярах ядра или агентах.

// 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. Эти метаданные хранятся в свойстве KernelFunction.Metadata.Parameters для каждой операции OpenAPI и предоставляются LLM вместе с запросом на создание правильных аргументов для вызовов функций.

По умолчанию исходное имя параметра предоставляется LLM и используется семантическим ядром для поиска соответствующего аргумента в списке аргументов, предоставленных LLM. Однако в некоторых случаях подключаемый модуль OpenAPI имеет несколько параметров с одинаковым именем. Предоставление метаданных этих параметров LLM может стать причиной путаницы, что потенциально препятствует генерации правильных аргументов для вызовов функций.

Кроме того, так как для каждой операции OpenAPI создается функция ядра, которая не позволяет использовать неуникальные имена параметров, добавление такого подключаемого модуля может привести к тому, что некоторые операции могут стать недоступными для использования. В частности, операции с не уникальными именами параметров будут пропущены, а соответствующее предупреждение будет зарегистрировано. Даже если можно было включить несколько параметров с одинаковым именем в функцию ядра, это может привести к неоднозначности в процессе выбора аргументов.

Учитывая все это, семантический ядро предлагает решение для управления подключаемыми модулями с не уникальными именами параметров. Это решение особенно полезно при изменении самого API невозможно, независимо от того, является ли она сторонней службой или устаревшей системой.

В следующем фрагменте кода показано, как обрабатывать имена не уникальных параметров в подключаемом модуле OpenAPI. Если у операции change_light_state есть дополнительный параметр с тем же именем, что и существующий параметр "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. Эти операции часто требуют включения полезной нагрузки в запрос.

Семантическое ядро предлагает несколько вариантов управления обработкой полезных данных для подключаемых модулей OpenAPI в зависимости от ваших конкретных сценариев и требований API.

Динамическая конструкция полезной нагрузки

Динамическое построение полезных данных позволяет динамически создавать полезные данные операций OpenAPI на основе схемы полезных данных и аргументов, предоставляемых LLM. Эта функция включена по умолчанию, но ее можно отключить, задав для свойства EnableDynamicPayload значение false в объекте OpenApiFunctionExecutionParameters при добавлении подключаемого модуля OpenAPI.

Например, рассмотрим операцию change_light_state, для которой требуется нагрузка, структурированная следующим образом:

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

Чтобы изменить состояние света и получить значения для свойств полезных данных, Semantic Kernel предоставляет 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. Обработайте вызов LLM к операции OpenAPI, формируя нагрузку на основе схемы и предоставляемых значениями свойств LLM.
  2. Отправьте HTTP-запрос с нагрузкой в API.

Ограничения динамического формирования нагрузки

Динамическое построение полезных данных наиболее эффективно для API с относительно простыми структурами полезных данных. Это может работать ненадежно или вообще не работать для загрузок API, демонстрирующих следующие характеристики:

  • Полезные данные с неуникальными именами свойств независимо от их расположения. Например, два свойства с именем id, один для объекта отправителя и другой для объекта получателя — json { "sender": { "id": ... }, "receiver": { "id": ... }}
  • Схемы полезной нагрузки, использующие любой из составных ключевых слов oneOf, anyOf, allOf.
  • Схемы пейлоада с рекурсивными ссылками. Например, json { "parent": { "child": { "$ref": "#parent" } } }

Чтобы обрабатывать нагрузки с неуникальными именами свойств, рассмотрите такие варианты:

Если схемы полезных данных используют любые составные ключевые слова oneOf, anyOf, allOf или рекурсивные ссылки, рассмотрите возможность отключения динамического создания полезных данных и разрешите LLM создавать полезные данные на основе их схемы, как описано в разделе «Параметр полезных данных».

Пространство имён полезной нагрузки

Неймспейсинг пейлоада помогает предотвратить конфликты именования, которые могут возникать из-за неуникальных имен свойств в пейлоаде подключаемого модуля OpenAPI.

Если включена функция namepacing, семантика ядра предоставляет 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. Обработайте вызов LLM к операции OpenAPI и найдите соответствующие аргументы среди предоставленных LLM для всех свойств полезной нагрузки, используя расширенные имена свойств и возвращаясь к исходным именам свойств при необходимости.
  2. Создайте пакет данных с помощью исходных имен свойств в качестве ключей и разрешенных аргументов в качестве значений.
  3. Отправьте HTTP-запрос с сформированным содержимым в API.

По умолчанию опция неймспейса полезной нагрузки отключена. Его можно включить, установив для свойства EnablePayloadNamespacing значение true в объекте OpenApiFunctionExecutionParameters при добавлении подключаемого модуля 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
    });

Заметка

Параметр EnablePayloadNamespace действует только в том случае, если также включена динамическая конструкция полезных данных; в противном случае он не действует.

Параметр полезной нагрузки

Семантическое ядро может работать с полезными нагрузками, созданными LLM, используя параметр нагрузки. Это полезно, если схема полезной нагрузки сложна и содержит неуникальные имена свойств, что делает невозможным для Semantic Kernel динамически создавать полезную нагрузку. В таких случаях вы будете полагаться на способность LLM понять схему и создать допустимые полезные данные. Новые модели, такие как gpt-4o, эффективны в создании допустимых JSON-данных.

Чтобы включить параметр нагрузки, установите свойство EnableDynamicPayload в false в объекте OpenApiFunctionExecutionParameters при добавлении подключаемого модуля OpenAPI.

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

Если параметр полезных данных включен, семантическое ядро предоставляет LLM метаданные для операции, которая включает схемы параметров полезных данных и типа_контента, позволяя 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. Обработайте вызов LLM к операции OpenAPI и используйте аргументы, предоставляемые LLM, для параметров типа данных и content_type.
  2. Отправьте HTTP-запрос в API с предоставленными полезными данными и типом контента.

Базовый URL-адрес сервера

Подключаемые модули OpenAPI семантического ядра требуют базового URL-адреса, который используется для подготовки путей конечных точек при выполнении запросов API. Этот базовый URL-адрес можно указать в документе OpenAPI, неявно загрузив документ из URL-адреса или указав при добавлении подключаемого модуля в ядро.

URL-адрес, указанный в документе OpenAPI

Документы OpenAPI версии 2 определяют URL-адрес сервера с помощью полей schemes, hostи basePath:

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

Семантическое ядро создаст URL-адрес сервера в виде https://example.com/v1.

В отличие от этого, документы OpenAPI версии 3 определяют URL-адрес сервера с помощью поля servers:

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

Семантический ядро будет использовать первый URL-адрес сервера, указанный в документе в качестве базового URL-адреса: https://example.com/v1.

OpenAPI версии 3 также позволяет использовать параметризованные URL-адреса сервера с помощью переменных, указанных фигурными скобками:

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

В этом случае семантическое ядро заменит заполнитель переменной значением, предоставленным в качестве аргумента для переменной, или значением по умолчанию, если аргумент не указан, что приведет к получению URL-адреса: https://prod.example.com/v1.

Если документ OpenAPI не указывает URL-адрес сервера, семантический ядро будет использовать базовый URL-адрес сервера, с которого был загружен документ OpenAPI:

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

Базовый URL-адрес будет https://api-host.com.

Переопределение URL-адреса сервера

В некоторых случаях URL-адрес сервера, указанный в документе OpenAPI или сервере, с которого был загружен документ, может быть не подходит для вариантов использования с подключаемым модулем OpenAPI.

Семантический ядро позволяет переопределить URL-адрес сервера, предоставив настраиваемый базовый URL-адрес при добавлении подключаемого модуля OpenAPI в ядро:

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, переопределяя URL-адрес сервера, указанный в документе OpenAPI, и URL-адрес сервера, из которого был загружен документ.

Аутентификация

Большинству REST API требуется проверка подлинности для доступа к ресурсам. Семантический ядро предоставляет механизм, позволяющий интегрировать различные методы проверки подлинности, необходимые подключаемым модулям OpenAPI.

Этот механизм использует функцию обратного вызова проверки подлинности, которая вызывается перед каждым запросом API. Эта функция обратного вызова имеет доступ к объекту HttpRequestMessage, представляющего HTTP-запрос, который будет отправлен в API. Этот объект можно использовать для добавления учетных данных проверки подлинности в запрос. Учетные данные можно добавлять в качестве заголовков, параметров запроса или в тексте запроса в зависимости от метода проверки подлинности, используемого 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, можно использовать метаданные документов и операций для получения этих сведений. Дополнительные сведения см. в метаданны документов и операций .

Настройка чтения содержимого ответа

Семантический ядро имеет встроенный механизм для чтения содержимого ответов HTTP из подключаемых модулей OpenAPI и преобразования их в соответствующие типы данных .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  
    });  

В этом примере метод ReadHttpResponseContentAsync считывает содержимое HTTP-ответа в виде потока, когда тип контента application/json или когда запрос содержит настраиваемый заголовок x-stream. Метод возвращает null для любого другого типа контента, указывая, что следует использовать средство чтения содержимого по умолчанию.

Метаданные документов и операций

Семантический ядро извлекает метаданные документа и операции OpenAPI, включая сведения API, схемы безопасности, идентификатор операции, описание, метаданные параметров и многое другое. Он обеспечивает доступ к данной информации через свойство 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, такие как идентификатор, описание, путь, метод и т. д.
безопасность IList<RestApiSecurityRequirement> Требования к безопасности API — тип, имя, параметр и т. д.

Советы и рекомендации по добавлению плагинов OpenAPI

Так как спецификации OpenAPI обычно предназначены для людей, может потребоваться внести некоторые изменения, чтобы сделать их проще для ИИ для понимания. Ниже приведены некоторые советы и рекомендации, которые помогут вам сделать это:

Рекомендация Описание
Контроль версий спецификаций API Вместо того чтобы ссылаться на актуальную спецификацию API, рассмотрите возможность регистрации и управления версиями файла Swagger. Это позволит исследователям ИИ протестировать (и изменить) спецификацию API, используемую агентом ИИ, не влияя на динамический API и наоборот.
Ограничить количество конечных точек Попробуйте ограничить количество конечных точек в API. Объединение аналогичных функций в отдельные конечные точки с дополнительными параметрами для уменьшения сложности.
Использование описательных имен для конечных точек и параметров Убедитесь, что имена конечных точек и параметров являются описательными и самообъяснительными. Это помогает ИИ понять свою цель, не нуждаясь в обширных объяснениях.
Использование соглашений о согласованном именовании Сохраняйте согласованные соглашения об именовании на протяжении всего API. Это снижает путаницу и помогает ИИ изучать и прогнозировать структуру API проще.
Упрощение спецификаций API Часто спецификации OpenAPI очень подробны и содержат много информации, которая не требуется для агента ИИ, чтобы помочь пользователю. Чем проще API, тем меньше маркеров, которые необходимо потратить для описания, и чем меньше маркеров, тем меньше маркеров, необходимых ИИ для отправки запросов в него.
Избегайте строковых параметров По возможности избегайте использования строковых параметров в API. Вместо этого используйте более конкретные типы, такие как целые числа, логические значения или перечисления. Это поможет ИИ лучше понять API.
Укажите примеры в описаниях Когда люди используют файлы Swagger, они обычно могут протестировать API с помощью пользовательского интерфейса Swagger, который включает примеры запросов и ответов. Так как агент ИИ не может сделать это, рассмотрите возможность предоставления примеров в описаниях параметров.
Ссылки на другие конечные точки в описаниях Искусственные интеллекты часто будут путать аналогичные конечные точки. Чтобы помочь ИИ различать конечные точки, рекомендуется ссылаться на другие конечные точки в описаниях. Например, можно сказать: "Эта конечная точка похожа на конечную точку get_all_lights, но она возвращает только один свет".
Предоставление полезных сообщений об ошибках Хотя это и не в спецификации OpenAPI, рассмотрите возможность предоставления сообщений об ошибках, которые помогают ИИ самостоятельно исправляться. Например, если пользователь предоставляет недопустимый идентификатор, рассмотрите возможность предоставления сообщения об ошибке, которое предполагает, что агент ИИ получает правильный идентификатор из конечной точки get_all_lights.

Дальнейшие действия

Теперь, когда вы знаете, как создать подключаемый модуль, вы можете узнать, как использовать его с вашим агентом ИИ. В зависимости от типа функций, которые вы добавили в подключаемые модули, следует следовать разным шаблонам. Сведения о функциях извлечения см. в статье с помощью функций извлечения. Сведения о функциях автоматизации задач можно найти в статье «Использование функций автоматизации задач».