Skip to content

Commit 0f61101

Browse files
authoredNov 28, 2024
.Net: Add OpenAPI operations filtering samples (microsoft#9834)
1. Add samples demonstrating the ways OpenAPI operations can be filtered. 2. Use OpenAIPromptExecutionSettings instead of AzureOpenAIPromptExecutionSettings with OpenAI connector.
1 parent b9263bb commit 0f61101

File tree

3 files changed

+203
-7
lines changed

3 files changed

+203
-7
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using Microsoft.SemanticKernel;
4+
using Microsoft.SemanticKernel.Connectors.OpenAI;
5+
using Microsoft.SemanticKernel.Plugins.OpenApi;
6+
7+
namespace Plugins;
8+
9+
/// <summary>
10+
/// These samples show different ways OpenAPI operations can be filtered out from the OpenAPI document before creating a plugin out of it.
11+
/// </summary>
12+
public sealed class OpenApiPlugin_Filtering : BaseTest
13+
{
14+
private readonly Kernel _kernel;
15+
private readonly ITestOutputHelper _output;
16+
17+
public OpenApiPlugin_Filtering(ITestOutputHelper output) : base(output)
18+
{
19+
IKernelBuilder builder = Kernel.CreateBuilder();
20+
builder.AddOpenAIChatCompletion(
21+
modelId: TestConfiguration.OpenAI.ChatModelId,
22+
apiKey: TestConfiguration.OpenAI.ApiKey);
23+
24+
this._kernel = builder.Build();
25+
26+
this._output = output;
27+
}
28+
29+
/// <summary>
30+
/// This sample demonstrates how to filter out specified operations from an OpenAPI plugin based on an exclusion list.
31+
/// In this scenario, only the `listRepairs` operation from the RepairService OpenAPI plugin is allowed to be invoked,
32+
/// while operations such as `createRepair`, `updateRepair`, and `deleteRepair` are excluded.
33+
/// Note: The filtering occurs at the pre-parsing stage, which is more efficient from a resource utilization perspective.
34+
/// </summary>
35+
[Fact]
36+
public async Task ExcludeOperationsBasedOnExclusionListAsync()
37+
{
38+
// The RepairService OpenAPI plugin being imported below includes the following operations: `listRepairs`, `createRepair`, `updateRepair`, and `deleteRepair`.
39+
// However, to meet our business requirements, we need to restrict state-modifying operations such as creating, updating, and deleting repairs, allowing only non-state-modifying operations like listing repairs.
40+
// To enforce this restriction, we will exclude the `createRepair`, `updateRepair`, and `deleteRepair` operations from the OpenAPI document prior to importing the plugin.
41+
OpenApiFunctionExecutionParameters executionParameters = new()
42+
{
43+
OperationsToExclude = ["createRepair", "updateRepair", "deleteRepair"]
44+
};
45+
46+
// Import the RepairService OpenAPI plugin and filter out all operations except `listRepairs` one.
47+
await this._kernel.ImportPluginFromOpenApiAsync(
48+
pluginName: "RepairService",
49+
filePath: "Resources/Plugins/RepairServicePlugin/repair-service.json",
50+
executionParameters: executionParameters);
51+
52+
// Tell the AI model not to call any function and show the list of functions it can call instead.
53+
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.None() };
54+
FunctionResult result = await this._kernel.InvokePromptAsync(promptTemplate: "Show me the list of the functions you can call", arguments: new KernelArguments(settings));
55+
56+
this._output.WriteLine(result);
57+
58+
// The AI model output:
59+
// I can call the following functions in the current context:
60+
// 1. `functions.RepairService - listRepairs`: Returns a list of repairs with their details and images. It takes an optional parameter `assignedTo` to filter the repairs based on the assigned individual.
61+
// I can also utilize the `multi_tool_use.parallel` function to execute multiple tools in parallel if required.
62+
}
63+
64+
/// <summary>
65+
/// This sample demonstrates how to include specified operations from an OpenAPI plugin based on an inclusion list.
66+
/// In this scenario, only the `createRepair` and `updateRepair` operations from the RepairService OpenAPI plugin are allowed to be invoked,
67+
/// while operations such as `listRepairs` and `deleteRepair` are excluded.
68+
/// Note: The filtering occurs at the pre-parsing stage, which is more efficient from a resource utilization perspective.
69+
/// </summary>
70+
[Fact]
71+
public async Task ImportOperationsBasedOnInclusionListAsync()
72+
{
73+
OpenApiDocumentParser parser = new();
74+
using StreamReader reader = System.IO.File.OpenText("Resources/Plugins/RepairServicePlugin/repair-service.json");
75+
76+
// The RepairService OpenAPI plugin, parsed and imported below, has the following operations: `listRepairs`, `createRepair`, `updateRepair`, and `deleteRepair`.
77+
// However, for our business scenario, we only want to permit the AI model to invoke the `createRepair` and `updateRepair` operations, excluding all others.
78+
// To accomplish this, we will define an inclusion list that specifies the allowed operations and filters out the rest.
79+
List<string> operationsToInclude = ["createRepair", "updateRepair"];
80+
81+
// The selection predicate is initialized to evaluate each operation in the OpenAPI document and include only those specified in the inclusion list.
82+
OpenApiDocumentParserOptions parserOptions = new()
83+
{
84+
OperationSelectionPredicate = (OperationSelectionPredicateContext context) => operationsToInclude.Contains(context.Id!)
85+
};
86+
87+
// Parse the OpenAPI document.
88+
RestApiSpecification specification = await parser.ParseAsync(stream: reader.BaseStream, options: parserOptions);
89+
90+
// Import the OpenAPI document specification.
91+
this._kernel.ImportPluginFromOpenApi("RepairService", specification);
92+
93+
// Tell the AI model not to call any function and show the list of functions it can call instead.
94+
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.None() };
95+
FunctionResult result = await this._kernel.InvokePromptAsync(promptTemplate: "Show me the list of the functions you can call", arguments: new KernelArguments(settings));
96+
97+
this._output.WriteLine(result);
98+
99+
// The AI model output:
100+
// Here are the functions I can call for you:
101+
// 1. **RepairService - createRepair **:
102+
// -Adds a new repair to the list with details about the repair.
103+
// 2. **RepairService - updateRepair **:
104+
// -Updates an existing repair in the list with new details.
105+
// If you need to perform any repair - related actions such as creating or updating repair records, feel free to ask!
106+
}
107+
108+
/// <summary>
109+
/// This sample demonstrates how to selectively include certain operations from an OpenAPI plugin based on HTTP method used.
110+
/// In this scenario, only `GET` operations from the RepairService OpenAPI plugin are allowed for invocation,
111+
/// while `POST`, `PUT`, and `DELETE` operations are excluded.
112+
/// Note: The filtering occurs at the pre-parsing stage, which is more efficient from a resource utilization perspective.
113+
/// </summary>
114+
[Fact]
115+
public async Task ImportOperationsBasedOnMethodAsync()
116+
{
117+
OpenApiDocumentParser parser = new();
118+
using StreamReader reader = System.IO.File.OpenText("Resources/Plugins/RepairServicePlugin/repair-service.json");
119+
120+
// The parsed RepairService OpenAPI plugin includes operations such as `listRepairs`, `createRepair`, `updateRepair`, and `deleteRepair`.
121+
// However, for our business requirements, we only permit non-state-modifying operations like listing repairs, excluding all others.
122+
// To achieve this, we set up the selection predicate to evaluate each operation in the OpenAPI document, including only those with the `GET` method.
123+
// Note: The selection predicate can assess operations based on operation ID, method, path, and description.
124+
OpenApiDocumentParserOptions parserOptions = new()
125+
{
126+
OperationSelectionPredicate = (OperationSelectionPredicateContext context) => context.Method == "Get"
127+
};
128+
129+
// Parse the OpenAPI document.
130+
RestApiSpecification specification = await parser.ParseAsync(stream: reader.BaseStream, options: parserOptions);
131+
132+
// Import the OpenAPI document specification.
133+
this._kernel.ImportPluginFromOpenApi("RepairService", specification);
134+
135+
// Tell the AI model not to call any function and show the list of functions it can call instead.
136+
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.None() };
137+
FunctionResult result = await this._kernel.InvokePromptAsync(promptTemplate: "Show me the list of the functions you can call", arguments: new KernelArguments(settings));
138+
139+
this._output.WriteLine(result);
140+
141+
// The AI model output:
142+
// I can call the following function:
143+
// 1. `RepairService - listRepairs`: This function returns a list of repairs with their details and images.
144+
// It can accept an optional parameter `assignedTo` to filter the repairs assigned to a specific person.
145+
}
146+
147+
/// <summary>
148+
/// This example illustrates how to selectively exclude specific operations from an OpenAPI plugin based on the HTTP method used and the presence of a payload.
149+
/// In this context, GET operations that are defined with a payload, which contradicts the HTTP semantic of being idempotent, are not imported.
150+
/// Note: The filtering happens at the post-parsing stage, which is less efficient in terms of resource utilization.
151+
/// </summary>
152+
[Fact]
153+
public async Task FilterOperationsAtPostParsingStageAsync()
154+
{
155+
OpenApiDocumentParser parser = new();
156+
using StreamReader reader = System.IO.File.OpenText("Resources/Plugins/RepairServicePlugin/repair-service.json");
157+
158+
// Parse the OpenAPI document.
159+
RestApiSpecification specification = await parser.ParseAsync(stream: reader.BaseStream);
160+
161+
// The parsed RepairService OpenAPI plugin includes operations like `listRepairs`, `createRepair`, `updateRepair`, and `deleteRepair`.
162+
// However, based on our business requirements, we need to identify all GET operations that are defined as non-idempotent (i.e., have a payload),
163+
// log a warning for each of them, and exclude these operations from the import.
164+
// To do this, we will locate all GET operations that contain a payload.
165+
// Note that the RepairService OpenAPI plugin does not have any GET operations with payloads, so no operations will be found in this case.
166+
// However, the code below demonstrates how to identify and exclude such operations if they were present.
167+
IEnumerable<RestApiOperation> operationsToExclude = specification.Operations.Where(o => o.Method == HttpMethod.Get && o.Payload is not null);
168+
169+
// Exclude operations that are declared as non-idempotent due to having a payload.
170+
foreach (RestApiOperation operation in operationsToExclude)
171+
{
172+
this.Output.WriteLine($"Warning: The `{operation.Id}` operation with `{operation.Method}` has payload which contradicts to being idempotent. This operation will not be imported.");
173+
specification.Operations.Remove(operation);
174+
}
175+
176+
// Import the OpenAPI document specification.
177+
this._kernel.ImportPluginFromOpenApi("RepairService", specification);
178+
179+
// Tell the AI model not to call any function and show the list of functions it can call instead.
180+
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.None() };
181+
FunctionResult result = await this._kernel.InvokePromptAsync(promptTemplate: "Show me the list of the functions you can call", arguments: new KernelArguments(settings));
182+
183+
this._output.WriteLine(result);
184+
185+
// The AI model output:
186+
// I can call the following functions:
187+
// 1. **RepairService - listRepairs **: Returns a list of repairs with their details and images.
188+
// 2. **RepairService - createRepair **: Adds a new repair to the list with the given details and image URL.
189+
// 3. **RepairService - updateRepair **: Updates an existing repair with new details and image URL.
190+
// 4. **RepairService - deleteRepair **: Deletes an existing repair from the list using its ID.
191+
}
192+
}

‎dotnet/samples/Concepts/Plugins/OpenApiPlugin_PayloadHandling.cs

+7-7
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
using System.Text;
55
using System.Text.Json;
66
using Microsoft.SemanticKernel;
7-
using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
7+
using Microsoft.SemanticKernel.Connectors.OpenAI;
88
using Microsoft.SemanticKernel.Plugins.OpenApi;
99

1010
namespace Plugins;
@@ -140,7 +140,7 @@ public async Task InvokeOpenApiFunctionWithPayloadProvidedByCallerAsync()
140140
await this._kernel.InvokeAsync(createMeetingFunction, arguments);
141141

142142
// Example of how to have the createEvent function invoked by the AI
143-
AzureOpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
143+
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
144144
await this._kernel.InvokePromptAsync("Schedule one hour IT Meeting for October 1st, 2023, at 10:00 AM UTC.", new KernelArguments(settings));
145145
}
146146

@@ -201,7 +201,7 @@ public async Task InvokeOpenApiFunctionWithArgumentsForPayloadLeafPropertiesAsyn
201201
await this._kernel.InvokeAsync(createMeetingFunction, arguments);
202202

203203
// Example of how to have the createEvent function invoked by the AI
204-
AzureOpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
204+
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
205205
await this._kernel.InvokePromptAsync("Schedule one hour IT Meeting for October 1st, 2023, at 10:00 AM UTC.", new KernelArguments(settings));
206206
}
207207

@@ -282,7 +282,7 @@ public async Task InvokeOpenApiFunctionWithArgumentsForPayloadLeafPropertiesWith
282282
await this._kernel.InvokeAsync(createMeetingFunction, arguments);
283283

284284
// Example of how to have the createEvent function invoked by the AI
285-
AzureOpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
285+
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
286286
await this._kernel.InvokePromptAsync("Schedule one hour IT Meeting for October 1st, 2023, at 10:00 AM UTC.", new KernelArguments(settings));
287287
}
288288

@@ -302,7 +302,7 @@ public async Task InvokeOpenApiFunctionWithArgumentsForPayloadOneOfAsync()
302302
});
303303

304304
// Example of how to have the updatePater function invoked by the AI
305-
AzureOpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
305+
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
306306
Console.WriteLine("\nExpected payload: Dog { breed=Husky, bark=false }");
307307
await this._kernel.InvokePromptAsync("My new dog is a Husky, he is very quiet, please create my pet information.", new KernelArguments(settings));
308308
Console.WriteLine("\nExpected payload: Dog { breed=Dingo, bark=true }");
@@ -331,7 +331,7 @@ public async Task InvokeOpenApiFunctionWithArgumentsForPayloadAllOfAsync()
331331
});
332332

333333
// Example of how to have the updatePater function invoked by the AI
334-
AzureOpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
334+
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
335335
Console.WriteLine("\nExpected payload: { pet_type=dog, breed=Husky, bark=false }");
336336
Console.WriteLine(await this._kernel.InvokePromptAsync("My new dog is a Husky, he is very quiet, please update my pet information.", new KernelArguments(settings)));
337337
Console.WriteLine("\nExpected payload: { pet_type=dog, breed=Dingo, bark=true }");
@@ -361,7 +361,7 @@ public async Task InvokeOpenApiFunctionWithArgumentsForPayloadAnyOfAsync()
361361
});
362362

363363
// Example of how to have the updatePater function invoked by the AI
364-
AzureOpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
364+
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
365365
Console.WriteLine("\nExpected payload: { pet_type=Dog, nickname=Fido }");
366366
Console.WriteLine(await this._kernel.InvokePromptAsync("My new dog is named Fido he is 2 years old, please create my pet information.", new KernelArguments(settings)));
367367
Console.WriteLine("\nExpected payload: { pet_type=Dog, nickname=Spot age=1 hunts=true }");

‎dotnet/samples/Concepts/README.md

+4
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,10 @@ dotnet test -l "console;verbosity=detailed" --filter "FullyQualifiedName=ChatCom
163163
- [CreatePluginFromOpenApiSpec_Klarna](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Plugins/CreatePluginFromOpenApiSpec_Klarna.cs)
164164
- [CreatePluginFromOpenApiSpec_RepairService](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Plugins/CreatePluginFromOpenApiSpec_RepairService.cs)
165165
- [OpenApiPlugin_PayloadHandling](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Plugins/OpenApiPlugin_PayloadHandling.cs)
166+
- [OpenApiPlugin_CustomHttpContentReader](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Plugins/OpenApiPlugin_CustomHttpContentReader.cs)
167+
- [OpenApiPlugin_Customization](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Plugins/OpenApiPlugin_Customization.cs)
168+
- [OpenApiPlugin_Filtering](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Plugins/OpenApiPlugin_Filtering.cs)
169+
- [OpenApiPlugin_Telemetry](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Plugins/OpenApiPlugin_Telemetry.cs)
166170
- [CustomMutablePlugin](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Plugins/CustomMutablePlugin.cs)
167171
- [DescribeAllPluginsAndFunctions](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Plugins/DescribeAllPluginsAndFunctions.cs)
168172
- [GroundednessChecks](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Plugins/GroundednessChecks.cs)

0 commit comments

Comments
 (0)
Please sign in to comment.