Skip to content

Commit 210e968

Browse files
Merge branch 'main' into obsolete-planners
2 parents 01d6364 + 65d4784 commit 210e968

File tree

5 files changed

+86
-27
lines changed

5 files changed

+86
-27
lines changed

dotnet/samples/Concepts/Plugins/OpenApiPlugin_Filtering.cs

+17-21
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,15 @@ public async Task ExcludeOperationsBasedOnExclusionListAsync()
3737
{
3838
// The RepairService OpenAPI plugin being imported below includes the following operations: `listRepairs`, `createRepair`, `updateRepair`, and `deleteRepair`.
3939
// 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.
40+
// To enforce this restriction, we will exclude the `createRepair`, `updateRepair`, and `deleteRepair` operations from the OpenAPI document at the plugin import time.
41+
List<string> operationsToExclude = ["createRepair", "updateRepair", "deleteRepair"];
42+
4143
OpenApiFunctionExecutionParameters executionParameters = new()
4244
{
43-
OperationsToExclude = ["createRepair", "updateRepair", "deleteRepair"]
45+
OperationSelectionPredicate = (OperationSelectionPredicateContext context) => !operationsToExclude.Contains(context.Id!)
4446
};
4547

46-
// Import the RepairService OpenAPI plugin and filter out all operations except `listRepairs` one.
48+
// Import the RepairService OpenAPI plugin
4749
await this._kernel.ImportPluginFromOpenApiAsync(
4850
pluginName: "RepairService",
4951
filePath: "Resources/Plugins/RepairServicePlugin/repair-service.json",
@@ -70,25 +72,22 @@ await this._kernel.ImportPluginFromOpenApiAsync(
7072
[Fact]
7173
public async Task ImportOperationsBasedOnInclusionListAsync()
7274
{
73-
OpenApiDocumentParser parser = new();
74-
using StreamReader reader = System.IO.File.OpenText("Resources/Plugins/RepairServicePlugin/repair-service.json");
75-
7675
// The RepairService OpenAPI plugin, parsed and imported below, has the following operations: `listRepairs`, `createRepair`, `updateRepair`, and `deleteRepair`.
7776
// However, for our business scenario, we only want to permit the AI model to invoke the `createRepair` and `updateRepair` operations, excluding all others.
7877
// To accomplish this, we will define an inclusion list that specifies the allowed operations and filters out the rest.
7978
List<string> operationsToInclude = ["createRepair", "updateRepair"];
8079

8180
// 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()
81+
OpenApiFunctionExecutionParameters executionParameters = new()
8382
{
8483
OperationSelectionPredicate = (OperationSelectionPredicateContext context) => operationsToInclude.Contains(context.Id!)
8584
};
8685

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);
86+
// Import the RepairService OpenAPI plugin
87+
await this._kernel.ImportPluginFromOpenApiAsync(
88+
pluginName: "RepairService",
89+
filePath: "Resources/Plugins/RepairServicePlugin/repair-service.json",
90+
executionParameters: executionParameters);
9291

9392
// Tell the AI model not to call any function and show the list of functions it can call instead.
9493
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.None() };
@@ -114,23 +113,20 @@ public async Task ImportOperationsBasedOnInclusionListAsync()
114113
[Fact]
115114
public async Task ImportOperationsBasedOnMethodAsync()
116115
{
117-
OpenApiDocumentParser parser = new();
118-
using StreamReader reader = System.IO.File.OpenText("Resources/Plugins/RepairServicePlugin/repair-service.json");
119-
120116
// The parsed RepairService OpenAPI plugin includes operations such as `listRepairs`, `createRepair`, `updateRepair`, and `deleteRepair`.
121117
// However, for our business requirements, we only permit non-state-modifying operations like listing repairs, excluding all others.
122118
// To achieve this, we set up the selection predicate to evaluate each operation in the OpenAPI document, including only those with the `GET` method.
123119
// Note: The selection predicate can assess operations based on operation ID, method, path, and description.
124-
OpenApiDocumentParserOptions parserOptions = new()
120+
OpenApiFunctionExecutionParameters executionParameters = new()
125121
{
126122
OperationSelectionPredicate = (OperationSelectionPredicateContext context) => context.Method == "Get"
127123
};
128124

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);
125+
// Import the RepairService OpenAPI plugin
126+
await this._kernel.ImportPluginFromOpenApiAsync(
127+
pluginName: "RepairService",
128+
filePath: "Resources/Plugins/RepairServicePlugin/repair-service.json",
129+
executionParameters: executionParameters);
134130

135131
// Tell the AI model not to call any function and show the list of functions it can call instead.
136132
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.None() };

dotnet/src/Functions/Functions.OpenApi/Extensions/OpenApiFunctionExecutionParameters.cs

+12-1
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,18 @@ public class OpenApiFunctionExecutionParameters
6262
/// <summary>
6363
/// Optional list of HTTP operations to skip when importing the OpenAPI document.
6464
/// </summary>
65-
[Experimental("SKEXP0040")]
65+
[Obsolete("Use OperationSelectionPredicate instead.")]
6666
public IList<string> OperationsToExclude { get; set; }
6767

68+
/// <summary>
69+
/// Operation selection predicate to apply to all OpenAPI document operations.
70+
/// If set, the predicate will be applied to each operation in the document.
71+
/// If the predicate returns true, the operation will be imported; otherwise, it will be skipped.
72+
/// This can be used to import or filter operations based on various operation properties: Id, Path, Method, and Description.
73+
/// </summary>
74+
[Experimental("SKEXP0040")]
75+
public Func<OperationSelectionPredicateContext, bool>? OperationSelectionPredicate { get; set; }
76+
6877
/// <summary>
6978
/// A custom HTTP response content reader. It can be useful when the internal reader
7079
/// for a specific content type is either missing, insufficient, or when custom behavior is desired.
@@ -128,6 +137,8 @@ public OpenApiFunctionExecutionParameters(
128137
this.IgnoreNonCompliantErrors = ignoreNonCompliantErrors;
129138
this.EnableDynamicPayload = enableDynamicOperationPayload;
130139
this.EnablePayloadNamespacing = enablePayloadNamespacing;
140+
#pragma warning disable CS0618 // Type or member is obsolete
131141
this.OperationsToExclude = operationsToExclude ?? [];
142+
#pragma warning restore CS0618 // Type or member is obsolete
132143
}
133144
}

dotnet/src/Functions/Functions.OpenApi/OpenApiKernelPluginFactory.cs

+24-4
Original file line numberDiff line numberDiff line change
@@ -179,10 +179,7 @@ internal static async Task<KernelPlugin> CreateOpenApiPluginAsync(
179179
options: new OpenApiDocumentParserOptions
180180
{
181181
IgnoreNonCompliantErrors = executionParameters?.IgnoreNonCompliantErrors ?? false,
182-
OperationSelectionPredicate = (context) =>
183-
{
184-
return !executionParameters?.OperationsToExclude.Contains(context.Id ?? string.Empty) ?? true;
185-
}
182+
OperationSelectionPredicate = (context) => SelectOperations(context, executionParameters)
186183
},
187184
cancellationToken: cancellationToken).ConfigureAwait(false);
188185

@@ -420,6 +417,29 @@ private static string ConvertOperationIdToValidFunctionName(string operationId,
420417
return result;
421418
}
422419

420+
/// <summary>
421+
/// Selects operations to parse and import.
422+
/// </summary>
423+
/// <param name="context">Operation selection context.</param>
424+
/// <param name="executionParameters">Execution parameters.</param>
425+
/// <returns>True if the operation should be selected; otherwise, false.</returns>
426+
private static bool SelectOperations(OperationSelectionPredicateContext context, OpenApiFunctionExecutionParameters? executionParameters)
427+
{
428+
#pragma warning disable CS0618 // Type or member is obsolete
429+
if (executionParameters?.OperationSelectionPredicate is not null && executionParameters?.OperationsToExclude is { Count: > 0 })
430+
{
431+
throw new ArgumentException($"{nameof(executionParameters.OperationSelectionPredicate)} and {nameof(executionParameters.OperationsToExclude)} cannot be used together.");
432+
}
433+
434+
if (executionParameters?.OperationSelectionPredicate is { } predicate)
435+
{
436+
return predicate(context);
437+
}
438+
439+
return !executionParameters?.OperationsToExclude.Contains(context.Id ?? string.Empty) ?? true;
440+
#pragma warning restore CS0618 // Type or member is obsolete
441+
}
442+
423443
/// <summary>
424444
/// Converts the parameter type to a C# <see cref="Type"/> object.
425445
/// </summary>

dotnet/src/Functions/Functions.UnitTests/Functions.UnitTests.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<Nullable>enable</Nullable>
88
<ImplicitUsings>disable</ImplicitUsings>
99
<IsPackable>false</IsPackable>
10-
<NoWarn>$(NoWarn);CA2007,CA1861,CA1869,VSTHRD111,CS1591,SKEXP0040,SKEXP0001</NoWarn>
10+
<NoWarn>$(NoWarn);CA2007,CA1861,CA1869,VSTHRD111,CS1591,CS0618,SKEXP0040,SKEXP0001</NoWarn>
1111
</PropertyGroup>
1212
<Import Project="$(RepoRoot)/dotnet/src/InternalUtilities/test/TestInternalUtilities.props" />
1313
<ItemGroup>

dotnet/src/Functions/Functions.UnitTests/OpenApi/OpenApiKernelPluginFactoryTests.cs

+32
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,38 @@ async Task<RestApiOperationResponse> RestApiOperationResponseFactory(RestApiOper
643643
Assert.True(restApiOperationResponseFactoryIsInvoked);
644644
}
645645

646+
[Fact]
647+
public async Task ItCanImportSpecifiedOperationsAsync()
648+
{
649+
// Arrange
650+
string[] operationsToInclude = ["GetSecret", "SetSecret"];
651+
652+
this._executionParameters.OperationSelectionPredicate = (context) => operationsToInclude.Contains(context.Id);
653+
654+
// Act
655+
var plugin = await OpenApiKernelPluginFactory.CreateFromOpenApiAsync("fakePlugin", this._openApiDocument, this._executionParameters);
656+
657+
// Assert
658+
Assert.Equal(2, plugin.Count());
659+
Assert.Contains(plugin, p => p.Name == "GetSecret");
660+
Assert.Contains(plugin, p => p.Name == "SetSecret");
661+
}
662+
663+
[Fact]
664+
public async Task ItCanFilterOutSpecifiedOperationsAsync()
665+
{
666+
// Arrange
667+
this._executionParameters.OperationsToExclude = ["GetSecret", "SetSecret"];
668+
669+
// Act
670+
var plugin = await OpenApiKernelPluginFactory.CreateFromOpenApiAsync("fakePlugin", this._openApiDocument, this._executionParameters);
671+
672+
// Assert
673+
Assert.True(plugin.Any());
674+
Assert.DoesNotContain(plugin, p => p.Name == "GetSecret");
675+
Assert.DoesNotContain(plugin, p => p.Name == "SetSecret");
676+
}
677+
646678
/// <summary>
647679
/// Generate theory data for ItAddSecurityMetadataToOperationAsync
648680
/// </summary>

0 commit comments

Comments
 (0)