Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<PackageVersion Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.23.0" />
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="8.0.15" />
<PackageVersion Include="Microsoft.Data.SqlClient" Version="6.0.2" />
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="9.6.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="9.0.4" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="9.0.4" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.4" />
Expand Down
17 changes: 12 additions & 5 deletions KernelMemory.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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}
Expand Down Expand Up @@ -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}
Expand All @@ -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}
Expand Down
70 changes: 70 additions & 0 deletions extensions/MicrosoftExtensionsAI/DependencyInjection.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Kernel Memory builder extensions
/// </summary>
public static partial class KernelMemoryBuilderExtensions
{
/// <summary>
/// Use an <see cref="IChatClient"/> as an <see cref="ITextGenerator"/> to generate text completions with this <see cref="IKernelMemoryBuilder"/>.
/// </summary>
/// <param name="builder">The builder</param>
/// <param name="chatClient">The <see cref="IChatClient"/> to use for text generation.</param>
/// <param name="config">Optional configuration for the instance.</param>
/// <param name="tokenizer">Optional text tokenizer to use for token counting.</param>
/// <returns>The builder provided as <paramref name="builder"/>.</returns>
public static IKernelMemoryBuilder WithChatClient(
this IKernelMemoryBuilder builder,
IChatClient chatClient,
ExtensionsAIConfig? config = null,
ITextTokenizer? tokenizer = null)
{
ArgumentNullExceptionEx.ThrowIfNull(builder);
ArgumentNullExceptionEx.ThrowIfNull(chatClient);

builder.Services.AddSingleton<ITextGenerator, ExtensionsAITextGenerator>(serviceProvider =>
new ExtensionsAITextGenerator(chatClient, config, tokenizer, serviceProvider.GetService<ILoggerFactory>()));

return builder;
}

/// <summary>
/// Use <see cref="IEmbeddingGenerator{TInput, TEmbedding}"/> to generate text embeddings.
/// </summary>
/// <param name="builder">Kernel Memory builder</param>
/// <param name="embeddingGenerator">The <see cref="IEmbeddingGenerator{TInput, TEmbedding}"/> to use for embedding generation.</param>
/// <param name="config">Optional configuration for the instance.</param>
/// <param name="tokenizer">Optional text tokenizer to use for token counting.</param>
/// <param name="onlyForRetrieval">Whether to use the <see cref="IEmbeddingGenerator"/> only for retrieval, not for ingestion.</param>
/// <returns>The builder provided as <paramref name="builder"/>.</returns>
public static IKernelMemoryBuilder WithEmbeddingGenerator(
this IKernelMemoryBuilder builder,
IEmbeddingGenerator<string, Embedding<float>> embeddingGenerator,
ExtensionsAIConfig? config = null,
ITextTokenizer? tokenizer = null,
bool onlyForRetrieval = false)
{
ArgumentNullExceptionEx.ThrowIfNull(builder);
ArgumentNullExceptionEx.ThrowIfNull(embeddingGenerator);

builder.Services.AddSingleton<ITextEmbeddingGenerator, ExtensionsAIEmbeddingGenerator>(serviceProvider => new ExtensionsAIEmbeddingGenerator(
embeddingGenerator, config, tokenizer, serviceProvider.GetService<ILoggerFactory>()));

if (!onlyForRetrieval)
{
builder.AddIngestionEmbeddingGenerator(new ExtensionsAIEmbeddingGenerator(embeddingGenerator, config, tokenizer));
}

return builder;
}
}
33 changes: 33 additions & 0 deletions extensions/MicrosoftExtensionsAI/ExtensionsAI.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<RollForward>LatestMajor</RollForward>
<AssemblyName>Microsoft.KernelMemory.AI.ExtensionsAI</AssemblyName>
<RootNamespace>Microsoft.KernelMemory.AI.ExtensionsAI</RootNamespace>
<NoWarn>$(NoWarn);KMEXP00;KMEXP01;CA1724;CA1308;SKEXP0010;SKEXP0011;SKEXP0001;CA2208;</NoWarn>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\service\Abstractions\Abstractions.csproj" />
<ProjectReference Include="..\Tiktoken\Tiktoken\Tiktoken.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.AI.ABstractions" />
</ItemGroup>

<PropertyGroup>
<IsPackable>true</IsPackable>
<PackageId>Microsoft.KernelMemory.AI.ExtensionsAI</PackageId>
<Product>Microsoft.Extensions.AI connector for Kernel Memory</Product>
<Description>Enables using any Microsoft.Extensions.AI IChatClient or IEmbeddingGenerator with Kernel Memory.</Description>
<PackageTags>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</PackageTags>
<DocumentationFile>bin/$(Configuration)/$(TargetFramework)/$(AssemblyName).xml</DocumentationFile>
</PropertyGroup>

<ItemGroup>
<None Include="README.md" Link="README.md" Pack="true" PackagePath="." Visible="false" />
</ItemGroup>

</Project>
15 changes: 15 additions & 0 deletions extensions/MicrosoftExtensionsAI/ExtensionsAIConfig.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>Gets or sets the maximum length of the response that the model will generate.</summary>
public int MaxTokens { get; set; } = 8192;

/// <summary>Gets or sets the name of the tokenizer used to count tokens.</summary>
public string Tokenizer { get; set; } = "o200k";
}
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>Provides an <see cref="ITextEmbeddingGenerator" /> that wraps an <see cref="IEmbeddingGenerator{TInput, TEmbedding}"/>.</summary>
public sealed class ExtensionsAIEmbeddingGenerator : ITextEmbeddingGenerator, ITextEmbeddingBatchGenerator
{
private readonly IEmbeddingGenerator<string, Embedding<float>> _embeddingGenerator;
private readonly ITextTokenizer _textTokenizer;
private readonly ILogger<ExtensionsAIEmbeddingGenerator> _log;

/// <inheritdoc/>
public int MaxTokens { get; }

/// <inheritdoc/>
public int MaxBatchSize { get; }

/// <summary>Initializes a new instance of the <see cref="ExtensionsAIEmbeddingGenerator"/> class.</summary>
/// <param name="embeddingGenerator">The underlying <see cref="IEmbeddingGenerator{TInput, TEmbedding}"/>.</param>
/// <param name="config">Optional configuration for the instance.</param>
/// <param name="textTokenizer">Optional text tokenizer to use for token counting.</param>
/// <param name="loggerFactory">Optional logging factory to use for logging.</param>
public ExtensionsAIEmbeddingGenerator(
IEmbeddingGenerator<string, Embedding<float>> embeddingGenerator,
ExtensionsAIConfig? config = null,
ITextTokenizer? textTokenizer = null,
ILoggerFactory? loggerFactory = null)
{
ArgumentNullExceptionEx.ThrowIfNull(embeddingGenerator);

config ??= new();

this._embeddingGenerator = embeddingGenerator;
this._log = (loggerFactory ?? DefaultLogger.Factory).CreateLogger<ExtensionsAIEmbeddingGenerator>();
this.MaxTokens = config.MaxTokens;
this.MaxBatchSize = 1;
this._textTokenizer = textTokenizer ?? TokenizerFactory.GetTokenizerForEncoding(string.IsNullOrEmpty(config.Tokenizer) ? "o200k" : config.Tokenizer)!;
}

/// <inheritdoc/>
public int CountTokens(string text) => this._textTokenizer.CountTokens(text);

/// <inheritdoc/>
public IReadOnlyList<string> GetTokens(string text) => this._textTokenizer.GetTokens(text);

/// <inheritdoc/>
public async Task<Embedding> 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];
}

/// <inheritdoc/>
public async Task<Embedding[]> GenerateEmbeddingBatchAsync(IEnumerable<string> 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();
}
}
95 changes: 95 additions & 0 deletions extensions/MicrosoftExtensionsAI/ExtensionsAITextGenerator.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>Provides an <see cref="ITextGenerator" /> that wraps an <see cref="IChatClient"/>.</summary>
public sealed class ExtensionsAITextGenerator : ITextGenerator
{
private readonly IChatClient _client;
private readonly ITextTokenizer _textTokenizer;
private readonly ILogger<ExtensionsAITextGenerator> _log;
private readonly string _textModel;

/// <summary>Initializes a new instance of the <see cref="ExtensionsAITextGenerator"/> class.</summary>
/// <param name="chatClient">The underlying <see cref="IChatClient"/>.</param>
/// <param name="config">Optional configuration for the instance.</param>
/// <param name="textTokenizer">Optional text tokenizer to use for token counting.</param>
/// <param name="loggerFactory">Optional logging factory to use for logging.</param>
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<ExtensionsAITextGenerator>();
this._textModel = this._client.GetService<ChatClientMetadata>()?.DefaultModelId ?? "";
this.MaxTokenTotal = config.MaxTokens;
this._textTokenizer = textTokenizer ?? TokenizerFactory.GetTokenizerForEncoding(string.IsNullOrEmpty(config.Tokenizer) ? "o200k" : config.Tokenizer)!;
}

/// <inheritdoc/>
public int MaxTokenTotal { get; }

/// <inheritdoc/>
public int CountTokens(string text) => this._textTokenizer.CountTokens(text);

/// <inheritdoc/>
public IReadOnlyList<string> GetTokens(string text) => this._textTokenizer.GetTokens(text);

/// <inheritdoc/>
public async IAsyncEnumerable<GeneratedTextContent> 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;
}
}
}
}
}
6 changes: 6 additions & 0 deletions extensions/MicrosoftExtensionsAI/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Kernel Memory with Microsoft.Extensions.AI

[![Nuget package](https://img.shields.io/nuget/v/Microsoft.KernelMemory.AI.ExtensionsAI)](https://www.nuget.org/packages/Microsoft.KernelMemory.AI.ExtensionsAI/)
[![Discord](https://img.shields.io/discord/1063152441819942922?label=Discord&logo=discord&logoColor=white&color=d82679)](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.