Skip to content

Commit

Permalink
.Net: Address API review feedback about DI integration (#4023)
Browse files Browse the repository at this point in the history
- Replaces WithServices/WithPlugins callback-based methods with
Services/Plugins properties. It makes it less fluent, but it brings it
into line with the current best-practices patterns used by ASP.NET, it
reduces nesting, it avoids confusion about when the callbacks are
invoked, it avoids async-related issues, and it makes the pre-Build
syntax for adding plugins to the builder much closer to the post-Build
syntax for adding plugins to the Kernel.
- Plugins added as services are now automatically resolved as part of
Kernel construction. The Kernel will prefer to use a collection it's
explicitly handed, but if it isn't given one, it'll try to fetch a
collection from the services, and if it can't find that, it'll try to
fetch all IKernelPlugins from the services.
- Renamed With methods to Add methods to improve parity both with
current ASP.NET practices as well as between when doing `builder.AddXx`
and `builder.Services.AddXx`.
- Renamed `AddPluginFromObject<T>` to `AddFromType<T>`.
- Remove `new()` constraint from `AddFromType<T>` and instead use
ActivatorUtilities.CreateInstance. The KernelBuilder.Plugins property
contains the same IServiceCollection as the builder, and so
`AddFromType<T>` has access to it and can use it with CreateInstance to
resolve any plugin object ctor arguments from the service provider. For
example, HttpPlugin has a ctor that takes an HttpClient, so now doing
`builder.Plugins.AddFromType<HttpPlugin>()` will find an HttpClient in
the container and use it automatically when the plugins are built as
part of Build.
- Removed WithLoggerFactory and WithCulture. The latter is basically
never used, and it's much more like the Data property and event handlers
on the Kernel, where it can easily be mutated on the Kernel instance and
doesn't need any builder support. WithLoggerFactory was used a lot, but
mainly because of the patterns employed in the tests, and it's easily
replacable with AddLogging.
- Added IKernelBuilder interface. It's implemented by KernelBuilder and
returned from a new AddKernel extension method on IServiceCollection,
adding a singleton KernelPluginCollection and a transient Kernel to the
container. IKernelBuilder has both the Services and Plugins properties,
and also has Build, but Build throws an exception if it's used on the
instance returned from AddKernel, per feedback that it can lead to folks
accidentally building the container multiple times.
- Changed all of the extensions on KernelBuilder to instead by on
IKernelBuilder.

@davidfowl, @halter73, please ensure this is in line with your feedback.
Thanks!

---------

Co-authored-by: Mark Wallace <[email protected]>
Co-authored-by: SergeyMenshykh <[email protected]>
  • Loading branch information
3 people authored Dec 6, 2023
1 parent 7eaa708 commit aa45ee6
Show file tree
Hide file tree
Showing 86 changed files with 1,136 additions and 1,214 deletions.
10 changes: 3 additions & 7 deletions dotnet/samples/KernelSyntaxExamples/Example03_Variables.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,18 @@
using System;
using System.Globalization;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
using Plugins;
using RepoUtils;

// ReSharper disable once InconsistentNaming
public static class Example03_Variables
{
private static readonly ILoggerFactory s_loggerFactory = ConsoleLogger.LoggerFactory;

public static async Task RunAsync()
{
Console.WriteLine("======== Variables ========");

Kernel kernel = new KernelBuilder().WithLoggerFactory(s_loggerFactory).Build();
var textPlugin = kernel.ImportPluginFromObject<StaticTextPlugin>();
Kernel kernel = new();
var textPlugin = kernel.ImportPluginFromType<StaticTextPlugin>();

var arguments = new KernelArguments("Today is: ")
{
Expand All @@ -28,7 +24,7 @@ public static async Task RunAsync()
// ** Different ways of executing function with arguments **

// Specify and get the value type as generic parameter
var resultValue = await kernel.InvokeAsync<string>(textPlugin["AppendDay"], arguments);
string? resultValue = await kernel.InvokeAsync<string>(textPlugin["AppendDay"], arguments);
Console.WriteLine($"string -> {resultValue}");

// If you need to access the result metadata, you can use the non-generic version to get the FunctionResult
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.AI.OpenAI;
using RepoUtils;

// ReSharper disable once InconsistentNaming
public static class Example05_InlineFunctionDefinition
Expand All @@ -29,8 +28,7 @@ public static async Task RunAsync()
*/

Kernel kernel = new KernelBuilder()
.WithLoggerFactory(ConsoleLogger.LoggerFactory)
.WithOpenAIChatCompletion(
.AddOpenAIChatCompletion(
modelId: openAIModelId,
apiKey: openAIApiKey)
.Build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.AI.OpenAI;
using Microsoft.SemanticKernel.Plugins.Core;
using RepoUtils;

// ReSharper disable once InconsistentNaming
public static class Example06_TemplateLanguage
Expand All @@ -28,15 +27,14 @@ public static async Task RunAsync()
}

Kernel kernel = new KernelBuilder()
.WithLoggerFactory(ConsoleLogger.LoggerFactory)
.WithOpenAIChatCompletion(
.AddOpenAIChatCompletion(
modelId: openAIModelId,
apiKey: openAIApiKey)
.Build();

// Load native plugin into the kernel function collection, sharing its functions with prompt templates
// Functions loaded here are available as "time.*"
kernel.ImportPluginFromObject<TimePlugin>("time");
kernel.ImportPluginFromType<TimePlugin>("time");

// Prompt Function invoking time.Date and time.Time method functions
const string FunctionDefinition = @"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using Microsoft.SemanticKernel.Plugins.Web;
using Microsoft.SemanticKernel.Plugins.Web.Bing;
using Microsoft.SemanticKernel.Plugins.Web.Google;
using RepoUtils;

/// <summary>
/// The example shows how to use Bing and Google to search for current data
Expand All @@ -30,8 +29,7 @@ public static async Task RunAsync()
}

Kernel kernel = new KernelBuilder()
.WithLoggerFactory(ConsoleLogger.LoggerFactory)
.WithOpenAIChatCompletion(
.AddOpenAIChatCompletion(
modelId: openAIModelId,
apiKey: openAIApiKey)
.Build();
Expand Down
21 changes: 10 additions & 11 deletions dotnet/samples/KernelSyntaxExamples/Example08_RetryHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,19 @@ public static class Example08_RetryHandler
{
public static async Task RunAsync()
{
// Create a Kernel with a customized HttpClient
var kernel = new KernelBuilder().WithServices(c =>
// Create a Kernel with the HttpClient
KernelBuilder builder = new();
builder.Services.AddLogging(c => c.AddConsole().SetMinimumLevel(LogLevel.Information));
builder.Services.ConfigureHttpClientDefaults(c =>
{
c.AddLogging(c => c.AddConsole().SetMinimumLevel(LogLevel.Information));
c.ConfigureHttpClientDefaults(c =>
// Use a standard resiliency policy, augmented to retry on 401 Unauthorized for this example
c.AddStandardResilienceHandler().Configure(o =>
{
// Use a standard resiliency policy, augmented to retry on 401 Unauthorized for this example
c.AddStandardResilienceHandler().Configure(o =>
{
o.Retry.ShouldHandle = args => ValueTask.FromResult(args.Outcome.Result?.StatusCode is HttpStatusCode.Unauthorized);
});
o.Retry.ShouldHandle = args => ValueTask.FromResult(args.Outcome.Result?.StatusCode is HttpStatusCode.Unauthorized);
});
c.AddOpenAIChatCompletion("gpt-4", "BAD_KEY"); // OpenAI settings - Setting OpenAI.ApiKey to an invalid value to see the retry policy in play
}).Build();
});
builder.Services.AddOpenAIChatCompletion("gpt-4", "BAD_KEY"); // OpenAI settings - you can set the OpenAI.ApiKey to an invalid value to see the retry policy in play
Kernel kernel = builder.Build();

var logger = kernel.LoggerFactory.CreateLogger(typeof(Example08_RetryHandler));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,11 @@ public static async Task RunAsync()
Console.WriteLine("======== Method Function types ========");

var kernel = new KernelBuilder()
.WithLoggerFactory(ConsoleLogger.LoggerFactory)
.WithOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey)
.AddOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey)
.Build();

// Load native plugin into the kernel function collection, sharing its functions with prompt templates
var plugin = kernel.ImportPluginFromObject<LocalExamplePlugin>("test");
var plugin = kernel.ImportPluginFromType<LocalExamplePlugin>("test");

string folder = RepoFiles.SamplePluginsPath();
kernel.ImportPluginFromPromptDirectory(Path.Combine(folder, "SummarizePlugin"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,16 @@ public static Task RunAsync()
Console.WriteLine("======== Describe all plugins and functions ========");

var kernel = new KernelBuilder()
.WithOpenAIChatCompletion(
.AddOpenAIChatCompletion(
modelId: TestConfiguration.OpenAI.ChatModelId,
apiKey: TestConfiguration.OpenAI.ApiKey)
.Build();

// Import a native plugin
var staticText = new StaticTextPlugin();
kernel.ImportPluginFromObject(staticText, "StaticTextPlugin");
kernel.ImportPluginFromType<StaticTextPlugin>();

// Import another native plugin
var text = new TextPlugin();
kernel.ImportPluginFromObject(text, "AnotherTextPlugin");
kernel.ImportPluginFromType<TextPlugin>("AnotherTextPlugin");

// Import a semantic plugin
string folder = RepoFiles.SamplePluginsPath();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Plugins.Web;
using RepoUtils;

// ReSharper disable once InconsistentNaming
public static class Example11_WebSearchQueries
Expand All @@ -13,11 +12,10 @@ public static async Task RunAsync()
{
Console.WriteLine("======== WebSearchQueries ========");

Kernel kernel = new KernelBuilder().WithLoggerFactory(ConsoleLogger.LoggerFactory).Build();
Kernel kernel = new();

// Load native plugins
var plugin = new SearchUrlPlugin();
var bing = kernel.ImportPluginFromObject(plugin, "search");
var bing = kernel.ImportPluginFromType<SearchUrlPlugin>("search");

// Run
var ask = "What's the tallest building in Europe?";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Plugins.Core;
using RepoUtils;

// ReSharper disable once InconsistentNaming

Expand Down Expand Up @@ -130,7 +129,7 @@ private static async Task ConversationSummaryPluginAsync()
Console.WriteLine("======== SamplePlugins - Conversation Summary Plugin - Summarize ========");
Kernel kernel = InitializeKernel();

IKernelPlugin conversationSummaryPlugin = kernel.ImportPluginFromObject<ConversationSummaryPlugin>();
IKernelPlugin conversationSummaryPlugin = kernel.ImportPluginFromType<ConversationSummaryPlugin>();

FunctionResult summary = await kernel.InvokeAsync(
conversationSummaryPlugin["SummarizeConversation"], new(ChatTranscript));
Expand All @@ -144,7 +143,7 @@ private static async Task GetConversationActionItemsAsync()
Console.WriteLine("======== SamplePlugins - Conversation Summary Plugin - Action Items ========");
Kernel kernel = InitializeKernel();

IKernelPlugin conversationSummary = kernel.ImportPluginFromObject<ConversationSummaryPlugin>();
IKernelPlugin conversationSummary = kernel.ImportPluginFromType<ConversationSummaryPlugin>();

FunctionResult summary = await kernel.InvokeAsync(
conversationSummary["GetConversationActionItems"], new(ChatTranscript));
Expand All @@ -158,7 +157,7 @@ private static async Task GetConversationTopicsAsync()
Console.WriteLine("======== SamplePlugins - Conversation Summary Plugin - Topics ========");
Kernel kernel = InitializeKernel();

IKernelPlugin conversationSummary = kernel.ImportPluginFromObject<ConversationSummaryPlugin>();
IKernelPlugin conversationSummary = kernel.ImportPluginFromType<ConversationSummaryPlugin>();

FunctionResult summary = await kernel.InvokeAsync(
conversationSummary["GetConversationTopics"], new(ChatTranscript));
Expand All @@ -170,13 +169,12 @@ private static async Task GetConversationTopicsAsync()
private static Kernel InitializeKernel()
{
Kernel kernel = new KernelBuilder()
.WithLoggerFactory(ConsoleLogger.LoggerFactory)
.WithAzureOpenAIChatCompletion(
.AddAzureOpenAIChatCompletion(
TestConfiguration.AzureOpenAI.ChatDeploymentName,
TestConfiguration.AzureOpenAI.ChatModelId,
TestConfiguration.AzureOpenAI.Endpoint,
TestConfiguration.AzureOpenAI.ApiKey)
.Build();
.Build();

return kernel;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.AI.OpenAI;
using Microsoft.SemanticKernel.Connectors.Memory.AzureAISearch;
using Microsoft.SemanticKernel.Memory;
using Microsoft.SemanticKernel.Plugins.Memory;
using RepoUtils;

/* The files contains two examples about SK Semantic Memory.
*
Expand Down Expand Up @@ -37,7 +35,6 @@ public static async Task RunAsync()
*/

var memoryWithACS = new MemoryBuilder()
.WithLoggerFactory(ConsoleLogger.LoggerFactory)
.WithOpenAITextEmbeddingGeneration("text-embedding-ada-002", TestConfiguration.OpenAI.ApiKey)
.WithMemoryStore(new AzureAISearchMemoryStore(TestConfiguration.AzureAISearch.Endpoint, TestConfiguration.AzureAISearch.ApiKey))
.Build();
Expand All @@ -58,7 +55,6 @@ public static async Task RunAsync()
*/

var memoryWithCustomDb = new MemoryBuilder()
.WithLoggerFactory(ConsoleLogger.LoggerFactory)
.WithOpenAITextEmbeddingGeneration("text-embedding-ada-002", TestConfiguration.OpenAI.ApiKey)
.WithMemoryStore(new VolatileMemoryStore())
.Build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,8 @@ private static IMemoryStore CreateSampleKustoMemoryStore()
private static async Task RunWithStoreAsync(IMemoryStore memoryStore, CancellationToken cancellationToken)
{
var kernel = new KernelBuilder()
.WithLoggerFactory(ConsoleLogger.LoggerFactory)
.WithOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey)
.WithOpenAITextEmbeddingGeneration(TestConfiguration.OpenAI.EmbeddingModelId, TestConfiguration.OpenAI.ApiKey)
.AddOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey)
.AddOpenAITextEmbeddingGeneration(TestConfiguration.OpenAI.EmbeddingModelId, TestConfiguration.OpenAI.ApiKey)
.Build();

// Create an embedding generator to use for semantic memory.
Expand Down
17 changes: 8 additions & 9 deletions dotnet/samples/KernelSyntaxExamples/Example16_CustomLLM.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,13 @@ private static async Task CustomTextGenerationWithSKFunctionAsync()
{
Console.WriteLine("======== Custom LLM - Text Completion - SKFunction ========");

Kernel kernel = new KernelBuilder().WithServices(c =>
{
c.AddSingleton(ConsoleLogger.LoggerFactory)
// Add your text generation service as a singleton instance
.AddKeyedSingleton<ITextGenerationService>("myService1", new MyTextGenerationService())
// Add your text generation service as a factory method
.AddKeyedSingleton<ITextGenerationService>("myService2", (_, _) => new MyTextGenerationService());
}).Build();
KernelBuilder builder = new();
builder.Services.AddSingleton(ConsoleLogger.LoggerFactory);
// Add your text generation service as a singleton instance
builder.Services.AddKeyedSingleton<ITextGenerationService>("myService1", new MyTextGenerationService());
// Add your text generation service as a factory method
builder.Services.AddKeyedSingleton<ITextGenerationService>("myService2", (_, _) => new MyTextGenerationService());
Kernel kernel = builder.Build();

const string FunctionDefinition = "Does the text contain grammar errors (Y/N)? Text: {{$input}}";

Expand Down Expand Up @@ -89,7 +88,7 @@ private static async Task CustomTextGenerationStreamAsync()
{
Console.WriteLine("======== Custom LLM - Text Completion - Raw Streaming ========");

Kernel kernel = new KernelBuilder().WithLoggerFactory(ConsoleLogger.LoggerFactory).Build();
Kernel kernel = new();
ITextGenerationService textGeneration = new MyTextGenerationService();

var prompt = "Write one paragraph why AI is awesome";
Expand Down
11 changes: 4 additions & 7 deletions dotnet/samples/KernelSyntaxExamples/Example18_DallE.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.AI.ChatCompletion;
using Microsoft.SemanticKernel.AI.TextToImage;
using RepoUtils;

/**
* The following example shows how to use Semantic Kernel with OpenAI Dall-E 2 to create images
Expand All @@ -24,11 +23,10 @@ private static async Task OpenAIDallEAsync()
Console.WriteLine("======== OpenAI Dall-E 2 Text To Image ========");

Kernel kernel = new KernelBuilder()
.WithLoggerFactory(ConsoleLogger.LoggerFactory)
// Add your text to image service
.WithOpenAITextToImage(TestConfiguration.OpenAI.ApiKey)
.AddOpenAITextToImage(TestConfiguration.OpenAI.ApiKey)
// Add your chat completion service
.WithOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey)
.AddOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey)
.Build();

ITextToImageService dallE = kernel.GetRequiredService<ITextToImageService>();
Expand Down Expand Up @@ -93,11 +91,10 @@ public static async Task AzureOpenAIDallEAsync()
Console.WriteLine("========Azure OpenAI Dall-E 2 Text To Image ========");

Kernel kernel = new KernelBuilder()
.WithLoggerFactory(ConsoleLogger.LoggerFactory)
// Add your text to image service
.WithAzureOpenAITextToImage(TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ImageModelId, TestConfiguration.AzureOpenAI.ApiKey)
.AddAzureOpenAITextToImage(TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ImageModelId, TestConfiguration.AzureOpenAI.ApiKey)
// Add your chat completion service
.WithAzureOpenAIChatCompletion(TestConfiguration.AzureOpenAI.ChatDeploymentName, TestConfiguration.AzureOpenAI.ChatModelId, TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ApiKey)
.AddAzureOpenAIChatCompletion(TestConfiguration.AzureOpenAI.ChatDeploymentName, TestConfiguration.AzureOpenAI.ChatModelId, TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ApiKey)
.Build();

ITextToImageService dallE = kernel.GetRequiredService<ITextToImageService>();
Expand Down
7 changes: 2 additions & 5 deletions dotnet/samples/KernelSyntaxExamples/Example20_HuggingFace.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using System;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using RepoUtils;

/**
* The following example shows how to use Semantic Kernel with HuggingFace API.
Expand All @@ -26,8 +25,7 @@ private static async Task RunInferenceApiExampleAsync()
Console.WriteLine("\n======== HuggingFace Inference API example ========\n");

Kernel kernel = new KernelBuilder()
.WithLoggerFactory(ConsoleLogger.LoggerFactory)
.WithHuggingFaceTextGeneration(
.AddHuggingFaceTextGeneration(
model: TestConfiguration.HuggingFace.ModelId,
apiKey: TestConfiguration.HuggingFace.ApiKey)
.Build();
Expand Down Expand Up @@ -61,8 +59,7 @@ private static async Task RunLlamaExampleAsync()
const string Endpoint = "http://localhost:5000/completions";

Kernel kernel = new KernelBuilder()
.WithLoggerFactory(ConsoleLogger.LoggerFactory)
.WithHuggingFaceTextGeneration(
.AddHuggingFaceTextGeneration(
model: Model,
endpoint: Endpoint,
apiKey: TestConfiguration.HuggingFace.ApiKey)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Plugins.OpenApi.Model;
using Microsoft.SemanticKernel.Plugins.OpenApi.OpenAI;
using RepoUtils;

// ReSharper disable once InconsistentNaming
public static class Example21_OpenAIPlugins
Expand All @@ -18,7 +17,7 @@ public static async Task RunAsync()

private static async Task RunOpenAIPluginAsync()
{
var kernel = new KernelBuilder().WithLoggerFactory(ConsoleLogger.LoggerFactory).Build();
Kernel kernel = new();

//This HTTP client is optional. SK will fallback to a default internal one if omitted.
using HttpClient httpClient = new();
Expand All @@ -38,7 +37,7 @@ private static async Task RunOpenAIPluginAsync()

//--------------- Example of using Klarna OpenAI plugin ------------------------

//var kernel = new KernelBuilder().WithLoggerFactory(ConsoleLogger.LoggerFactory).Build();
//Kernel kernel = new();

//var plugin = await kernel.ImportPluginFromOpenAIAsync("Klarna", new Uri("https://www.klarna.com/.well-known/ai-plugin.json"));

Expand Down
Loading

0 comments on commit aa45ee6

Please sign in to comment.