docs/decisions/0012-kernel-service-registration.md
Plugins may have dependencies to support complex scenarios. For example, there is TextMemoryPlugin, which supports functions like retrieve, recall, save, remove. Constructor is implemented in following way:
public TextMemoryPlugin(ISemanticTextMemory memory)
{
this._memory = memory;
}
TextMemoryPlugin depends on ISemanticTextMemory interface. In similar way, other Plugins may have multiple dependencies and there should be a way how to resolve required dependencies manually or automatically.
At the moment, ISemanticTextMemory is a property of IKernel interface, which allows to inject ISemanticTextMemory into TextMemoryPlugin during Plugin initialization:
kernel.ImportFunctions(new TextMemoryPlugin(kernel.Memory));
There should be a way how to support not only Memory-related interface, but any kind of service, which can be used in Plugin - ISemanticTextMemory, IPromptTemplateEngine, IDelegatingHandlerFactory or any other service.
User is responsible for all Plugins initialization and dependency resolution with manual approach.
var memoryStore = new VolatileMemoryStore();
var embeddingGeneration = new OpenAITextEmbeddingGeneration(modelId, apiKey);
var semanticTextMemory = new SemanticTextMemory(memoryStore, embeddingGeneration);
var memoryPlugin = new TextMemoryPlugin(semanticTextMemory);
var kernel = Kernel.Builder.Build();
kernel.ImportFunctions(memoryPlugin);
Note: this is native .NET approach how to resolve service dependencies manually, and this approach should always be available by default. Any other solutions which could help to improve dependency resolution can be added on top of this approach.
User is responsible for all Plugins initialization and dependency resolution with dependency injection approach.
var serviceCollection = new ServiceCollection();
serviceCollection.AddTransient<IMemoryStore, VolatileMemoryStore>();
serviceCollection.AddTransient<ITextEmbeddingGeneration>(
(serviceProvider) => new OpenAITextEmbeddingGeneration(modelId, apiKey));
serviceCollection.AddTransient<ISemanticTextMemory, SemanticTextMemory>();
var services = serviceCollection.BuildServiceProvider();
// In theory, TextMemoryPlugin can be also registered in DI container.
var memoryPlugin = new TextMemoryPlugin(services.GetService<ISemanticTextMemory>());
var kernel = Kernel.Builder.Build();
kernel.ImportFunctions(memoryPlugin);
Note: in similar way as Solution #1.1, this way should be supported out of the box. Users always can handle all the dependencies on their side and just provide required Plugins to Kernel.
Custom service collection and service provider on Kernel level to simplify dependency resolution process, as addition to Solution #1.1 and Solution #1.2.
Interface IKernel will have its own service provider KernelServiceProvider with minimal functionality to get required service.
public interface IKernelServiceProvider
{
T? GetService<T>(string? name = null);
}
public interface IKernel
{
IKernelServiceProvider Services { get; }
}
var kernel = Kernel.Builder
.WithLoggerFactory(ConsoleLogger.LoggerFactory)
.WithOpenAITextEmbeddingGenerationService(modelId, apiKey)
.WithService<IMemoryStore, VolatileMemoryStore>(),
.WithService<ISemanticTextMemory, SemanticTextMemory>()
.Build();
var semanticTextMemory = kernel.Services.GetService<ISemanticTextMemory>();
var memoryPlugin = new TextMemoryPlugin(semanticTextMemory);
kernel.ImportFunctions(memoryPlugin);
Pros:
Cons:
This solution is an improvement for last disadvantage of Solution #2.1 to handle case, when Plugin instance should be initialized manually. This will require to add new way how to import Plugin into Kernel - not with object instance, but with object type. In this case, Kernel will be responsible for TextMemoryPlugin initialization and injection of all required dependencies from custom service collection.
// Instead of this
var semanticTextMemory = kernel.Services.GetService<ISemanticTextMemory>();
var memoryPlugin = new TextMemoryPlugin(semanticTextMemory);
kernel.ImportFunctions(memoryPlugin);
// Use this
kernel.ImportFunctions<TextMemoryPlugin>();
Instead of custom service collection and service provider in Kernel, use already existing DI library - Microsoft.Extensions.DependencyInjection.
var serviceCollection = new ServiceCollection();
serviceCollection.AddTransient<IMemoryStore, VolatileMemoryStore>();
serviceCollection.AddTransient<ITextEmbeddingGeneration>(
(serviceProvider) => new OpenAITextEmbeddingGeneration(modelId, apiKey));
serviceCollection.AddTransient<ISemanticTextMemory, SemanticTextMemory>();
var services = serviceCollection.BuildServiceProvider();
var kernel = Kernel.Builder
.WithLoggerFactory(ConsoleLogger.LoggerFactory)
.WithOpenAITextEmbeddingGenerationService(modelId, apiKey)
.WithServices(services) // Pass all registered services from host application to Kernel
.Build();
// Plugin Import - option #1
var semanticTextMemory = kernel.Services.GetService<ISemanticTextMemory>();
var memoryPlugin = new TextMemoryPlugin(semanticTextMemory);
kernel.ImportFunctions(memoryPlugin);
// Plugin Import - option #2
kernel.ImportFunctions<TextMemoryPlugin>();
Pros:
Cons:
Microsoft.Extensions.DependencyInjection.Microsoft.Extensions.DependencyInjection version mismatch and runtime errors (e.g. users have Microsoft.Extensions.DependencyInjection --version 2.0 while Semantic Kernel uses --version 6.0)As for now, support Solution #1.1 and Solution #1.2 only, to keep Kernel as unit of single responsibility. Plugin dependencies should be resolved before passing Plugin instance to the Kernel.