Dodaj wtyczki z specyfikacji OpenAPI
Często w przedsiębiorstwie masz już zestaw interfejsów API, które wykonują rzeczywistą pracę. Mogą one być używane przez inne usługi automatyzacji lub aplikacje front-endowe, z którymi ludzie wchodzą w interakcje. W jądrze semantycznym można dodać dokładnie te same interfejsy API co wtyczki, aby agenci mogli ich również używać.
Przykładowa specyfikacja interfejsu OpenAPI
Weźmy na przykład interfejs API, który umożliwia zmianę stanu żarówek. Specyfikacja OpenAPI, znana jako specyfikacja Swagger lub po prostu Swagger, dla tego API może wyglądać następująco:
{
"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
}
}
}
}
Ta specyfikacja udostępnia wszystkie elementy potrzebne przez sztuczną inteligencję do zrozumienia interfejsu API i sposobu interakcji z nim. Interfejs API zawiera dwa punkty końcowe: jeden, aby uzyskać wszystkie światła, a drugi, aby zmienić stan światła. Udostępnia również następujące elementy:
- Semantyczne opisy punktów końcowych i ich parametrów
- Typy parametrów
- Oczekiwane odpowiedzi
Ponieważ agent sztucznej inteligencji może zrozumieć tę specyfikację, możesz dodać go jako wtyczkę do agenta.
Semantic Kernel obsługuje wersje OpenAPI w wersji 2.0 i 3.0 i ma na celu dostosowanie specyfikacji wersji 3.1 przez obniżenie jej do wersji 3.0.
Napiwek
Jeśli masz istniejące specyfikacje interfejsu OpenAPI, może być konieczne wprowadzenie zmian, aby ułatwić im zrozumienie sztucznej inteligencji. Na przykład może być konieczne podanie wskazówek w opisach. Aby uzyskać więcej wskazówek dotyczących tworzenia specyfikacji interfejsu OpenAPI przyjaznych dla sztucznej inteligencji, zobacz Porady i wskazówki dotyczące dodawania wtyczek openAPI.
Dodawanie wtyczki OpenAPI
Za pomocą kilku wierszy kodu możesz dodać wtyczkę OpenAPI do agenta. Poniższy fragment kodu pokazuje, jak dodać wtyczkę light z powyższej specyfikacji 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
}
);
Za pomocą jądra semantycznego można dodawać wtyczki OpenAPI z różnych źródeł, takich jak adres URL, plik lub strumień. Ponadto wtyczki można tworzyć raz i ponownie używać w wielu wystąpieniach jądra lub agentach.
// 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();
Następnie możesz użyć wtyczki w agencie tak, jakby była to wtyczka natywna.
Obsługa parametrów wtyczki OpenAPI
Semantyczne jądro automatycznie wyodrębnia metadane — takie jak nazwa, opis, typ i schemat dla wszystkich parametrów zdefiniowanych w dokumentach OpenAPI.
Te metadane są przechowywane we właściwości KernelFunction.Metadata.Parameters
dla każdej operacji interfejsu OpenAPI i są dostarczane do usługi LLM wraz z monitem o wygenerowanie prawidłowych argumentów dla wywołań funkcji.
Domyślnie oryginalna nazwa parametru jest dostarczana do usługi LLM i jest używana przez jądro semantyczne w celu wyszukania odpowiedniego argumentu na liście argumentów dostarczonych przez moduł LLM. Jednak mogą wystąpić przypadki, w których wtyczka OpenAPI ma wiele parametrów o tej samej nazwie. Podanie tych metadanych parametru w usłudze LLM może spowodować zamieszanie, co może potencjalnie uniemożliwić usłudze LLM generowanie prawidłowych argumentów dla wywołań funkcji.
Ponadto, ponieważ funkcja jądra, która nie zezwala na nazwy parametrów innych niż unikatowa, jest tworzona dla każdej operacji interfejsu OpenAPI, dodanie takiej wtyczki może spowodować, że niektóre operacje staną się niedostępne do użycia. W szczególności operacje z nazwami parametrów innych niż unikatowe zostaną pominięte, a odpowiednie ostrzeżenie zostanie zarejestrowane. Nawet jeśli istnieje możliwość uwzględnienia wielu parametrów o tej samej nazwie w funkcji jądra, może to prowadzić do niejednoznaczności w procesie wyboru argumentu.
Biorąc pod uwagę wszystkie te elementy, semantyczne jądro oferuje rozwiązanie do zarządzania wtyczkami z nazwami parametrów, które nie są unikatowe. To rozwiązanie jest szczególnie przydatne, gdy zmiana samego interfejsu API nie jest możliwa, niezależnie od tego, czy jest to usługa innej firmy, czy starszy system.
Poniższy fragment kodu pokazuje, jak obsługiwać nazwy parametrów innych niż unikatowe w wtyczki OpenAPI. Jeśli operacja change_light_state miała dodatkowy parametr o takiej samej nazwie jak istniejący parametr "id" — w szczególności do reprezentowania identyfikatora sesji oprócz bieżącego "id", który reprezentuje identyfikator światła — można go obsłużyć, jak pokazano poniżej:
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);
Ten fragment kodu wykorzystuje klasę OpenApiDocumentParser
do analizowania dokumentu OpenAPI i uzyskiwania dostępu do obiektu modelu RestApiSpecification
reprezentującego dokument. Przypisuje nazwy argumentów do parametrów i importuje przekształconą specyfikację wtyczki OpenAPI do jądra. Semantyczne jądro udostępnia nazwy argumentów llM zamiast oryginalnych nazw i używa ich do wyszukiwania odpowiednich argumentów na liście dostarczonej przez llM.
Należy pamiętać, że nazwy argumentów nie są używane zamiast oryginalnych nazw podczas wywoływania operacji OpenAPI. W powyższym przykładzie parametr "id" w ścieżce zostanie zastąpiony wartością zwróconą przez llM dla argumentu "lightId". To samo dotyczy parametru nagłówka "id"; wartość zwrócona przez llM dla argumentu "sessionId" będzie używana jako wartość nagłówka o nazwie "id".
Obsługa ładunku wtyczek interfejsu OpenAPI
Wtyczki OpenAPI mogą modyfikować stan systemu przy użyciu operacji POST, PUT lub PATCH. Te operacje często wymagają ładunku, który ma zostać dołączony do żądania.
Semantyczne jądro oferuje kilka opcji zarządzania obsługą ładunków dla wtyczek OpenAPI, w zależności od konkretnego scenariusza i wymagań interfejsu API.
Dynamiczna konstrukcja ładunku
Konstrukcja ładunku dynamicznego umożliwia dynamiczne tworzenie ładunków operacji OpenAPI na podstawie schematu ładunku i argumentów dostarczonych przez moduł LLM.
Ta funkcja jest domyślnie włączona, ale można jej wyłączyć, ustawiając właściwość EnableDynamicPayload
na false
w obiekcie OpenApiFunctionExecutionParameters
podczas dodawania wtyczki OpenAPI.
Rozważmy na przykład operację change_light_state, która wymaga ładunku ustrukturyzowanego w następujący sposób:
{
"isOn": true,
"hexColor": "#FF0000",
"brightness": 100,
"fadeDurationInMilliseconds": 500,
"scheduledTime": "2023-07-12T12:00:00Z"
}
Aby zmienić stan światła i uzyskać wartości właściwości ładunku, Jądro Semantyczne udostępnia LLM metadane dla operacji, aby mogło przeanalizować operację.
{
"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"}},
]
}
Oprócz przekazywania metadanych operacyjnych do LLM, Semantyczne Jądro wykona następujące kroki:
- Obsłuż wywołanie LLM do operacji OpenAPI, tworząc ładunek na podstawie schematu oraz wartości właściwości LLM.
- Wyślij żądanie HTTP z ładunkiem do interfejsu API.
Ograniczenia konstrukcji ładunku dynamicznego
Dynamiczna konstrukcja ładunku jest najbardziej skuteczna w przypadku interfejsów API ze stosunkowo prostymi strukturami ładunków. Może to nie działać niezawodnie lub wcale, w przypadku ładunków API wykazujących następujące cechy:
- Ładunki z nieunikatowymi nazwami właściwości niezależnie od ich lokalizacji. Na przykład dwie właściwości o nazwie
id
, jeden dla obiektu nadawcy, a drugi dla obiektu odbiorcy —json { "sender": { "id": ... }, "receiver": { "id": ... }}
- Schematy ładunku, które używają dowolnych kluczowych słów złożonych
oneOf
,anyOf
,allOf
. - Schematy ładunku z rekursywnymi odwołaniami. Na przykład
json { "parent": { "child": { "$ref": "#parent" } } }
Aby obsłużyć ładunki przy użyciu nazw właściwości innych niż unikatowe, rozważ następujące alternatywy:
- Podaj unikatową nazwę argumentu dla każdej nieunikatowej właściwości, używając metody podobnej do opisanej w sekcji Handling OpenAPI plugin parameters.
- Użyj przestrzeni nazw, aby uniknąć konfliktów nazewnictwa, jak opisano w następnej sekcji dotyczącej przestrzeni nazw dla ładunku .
- Wyłącz konstrukcję ładunku dynamicznego i zezwól LLM na utworzenie ładunku na podstawie tego schematu, co wyjaśniono w sekcji parametr ładunku.
Jeśli schematy ładunków używają dowolnego z oneOf
, anyOf
, allOf
złożonych słów kluczowych lub odwołań cyklicznych, rozważ wyłączenie konstrukcji ładunku dynamicznego i zezwól usłudze LLM na utworzenie ładunku na podstawie jego schematu, jak wyjaśniono w sekcji parametr ładunku.
Przestrzeń nazw ładunków
Przestrzeganie nazw w ładunku pomaga zapobiegać konfliktom nazewnictwa, które mogą wystąpić z powodu nieunikatowych nazw właściwości w ładunkach wtyczki OpenAPI.
Po włączeniu funkcji namespacing, jądro semantyczne dostarcza LLM metadane operacji OpenAPI, które zawierają rozszerzone nazwy właściwości. Te rozszerzone nazwy są tworzone przez dodanie nazwy właściwości nadrzędnej jako prefiksu oddzielonego kropką do nazw właściwości podrzędnych.
Jeśli na przykład operacja change_light_state zawierała zagnieżdżony obiekt offTimer
z właściwością scheduledTime
:
{
"isOn": true,
"hexColor": "#FF0000",
"brightness": 100,
"fadeDurationInMilliseconds": 500,
"scheduledTime": "2023-07-12T12:00:00Z",
"offTimer": {
"scheduledTime": "2023-07-12T12:00:00Z"
}
}
Jądro semantyczne dostarczyło usłudze LLM metadane dla operacji zawierającej następujące nazwy właściwości:
{
"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"}},
]
}
Oprócz udostępniania metadanych operacji z rozszerzonymi nazwami właściwości dla LLM, Semantic Kernel wykonuje następujące kroki:
- Obsłuż wywołanie llm do operacji OpenAPI i wyszukaj odpowiednie argumenty wśród tych dostarczonych przez LLM dla wszystkich właściwości ładunku, używając rozszerzonych nazw właściwości i wracając do oryginalnych nazw właściwości, jeśli to konieczne.
- Skonstruuj ładunek przy użyciu oryginalnych nazw właściwości jako kluczy i rozpoznanych argumentów jako wartości.
- Wyślij żądanie HTTP z skonstruowanym ładunkiem do API.
Domyślnie opcja namespacingu ładunku jest wyłączona. Można ją włączyć, ustawiając właściwość EnablePayloadNamespacing
na true
w obiekcie OpenApiFunctionExecutionParameters
podczas dodawania wtyczki 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
});
Notatka
Opcja EnablePayloadNamespace
ma zastosowanie tylko wtedy, gdy jest również włączona konstrukcja ładunku dynamicznego; w przeciwnym razie nie ma żadnego efektu.
Parametr ładunku
Semantyczne jądro może pracować z ładunkami utworzonymi przez moduł LLM przy użyciu parametru ładunku. Jest to przydatne, gdy schemat ładunku jest złożony i zawiera nieunikatowe nazwy właściwości, co sprawia, że jest to niemożliwe dla Jądra Semantycznego do dynamicznego konstruowania ładunku.
W takich przypadkach będziesz polegać na zdolności LLM do zrozumienia schematu i utworzenia prawidłowych danych. Ostatnie modele, takie jak gpt-4o
, są skuteczne w generowaniu prawidłowych ładunków JSON.
Aby włączyć parametr ładunku, ustaw właściwość EnableDynamicPayload
na false
w obiekcie OpenApiFunctionExecutionParameters
podczas dodawania wtyczki OpenAPI:
await kernel.ImportPluginFromOpenApiAsync(
pluginName: "lights",
uri: new Uri("https://example.com/v1/swagger.json"),
executionParameters: new OpenApiFunctionExecutionParameters()
{
EnableDynamicPayload = false, // Disable dynamic payload construction
});
Po włączeniu parametru ładunku, Semantic Kernel dostarcza modułowi LLM metadane dla operacji, które zawierają schematy dla parametrów ładunku i content_type, co pozwala modułowi LLM zrozumieć strukturę ładunku i skonstruować ją odpowiednio.
{
"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."
}
}
}
Oprócz dostarczania metadanych operacji razem ze schematem dla ładunku i parametrów typu zawartości do LLM, jądro semantyczne wykonuje następujące kroki:
- Zarządzaj wywołaniem LLM do operacji OpenAPI i wykorzystuje argumenty dostarczone przez LLM dla parametrów payload i content_type.
- Wyślij żądanie HTTP do interfejsu API z podanym ładunkiem i typem zawartości.
Podstawowy adres URL serwera
Wtyczki OpenAPI dla jądra semantycznego wymagają podstawowego adresu URL, który jest używany do poprzedzania ścieżek punktów dostępu dla żądań API. Ten podstawowy adres URL można określić w dokumencie OpenAPI uzyskanym niejawnie przez załadowanie dokumentu z adresu URL lub podany podczas dodawania wtyczki do jądra.
Adres URL określony w dokumencie OpenAPI
Dokumenty openAPI w wersji 2 definiują adres URL serwera przy użyciu pól schemes
, host
i basePath
:
{
"swagger": "2.0",
"host": "example.com",
"basePath": "/v1",
"schemes": ["https"]
...
}
Semantyczne jądro skonstruuje adres URL serwera jako https://example.com/v1
.
Natomiast dokumenty openAPI w wersji 3 definiują adres URL serwera przy użyciu pola servers
:
{
"openapi": "3.0.1",
"servers": [
{
"url": "https://example.com/v1"
}
],
...
}
Jądro semantyczne będzie używać pierwszego adresu URL serwera określonego w dokumencie jako podstawowego adresu URL: https://example.com/v1
.
Interfejs OpenAPI w wersji 3 umożliwia również sparametryzowane adresy URL serwera przy użyciu zmiennych wskazywanych przez nawiasy klamrowe:
{
"openapi": "3.0.1",
"servers": [
{
"url": "https://{environment}.example.com/v1",
"variables": {
"environment": {
"default": "prod"
}
}
}
],
...
}
W takim przypadku semantyczne jądro zastąpi symbol zastępczy zmiennej wartością podaną jako argument zmiennej lub wartością domyślną, jeśli nie podano argumentu, co spowoduje adres URL: https://prod.example.com/v1
.
Jeśli dokument OpenAPI nie określa adresu URL serwera, semantyczne jądro będzie używać podstawowego adresu URL serwera, z którego załadowano dokument OpenAPI:
await kernel.ImportPluginFromOpenApiAsync(pluginName: "lights", uri: new Uri("https://api-host.com/swagger.json"));
Podstawowy adres URL będzie https://api-host.com
.
Zastępowanie adresu URL serwera
W niektórych przypadkach adres URL serwera określony w dokumencie OpenAPI lub serwerze, z którego załadowano dokument, może nie być odpowiedni w przypadku przypadków użycia dotyczących wtyczki OpenAPI.
Semantyczne jądro umożliwia zastąpienie adresu URL serwera przez podanie niestandardowego podstawowego adresu URL podczas dodawania wtyczki OpenAPI do jądra:
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")
});
W tym przykładzie podstawowy adres URL będzie https://custom-server.com/v1
, przesłaniając adres URL serwera określony w dokumencie OpenAPI i adres URL serwera, z którego załadowano dokument.
Uwierzytelnianie
Większość interfejsów API REST wymaga uwierzytelniania w celu uzyskania dostępu do swoich zasobów. Semantyczne jądro udostępnia mechanizm, który umożliwia integrację różnych metod uwierzytelniania wymaganych przez wtyczki OpenAPI.
Ten mechanizm opiera się na funkcji wywołania zwrotnego uwierzytelniania, która jest wywoływana przed każdym żądaniem interfejsu API. Ta funkcja wywołania zwrotnego ma dostęp do obiektu HttpRequestMessage reprezentującego żądanie HTTP, które zostanie wysłane do interfejsu API. Tego obiektu można użyć do dodawania poświadczeń uwierzytelniania do żądania. Poświadczenia można dodawać jako nagłówki, parametry zapytania lub w treści żądania, w zależności od metody uwierzytelniania używanej przez interfejs API.
Należy zarejestrować tę funkcję wywołania zwrotnego podczas dodawania wtyczki OpenAPI do jądra. Poniższy fragment kodu pokazuje, jak zarejestrować go w celu uwierzytelnienia żądań:
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
});
W przypadku bardziej złożonych scenariuszy uwierzytelniania, które wymagają dynamicznego dostępu do szczegółów schematów uwierzytelniania obsługiwanych przez interfejs API, można użyć metadanych dokumentów i operacji, aby uzyskać te informacje. Aby uzyskać więcej informacji, zobacz metadane dokumentu i operacji .
Dostosowywanie odczytywania zawartości odpowiedzi
Semantyczne jądro ma wbudowany mechanizm odczytywania zawartości odpowiedzi HTTP z wtyczek interfejsu OpenAPI i konwertowania ich na odpowiednie typy danych platformy .NET. Na przykład odpowiedź obrazu może być odczytywana jako tablica bajtów, podczas gdy odpowiedź JSON lub XML może być odczytywana jako ciąg.
Jednak mogą wystąpić przypadki, gdy wbudowany mechanizm jest niewystarczający do Twoich potrzeb. Na przykład gdy odpowiedź jest dużym obiektem JSON lub obrazem, który musi być odczytywany jako strumień, aby można go było dostarczyć jako dane wejściowe do innego interfejsu API. W takich przypadkach odczytywanie zawartości odpowiedzi jako ciągu lub tablicy bajtów, a następnie konwertowanie jej z powrotem na strumień może być nieefektywne i może prowadzić do problemów z wydajnością. Aby rozwiązać ten problem, semantyczne jądro umożliwia dostosowywanie odczytu zawartości odpowiedzi przez udostępnienie niestandardowego czytnika zawartości:
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
});
W tym przykładzie metoda ReadHttpResponseContentAsync
odczytuje zawartość odpowiedzi HTTP jako strumień, gdy typ zawartości jest application/json
lub gdy żądanie zawiera niestandardowy nagłówek x-stream
. Metoda zwraca null
dla innych typów zawartości, co oznacza, że powinien być używany domyślny czytnik zawartości.
Metadane dokumentu i operacji
Semantyczne jądro wyodrębnia metadane dokumentu OpenAPI i operacji, w tym informacje o interfejsie API, schematy zabezpieczeń, identyfikator operacji, opis, metadane parametrów i wiele innych.
Zapewnia dostęp do tych informacji za pośrednictwem właściwości KernelFunction.Metadata.AdditionalParameters
. Te metadane mogą być przydatne w scenariuszach, w których wymagane są dodatkowe informacje o interfejsie API lub operacji, takie jak na potrzeby uwierzytelniania:
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");
W tym przykładzie metoda AuthenticateRequestAsyncCallbackAsync
odczytuje metadane operacji z kontekstu funkcji i wyodrębnia wymagania dotyczące zabezpieczeń operacji w celu określenia schematu uwierzytelniania. Następnie pobiera klucz interfejsu API dla schematu i zakresów z dostawcy tożsamości aplikacji i dodaje go do nagłówków żądania lub parametrów zapytania.
W poniższej tabeli wymieniono metadane dostępne w słowniku KernelFunction.Metadata.AdditionalParameters
:
Klucz | Typ | Opis |
---|---|---|
Informacje | RestApiInfo |
Informacje o interfejsie API, w tym tytuł, opis i wersja. |
operacja | RestApiOperation |
Szczegóły operacji interfejsu API, takie jak identyfikator, opis, ścieżka, metoda itp. |
bezpieczeństwo | IList<RestApiSecurityRequirement > |
Wymagania dotyczące zabezpieczeń interfejsu API — typ, nazwa, in itp. |
Porady i wskazówki dotyczące dodawania wtyczek interfejsu OpenAPI
Ponieważ specyfikacje interfejsu OpenAPI są zwykle przeznaczone dla ludzi, może być konieczne wprowadzenie pewnych zmian, aby ułatwić zrozumienie sztucznej inteligencji. Oto kilka wskazówek i wskazówek, które pomogą Ci to zrobić:
Zalecenie | Opis |
---|---|
Zarządzaj wersjami specyfikacji interfejsu API | Zamiast wskazywać na specyfikację API na żywo, rozważ zatwierdzenie i wersjonowanie pliku Swagger. Pozwoli to badaczom sztucznej inteligencji przetestować (i zmienić) specyfikację interfejsu API używaną przez agenta sztucznej inteligencji bez wpływu na dynamiczny interfejs API i odwrotnie. |
Ogranicz liczbę punktów końcowych | Spróbuj ograniczyć liczbę punktów końcowych w interfejsie API. Skonsoliduj podobne funkcje do pojedynczych punktów końcowych z opcjonalnymi parametrami, aby zmniejszyć złożoność. |
Używanie nazw opisowych dla punktów końcowych i parametrów | Upewnij się, że nazwy punktów końcowych i parametrów są opisowe i objaśniające. Pomaga to sztucznej inteligencji zrozumieć swój cel bez konieczności obszernych wyjaśnień. |
Używanie spójnych konwencji nazewnictwa | Zachowaj spójne konwencje nazewnictwa w całym interfejsie API. Zmniejsza to zamieszanie i ułatwia uczenie się sztucznej inteligencji i przewidywanie struktury interfejsu API. |
Upraszczaj swoje specyfikacje API | Często specyfikacje interfejsu OpenAPI są bardzo szczegółowe i zawierają wiele informacji, które nie są niezbędne dla agenta sztucznej inteligencji, aby pomóc użytkownikowi. Im prostszy interfejs API, tym mniej tokenów jest potrzebnych do jego opisania oraz do wysyłania do niego żądań przez sztuczną inteligencję. |
Unikaj parametrów ciągu | Jeśli to możliwe, unikaj używania parametrów ciągu w interfejsie API. Zamiast tego należy używać bardziej szczegółowych typów, takich jak liczby całkowite, wartości logiczne lub wyliczenia. Ułatwi to lepsze zrozumienie interfejsu API przez sztuczną inteligencję. |
Podaj przykłady w opisach | Gdy ludzie używają plików struktury Swagger, zazwyczaj mogą testować interfejs API przy użyciu interfejsu użytkownika struktury Swagger, który obejmuje przykładowe żądania i odpowiedzi. Ponieważ agent sztucznej inteligencji nie może tego zrobić, rozważ podanie przykładów w opisach parametrów. |
Odwołaj się do innych punktów końcowych w opisach | Często sztuczna inteligencja myli podobne punkty końcowe. Aby ułatwić odróżnienie sztucznej inteligencji między punktami końcowymi, rozważ odwołanie się do innych punktów końcowych w opisach. Na przykład można powiedzieć: "Ten punkt końcowy jest podobny do punktu końcowego get_all_lights , ale zwraca tylko pojedyncze światło". |
Podaj przydatne komunikaty o błędach | Chociaż nie należy do specyfikacji interfejsu OpenAPI, rozważ podanie komunikatów o błędach, które ułatwiają samodzielne poprawianie sztucznej inteligencji. Jeśli na przykład użytkownik podaje nieprawidłowy identyfikator, rozważ podanie komunikatu o błędzie, który sugeruje, że agent sztucznej inteligencji uzyska prawidłowy identyfikator z punktu końcowego get_all_lights . |
Następne kroki
Teraz, gdy wiesz, jak utworzyć wtyczkę, możesz teraz dowiedzieć się, jak używać ich z agentem sztucznej inteligencji. W zależności od typu funkcji dodanych do wtyczek należy przestrzegać różnych wzorców. Aby zapoznać się z funkcjami pobierania, odwołaj się do artykułu dotyczącego użycia funkcji pobierania. W przypadku funkcji automatyzacji zadań zapoznaj się z artykułem korzystając z funkcji automatyzacji zadań.