Freigeben über


Plugins aus OpenAPI-Spezifikationen hinzufügen

Häufig verfügen Sie in einem Unternehmen bereits über eine Reihe von APIs, die echte Arbeit ausführen. Diese können von anderen Automatisierungsdiensten oder Front-End-Anwendungen verwendet werden, mit denen Menschen interagieren. Im semantischen Kernel können Sie diese genau gleichen APIs wie Plug-Ins hinzufügen, damit Ihre Agents sie auch verwenden können.

Ein Beispiel für eine OpenAPI-Spezifikation

Nehmen Sie beispielsweise eine API, mit der Sie den Zustand von Glühbirnen ändern können. Die OpenAPI-Spezifikation, die als Swagger Specification oder nur Swagger bezeichnet wird, könnte für diese API wie folgt aussehen:

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

Diese Spezifikation bietet alles, was von der KI benötigt wird, um die API zu verstehen und mit ihr zu interagieren. Die API enthält zwei Endpunkte: eine, um alle Lichter abzurufen und eine andere, um den Zustand eines Lichts zu ändern. Sie bietet außerdem Folgendes:

  • Semantische Beschreibungen für die Endpunkte und deren Parameter
  • Die Typen der Parameter
  • Die erwarteten Antworten

Da der KI-Agent diese Spezifikation verstehen kann, können Sie sie als Plug-In zum Agent hinzufügen.

Der semantische Kernel unterstützt die OpenAPI-Versionen 2.0 und 3.0 und zielt darauf ab, Die Spezifikationen der Version 3.1 zu berücksichtigen, indem sie auf Version 3.0 herabgestuft wird.

Trinkgeld

Wenn Sie über OpenAPI-Spezifikationen verfügen, müssen Sie möglicherweise Änderungen vornehmen, damit eine KI sie einfacher verstehen kann. Möglicherweise müssen Sie in den Beschreibungen Anleitungen bereitstellen. Weitere Tipps zum Erstellen von AI-freundlichen OpenAPI-Spezifikationen finden Sie unter Tipps und Tricks zum Hinzufügen von OpenAPI-Plug-Ins.

Hinzufügen des OpenAPI-Plug-Ins

Mit einigen Codezeilen können Sie ihrem Agent das OpenAPI-Plug-In hinzufügen. Der folgende Codeausschnitt zeigt, wie Sie das Light-Plug-In aus der obigen OpenAPI-Spezifikation hinzufügen:

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

Mit dem semantischen Kernel können Sie OpenAPI-Plug-Ins aus verschiedenen Quellen hinzufügen, z. B. eine URL, eine Datei oder einen Stream. Darüber hinaus können Plug-Ins einmal erstellt und in mehreren Kernelinstanzen oder Agents wiederverwendet werden.

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

Danach können Sie das Plug-In in Ihrem Agenten verwenden, als wäre es ein natives Plug-In.

Behandeln von OpenAPI-Plug-In-Parametern

Der semantische Kernel extrahiert automatisch Metadaten – z. B. Name, Beschreibung, Typ und Schema für alle parameter, die in OpenAPI-Dokumenten definiert sind. Diese Metadaten werden in der KernelFunction.Metadata.Parameters-Eigenschaft für jeden OpenAPI-Vorgang gespeichert und dem LLM zusammen mit der Eingabeaufforderung übermittelt, um korrekte Argumente für Funktionsaufrufe zu erzeugen.

Standardmäßig wird der ursprüngliche Parametername für die LLM bereitgestellt und vom semantischen Kernel verwendet, um das entsprechende Argument in der Liste der argumente nachzuschlagen, die von der LLM bereitgestellt werden. Es kann jedoch vorkommen, dass das OpenAPI-Plug-In mehrere Parameter mit demselben Namen hat. Durch die Bereitstellung dieser Parametermetadaten an das LLM kann Verwirrung entstehen, wodurch die LLM möglicherweise daran gehindert wird, die richtigen Argumente für Funktionsaufrufe zu generieren.

Da eine Kernelfunktion, die nicht eindeutige Parameternamen zulässt, für jeden OpenAPI-Vorgang erstellt wird, kann das Hinzufügen eines solchen Plug-Ins dazu führen, dass einige Vorgänge für die Verwendung nicht verfügbar sind. Insbesondere werden Vorgänge mit nicht eindeutigen Parameternamen übersprungen, und eine entsprechende Warnung wird protokolliert. Auch wenn es möglich wäre, mehrere Parameter mit demselben Namen in die Kernelfunktion einzuschließen, könnte dies zu Mehrdeutigkeit im Argumentauswahlprozess führen.

In Anbetracht dessen bietet semantic Kernel eine Lösung für die Verwaltung von Plug-Ins mit nicht eindeutigen Parameternamen. Diese Lösung ist besonders nützlich, wenn die API selbst nicht geändert werden kann, sei es, weil dies an einem Drittanbieterdienst oder einem Legacysystem liegt.

Der folgende Codeausschnitt veranschaulicht, wie nicht eindeutige Parameternamen in einem OpenAPI-Plug-In behandelt werden. Wenn der change_light_state-Vorgang einen zusätzlichen Parameter mit demselben Namen wie der vorhandene Parameter "ID" hätte – speziell, um zusätzlich zur aktuellen "ID", die die ID des Lichts darstellt, eine Sitzungs-ID zu repräsentieren –, könnte es wie unten dargestellt behandelt werden.

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

Dieser Codeausschnitt verwendet die OpenApiDocumentParser Klasse, um das OpenAPI-Dokument zu analysieren und auf das RestApiSpecification Modellobjekt zuzugreifen, das das Dokument darstellt. Er weist Argument-Namen den Parametern zu und importiert die transformierte OpenAPI-Plugin-Spezifikation in den Kernel. Der semantische Kernel stellt die Argumentnamen anstelle der ursprünglichen Namen für die LLM bereit und verwendet sie, um die entsprechenden Argumente in der von der LLM bereitgestellten Liste nachzuschlagen.

Beachten Sie, dass die Argumentnamen beim Aufrufen des OpenAPI-Vorgangs nicht anstelle der ursprünglichen Namen verwendet werden. Im obigen Beispiel wird der Parameter "id" im Pfad durch einen Wert ersetzt, der von der LLM für das Argument "lightId" zurückgegeben wird. Das gleiche gilt für den Headerparameter "id"; Der vom LLM für das Argument 'sessionId' zurückgegebene Wert wird als Wert für den Header mit dem Namen 'id' verwendet.

Verarbeitung der Nutzlast von OpenAPI-Plug-ins

OpenAPI-Plug-Ins können den Status des Systems mithilfe von POST-, PUT- oder PATCH-Vorgängen ändern. Bei diesen Operationen muss häufig eine Nutzlast in die Anforderung einbezogen werden.

Der semantische Kernel bietet einige Optionen für die Verwaltung der Nutzlastbehandlung für OpenAPI-Plug-Ins, abhängig von Ihren spezifischen Szenario- und API-Anforderungen.

Dynamische Nutzlastkonstruktion

Dynamische Nutzlastkonstruktion ermöglicht die dynamische Erstellung der Nutzlasten von OpenAPI-Vorgängen basierend auf dem Nutzlastschema und den argumenten, die von der LLM bereitgestellt werden. Dieses Feature ist standardmäßig aktiviert, kann jedoch deaktiviert werden, indem die EnableDynamicPayload-Eigenschaft beim Hinzufügen eines OpenAPI-Plug-Ins im OpenApiFunctionExecutionParameters-Objekt auf false festgelegt wird.

Betrachten Sie zum Beispiel die Operation zur Änderung des Lichtzustands, die eine Nutzlast erfordert, die wie folgt strukturiert ist:

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

Um den Zustand des Lichts zu ändern und Werte für die Nutzlast-Eigenschaften abzurufen, stellt Semantic Kernel der LLM Metadaten für den Vorgang zur Verfügung, damit sie darüber nachdenken kann.

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

Neben der Bereitstellung von Betriebsmetadaten für die LLM führt der semantische Kernel die folgenden Schritte aus:

  1. Behandeln Sie den LLM-Aufruf des OpenAPI-Vorgangs und erstellen Sie dabei die Nutzlast basierend auf dem Schema, das durch LLM-Eigenschaftswerte bereitgestellt wird.
  2. Senden Sie die HTTP-Anforderung mit der Nutzlast an die API.

Einschränkungen bei der dynamischen Nutzlastkonstruktion

Dynamische Nutzlastkonstruktion ist für APIs mit relativ einfachen Nutzlaststrukturen am effektivsten. Es kann möglicherweise nicht zuverlässig funktionieren oder gar nicht funktionieren, wenn die API-Nutzlasten die folgenden Merkmale aufweisen:

  • Nutzlasten mit nicht eindeutigen Eigenschaftsnamen unabhängig vom Speicherort der Eigenschaften. Z. B. zwei Eigenschaften mit dem Namen id, eine für das Absenderobjekt und eine für das Empfängerobjekt - json { "sender": { "id": ... }, "receiver": { "id": ... }}
  • Nutzlastschemas, die eines der zusammengesetzten Schlüsselwörter oneOf, anyOf, allOfverwenden.
  • Nutzlastschemas mit rekursiven Verweisen. Z. B. json { "parent": { "child": { "$ref": "#parent" } } }

Um Payloads mit nicht eindeutigen Eigenschaftsnamen zu behandeln, sollten Sie die folgenden Alternativen in Betracht ziehen:

  • Geben Sie einen eindeutigen Argumentnamen für jede nicht eindeutige Eigenschaft an, wobei eine Methode verwendet wird, die im Abschnitt Handhabung von OpenAPI-Plugin-Parametern beschrieben ist.
  • Verwenden Sie Namespaces, um Namenskonflikte zu vermeiden, wie im nächsten Abschnitt über Nutzlast-Namensräumebeschrieben.
  • Deaktivieren Sie die Erstellung dynamischer Nutzlast und ermöglichen Sie es dem LLM, die Nutzlast basierend auf seinem Schema zu erstellen, wie im Abschnitt Der Nutzlastparameter erläutert.

Wenn Nutzlastschemas eines der oneOf, anyOf, allOf zusammengesetzten Schlüsselwörter oder rekursiven Bezüge verwenden, erwägen Sie, die Konstruktion dynamischer Nutzlast zu deaktivieren und es dem LLM zu ermöglichen, die Nutzlast basierend auf seinem Schema zu erstellen, wie im abschnitt Der Nutzlastparameter beschrieben.

Nutzlastnamenpacing

Payload namespacing hilft, Benennungskonflikte zu verhindern, die aufgrund nicht eindeutiger Eigenschaftsnamen in OpenAPI-Plug-In-Nutzlasten auftreten können.

Wenn Namespacing aktiviert ist, stellt das Semantic Kernel dem LLM OpenAPI-Vorgangsmetadaten bereit, die erweiterte Eigenschaftsnamen enthalten. Diese erweiterten Namen werden durch Hinzufügen des übergeordneten Eigenschaftsnamens als Präfix, getrennt durch einen Punkt, zu den untergeordneten Eigenschaftennamen erstellt.

Wenn beispielsweise der „change_light_state“-Vorgang ein geschachteltes offTimer-Objekt mit einer scheduledTime-Eigenschaft enthalten hätte:

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

Der semantische Kernel hätte die LLM mit Metadaten für den Vorgang bereitgestellt, der die folgenden Eigenschaftennamen enthält:

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

Neben der Bereitstellung von Operationsmetadaten mit erweiterten Eigenschaftsnamen für die LLM führt der Semantische Kernel die folgenden Schritte aus:

  1. Behandeln Sie den LLM-Aufruf des OpenAPI-Vorgangs und suchen Sie unter den von der LLM bereitgestellten Argumenten nach den entsprechenden für alle Eigenschaften in der Nutzlast, wobei Sie die erweiterten Eigenschaftsnamen verwenden und bei Bedarf zu den ursprünglichen Eigenschaftsnamen zurückkehren.
  2. Erstellen Sie die Payload, indem Sie die ursprünglichen Eigenschaftsnamen als Schlüssel verwenden und die aufgelösten Argumente als Werte zuordnen.
  3. Senden Sie die HTTP-Anforderung mit der konstruierten Nutzlast an die API.

Standardmäßig ist die Option "Payload namespacing" deaktiviert. Sie kann aktiviert werden, indem Sie die EnablePayloadNamespacing Eigenschaft beim Hinzufügen eines OpenAPI-Plug-Ins auf true im OpenApiFunctionExecutionParameters-Objekt festlegen:

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

Anmerkung

Die Option EnablePayloadNamespace tritt nur in Kraft, wenn die dynamische Nutzlastkonstruktion ebenfalls aktiviert ist; andernfalls hat sie keine Wirkung.

Der Nutzlastparameter

Der semantische Kernel kann mit Nutzlasten arbeiten, die vom LLM mithilfe des Nutzlastparameters erstellt wurden. Dies ist nützlich, wenn das Nutzlastschema komplex ist und nicht eindeutige Eigenschaftsnamen enthält, wodurch es für den semantischen Kernel unmöglich wird, die Nutzlast dynamisch zu konstruieren. In solchen Fällen verlassen Sie sich auf die Fähigkeit des LLM, das Schema zu verstehen und eine gültige Nutzlast zu erstellen. Aktuelle Modelle, z. B. gpt-4o, sind effektiv beim Generieren gültiger JSON-Nutzlasten.

Um den Nutzlastparameter zu aktivieren, legen Sie die EnableDynamicPayload-Eigenschaft beim Hinzufügen eines OpenAPI-Plug-Ins auf false im OpenApiFunctionExecutionParameters-Objekt fest:

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

Wenn der Nutzlastparameter aktiviert ist, stellt der semantische Kernel der LLM Metadaten für den Vorgang bereit, die Schemas für die Nutzlast und den "content_type"-Parameter enthalten, damit das LLM die Nutzlaststruktur versteht und sie entsprechend konstruieren kann.

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

Neben der Bereitstellung der Operationsmetadaten mit dem Schema für Nutzlast- und Inhaltstypparameter für das LLM führt der semantische Kernel die folgenden Schritte aus:

  1. Behandeln Sie den LLM-Aufruf des OpenAPI-Vorgangs, indem Sie Argumente verwenden, die vom LLM für die Parameter "Nutzlast" und "content_type" bereitgestellt werden.
  2. Senden Sie die HTTP-Anforderung an die API mit bereitgestellter Nutzlast und Inhaltstyp.

Serverbasis-URL

Für semantische Kernel OpenAPI-Plug-Ins ist eine Basis-URL erforderlich, die genutzt wird, um Endpunktpfade beim Senden von API-Anfragen voranzustellen. Diese Basis-URL kann im OpenAPI-Dokument angegeben, implizit abgerufen werden, indem das Dokument von einer URL geladen oder beim Hinzufügen des Plug-Ins zum Kernel bereitgestellt wird.

Im OpenAPI-Dokument angegebene URL

OpenAPI v2-Dokumente definieren die Server-URL mithilfe der Felder schemes, hostund basePath:

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

Der semantische Kernel erstellt die Server-URL als https://example.com/v1.

Im Gegensatz dazu definieren OpenAPI v3-Dokumente die Server-URL mithilfe des felds servers:

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

Der semantische Kernel verwendet die erste Server-URL, die im Dokument als Basis-URL angegeben wird: https://example.com/v1.

OpenAPI v3 ermöglicht auch parametrisierte Server-URLs mithilfe von Variablen, die durch geschweifte Klammern gekennzeichnet sind:

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

In diesem Fall ersetzt der semantische Kernel den Variablenplatzhalter durch den als Argument für die Variable bereitgestellten Wert oder den Standardwert, wenn kein Argument angegeben wird, was zu der URL: https://prod.example.com/v1führt.

Wenn das OpenAPI-Dokument keine Server-URL angibt, verwendet der semantische Kernel die Basis-URL des Servers, von dem das OpenAPI-Dokument geladen wurde:

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

Die Basis-URL wird https://api-host.comsein.

Überschreiben der Server-URL

In einigen Fällen ist die im OpenAPI-Dokument angegebene Server-URL oder der Server, von dem das Dokument geladen wurde, möglicherweise nicht für Anwendungsfälle geeignet, die das OpenAPI-Plug-In betreffen.

Mit dem semantischen Kernel können Sie die Server-URL überschreiben, indem Sie beim Hinzufügen des OpenAPI-Plug-Ins zum Kernel eine benutzerdefinierte Basis-URL bereitstellen:

await kernel.ImportPluginFromOpenApiAsync(  
    pluginName: "lights",  
    uri: new Uri("https://example.com/v1/swagger.json"),  
    executionParameters: new OpenApiFunctionExecutionParameters()  
    {  
        ServerUrlOverride = new Uri("https://custom-server.com/v1")  
    });  

In diesem Beispiel wird die Basis-URL https://custom-server.com/v1verwendet, wodurch die im OpenAPI-Dokument angegebene Server-URL und die Server-URL, von der das Dokument geladen wurde, überschrieben werden.

Authentifizierung

Die meisten REST-APIs erfordern eine Authentifizierung für den Zugriff auf ihre Ressourcen. Der semantische Kernel bietet einen Mechanismus, mit dem Sie eine Vielzahl von Authentifizierungsmethoden integrieren können, die von OpenAPI-Plug-Ins benötigt werden.

Dieser Mechanismus basiert auf einer Authentifizierungsrückruffunktion, die vor jeder API-Anforderung aufgerufen wird. Diese Rückruffunktion hat Zugriff auf das HttpRequestMessage-Objekt, das die HTTP-Anforderung darstellt, die an die API gesendet wird. Sie können dieses Objekt verwenden, um der Anforderung Authentifizierungsdaten hinzuzufügen. Die Anmeldeinformationen können abhängig von der von der API verwendeten Authentifizierungsmethode als Header, Abfrageparameter oder im Anforderungstext hinzugefügt werden.

Sie müssen diese Rückruffunktion registrieren, wenn Sie das OpenAPI-Plug-In zum Kernel hinzufügen. Der folgende Codeausschnitt zeigt, wie es registriert wird, um Anforderungen zu authentifizieren:

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

Für komplexere Authentifizierungsszenarien, die dynamischen Zugriff auf die Details der von einer API unterstützten Authentifizierungsschemas erfordern, können Sie Dokument- und Vorgangsmetadaten verwenden, um diese Informationen abzurufen. Weitere Informationen finden Sie unter Dokument- und Vorgangsmetadaten.

Anpassung des Lesens von Antwortinhalten

Der semantische Kernel verfügt über einen integrierten Mechanismus zum Lesen des Inhalts von HTTP-Antworten aus OpenAPI-Plug-Ins und zum Konvertieren in die entsprechenden .NET-Datentypen. Beispielsweise kann eine Bildantwort als Bytearray gelesen werden, während eine JSON- oder XML-Antwort als Zeichenfolge gelesen werden kann.

Es kann jedoch vorkommen, dass der integrierte Mechanismus für Ihre Anforderungen nicht ausreicht. Wenn die Antwort beispielsweise ein großes JSON-Objekt oder -Bild ist, das als Datenstrom gelesen werden muss, um als Eingabe für eine andere API bereitgestellt zu werden. In solchen Fällen kann das Lesen des Antwortinhalts als Zeichenfolge oder Bytearray und die anschließende Rückkonvertierung in einen Datenstrom ineffizient sein und zu Leistungsproblemen führen. Um dies zu beheben, ermöglicht der semantische Kernel die Anpassung des Lesens von Antwortinhalten, indem ein benutzerdefinierter Inhaltsleser bereitgestellt wird:

private static async Task<object?> ReadHttpResponseContentAsync(HttpResponseContentReaderContext context, CancellationToken cancellationToken)  
{  
    // Read JSON content as a stream instead of as a string, which is the default behavior.  
    if (context.Response.Content.Headers.ContentType?.MediaType == "application/json")  
    {  
        return await context.Response.Content.ReadAsStreamAsync(cancellationToken);  
    }  

    // HTTP request and response properties can be used to determine how to read the content.  
    if (context.Request.Headers.Contains("x-stream"))  
    {  
        return await context.Response.Content.ReadAsStreamAsync(cancellationToken);  
    }  

    // Return null to indicate that any other HTTP content not handled above should be read by the default reader.  
    return null;  
}  

await kernel.ImportPluginFromOpenApiAsync(  
    pluginName: "lights",  
    uri: new Uri("https://example.com/v1/swagger.json"),  
    executionParameters: new OpenApiFunctionExecutionParameters()  
    {  
        HttpResponseContentReader = ReadHttpResponseContentAsync  
    });  

In diesem Beispiel liest die ReadHttpResponseContentAsync-Methode den HTTP-Antwortinhalt als Datenstrom, wenn der Inhaltstyp application/json ist oder wenn die Anforderung einen benutzerdefinierten Header x-streamenthält. Die Methode gibt null bei allen anderen Inhaltstypen zurück, wobei darauf hingewiesen wird, dass der Standardinhaltsleser verwendet werden soll.

Dokument- und Vorgangsmetadaten

Der semantische Kernel extrahiert OpenAPI-Dokument- und Vorgangsmetadaten, einschließlich API-Informationen, Sicherheitsschemas, Vorgangs-ID, Beschreibung, Parametermetadaten und vieles mehr. Sie ermöglicht den Zugriff auf diese Informationen über die eigenschaft KernelFunction.Metadata.AdditionalParameters. Diese Metadaten können in Szenarien hilfreich sein, in denen zusätzliche Informationen zur API oder zum Vorgang erforderlich sind, z. B. für Authentifizierungszwecke:

static async Task AuthenticateRequestAsyncCallbackAsync(HttpRequestMessage request, CancellationToken cancellationToken = default)
{
    // Get the function context
    if (request.Options.TryGetValue(OpenApiKernelFunctionContext.KernelFunctionContextKey, out OpenApiKernelFunctionContext? functionContext))
    {
        // Get the operation metadata
        if (functionContext!.Function!.Metadata.AdditionalProperties["operation"] is RestApiOperation operation)
        {
            // Handle API key-based authentication
            IEnumerable<KeyValuePair<RestApiSecurityScheme, IList<string>>> apiKeySchemes = operation.SecurityRequirements.Select(requirement => requirement.FirstOrDefault(schema => schema.Key.SecuritySchemeType == "apiKey"));
            if (apiKeySchemes.Any())
            {
                (RestApiSecurityScheme scheme, IList<string> scopes) = apiKeySchemes.First();

                // Get the API key for the scheme and scopes from your app identity provider
                var apiKey = await this.identityPropvider.GetApiKeyAsync(scheme, scopes);

                // Add the API key to the request headers
                if (scheme.In == RestApiParameterLocation.Header)
                {
                    request.Headers.Add(scheme.Name, apiKey);
                }
                else if (scheme.In == RestApiParameterLocation.Query)
                {
                    request.RequestUri = new Uri($"{request.RequestUri}?{scheme.Name}={apiKey}");
                }
                else
                {
                    throw new NotSupportedException($"API key location '{scheme.In}' is not supported.");
                }
            }

            // Handle other authentication types like Basic, Bearer, OAuth2, etc. For more information, see https://swagger.io/docs/specification/v3_0/authentication/
        }
    }
}

// Import the transformed OpenAPI plugin specification
var plugin = kernel.ImportPluginFromOpenApi(
    pluginName: "lights",
    uri: new Uri("https://example.com/v1/swagger.json"),
    new OpenApiFunctionExecutionParameters()
    {
        AuthCallback = AuthenticateRequestAsyncCallbackAsync
    });

await kernel.InvokePromptAsync("Test");

In diesem Beispiel liest die AuthenticateRequestAsyncCallbackAsync-Methode die Vorgangsmetadaten aus dem Funktionskontext und extrahiert die Sicherheitsanforderungen für den Vorgang, um das Authentifizierungsschema zu bestimmen. Anschließend ruft er den API-Schlüssel für das Schema und die Bereiche vom App-Identitätsanbieter ab und fügt ihn den Anforderungsheadern oder Abfrageparametern hinzu.

In der folgenden Tabelle sind die im KernelFunction.Metadata.AdditionalParameters Wörterbuch verfügbaren Metadaten aufgeführt:

Schlüssel Art Beschreibung
Informationen RestApiInfo API-Informationen, einschließlich Titel, Beschreibung und Version.
Operation RestApiOperation API-Vorgangsdetails, z. B. ID, Beschreibung, Pfad, Methode usw.
Sicherheit IList<RestApiSecurityRequirement> API-Sicherheitsanforderungen – Typ, Name, Parameter, usw.

Tipps und Tricks zum Hinzufügen von OpenAPI-Plug-Ins

Da OpenAPI-Spezifikationen in der Regel für Menschen konzipiert sind, müssen Sie möglicherweise einige Änderungen vornehmen, um sie für eine KI verständlicher zu machen. Hier sind einige Tipps und Tricks, die Ihnen dabei helfen:

Empfehlung Beschreibung
Versionieren Sie Ihre API-Spezifikationen Anstatt auf eine Live-API-Spezifikation zu verweisen, sollten Sie die Überprüfung und Versionsverwaltung Ihrer Swagger-Datei in Betracht ziehen. Auf diese Weise können Ihre KI-Forscher die vom KI-Agent verwendete API-Spezifikation testen (und ändern), ohne dass sich dies auf die Live-API auswirkt und umgekehrt.
Beschränken der Anzahl der Endpunkte Versuchen Sie, die Anzahl der Endpunkte in Ihrer API zu begrenzen. Konsolidieren Sie ähnliche Funktionen in einzelnen Endpunkten mit optionalen Parametern, um die Komplexität zu reduzieren.
Verwenden aussagekräftiger Namen für Endpunkte und Parameter Stellen Sie sicher, dass die Namen Ihrer Endpunkte und Parameter beschreibend und selbsterklärend sind. Dies hilft der KI, ihren Zweck zu verstehen, ohne umfangreiche Erklärungen zu benötigen.
Verwenden konsistenter Benennungskonventionen Verwalten Sie einheitliche Benennungskonventionen in der gesamten API. Dies reduziert Verwirrung und hilft der KI, die Struktur Ihrer API einfacher zu erlernen und vorherzusagen.
Vereinfachen Ihrer API-Spezifikationen Häufig sind OpenAPI-Spezifikationen sehr detailliert und enthalten viele Informationen, die für den KI-Agent nicht erforderlich sind, um einem Benutzer zu helfen. Je einfacher die API ist, desto weniger Token müssen Sie verbrauchen, um sie zu beschreiben, und desto weniger Token muss die KI für Anfragen an sie senden.
Vermeiden Sie Zeichenfolgenparameter Vermeiden Sie nach Möglichkeit die Verwendung von Zeichenfolgenparametern in Ihrer API. Verwenden Sie stattdessen spezifischere Typen wie ganze Zahlen, Booleane oder Enumerationen. Dies hilft der KI, die API besser zu verstehen.
Beispiele in Beschreibungen anführen Wenn Menschen Swagger-Dateien verwenden, können sie die API in der Regel mithilfe der Swagger-Benutzeroberfläche testen, die Beispielanforderungen und -antworten enthält. Da der KI-Agent dies nicht tun kann, sollten Sie Beispiele in den Beschreibungen der Parameter angeben.
Verweisen auf andere Endpunkte in Beschreibungen Häufig verwechseln AIs ähnliche Endpunkte. Um die KI bei der Unterscheidung zwischen Endpunkten zu unterstützen, sollten Sie in den Beschreibungen auf andere Endpunkte verweisen. Sie könnten z. B. sagen: "Dieser Endpunkt ähnelt dem get_all_lights Endpunkt, gibt aber nur ein einzelnes Licht zurück."
Bereitstellung von hilfreichen Fehlermeldungen Obwohl dies nicht innerhalb der OpenAPI-Spezifikation liegt, sollten Sie Fehlermeldungen bereitstellen, die der KI helfen, sich selbstständig zu korrigieren. Wenn ein Benutzer z. B. eine ungültige ID bereitstellt, sollten Sie eine Fehlermeldung bereitstellen, die dem KI-Agenten vorschlägt, die richtige ID vom get_all_lights-Endpunkt abzurufen.

Nächste Schritte

Nachdem Sie nun wissen, wie Sie ein Plug-In erstellen, können Sie jetzt erfahren, wie Sie sie mit Ihrem KI-Agent verwenden können. Abhängig von der Art der Funktionen, die Sie Ihren Plug-Ins hinzugefügt haben, gibt es verschiedene Muster, denen Sie folgen sollten. Weitere Informationen zu Abruffunktionen finden Sie im Artikel über das Verwenden der Abruffunktionen. Informationen zu Aufgabenautomatisierungs-Funktionen finden Sie in der verwendung von Aufgabenautomatisierungsfunktionen Artikel.