.Net: Add missing directory Plugin in concepts + Warning fixes (#11858)

### Motivation and Context

I noted that we don't actually have any Concept in `Plugins` folder
showcasing a good and also long available feature from Semantic Kernel
for importing File/Directory based Template Plugins.

- Added some minor "message level" fixes to this change.

---------

Co-authored-by: Mark Wallace <[email protected]>
This commit is contained in:
Roger Barreto
2025-05-19 12:56:01 +00:00
committed by GitHub
co-authored by Mark Wallace
parent 854330ca5f
commit d4270406c7
11 changed files with 129 additions and 13 deletions
+1
View File
@@ -9,6 +9,7 @@
<LangVersion>12</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>disable</ImplicitUsings>
<NoWarn>$(NoWarn);IDE0290;IDE0079</NoWarn>
</PropertyGroup>
<PropertyGroup>
@@ -0,0 +1,98 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
namespace Plugins;
/// <summary>
/// This sample shows how to create templated plugins from file directories.
/// </summary>
public class CreatePromptPluginFromDirectory(ITestOutputHelper output) : BaseTest(output)
{
[Fact]
public async Task ImportAndUsePromptPluginFromDirectoryWithOpenAI()
{
// Get the current directory of the application
var pluginDirectory = Path.Combine(AppContext.BaseDirectory, "Plugins", "FunPlugin");
var kernel = Kernel.CreateBuilder()
.AddOpenAIChatCompletion(
modelId: TestConfiguration.OpenAI.ChatModelId,
apiKey: TestConfiguration.OpenAI.ApiKey)
.Build();
CreateFileBasedPluginTemplate(pluginDirectory);
var funPlugin = kernel.ImportPluginFromPromptDirectoryYaml(pluginDirectory, "FunPlugin");
// Invoke the plugin with a prompt
var result = await kernel.InvokeAsync(funPlugin["Joke"], new()
{
["input"] = "Why did the chicken cross the road?",
["style"] = "dad joke"
});
Console.WriteLine(result);
}
/// <summary>
/// After running this method, a new importable plugin directory structure will be created at the application root.
/// <code>
/// ./Plugins/FunPlugin/
/// joke.yml
/// </code>
/// Within the <c>FunPlugin</c> directory, any yml file will be imported as a distinct prompt function for the <see cref="KernelPlugin"/>.
/// </summary>
private static void CreateFileBasedPluginTemplate(string pluginRootDirectory)
{
// Create the sub-directory for the plugin function "Joke"
var pluginRelativeDirectory = Path.Combine(pluginRootDirectory, "Joke");
const string PluginYmlFileContent =
"""
name: Joke
template: |
WRITE EXACTLY ONE JOKE or HUMOROUS STORY ABOUT THE TOPIC BELOW
JOKE MUST BE:
- G RATED
- WORKPLACE/FAMILY SAFE
NO SEXISM, RACISM OR OTHER BIAS/BIGOTRY
BE CREATIVE AND FUNNY. I WANT TO LAUGH.
Incorporate the style suggestion, if provided: {{$style}}
+++++
{{$input}}
+++++
template_format: semantic-kernel
description: A function that generates a story about a topic.
input_variables:
- name: input
description: Joke subject.
is_required: true
- name: style
description: Give a hint about the desired joke style.
is_required: true
output_variable:
description: The generated funny joke.
execution_settings:
default:
temperature: 0.9
max_tokens: 1000
top_p: 0.0
presence_penalty: 0.0
frequency_penalty: 0.0
""";
// Create the directory structure
if (!Directory.Exists(pluginRootDirectory))
{
Directory.CreateDirectory(pluginRootDirectory);
}
// Create the config.json file if not exists
var ymlFilePath = Path.Combine(pluginRootDirectory, "joke.yml");
File.WriteAllText(ymlFilePath, PluginYmlFileContent);
}
}
+1
View File
@@ -170,6 +170,7 @@ dotnet test -l "console;verbosity=detailed" --filter "FullyQualifiedName=ChatCom
- [CreatePluginFromOpenApiSpec_Jira](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Plugins/CreatePluginFromOpenApiSpec_Jira.cs)
- [CreatePluginFromOpenApiSpec_Klarna](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Plugins/CreatePluginFromOpenApiSpec_Klarna.cs)
- [CreatePluginFromOpenApiSpec_RepairService](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Plugins/CreatePluginFromOpenApiSpec_RepairService.cs)
- [CreatePromptPluginFromDirectory](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Plugins/CreatePromptPluginFromDirectory.cs)
- [CrewAI_Plugin](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Plugins/CrewAI_Plugin.cs)
- [OpenApiPlugin_PayloadHandling](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Plugins/OpenApiPlugin_PayloadHandling.cs)
- [OpenApiPlugin_CustomHttpContentReader](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Plugins/OpenApiPlugin_CustomHttpContentReader.cs)
@@ -168,8 +168,10 @@ public sealed class AzureAISearchVectorStoreRecordCollection<TKey, TRecord> :
}
// Create the index.
var searchIndex = new SearchIndex(this._collectionName, searchFields);
searchIndex.VectorSearch = vectorSearchConfig;
var searchIndex = new SearchIndex(this._collectionName, searchFields)
{
VectorSearch = vectorSearchConfig
};
return this.RunOperationAsync(
"CreateIndex",
@@ -174,7 +174,7 @@ DO UPDATE SET {updateColumnsWithParams};
var valuesRows = string.Join(", ",
rows.Select((row, rowIndex) =>
$"({string.Join(", ",
columns.Select((c, colIndex) => $"${rowIndex * columns.Count + colIndex + 1}"))})"));
columns.Select((c, colIndex) => $"${(rowIndex * columns.Count) + colIndex + 1}"))})"));
// Generate the update set clause
var updateSetClause = string.Join(", ", columns.Where(c => c != keyColumn).Select(c => $"\"{c}\" = EXCLUDED.\"{c}\""));
@@ -585,8 +585,10 @@ public sealed class QdrantVectorStoreRecordCollection<TKey, TRecord> : IVectorSt
#pragma warning restore CS0618 // Type or member is obsolete
// Specify whether to include vectors in the search results.
var vectorsSelector = new WithVectorsSelector();
vectorsSelector.Enable = options.IncludeVectors;
var vectorsSelector = new WithVectorsSelector
{
Enable = options.IncludeVectors
};
var query = new Query
{
@@ -71,8 +71,10 @@ public class OllamaPromptExecutionSettingsTests
// Arrange
var functionChoiceBehavior = FunctionChoiceBehavior.Auto();
var originalExecutionSettings = new PromptExecutionSettings();
originalExecutionSettings.FunctionChoiceBehavior = functionChoiceBehavior;
var originalExecutionSettings = new PromptExecutionSettings
{
FunctionChoiceBehavior = functionChoiceBehavior
};
// Act
var result = OllamaPromptExecutionSettings.FromExecutionSettings(originalExecutionSettings);
@@ -172,8 +172,10 @@ public class AzureAISearchVectorStoreFixture : IAsyncLifetime
searchFields.Add(new VectorSearchField("DescriptionEmbedding", 1536, "my-vector-profile"));
// Create an index definition with a vectorizer to use when doing vector searches using text.
var definition = new SearchIndex(indexName, searchFields);
definition.VectorSearch = new VectorSearch();
var definition = new SearchIndex(indexName, searchFields)
{
VectorSearch = new VectorSearch()
};
definition.VectorSearch.Vectorizers.Add(new AzureOpenAIVectorizer("text-embedding-vectorizer")
{
Parameters = new AzureOpenAIVectorizerParameters
@@ -143,10 +143,12 @@ internal sealed class KernelRequestTracer : IDisposable
{
this.DisposeHttpResources();
this._memoryDummyResponse = new MemoryStream(Encoding.UTF8.GetBytes(DummyResponse));
this._httpMessageHandlerStub = new HttpMessageHandlerStub();
this._httpMessageHandlerStub.ResponseToReturn = new HttpResponseMessage(HttpStatusCode.OK)
this._httpMessageHandlerStub = new HttpMessageHandlerStub
{
Content = new StreamContent(this._memoryDummyResponse)
ResponseToReturn = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StreamContent(this._memoryDummyResponse)
}
};
this._httpClient = new HttpClient(this._httpMessageHandlerStub);
}
@@ -9,9 +9,15 @@
#pragma warning disable RCS1168
#pragma warning disable RCS1211
#pragma warning disable RCS1220
#pragma warning disable IDE0009
#pragma warning disable IDE0011
#pragma warning disable IDE0038
#pragma warning disable IDE0046
#pragma warning disable IDE0048
#pragma warning disable IDE0090
#pragma warning disable IDE0301
#pragma warning disable CS8604
using System.Runtime.CompilerServices;
@@ -22,4 +22,4 @@
"default": ""
}
]
}
}