diff --git a/Directory.Packages.props b/Directory.Packages.props
index dea329a59..3f4ce271e 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -16,6 +16,7 @@
+
diff --git a/KernelMemory.sln b/KernelMemory.sln
index 7ceee262b..2ff409a3a 100644
--- a/KernelMemory.sln
+++ b/KernelMemory.sln
@@ -226,6 +226,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testapps", "testapps", "{AE
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "213-onnx", "examples\213-onnx\213-onnx.csproj", "{E7ECB0D7-A4AA-4529-B191-3FDFE8674784}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExtensionsAI", "extensions\MicrosoftExtensionsAI\ExtensionsAI.csproj", "{B9E80FE9-ACB8-0C00-FBFE-3D0528BE9B8D}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -535,6 +537,10 @@ Global
{E7ECB0D7-A4AA-4529-B191-3FDFE8674784}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E7ECB0D7-A4AA-4529-B191-3FDFE8674784}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E7ECB0D7-A4AA-4529-B191-3FDFE8674784}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B9E80FE9-ACB8-0C00-FBFE-3D0528BE9B8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B9E80FE9-ACB8-0C00-FBFE-3D0528BE9B8D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B9E80FE9-ACB8-0C00-FBFE-3D0528BE9B8D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B9E80FE9-ACB8-0C00-FBFE-3D0528BE9B8D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -576,18 +582,22 @@ Global
{40CB6452-68DD-4C78-9852-78281A161950} = {155DA079-E267-49AF-973A-D1D44681970F}
{CFE7C192-2561-40CC-8592-136293451EC1} = {155DA079-E267-49AF-973A-D1D44681970F}
{93FA6DD6-D0B2-4751-8680-3F959E1F7AF2} = {155DA079-E267-49AF-973A-D1D44681970F}
+ {11445C36-1B94-4AFB-AC23-976C94924603} = {AEF463F6-F813-498C-830A-3B4CED6DC4A7}
{8F2185AB-F87C-4DD0-9DB8-E97920500A37} = {155DA079-E267-49AF-973A-D1D44681970F}
{E2AD3323-42AA-47D5-87E4-1E90B0A7C6E9} = {3C17F42B-CFC8-4900-8CFB-88936311E919}
{9F564F2D-EADD-47DE-9293-92B3E9CFFE36} = {3C17F42B-CFC8-4900-8CFB-88936311E919}
{6577B501-E295-4DCE-B640-BF40C9881A00} = {155DA079-E267-49AF-973A-D1D44681970F}
{0675D9B5-B3BB-4C9A-A1A5-11540E2ED715} = {3C17F42B-CFC8-4900-8CFB-88936311E919}
+ {E9EAD4A8-05B8-4138-8B17-E33127C83CF4} = {AEF463F6-F813-498C-830A-3B4CED6DC4A7}
{14106EE4-AB77-494D-B16A-3EC042539456} = {3C17F42B-CFC8-4900-8CFB-88936311E919}
{969625B8-039F-4E5E-A484-6A30DC417FBB} = {155DA079-E267-49AF-973A-D1D44681970F}
{9D3C9277-648D-441D-834D-565076EE2E87} = {3C17F42B-CFC8-4900-8CFB-88936311E919}
+ {CCA96699-483E-4B2A-95DF-25F0C98E3BB6} = {AEF463F6-F813-498C-830A-3B4CED6DC4A7}
{F25BC232-6161-4D45-8AFF-E92A52F0F85A} = {155DA079-E267-49AF-973A-D1D44681970F}
{C8A18E8F-34CC-4226-9831-083335DAB21A} = {3C17F42B-CFC8-4900-8CFB-88936311E919}
{9D078C9B-42A3-4558-898A-2396F59A54D3} = {155DA079-E267-49AF-973A-D1D44681970F}
{9FB79D60-E87E-420A-984D-C4D5DD2C3125} = {3C17F42B-CFC8-4900-8CFB-88936311E919}
+ {3897E2B7-85A3-4D69-B1C0-AEF41236F940} = {AEF463F6-F813-498C-830A-3B4CED6DC4A7}
{62B96766-AA6C-4CFF-A6FB-6370C89C2509} = {3C17F42B-CFC8-4900-8CFB-88936311E919}
{E3877E49-958E-4DC8-B5E8-834010F5C4B7} = {155DA079-E267-49AF-973A-D1D44681970F}
{A6AE31A1-4F60-47B0-8534-7B083D68118C} = {155DA079-E267-49AF-973A-D1D44681970F}
@@ -619,6 +629,7 @@ Global
{F192513B-265B-4943-A2A9-44E23B15BA18} = {155DA079-E267-49AF-973A-D1D44681970F}
{7BBD348E-CDD9-4462-B8C9-47613C5EC682} = {3C17F42B-CFC8-4900-8CFB-88936311E919}
{345DEF9B-6EE1-49DF-B46A-25E38CE9B151} = {155DA079-E267-49AF-973A-D1D44681970F}
+ {82670921-FDCD-4672-84BD-4353F5AC24A0} = {AEF463F6-F813-498C-830A-3B4CED6DC4A7}
{58E65B3F-EFF0-401A-AC76-A49835AE0220} = {155DA079-E267-49AF-973A-D1D44681970F}
{AB097B62-5A0B-4D74-9F8B-A41FE8241447} = {155DA079-E267-49AF-973A-D1D44681970F}
{8E907766-4A7D-46E2-B5E3-EB2994B1AA54} = {3C17F42B-CFC8-4900-8CFB-88936311E919}
@@ -629,13 +640,9 @@ Global
{BFF9BE1A-B0E4-4ABE-B384-01B200D4FEFB} = {155DA079-E267-49AF-973A-D1D44681970F}
{FD1EB2C1-581E-4EB8-AF4A-BC4773453226} = {3C17F42B-CFC8-4900-8CFB-88936311E919}
{D6BC74A5-41C7-4A60-9C2E-F246DC40145A} = {87DEAE8D-138C-4FDD-B4C9-11C3A7817E8F}
- {11445C36-1B94-4AFB-AC23-976C94924603} = {AEF463F6-F813-498C-830A-3B4CED6DC4A7}
- {E9EAD4A8-05B8-4138-8B17-E33127C83CF4} = {AEF463F6-F813-498C-830A-3B4CED6DC4A7}
- {3897E2B7-85A3-4D69-B1C0-AEF41236F940} = {AEF463F6-F813-498C-830A-3B4CED6DC4A7}
- {82670921-FDCD-4672-84BD-4353F5AC24A0} = {AEF463F6-F813-498C-830A-3B4CED6DC4A7}
- {CCA96699-483E-4B2A-95DF-25F0C98E3BB6} = {AEF463F6-F813-498C-830A-3B4CED6DC4A7}
{AEF463F6-F813-498C-830A-3B4CED6DC4A7} = {5E7DD43D-B5E7-4827-B57D-447E5B428589}
{E7ECB0D7-A4AA-4529-B191-3FDFE8674784} = {0A43C65C-6007-4BB4-B3FE-8D439FC91841}
+ {B9E80FE9-ACB8-0C00-FBFE-3D0528BE9B8D} = {155DA079-E267-49AF-973A-D1D44681970F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {CC136C62-115C-41D1-B414-F9473EFF6EA8}
diff --git a/extensions/MicrosoftExtensionsAI/DependencyInjection.cs b/extensions/MicrosoftExtensionsAI/DependencyInjection.cs
new file mode 100644
index 000000000..9e89a915b
--- /dev/null
+++ b/extensions/MicrosoftExtensionsAI/DependencyInjection.cs
@@ -0,0 +1,70 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Microsoft.Extensions.AI;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.KernelMemory.AI;
+using Microsoft.KernelMemory.AI.ExtensionsAI;
+
+#pragma warning disable IDE0130 // reduce number of "using" statements
+// ReSharper disable once CheckNamespace - reduce number of "using" statements
+namespace Microsoft.KernelMemory;
+
+///
+/// Kernel Memory builder extensions
+///
+public static partial class KernelMemoryBuilderExtensions
+{
+ ///
+ /// Use an as an to generate text completions with this .
+ ///
+ /// The builder
+ /// The to use for text generation.
+ /// Optional configuration for the instance.
+ /// Optional text tokenizer to use for token counting.
+ /// The builder provided as .
+ public static IKernelMemoryBuilder WithChatClient(
+ this IKernelMemoryBuilder builder,
+ IChatClient chatClient,
+ ExtensionsAIConfig? config = null,
+ ITextTokenizer? tokenizer = null)
+ {
+ ArgumentNullExceptionEx.ThrowIfNull(builder);
+ ArgumentNullExceptionEx.ThrowIfNull(chatClient);
+
+ builder.Services.AddSingleton(serviceProvider =>
+ new ExtensionsAITextGenerator(chatClient, config, tokenizer, serviceProvider.GetService()));
+
+ return builder;
+ }
+
+ ///
+ /// Use to generate text embeddings.
+ ///
+ /// Kernel Memory builder
+ /// The to use for embedding generation.
+ /// Optional configuration for the instance.
+ /// Optional text tokenizer to use for token counting.
+ /// Whether to use the only for retrieval, not for ingestion.
+ /// The builder provided as .
+ public static IKernelMemoryBuilder WithEmbeddingGenerator(
+ this IKernelMemoryBuilder builder,
+ IEmbeddingGenerator> embeddingGenerator,
+ ExtensionsAIConfig? config = null,
+ ITextTokenizer? tokenizer = null,
+ bool onlyForRetrieval = false)
+ {
+ ArgumentNullExceptionEx.ThrowIfNull(builder);
+ ArgumentNullExceptionEx.ThrowIfNull(embeddingGenerator);
+
+ builder.Services.AddSingleton(serviceProvider => new ExtensionsAIEmbeddingGenerator(
+ embeddingGenerator, config, tokenizer, serviceProvider.GetService()));
+
+ if (!onlyForRetrieval)
+ {
+ builder.AddIngestionEmbeddingGenerator(new ExtensionsAIEmbeddingGenerator(embeddingGenerator, config, tokenizer));
+ }
+
+ return builder;
+ }
+}
diff --git a/extensions/MicrosoftExtensionsAI/ExtensionsAI.csproj b/extensions/MicrosoftExtensionsAI/ExtensionsAI.csproj
new file mode 100644
index 000000000..3f9b7cc83
--- /dev/null
+++ b/extensions/MicrosoftExtensionsAI/ExtensionsAI.csproj
@@ -0,0 +1,33 @@
+
+
+
+ net8.0
+ LatestMajor
+ Microsoft.KernelMemory.AI.ExtensionsAI
+ Microsoft.KernelMemory.AI.ExtensionsAI
+ $(NoWarn);KMEXP00;KMEXP01;CA1724;CA1308;SKEXP0010;SKEXP0011;SKEXP0001;CA2208;
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ Microsoft.KernelMemory.AI.ExtensionsAI
+ Microsoft.Extensions.AI connector for Kernel Memory
+ Enables using any Microsoft.Extensions.AI IChatClient or IEmbeddingGenerator with Kernel Memory.
+ Microsoft.Extensions.AI, IChatClient, OpenAI, Plugin, Memory, RAG, Kernel Memory, Azure Blob, Semantic Memory, Episodic Memory, Declarative Memory, AI, Artificial Intelligence, Embeddings, Vector DB, Vector Search, Memory DB, ETL
+ bin/$(Configuration)/$(TargetFramework)/$(AssemblyName).xml
+
+
+
+
+
+
+
diff --git a/extensions/MicrosoftExtensionsAI/ExtensionsAIConfig.cs b/extensions/MicrosoftExtensionsAI/ExtensionsAIConfig.cs
new file mode 100644
index 000000000..e8ed7630e
--- /dev/null
+++ b/extensions/MicrosoftExtensionsAI/ExtensionsAIConfig.cs
@@ -0,0 +1,15 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+#pragma warning disable IDE0130 // reduce number of "using" statements
+// ReSharper disable once CheckNamespace - reduce number of "using" statements
+
+namespace Microsoft.KernelMemory;
+
+public class ExtensionsAIConfig
+{
+ /// Gets or sets the maximum length of the response that the model will generate.
+ public int MaxTokens { get; set; } = 8192;
+
+ /// Gets or sets the name of the tokenizer used to count tokens.
+ public string Tokenizer { get; set; } = "o200k";
+}
diff --git a/extensions/MicrosoftExtensionsAI/ExtensionsAIEmbeddingGenerator.cs b/extensions/MicrosoftExtensionsAI/ExtensionsAIEmbeddingGenerator.cs
new file mode 100644
index 000000000..1c94c4526
--- /dev/null
+++ b/extensions/MicrosoftExtensionsAI/ExtensionsAIEmbeddingGenerator.cs
@@ -0,0 +1,76 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.AI;
+using Microsoft.Extensions.Logging;
+using Microsoft.KernelMemory.Diagnostics;
+
+namespace Microsoft.KernelMemory.AI.ExtensionsAI;
+
+/// Provides an that wraps an .
+public sealed class ExtensionsAIEmbeddingGenerator : ITextEmbeddingGenerator, ITextEmbeddingBatchGenerator
+{
+ private readonly IEmbeddingGenerator> _embeddingGenerator;
+ private readonly ITextTokenizer _textTokenizer;
+ private readonly ILogger _log;
+
+ ///
+ public int MaxTokens { get; }
+
+ ///
+ public int MaxBatchSize { get; }
+
+ /// Initializes a new instance of the class.
+ /// The underlying .
+ /// Optional configuration for the instance.
+ /// Optional text tokenizer to use for token counting.
+ /// Optional logging factory to use for logging.
+ public ExtensionsAIEmbeddingGenerator(
+ IEmbeddingGenerator> embeddingGenerator,
+ ExtensionsAIConfig? config = null,
+ ITextTokenizer? textTokenizer = null,
+ ILoggerFactory? loggerFactory = null)
+ {
+ ArgumentNullExceptionEx.ThrowIfNull(embeddingGenerator);
+
+ config ??= new();
+
+ this._embeddingGenerator = embeddingGenerator;
+ this._log = (loggerFactory ?? DefaultLogger.Factory).CreateLogger();
+ this.MaxTokens = config.MaxTokens;
+ this.MaxBatchSize = 1;
+ this._textTokenizer = textTokenizer ?? TokenizerFactory.GetTokenizerForEncoding(string.IsNullOrEmpty(config.Tokenizer) ? "o200k" : config.Tokenizer)!;
+ }
+
+ ///
+ public int CountTokens(string text) => this._textTokenizer.CountTokens(text);
+
+ ///
+ public IReadOnlyList GetTokens(string text) => this._textTokenizer.GetTokens(text);
+
+ ///
+ public async Task GenerateEmbeddingAsync(string text, CancellationToken cancellationToken = default)
+ {
+ ArgumentNullExceptionEx.ThrowIfNull(text);
+ var results = await this.GenerateEmbeddingBatchAsync([text], cancellationToken).ConfigureAwait(false);
+ if (results.Length != 1)
+ {
+ throw new InvalidOperationException($"Expected exactly one embedding result, but received {results.Length}.");
+ }
+
+ return results[0];
+ }
+
+ ///
+ public async Task GenerateEmbeddingBatchAsync(IEnumerable textList, CancellationToken cancellationToken = default)
+ {
+ ArgumentNullExceptionEx.ThrowIfNull(textList);
+
+ var results = await this._embeddingGenerator.GenerateAsync(textList, cancellationToken: cancellationToken).ConfigureAwait(false);
+ return results.Select(embedding => new Embedding(embedding.Vector)).ToArray();
+ }
+}
diff --git a/extensions/MicrosoftExtensionsAI/ExtensionsAITextGenerator.cs b/extensions/MicrosoftExtensionsAI/ExtensionsAITextGenerator.cs
new file mode 100644
index 000000000..1105a0acd
--- /dev/null
+++ b/extensions/MicrosoftExtensionsAI/ExtensionsAITextGenerator.cs
@@ -0,0 +1,95 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.AI;
+using Microsoft.Extensions.Logging;
+using Microsoft.KernelMemory.Diagnostics;
+
+namespace Microsoft.KernelMemory.AI.ExtensionsAI;
+
+/// Provides an that wraps an .
+public sealed class ExtensionsAITextGenerator : ITextGenerator
+{
+ private readonly IChatClient _client;
+ private readonly ITextTokenizer _textTokenizer;
+ private readonly ILogger _log;
+ private readonly string _textModel;
+
+ /// Initializes a new instance of the class.
+ /// The underlying .
+ /// Optional configuration for the instance.
+ /// Optional text tokenizer to use for token counting.
+ /// Optional logging factory to use for logging.
+ public ExtensionsAITextGenerator(
+ IChatClient chatClient,
+ ExtensionsAIConfig? config = null,
+ ITextTokenizer? textTokenizer = null,
+ ILoggerFactory? loggerFactory = null)
+ {
+ ArgumentNullExceptionEx.ThrowIfNull(chatClient);
+
+ config ??= new();
+
+ this._client = chatClient;
+ this._log = (loggerFactory ?? DefaultLogger.Factory).CreateLogger();
+ this._textModel = this._client.GetService()?.DefaultModelId ?? "";
+ this.MaxTokenTotal = config.MaxTokens;
+ this._textTokenizer = textTokenizer ?? TokenizerFactory.GetTokenizerForEncoding(string.IsNullOrEmpty(config.Tokenizer) ? "o200k" : config.Tokenizer)!;
+ }
+
+ ///
+ public int MaxTokenTotal { get; }
+
+ ///
+ public int CountTokens(string text) => this._textTokenizer.CountTokens(text);
+
+ ///
+ public IReadOnlyList GetTokens(string text) => this._textTokenizer.GetTokens(text);
+
+ ///
+ public async IAsyncEnumerable GenerateTextAsync(
+ string prompt,
+ TextGenerationOptions options,
+ [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ ChatOptions chatOptions = new()
+ {
+ FrequencyPenalty = (float)options.FrequencyPenalty,
+ MaxOutputTokens = options.MaxTokens ?? this.MaxTokenTotal,
+ PresencePenalty = (float)options.PresencePenalty,
+ StopSequences = options.StopSequences,
+ Temperature = (float)options.Temperature,
+ TopP = (float)options.NucleusSampling,
+ };
+
+ string? responseModel = null;
+ await foreach (var update in this._client.GetStreamingResponseAsync(prompt, chatOptions, cancellationToken).WithCancellation(cancellationToken))
+ {
+ responseModel ??= update.ModelId;
+ foreach (var content in update.Contents)
+ {
+ switch (content)
+ {
+ case Microsoft.Extensions.AI.TextContent tc:
+ yield return new GeneratedTextContent(tc.Text, null);
+ break;
+
+ case UsageContent uc:
+ yield return new GeneratedTextContent(string.Empty, new TokenUsage
+ {
+ Timestamp = update.CreatedAt ?? DateTimeOffset.UtcNow,
+ ModelType = Constants.ModelType.TextGeneration,
+ ModelName = responseModel ?? this._textModel,
+ ServiceTokensIn = (int?)uc.Details.InputTokenCount,
+ ServiceTokensOut = (int?)uc.Details.OutputTokenCount,
+ });
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/extensions/MicrosoftExtensionsAI/README.md b/extensions/MicrosoftExtensionsAI/README.md
new file mode 100644
index 000000000..2cba62689
--- /dev/null
+++ b/extensions/MicrosoftExtensionsAI/README.md
@@ -0,0 +1,6 @@
+# Kernel Memory with Microsoft.Extensions.AI
+
+[](https://www.nuget.org/packages/Microsoft.KernelMemory.AI.ExtensionsAI/)
+[](https://aka.ms/KMdiscord)
+
+This project contains the [Microsoft.Extensions.AI](https://www.nuget.org/packages/Microsoft.Extensions.AI.Abstractions/latest) connector for using any IChatClient or IEmbeddingGenerator with Kernel Memory.