Skip to content

Commit cc2b70f

Browse files
authored
.Net: Issue 11562 (add plugin description) (#11601)
### Motivation and Context The current ApiManifestKernelExtensions implementations (`ImportPluginFromApiManifestAsync` and `CreatePluginFromApiManifestAsync`) in Microsoft.SemanticKernel.Plugins.OpenApi.Extensions lack the capability to provide a description for the newly created KernelPlugin. Plugins should have a description to provide adequate context to the LLM regarding their purpose and use. Ideally, the description is derived from the ApiManifestDocument, but the current `Microsoft.OpenApi.ApiManifest.ApiManifestDocument` implementation does not support a Description property. This PR addresses issue [https://github.com/microsoft/semantic-kernel/issues/11562](https://github.com/microsoft/semantic-kernel/issues/11562) <!-- Thank you for your contribution to the semantic-kernel repo! Please help reviewers and future users, providing the following information: 1. Why is this change required? 2. What problem does it solve? 3. What scenario does it contribute to? 4. If it fixes an open issue, please link to the issue here. --> ### Description To get around this limitation, I've modified the `CreatePluginFromApiManifestAsync` implementation to accept a description parameter which is passed to the `KernelPluginFactory.CreateFromFunctions()` call and added two new method signatures to ensure backwards compatibility. I've also added a new testclass (`ApiManifestKernelExtensionsTests.cs`) and apimanifest.json sample (which was missing so far) in `SemanticKernel.Functions.UnitTests`. <!-- Describe your changes, the overall approach, the underlying design. These notes will help understanding how your code works. Thanks! --> ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [x] The code builds clean without any errors or warnings - [x] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [x] All unit tests pass, and I have added new tests where possible - [x] I didn't break anyone 😄
1 parent 8756868 commit cc2b70f

File tree

4 files changed

+182
-2
lines changed

4 files changed

+182
-2
lines changed

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

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,27 @@ public static async Task<KernelPlugin> ImportPluginFromApiManifestAsync(
3939
string filePath,
4040
ApiManifestPluginParameters? pluginParameters = null,
4141
CancellationToken cancellationToken = default)
42+
=> await kernel.ImportPluginFromApiManifestAsync(pluginName, filePath, null, pluginParameters, cancellationToken).ConfigureAwait(false);
43+
44+
/// <summary>
45+
/// Imports a plugin from an API manifest asynchronously.
46+
/// </summary>
47+
/// <param name="kernel">The kernel instance.</param>
48+
/// <param name="pluginName">The name of the plugin.</param>
49+
/// <param name="filePath">The file path of the API manifest.</param>
50+
/// <param name="description">The description of the plugin.</param>
51+
/// <param name="pluginParameters">Optional parameters for the plugin setup.</param>
52+
/// <param name="cancellationToken">Optional cancellation token.</param>
53+
/// <returns>The imported plugin.</returns>
54+
public static async Task<KernelPlugin> ImportPluginFromApiManifestAsync(
55+
this Kernel kernel,
56+
string pluginName,
57+
string filePath,
58+
string? description,
59+
ApiManifestPluginParameters? pluginParameters = null,
60+
CancellationToken cancellationToken = default)
4261
{
43-
KernelPlugin plugin = await kernel.CreatePluginFromApiManifestAsync(pluginName, filePath, pluginParameters, cancellationToken).ConfigureAwait(false);
62+
KernelPlugin plugin = await kernel.CreatePluginFromApiManifestAsync(pluginName, filePath, description, pluginParameters, cancellationToken).ConfigureAwait(false);
4463
kernel.Plugins.Add(plugin);
4564
return plugin;
4665
}
@@ -60,6 +79,25 @@ public static async Task<KernelPlugin> CreatePluginFromApiManifestAsync(
6079
string filePath,
6180
ApiManifestPluginParameters? pluginParameters = null,
6281
CancellationToken cancellationToken = default)
82+
=> await kernel.CreatePluginFromApiManifestAsync(pluginName, filePath, null, pluginParameters, cancellationToken).ConfigureAwait(false);
83+
84+
/// <summary>
85+
/// Creates a kernel plugin from an API manifest file asynchronously.
86+
/// </summary>
87+
/// <param name="kernel">The kernel instance.</param>
88+
/// <param name="pluginName">The name of the plugin.</param>
89+
/// <param name="filePath">The file path of the API manifest.</param>
90+
/// <param name="description">The description of the plugin.</param>
91+
/// <param name="pluginParameters">Optional parameters for the plugin setup.</param>
92+
/// <param name="cancellationToken">Optional cancellation token.</param>
93+
/// <returns>A task that represents the asynchronous operation. The task result contains the created kernel plugin.</returns>
94+
public static async Task<KernelPlugin> CreatePluginFromApiManifestAsync(
95+
this Kernel kernel,
96+
string pluginName,
97+
string filePath,
98+
string? description,
99+
ApiManifestPluginParameters? pluginParameters = null,
100+
CancellationToken cancellationToken = default)
63101
{
64102
Verify.NotNull(kernel);
65103
Verify.ValidPluginName(pluginName, kernel.Plugins);
@@ -187,6 +225,6 @@ await DocumentLoader.LoadDocumentFromUriAsStreamAsync(new Uri(apiDescriptionUrl)
187225
}
188226
}
189227

190-
return KernelPluginFactory.CreateFromFunctions(pluginName, null, functions);
228+
return KernelPluginFactory.CreateFromFunctions(pluginName, description, functions);
191229
}
192230
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@
7373
<EmbeddedResource Include="OpenApi\TestPlugins\multipart-form-data.json" />
7474
</ItemGroup>
7575
<ItemGroup>
76+
<None Update="OpenApi\TestPlugins\example-apimanifest.json">
77+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
78+
</None>
7679
<None Update="OpenApi\TestPlugins\messages-apiplugin.json">
7780
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
7881
</None>
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using System.IO;
4+
using System.Threading.Tasks;
5+
using Microsoft.SemanticKernel;
6+
using Xunit;
7+
8+
namespace SemanticKernel.Functions.UnitTests.OpenApi.Extensions;
9+
public sealed class ApiManifestKernelExtensionsTests
10+
{
11+
[Fact]
12+
public async Task ItCanCreatePluginFromApiManifestAsync()
13+
{
14+
// Act
15+
var kernel = new Kernel();
16+
var testPluginsDir = Path.Combine(Directory.GetCurrentDirectory(), "OpenApi", "TestPlugins");
17+
var manifestFilePath = Path.Combine(testPluginsDir, "example-apimanifest.json");
18+
19+
// Arrange
20+
var plugin = await kernel.CreatePluginFromApiManifestAsync("ApiManifestPlugin", manifestFilePath);
21+
22+
// Assert
23+
Assert.NotNull(plugin);
24+
Assert.Equal(3, plugin.FunctionCount);
25+
}
26+
27+
[Fact]
28+
public async Task ItCanCreatePluginFromApiManifestWithDescriptionParameterAsync()
29+
{
30+
// Act
31+
var kernel = new Kernel();
32+
var testPluginsDir = Path.Combine(Directory.GetCurrentDirectory(), "OpenApi", "TestPlugins");
33+
var manifestFilePath = Path.Combine(testPluginsDir, "example-apimanifest.json");
34+
var description = "My plugin description";
35+
36+
// Arrange
37+
var plugin = await kernel.CreatePluginFromApiManifestAsync("ApiManifestPlugin", manifestFilePath, description);
38+
39+
// Assert
40+
Assert.NotNull(plugin);
41+
Assert.Equal(description, plugin.Description);
42+
}
43+
44+
[Fact]
45+
public async Task ItCanCreatePluginFromApiManifestWithEmptyDescriptionParameterAsync()
46+
{
47+
// Act
48+
var kernel = new Kernel();
49+
var testPluginsDir = Path.Combine(Directory.GetCurrentDirectory(), "OpenApi", "TestPlugins");
50+
var manifestFilePath = Path.Combine(testPluginsDir, "example-apimanifest.json");
51+
52+
// Arrange
53+
var plugin = await kernel.CreatePluginFromApiManifestAsync("ApiManifestPlugin", manifestFilePath, description: null);
54+
55+
// Assert
56+
Assert.NotNull(plugin);
57+
Assert.Empty(plugin.Description);
58+
}
59+
60+
[Fact]
61+
public async Task ItCanImportPluginFromApiManifestAsync()
62+
{
63+
// Act
64+
var kernel = new Kernel();
65+
var testPluginsDir = Path.Combine(Directory.GetCurrentDirectory(), "OpenApi", "TestPlugins");
66+
var manifestFilePath = Path.Combine(testPluginsDir, "example-apimanifest.json");
67+
68+
// Arrange
69+
var plugin = await kernel.ImportPluginFromApiManifestAsync("ApiManifestPlugin", manifestFilePath);
70+
71+
// Assert
72+
Assert.NotNull(plugin);
73+
Assert.Equal(3, plugin.FunctionCount);
74+
Assert.Single(kernel.Plugins);
75+
}
76+
77+
[Fact]
78+
public async Task ItCanImportPluginFromApiManifestWithDescriptionParameterAsync()
79+
{
80+
// Act
81+
var kernel = new Kernel();
82+
var testPluginsDir = Path.Combine(Directory.GetCurrentDirectory(), "OpenApi", "TestPlugins");
83+
var manifestFilePath = Path.Combine(testPluginsDir, "example-apimanifest.json");
84+
var description = "My plugin description";
85+
86+
// Arrange
87+
var plugin = await kernel.ImportPluginFromApiManifestAsync("ApiManifestPlugin", manifestFilePath, description);
88+
89+
// Assert
90+
Assert.NotNull(plugin);
91+
Assert.Equal(description, plugin.Description);
92+
}
93+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"applicationName": "My Application",
3+
"publisher": {
4+
"name": "Alice",
5+
"contactEmail": "[email protected]"
6+
},
7+
"apiDependencies": {
8+
"moostodon": {
9+
"apiDescriptionUrl": "https://raw.githubusercontent.com/APIPatterns/Moostodon/main/spec/tsp-output/%40typespec/openapi3/openapi.yaml",
10+
"auth": {
11+
"clientIdentifier": "some-uuid-here",
12+
"access": [
13+
"resourceA.ReadWrite",
14+
"resourceB.ReadWrite",
15+
"resourceB.Read"
16+
]
17+
},
18+
"requests": [
19+
{
20+
"method": "GET",
21+
"uriTemplate": "/api/v1/accounts/search"
22+
},
23+
{
24+
"method": "GET",
25+
"uriTemplate": "/api/v1/accounts/{id}"
26+
}
27+
]
28+
},
29+
"MicrosoftGraph": {
30+
"apiDescriptionUrl": "https://raw.githubusercontent.com/microsoftgraph/msgraph-sdk-powershell/dev/openApiDocs/v1.0/DirectoryObjects.yml",
31+
"auth": {
32+
"clientIdentifier": "some-uuid-here",
33+
"access": [
34+
"resourceA.ReadWrite",
35+
"resourceB.Read"
36+
]
37+
},
38+
"requests": [
39+
{
40+
"method": "GET",
41+
"uriTemplate": "/directoryObjects/{directoryObject-id}"
42+
}
43+
]
44+
}
45+
}
46+
}

0 commit comments

Comments
 (0)