From 6173e9b4c2ad8071641b5a47861115dafb5f5b1c Mon Sep 17 00:00:00 2001 From: Devis Lucato Date: Mon, 18 Nov 2024 18:11:35 -0800 Subject: [PATCH] Allow to reuse KM service Builder and config extensions (#901) Allow to reuse KM configuration utilities in other apps. * `IConfigurationBuilder.AddKernelMemoryConfigurationSources(...)`: adds to .NET ConfigurationBuilder the sources used by KM service, such as `appsettings.json`, `appsettings..json` (not case sensitive), user secrets and env vars. Extension available in [Microsoft.KernelMemory.Core](https://www.nuget.org/packages/Microsoft.KernelMemory.Core) nuget. * `IKernelMemoryBuilder.ConfigureDependencies(...)`: configures and wires up all .NET dependencies accordingly to the given configuration. Extension available in [Microsoft.KernelMemory](https://www.nuget.org/packages/Microsoft.KernelMemory) nuget. --- .../Internals/KernelMemoryComposer.cs | 264 ++++++++---------- .../KernelMemoryBuilderExtensions.cs | 49 ++++ .../ConfigurationBuilderExtensions.cs | 17 +- .../HttpAuthEndpointFilter.cs | 0 .../HttpErrorsEndpointFilter.cs | 0 service/Service/{ => Internals}/OpenAPI.cs | 0 .../Service/KernelMemoryBuilderExtensions.cs | 54 ---- service/Service/Program.cs | 8 +- 8 files changed, 189 insertions(+), 203 deletions(-) rename service/Service/ServiceConfiguration.cs => extensions/KM/KernelMemory/Internals/KernelMemoryComposer.cs (74%) create mode 100644 extensions/KM/KernelMemory/KernelMemoryBuilderExtensions.cs rename service/{Service => Core/Configuration}/ConfigurationBuilderExtensions.cs (83%) rename service/Service/{Auth => HttpFilters}/HttpAuthEndpointFilter.cs (100%) rename service/Service/{Auth => HttpFilters}/HttpErrorsEndpointFilter.cs (100%) rename service/Service/{ => Internals}/OpenAPI.cs (100%) delete mode 100644 service/Service/KernelMemoryBuilderExtensions.cs diff --git a/service/Service/ServiceConfiguration.cs b/extensions/KM/KernelMemory/Internals/KernelMemoryComposer.cs similarity index 74% rename from service/Service/ServiceConfiguration.cs rename to extensions/KM/KernelMemory/Internals/KernelMemoryComposer.cs index 65c638c6a..a850099cb 100644 --- a/service/Service/ServiceConfiguration.cs +++ b/extensions/KM/KernelMemory/Internals/KernelMemoryComposer.cs @@ -16,78 +16,53 @@ using Microsoft.KernelMemory.Pipeline.Queue.DevTools; using Microsoft.KernelMemory.Safety.AzureAIContentSafety; -namespace Microsoft.KernelMemory.Service; +namespace Microsoft.KernelMemory.Internals; -internal sealed class ServiceConfiguration +/// +/// Meta factory class responsible for configuring IKernelMemoryBuilder +/// with the components selected in the configuration. +/// +internal sealed class KernelMemoryComposer { - // Content of appsettings.json, used to access dynamic data under "Services" - private IConfiguration _rawAppSettings; - - // Normalized configuration - private KernelMemoryConfig _memoryConfiguration; - - // appsettings.json root node name - private const string ConfigRoot = "KernelMemory"; - - // ASP.NET env var - private const string AspnetEnvVar = "ASPNETCORE_ENVIRONMENT"; - - // OpenAI env var - private const string OpenAIEnvVar = "OPENAI_API_KEY"; - - public ServiceConfiguration(string? settingsDirectory = null) - : this(ReadAppSettings(settingsDirectory)) - { - } + // appsettings.json root node name (and prefix of env vars) + public const string ConfigRoot = "KernelMemory"; - public ServiceConfiguration(IConfiguration rawAppSettings) - : this(rawAppSettings, - rawAppSettings.GetSection(ConfigRoot).Get() - ?? throw new ConfigurationException($"Unable to load Kernel Memory settings from the given configuration. " + - $"There should be a '{ConfigRoot}' root node, " + - $"with data mapping to '{nameof(KernelMemoryConfig)}'")) - { - } - - public ServiceConfiguration( - IConfiguration rawAppSettings, + public KernelMemoryComposer( + IKernelMemoryBuilder builder, + IConfiguration globalSettings, KernelMemoryConfig memoryConfiguration) { - this._rawAppSettings = rawAppSettings ?? throw new ConfigurationException("The given app settings configuration is NULL"); - this._memoryConfiguration = memoryConfiguration ?? throw new ConfigurationException("The given memory configuration is NULL"); + this._builder = builder; + this._globalSettings = globalSettings; + this._memoryConfiguration = memoryConfiguration; if (!this.MinimumConfigurationIsAvailable(false)) { this.SetupForOpenAI(); } this.MinimumConfigurationIsAvailable(true); } - public IKernelMemoryBuilder PrepareBuilder(IKernelMemoryBuilder builder) - { - return this.BuildUsingConfiguration(builder); - } - - private IKernelMemoryBuilder BuildUsingConfiguration(IKernelMemoryBuilder builder) + public void ConfigureBuilder() { if (this._memoryConfiguration == null) { throw new ConfigurationException("The given memory configuration is NULL"); } - if (this._rawAppSettings == null) + if (this._globalSettings == null) { throw new ConfigurationException("The given app settings configuration is NULL"); } // Required by ctors expecting KernelMemoryConfig via DI - builder.AddSingleton(this._memoryConfiguration); + this._builder.AddSingleton(this._memoryConfiguration); - this.ConfigureMimeTypeDetectionDependency(builder); + this.ConfigureMimeTypeDetectionDependency(); - this.ConfigureTextPartitioning(builder); + this.ConfigureTextPartitioning(); - this.ConfigureQueueDependency(builder); + this.ConfigureQueueDependency(); - this.ConfigureStorageDependency(builder); + this.ConfigureStorageDependency(); // The ingestion embedding generators is a list of generators that the "gen_embeddings" handler uses, // to generate embeddings for each partition. While it's possible to use multiple generators (e.g. to compare embedding quality) @@ -95,38 +70,46 @@ private IKernelMemoryBuilder BuildUsingConfiguration(IKernelMemoryBuilder builde // - config.DataIngestion.EmbeddingGeneratorTypes => list of generators, embeddings to generate and store in memory DB // - config.Retrieval.EmbeddingGeneratorType => one embedding generator, used to search, and usually injected into Memory DB constructor - this.ConfigureIngestionEmbeddingGenerators(builder); + this.ConfigureIngestionEmbeddingGenerators(); - this.ConfigureContentModeration(builder); + this.ConfigureContentModeration(); - this.ConfigureSearchClient(builder); + this.ConfigureSearchClient(); - this.ConfigureRetrievalEmbeddingGenerator(builder); + this.ConfigureRetrievalEmbeddingGenerator(); // The ingestion Memory DBs is a list of DBs where handlers write records to. While it's possible // to write to multiple DBs, e.g. for replication purpose, there is only one Memory DB used to // read/search, and it doesn't come from this list. See "config.Retrieval.MemoryDbType". // Note: use the aux service collection to avoid mixing ingestion and retrieval dependencies. - this.ConfigureIngestionMemoryDb(builder); - - this.ConfigureRetrievalMemoryDb(builder); + this.ConfigureIngestionMemoryDb(); - this.ConfigureTextGenerator(builder); + this.ConfigureRetrievalMemoryDb(); - this.ConfigureImageOCR(builder); + this.ConfigureTextGenerator(); - return builder; + this.ConfigureImageOCR(); } - private static IConfiguration ReadAppSettings(string? settingsDirectory) - { - var builder = new ConfigurationBuilder(); - builder.AddKMConfigurationSources(settingsDirectory: settingsDirectory); - return builder.Build(); - } + #region private =============================== + + // Builder to be configured with the required components + private readonly IKernelMemoryBuilder _builder; + + // Content of appsettings.json, used to access dynamic data under "Services" + private IConfiguration _globalSettings; + + // Normalized configuration + private KernelMemoryConfig _memoryConfiguration; + + // ASP.NET env var + private const string AspnetEnvVar = "ASPNETCORE_ENVIRONMENT"; - private void ConfigureQueueDependency(IKernelMemoryBuilder builder) + // OpenAI env var + private const string OpenAIEnvVar = "OPENAI_API_KEY"; + + private void ConfigureQueueDependency() { if (string.Equals(this._memoryConfiguration.DataIngestion.OrchestrationType, "Distributed", StringComparison.OrdinalIgnoreCase)) { @@ -135,18 +118,18 @@ private void ConfigureQueueDependency(IKernelMemoryBuilder builder) case string y1 when y1.Equals("AzureQueue", StringComparison.OrdinalIgnoreCase): case string y2 when y2.Equals("AzureQueues", StringComparison.OrdinalIgnoreCase): // Check 2 keys for backward compatibility - builder.Services.AddAzureQueuesOrchestration(this.GetServiceConfig("AzureQueues") - ?? this.GetServiceConfig("AzureQueue")); + this._builder.Services.AddAzureQueuesOrchestration(this.GetServiceConfig("AzureQueues") + ?? this.GetServiceConfig("AzureQueue")); break; case string y when y.Equals("RabbitMQ", StringComparison.OrdinalIgnoreCase): // Check 2 keys for backward compatibility - builder.Services.AddRabbitMQOrchestration(this.GetServiceConfig("RabbitMQ") - ?? this.GetServiceConfig("RabbitMq")); + this._builder.Services.AddRabbitMQOrchestration(this.GetServiceConfig("RabbitMQ") + ?? this.GetServiceConfig("RabbitMq")); break; case string y when y.Equals("SimpleQueues", StringComparison.OrdinalIgnoreCase): - builder.Services.AddSimpleQueues(this.GetServiceConfig("SimpleQueues")); + this._builder.Services.AddSimpleQueues(this.GetServiceConfig("SimpleQueues")); break; default: @@ -156,27 +139,27 @@ private void ConfigureQueueDependency(IKernelMemoryBuilder builder) } } - private void ConfigureStorageDependency(IKernelMemoryBuilder builder) + private void ConfigureStorageDependency() { switch (this._memoryConfiguration.DocumentStorageType) { case string x1 when x1.Equals("AzureBlob", StringComparison.OrdinalIgnoreCase): case string x2 when x2.Equals("AzureBlobs", StringComparison.OrdinalIgnoreCase): // Check 2 keys for backward compatibility - builder.Services.AddAzureBlobsAsDocumentStorage(this.GetServiceConfig("AzureBlobs") - ?? this.GetServiceConfig("AzureBlob")); + this._builder.Services.AddAzureBlobsAsDocumentStorage(this.GetServiceConfig("AzureBlobs") + ?? this.GetServiceConfig("AzureBlob")); break; case string x when x.Equals("AWSS3", StringComparison.OrdinalIgnoreCase): - builder.Services.AddAWSS3AsDocumentStorage(this.GetServiceConfig("AWSS3")); + this._builder.Services.AddAWSS3AsDocumentStorage(this.GetServiceConfig("AWSS3")); break; case string x when x.Equals("MongoDbAtlas", StringComparison.OrdinalIgnoreCase): - builder.Services.AddMongoDbAtlasAsDocumentStorage(this.GetServiceConfig("MongoDbAtlas")); + this._builder.Services.AddMongoDbAtlasAsDocumentStorage(this.GetServiceConfig("MongoDbAtlas")); break; case string x when x.Equals("SimpleFileStorage", StringComparison.OrdinalIgnoreCase): - builder.Services.AddSimpleFileStorageAsDocumentStorage(this.GetServiceConfig("SimpleFileStorage")); + this._builder.Services.AddSimpleFileStorageAsDocumentStorage(this.GetServiceConfig("SimpleFileStorage")); break; default: @@ -185,21 +168,21 @@ private void ConfigureStorageDependency(IKernelMemoryBuilder builder) } } - private void ConfigureTextPartitioning(IKernelMemoryBuilder builder) + private void ConfigureTextPartitioning() { if (this._memoryConfiguration.DataIngestion.TextPartitioning != null) { this._memoryConfiguration.DataIngestion.TextPartitioning.Validate(); - builder.WithCustomTextPartitioningOptions(this._memoryConfiguration.DataIngestion.TextPartitioning); + this._builder.WithCustomTextPartitioningOptions(this._memoryConfiguration.DataIngestion.TextPartitioning); } } - private void ConfigureMimeTypeDetectionDependency(IKernelMemoryBuilder builder) + private void ConfigureMimeTypeDetectionDependency() { - builder.WithDefaultMimeTypeDetection(); + this._builder.WithDefaultMimeTypeDetection(); } - private void ConfigureIngestionEmbeddingGenerators(IKernelMemoryBuilder builder) + private void ConfigureIngestionEmbeddingGenerators() { // Note: using multiple embeddings is not fully supported yet and could cause write errors or incorrect search results if (this._memoryConfiguration.DataIngestion.EmbeddingGeneratorTypes.Count > 1) @@ -216,40 +199,40 @@ private void ConfigureIngestionEmbeddingGenerators(IKernelMemoryBuilder builder) case string x when x.Equals("AzureOpenAI", StringComparison.OrdinalIgnoreCase): case string y when y.Equals("AzureOpenAIEmbedding", StringComparison.OrdinalIgnoreCase): { - var instance = this.GetServiceInstance(builder, + var instance = this.GetServiceInstance( s => s.AddAzureOpenAIEmbeddingGeneration( config: this.GetServiceConfig("AzureOpenAIEmbedding"), textTokenizer: new GPT4oTokenizer())); - builder.AddIngestionEmbeddingGenerator(instance); + this._builder.AddIngestionEmbeddingGenerator(instance); break; } case string x when x.Equals("OpenAI", StringComparison.OrdinalIgnoreCase): { - var instance = this.GetServiceInstance(builder, + var instance = this.GetServiceInstance( s => s.AddOpenAITextEmbeddingGeneration( config: this.GetServiceConfig("OpenAI"), textTokenizer: new GPT4oTokenizer())); - builder.AddIngestionEmbeddingGenerator(instance); + this._builder.AddIngestionEmbeddingGenerator(instance); break; } case string x when x.Equals("Ollama", StringComparison.OrdinalIgnoreCase): { - var instance = this.GetServiceInstance(builder, + var instance = this.GetServiceInstance( s => s.AddOllamaTextEmbeddingGeneration( config: this.GetServiceConfig("Ollama"), textTokenizer: new GPT4oTokenizer())); - builder.AddIngestionEmbeddingGenerator(instance); + this._builder.AddIngestionEmbeddingGenerator(instance); break; } case string x when x.Equals("LlamaSharp", StringComparison.OrdinalIgnoreCase): { - var instance = this.GetServiceInstance(builder, + var instance = this.GetServiceInstance( s => s.AddLlamaSharpTextEmbeddingGeneration( config: this.GetServiceConfig("LlamaSharp").EmbeddingModel)); - builder.AddIngestionEmbeddingGenerator(instance); + this._builder.AddIngestionEmbeddingGenerator(instance); break; } @@ -260,7 +243,7 @@ private void ConfigureIngestionEmbeddingGenerators(IKernelMemoryBuilder builder) } } - private void ConfigureIngestionMemoryDb(IKernelMemoryBuilder builder) + private void ConfigureIngestionMemoryDb() { foreach (var type in this._memoryConfiguration.DataIngestion.MemoryDbTypes) { @@ -278,94 +261,94 @@ private void ConfigureIngestionMemoryDb(IKernelMemoryBuilder builder) case string x when x.Equals("AzureAISearch", StringComparison.OrdinalIgnoreCase): { - var instance = this.GetServiceInstance(builder, + var instance = this.GetServiceInstance( s => s.AddAzureAISearchAsMemoryDb(this.GetServiceConfig("AzureAISearch")) ); - builder.AddIngestionMemoryDb(instance); + this._builder.AddIngestionMemoryDb(instance); break; } case string x when x.Equals("Elasticsearch", StringComparison.OrdinalIgnoreCase): { - var instance = this.GetServiceInstance(builder, + var instance = this.GetServiceInstance( s => s.AddElasticsearchAsMemoryDb(this.GetServiceConfig("Elasticsearch")) ); - builder.AddIngestionMemoryDb(instance); + this._builder.AddIngestionMemoryDb(instance); break; } case string x when x.Equals("MongoDbAtlas", StringComparison.OrdinalIgnoreCase): { - var instance = this.GetServiceInstance(builder, + var instance = this.GetServiceInstance( s => s.AddMongoDbAtlasAsMemoryDb(this.GetServiceConfig("MongoDbAtlas")) ); - builder.AddIngestionMemoryDb(instance); + this._builder.AddIngestionMemoryDb(instance); break; } case string x when x.Equals("Postgres", StringComparison.OrdinalIgnoreCase): { - var instance = this.GetServiceInstance(builder, + var instance = this.GetServiceInstance( s => s.AddPostgresAsMemoryDb(this.GetServiceConfig("Postgres")) ); - builder.AddIngestionMemoryDb(instance); + this._builder.AddIngestionMemoryDb(instance); break; } case string x when x.Equals("Qdrant", StringComparison.OrdinalIgnoreCase): { - var instance = this.GetServiceInstance(builder, + var instance = this.GetServiceInstance( s => s.AddQdrantAsMemoryDb(this.GetServiceConfig("Qdrant")) ); - builder.AddIngestionMemoryDb(instance); + this._builder.AddIngestionMemoryDb(instance); break; } case string x when x.Equals("Redis", StringComparison.OrdinalIgnoreCase): { - var instance = this.GetServiceInstance(builder, + var instance = this.GetServiceInstance( s => s.AddRedisAsMemoryDb(this.GetServiceConfig("Redis")) ); - builder.AddIngestionMemoryDb(instance); + this._builder.AddIngestionMemoryDb(instance); break; } case string x when x.Equals("SimpleVectorDb", StringComparison.OrdinalIgnoreCase): { - var instance = this.GetServiceInstance(builder, + var instance = this.GetServiceInstance( s => s.AddSimpleVectorDbAsMemoryDb(this.GetServiceConfig("SimpleVectorDb")) ); - builder.AddIngestionMemoryDb(instance); + this._builder.AddIngestionMemoryDb(instance); break; } case string x when x.Equals("SimpleTextDb", StringComparison.OrdinalIgnoreCase): { - var instance = this.GetServiceInstance(builder, + var instance = this.GetServiceInstance( s => s.AddSimpleTextDbAsMemoryDb(this.GetServiceConfig("SimpleTextDb")) ); - builder.AddIngestionMemoryDb(instance); + this._builder.AddIngestionMemoryDb(instance); break; } case string x when x.Equals("SqlServer", StringComparison.OrdinalIgnoreCase): { - var instance = this.GetServiceInstance(builder, + var instance = this.GetServiceInstance( s => s.AddSqlServerAsMemoryDb(this.GetServiceConfig("SqlServer")) ); - builder.AddIngestionMemoryDb(instance); + this._builder.AddIngestionMemoryDb(instance); break; } } } } - private void ConfigureContentModeration(IKernelMemoryBuilder builder) + private void ConfigureContentModeration() { switch (this._memoryConfiguration.ContentModerationType) { case string x when x.Equals("AzureAIContentSafety", StringComparison.OrdinalIgnoreCase): - builder.Services.AddAzureAIContentSafetyModeration(config: this.GetServiceConfig("AzureAIContentSafety")); + this._builder.Services.AddAzureAIContentSafetyModeration(config: this.GetServiceConfig("AzureAIContentSafety")); break; default: @@ -374,38 +357,38 @@ private void ConfigureContentModeration(IKernelMemoryBuilder builder) } } - private void ConfigureSearchClient(IKernelMemoryBuilder builder) + private void ConfigureSearchClient() { // Search settings - builder.WithSearchClientConfig(this._memoryConfiguration.Retrieval.SearchClient); + this._builder.WithSearchClientConfig(this._memoryConfiguration.Retrieval.SearchClient); } - private void ConfigureRetrievalEmbeddingGenerator(IKernelMemoryBuilder builder) + private void ConfigureRetrievalEmbeddingGenerator() { // Retrieval embeddings - ITextEmbeddingGeneration interface switch (this._memoryConfiguration.Retrieval.EmbeddingGeneratorType) { case string x when x.Equals("AzureOpenAI", StringComparison.OrdinalIgnoreCase): case string y when y.Equals("AzureOpenAIEmbedding", StringComparison.OrdinalIgnoreCase): - builder.Services.AddAzureOpenAIEmbeddingGeneration( + this._builder.Services.AddAzureOpenAIEmbeddingGeneration( config: this.GetServiceConfig("AzureOpenAIEmbedding"), textTokenizer: new GPT4oTokenizer()); break; case string x when x.Equals("OpenAI", StringComparison.OrdinalIgnoreCase): - builder.Services.AddOpenAITextEmbeddingGeneration( + this._builder.Services.AddOpenAITextEmbeddingGeneration( config: this.GetServiceConfig("OpenAI"), textTokenizer: new GPT4oTokenizer()); break; case string x when x.Equals("Ollama", StringComparison.OrdinalIgnoreCase): - builder.Services.AddOllamaTextEmbeddingGeneration( + this._builder.Services.AddOllamaTextEmbeddingGeneration( config: this.GetServiceConfig("Ollama"), textTokenizer: new GPT4oTokenizer()); break; case string x when x.Equals("LlamaSharp", StringComparison.OrdinalIgnoreCase): - builder.Services.AddLlamaSharpTextEmbeddingGeneration( + this._builder.Services.AddLlamaSharpTextEmbeddingGeneration( config: this.GetServiceConfig("LlamaSharp").EmbeddingModel); break; @@ -415,45 +398,45 @@ private void ConfigureRetrievalEmbeddingGenerator(IKernelMemoryBuilder builder) } } - private void ConfigureRetrievalMemoryDb(IKernelMemoryBuilder builder) + private void ConfigureRetrievalMemoryDb() { // Retrieval Memory DB - IMemoryDb interface switch (this._memoryConfiguration.Retrieval.MemoryDbType) { case string x when x.Equals("AzureAISearch", StringComparison.OrdinalIgnoreCase): - builder.Services.AddAzureAISearchAsMemoryDb(this.GetServiceConfig("AzureAISearch")); + this._builder.Services.AddAzureAISearchAsMemoryDb(this.GetServiceConfig("AzureAISearch")); break; case string x when x.Equals("Elasticsearch", StringComparison.OrdinalIgnoreCase): - builder.Services.AddElasticsearchAsMemoryDb(this.GetServiceConfig("Elasticsearch")); + this._builder.Services.AddElasticsearchAsMemoryDb(this.GetServiceConfig("Elasticsearch")); break; case string x when x.Equals("MongoDbAtlas", StringComparison.OrdinalIgnoreCase): - builder.Services.AddMongoDbAtlasAsMemoryDb(this.GetServiceConfig("MongoDbAtlas")); + this._builder.Services.AddMongoDbAtlasAsMemoryDb(this.GetServiceConfig("MongoDbAtlas")); break; case string x when x.Equals("Postgres", StringComparison.OrdinalIgnoreCase): - builder.Services.AddPostgresAsMemoryDb(this.GetServiceConfig("Postgres")); + this._builder.Services.AddPostgresAsMemoryDb(this.GetServiceConfig("Postgres")); break; case string x when x.Equals("Qdrant", StringComparison.OrdinalIgnoreCase): - builder.Services.AddQdrantAsMemoryDb(this.GetServiceConfig("Qdrant")); + this._builder.Services.AddQdrantAsMemoryDb(this.GetServiceConfig("Qdrant")); break; case string x when x.Equals("Redis", StringComparison.OrdinalIgnoreCase): - builder.Services.AddRedisAsMemoryDb(this.GetServiceConfig("Redis")); + this._builder.Services.AddRedisAsMemoryDb(this.GetServiceConfig("Redis")); break; case string x when x.Equals("SimpleVectorDb", StringComparison.OrdinalIgnoreCase): - builder.Services.AddSimpleVectorDbAsMemoryDb(this.GetServiceConfig("SimpleVectorDb")); + this._builder.Services.AddSimpleVectorDbAsMemoryDb(this.GetServiceConfig("SimpleVectorDb")); break; case string x when x.Equals("SimpleTextDb", StringComparison.OrdinalIgnoreCase): - builder.Services.AddSimpleTextDbAsMemoryDb(this.GetServiceConfig("SimpleTextDb")); + this._builder.Services.AddSimpleTextDbAsMemoryDb(this.GetServiceConfig("SimpleTextDb")); break; case string x when x.Equals("SqlServer", StringComparison.OrdinalIgnoreCase): - builder.Services.AddSqlServerAsMemoryDb(this.GetServiceConfig("SqlServer")); + this._builder.Services.AddSqlServerAsMemoryDb(this.GetServiceConfig("SqlServer")); break; default: @@ -462,38 +445,38 @@ private void ConfigureRetrievalMemoryDb(IKernelMemoryBuilder builder) } } - private void ConfigureTextGenerator(IKernelMemoryBuilder builder) + private void ConfigureTextGenerator() { // Text generation switch (this._memoryConfiguration.TextGeneratorType) { case string x when x.Equals("AzureOpenAI", StringComparison.OrdinalIgnoreCase): case string y when y.Equals("AzureOpenAIText", StringComparison.OrdinalIgnoreCase): - builder.Services.AddAzureOpenAITextGeneration( + this._builder.Services.AddAzureOpenAITextGeneration( config: this.GetServiceConfig("AzureOpenAIText"), textTokenizer: new GPT4oTokenizer()); break; case string x when x.Equals("OpenAI", StringComparison.OrdinalIgnoreCase): - builder.Services.AddOpenAITextGeneration( + this._builder.Services.AddOpenAITextGeneration( config: this.GetServiceConfig("OpenAI"), textTokenizer: new GPT4oTokenizer()); break; case string x when x.Equals("Anthropic", StringComparison.OrdinalIgnoreCase): - builder.Services.AddAnthropicTextGeneration( + this._builder.Services.AddAnthropicTextGeneration( config: this.GetServiceConfig("Anthropic"), textTokenizer: new GPT4oTokenizer()); break; case string x when x.Equals("Ollama", StringComparison.OrdinalIgnoreCase): - builder.Services.AddOllamaTextGeneration( + this._builder.Services.AddOllamaTextGeneration( config: this.GetServiceConfig("Ollama"), textTokenizer: new GPT4oTokenizer()); break; case string x when x.Equals("LlamaSharp", StringComparison.OrdinalIgnoreCase): - builder.Services.AddLlamaSharpTextGeneration( + this._builder.Services.AddLlamaSharpTextGeneration( config: this.GetServiceConfig("LlamaSharp").TextModel); break; @@ -503,7 +486,7 @@ private void ConfigureTextGenerator(IKernelMemoryBuilder builder) } } - private void ConfigureImageOCR(IKernelMemoryBuilder builder) + private void ConfigureImageOCR() { // Image OCR switch (this._memoryConfiguration.DataIngestion.ImageOcrType) @@ -513,7 +496,7 @@ private void ConfigureImageOCR(IKernelMemoryBuilder builder) break; case string x when x.Equals("AzureAIDocIntel", StringComparison.OrdinalIgnoreCase): - builder.Services.AddAzureAIDocIntel(this.GetServiceConfig("AzureAIDocIntel")); + this._builder.Services.AddAzureAIDocIntel(this.GetServiceConfig("AzureAIDocIntel")); break; default: @@ -616,11 +599,11 @@ private void SetupForOpenAI() }; var newAppSettings = new ConfigurationBuilder(); - newAppSettings.AddConfiguration(this._rawAppSettings); + newAppSettings.AddConfiguration(this._globalSettings); newAppSettings.AddInMemoryCollection(inMemoryConfig); - this._rawAppSettings = newAppSettings.Build(); - this._memoryConfiguration = this._rawAppSettings.GetSection(ConfigRoot).Get()!; + this._globalSettings = newAppSettings.Build(); + this._memoryConfiguration = this._globalSettings.GetSection(ConfigRoot).Get()!; } /// @@ -630,14 +613,13 @@ private void SetupForOpenAI() /// Return an instance of T built using the definition provided by /// the action. /// - /// KM builder /// Action used to configure the service collection /// Target type/interface - private T GetServiceInstance(IKernelMemoryBuilder builder, Action addCustomService) + private T GetServiceInstance(Action addCustomService) { // Clone the list of service descriptors, skipping T descriptor IServiceCollection services = new ServiceCollection(); - foreach (ServiceDescriptor d in builder.Services) + foreach (ServiceDescriptor d in this._builder.Services) { if (d.ServiceType == typeof(T)) { continue; } @@ -671,6 +653,8 @@ private T GetServiceInstance(IKernelMemoryBuilder builder, ActionConfiguration instance, settings for the dependency specified private T GetServiceConfig(string serviceName) { - return this._memoryConfiguration.GetServiceConfig(this._rawAppSettings, serviceName); + return this._memoryConfiguration.GetServiceConfig(this._globalSettings, serviceName); } + + #endregion } diff --git a/extensions/KM/KernelMemory/KernelMemoryBuilderExtensions.cs b/extensions/KM/KernelMemory/KernelMemoryBuilderExtensions.cs new file mode 100644 index 000000000..5937d6384 --- /dev/null +++ b/extensions/KM/KernelMemory/KernelMemoryBuilderExtensions.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Extensions.Configuration; +using Microsoft.KernelMemory.Internals; + +// ReSharper disable once CheckNamespace - reduce number of "using" statements +namespace Microsoft.KernelMemory; + +/// +/// Kernel Memory builder extensions for ASP.NET apps using settings in appsettings.json +/// and using IConfiguration. The following methods allow to fully configure KM via +/// IConfiguration, without having to change the code using KernelMemoryBuilder and recompile. +/// +public static partial class KernelMemoryBuilderExtensions +{ + /// + /// Configure the builder using settings from the given IConfiguration instance. + /// + /// KernelMemory builder instance + /// App settings, which might include KM settings + /// Optional KM settings, overriding those in appsettings + public static IKernelMemoryBuilder ConfigureDependencies( + this IKernelMemoryBuilder builder, + IConfiguration appSettings, + KernelMemoryConfig? memoryConfig = null) + { + if (appSettings is null) + { + throw new ConfigurationException("The given app settings configuration is NULL"); + } + + if (memoryConfig is null) + { + memoryConfig = appSettings.GetSection(KernelMemoryComposer.ConfigRoot).Get(); + } + + if (memoryConfig is null) + { + throw new ConfigurationException($"Unable to load Kernel Memory settings from the given configuration. " + + $"There should be a '{KernelMemoryComposer.ConfigRoot}' root node, " + + $"with data mapping to '{nameof(KernelMemoryConfig)}'"); + } + + var composer = new KernelMemoryComposer(builder, appSettings, memoryConfig); + composer.ConfigureBuilder(); + + return builder; + } +} diff --git a/service/Service/ConfigurationBuilderExtensions.cs b/service/Core/Configuration/ConfigurationBuilderExtensions.cs similarity index 83% rename from service/Service/ConfigurationBuilderExtensions.cs rename to service/Core/Configuration/ConfigurationBuilderExtensions.cs index ba3ea5293..194e61481 100644 --- a/service/Service/ConfigurationBuilderExtensions.cs +++ b/service/Core/Configuration/ConfigurationBuilderExtensions.cs @@ -5,22 +5,27 @@ using System.Reflection; using Microsoft.Extensions.Configuration; -namespace Microsoft.KernelMemory.Service; +#pragma warning disable IDE0130 // reduce number of "using" statements +// ReSharper disable once CheckNamespace - reduce number of "using" statements +namespace Microsoft.KernelMemory; -internal static class ConfigurationBuilderExtensions +public static partial class ConfigurationBuilderExtensions { // ASP.NET env var - private const string AspnetEnvVar = "ASPNETCORE_ENVIRONMENT"; + private const string AspNetCoreEnvVar = "ASPNETCORE_ENVIRONMENT"; - public static void AddKMConfigurationSources( + // .NET env var + private const string DotNetEnvVar = "DOTNET_ENVIRONMENT"; + + public static void AddKernelMemoryConfigurationSources( this IConfigurationBuilder builder, bool useAppSettingsFiles = true, bool useEnvVars = true, bool useSecretManager = true, string? settingsDirectory = null) { - // Load env var name, either Development or Production - var env = Environment.GetEnvironmentVariable(AspnetEnvVar) ?? string.Empty; + // ASPNETCORE_ENVIRONMENT env var takes precedence. Env should be either Development or Production. + var env = Environment.GetEnvironmentVariable(AspNetCoreEnvVar) ?? Environment.GetEnvironmentVariable(DotNetEnvVar) ?? string.Empty; // Detect the folder containing configuration files settingsDirectory ??= Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) diff --git a/service/Service/Auth/HttpAuthEndpointFilter.cs b/service/Service/HttpFilters/HttpAuthEndpointFilter.cs similarity index 100% rename from service/Service/Auth/HttpAuthEndpointFilter.cs rename to service/Service/HttpFilters/HttpAuthEndpointFilter.cs diff --git a/service/Service/Auth/HttpErrorsEndpointFilter.cs b/service/Service/HttpFilters/HttpErrorsEndpointFilter.cs similarity index 100% rename from service/Service/Auth/HttpErrorsEndpointFilter.cs rename to service/Service/HttpFilters/HttpErrorsEndpointFilter.cs diff --git a/service/Service/OpenAPI.cs b/service/Service/Internals/OpenAPI.cs similarity index 100% rename from service/Service/OpenAPI.cs rename to service/Service/Internals/OpenAPI.cs diff --git a/service/Service/KernelMemoryBuilderExtensions.cs b/service/Service/KernelMemoryBuilderExtensions.cs deleted file mode 100644 index ca2f6e43e..000000000 --- a/service/Service/KernelMemoryBuilderExtensions.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using Microsoft.Extensions.Configuration; -using Microsoft.KernelMemory.Service; - -// ReSharper disable once CheckNamespace - reduce number of "using" statements -namespace Microsoft.KernelMemory; - -/// -/// Kernel Memory builder extensions for ASP.NET apps using settings in appsettings.json -/// and using IConfiguration. The following methods allow to fully configure KM via -/// IConfiguration, without having to change the code using KernelMemoryBuilder and recompile. -/// -public static partial class KernelMemoryBuilderExtensions -{ - /// - /// Configure the builder using settings stored in the specified directory. - /// If the directory is empty, use the current assembly folder - /// - /// KernelMemory builder instance - /// Directory containing appsettings.json (incl. dev/prod) - public static IKernelMemoryBuilder FromAppSettings( - this IKernelMemoryBuilder builder, - string? settingsDirectory = null) - { - return new ServiceConfiguration(settingsDirectory).PrepareBuilder(builder); - } - - /// - /// Configure the builder using settings from the given IConfiguration instance. - /// - /// KernelMemory builder instance - /// KM configuration + Dependencies configuration - public static IKernelMemoryBuilder FromIConfiguration( - this IKernelMemoryBuilder builder, - IConfiguration servicesConfiguration) - { - return new ServiceConfiguration(servicesConfiguration).PrepareBuilder(builder); - } - - /// - /// Configure the builder using settings from the given KernelMemoryConfig and IConfiguration instances. - /// - /// KernelMemory builder instance - /// KM configuration - /// Dependencies configuration, e.g. queue, embedding, storage, etc. - public static IKernelMemoryBuilder FromMemoryConfiguration( - this IKernelMemoryBuilder builder, - KernelMemoryConfig memoryConfiguration, - IConfiguration servicesConfiguration) - { - return new ServiceConfiguration(servicesConfiguration, memoryConfiguration).PrepareBuilder(builder); - } -} diff --git a/service/Service/Program.cs b/service/Service/Program.cs index f0db0bed2..4be864ef4 100644 --- a/service/Service/Program.cs +++ b/service/Service/Program.cs @@ -57,7 +57,7 @@ public static void Main(string[] args) // *************************** CONFIG WIZARD *************************** - // Run `dotnet run setup` to run this code and setup the service + // Run `dotnet run setup` to run this code and set up the service if (new[] { "setup", "-setup", "config" }.Contains(args.FirstOrDefault(), StringComparer.OrdinalIgnoreCase)) { InteractiveSetup.Main.InteractiveSetup(args.Skip(1).ToArray()); @@ -77,7 +77,8 @@ public static void Main(string[] args) appBuilder.Services.AddApplicationInsightsTelemetry(); } - appBuilder.Configuration.AddKMConfigurationSources(); + // Add config files, user secretes, and env vars + appBuilder.Configuration.AddKernelMemoryConfigurationSources(); // Read KM settings, needed before building the app. KernelMemoryConfig config = appBuilder.Configuration.GetSection("KernelMemory").Get() @@ -90,7 +91,8 @@ public static void Main(string[] args) // Internally build the memory client and make it available for dependency injection appBuilder.AddKernelMemory(memoryBuilder => { - memoryBuilder.FromAppSettings().WithoutDefaultHandlers(); + // Prepare the builder with settings from config files + memoryBuilder.ConfigureDependencies(appBuilder.Configuration).WithoutDefaultHandlers(); // When using distributed orchestration, handlers are hosted in the current app and need to be con asyncHandlersCount = AddHandlersAsHostedServices(config, memoryBuilder, appBuilder);