diff --git a/src/Infrastructure/BotSharp.Abstraction/MLTasks/ITextEmbedding.cs b/src/Infrastructure/BotSharp.Abstraction/MLTasks/ITextEmbedding.cs index 8a0df8a5..3e53dd0f 100644 --- a/src/Infrastructure/BotSharp.Abstraction/MLTasks/ITextEmbedding.cs +++ b/src/Infrastructure/BotSharp.Abstraction/MLTasks/ITextEmbedding.cs @@ -1,8 +1,10 @@ +using System.Threading; + namespace BotSharp.Abstraction.MLTasks; public interface ITextEmbedding { int Dimension { get; } - float[] GetVector(string text); - List GetVectors(List texts); + Task GetVectorAsync(string text); + Task> GetVectorsAsync(List texts); } diff --git a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.cs b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.cs index 8a818534..eef38f63 100644 --- a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.cs +++ b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.cs @@ -31,7 +31,7 @@ public class KnowledgeService : IKnowledgeService await db.CreateCollection(knowledge.AgentId, textEmbedding.Dimension); foreach (var line in lines) { - var vec = textEmbedding.GetVector(line); + var vec = await textEmbedding.GetVectorAsync(line); await db.Upsert(knowledge.AgentId, idStart, vec, line); idStart++; Console.WriteLine($"Saved vector {idStart}/{lines.Count}: {line}\n"); @@ -41,7 +41,7 @@ public class KnowledgeService : IKnowledgeService public async Task GetKnowledges(KnowledgeRetrievalModel retrievalModel) { var textEmbedding = GetTextEmbedding(); - var vector = textEmbedding.GetVector(retrievalModel.Question); + var vector = await textEmbedding.GetVectorAsync(retrievalModel.Question); // Vector search var result = await GetVectorDb().Search(retrievalModel.AgentId, vector, limit: 10); diff --git a/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/TextEmbeddingProvider.cs b/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/TextEmbeddingProvider.cs index 1a485bf1..4a433e7e 100644 --- a/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/TextEmbeddingProvider.cs +++ b/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/TextEmbeddingProvider.cs @@ -15,7 +15,7 @@ public class TextEmbeddingProvider : ITextEmbedding _settings = settings; } - public float[] GetVector(string text) + public Task GetVectorAsync(string text) { if (_embedder == null) { @@ -23,10 +23,10 @@ public class TextEmbeddingProvider : ITextEmbedding _embedder = new LLamaEmbedder(new ModelParams(path)); } - return _embedder.GetEmbeddings(text); + return Task.FromResult(_embedder.GetEmbeddings(text)); } - public List GetVectors(List texts) + public Task> GetVectorsAsync(List texts) { throw new NotImplementedException(); } diff --git a/src/Plugins/BotSharp.Plugin.MetaAI/Providers/fastTextEmbeddingProvider.cs b/src/Plugins/BotSharp.Plugin.MetaAI/Providers/fastTextEmbeddingProvider.cs index 3388f04c..9bfecfe4 100644 --- a/src/Plugins/BotSharp.Plugin.MetaAI/Providers/fastTextEmbeddingProvider.cs +++ b/src/Plugins/BotSharp.Plugin.MetaAI/Providers/fastTextEmbeddingProvider.cs @@ -3,6 +3,7 @@ using BotSharp.Plugin.MetaAI.Settings; using FastText.NetWrapper; using System.Collections.Generic; using System.IO; +using System.Threading.Tasks; namespace BotSharp.Plugin.MetaAI.Providers; @@ -26,19 +27,19 @@ public class fastTextEmbeddingProvider : ITextEmbedding } - public float[] GetVector(string text) + public Task GetVectorAsync(string text) { LoadModel(); - return _fastText.GetSentenceVector(text); + return Task.FromResult(_fastText.GetSentenceVector(text)); } - public List GetVectors(List texts) + public async Task> GetVectorsAsync(List texts) { LoadModel(); var vectors = new List(); for (int i = 0; i < texts.Count; i++) { - vectors.Add(GetVector(texts[i])); + vectors.Add(await GetVectorAsync(texts[i])); } return vectors; } diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/IntentClassifier.cs b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/IntentClassifier.cs index 977e43bb..089b5168 100644 --- a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/IntentClassifier.cs +++ b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/IntentClassifier.cs @@ -33,8 +33,8 @@ public class IntentClassifier private string[] _labels; public string[] Labels => _labels == null ? GetLabels() : _labels; - public IntentClassifier(IServiceProvider services, - ClassifierSetting settings, + public IntentClassifier(IServiceProvider services, + ClassifierSetting settings, KnowledgeBaseSettings knowledgeBaseSettings, ILogger logger) { @@ -140,7 +140,7 @@ public class IntentClassifier .FirstOrDefault(x => x.GetType().FullName.EndsWith(knowledgeSettings.TextEmbedding)); var x = np.zeros((1, embedding.Dimension), dtype: np.float32); - x[0] = embedding.GetVector(text); + x[0] = embedding.GetVectorAsync(text).GetAwaiter().GetResult(); return x; } @@ -178,7 +178,7 @@ public class IntentClassifier { var texts = File.ReadAllLines(filePath, Encoding.UTF8).ToList(); - vectorList.AddRange(vector.GetVectors(texts)); + vectorList.AddRange(vector.GetVectorsAsync(texts).GetAwaiter().GetResult()); string fileName = Path.GetFileNameWithoutExtension(filePath); labelList.AddRange(Enumerable.Repeat(fileName, texts.Count).ToList()); } diff --git a/src/Plugins/BotSharp.Plugin.SemanticKernel/BotSharp.Plugin.SemanticKernel.csproj b/src/Plugins/BotSharp.Plugin.SemanticKernel/BotSharp.Plugin.SemanticKernel.csproj index 9ff34130..2dd2e3b3 100644 --- a/src/Plugins/BotSharp.Plugin.SemanticKernel/BotSharp.Plugin.SemanticKernel.csproj +++ b/src/Plugins/BotSharp.Plugin.SemanticKernel/BotSharp.Plugin.SemanticKernel.csproj @@ -1,4 +1,4 @@ - + netstandard2.1 @@ -10,8 +10,10 @@ - - + + + + diff --git a/src/Plugins/BotSharp.Plugin.SemanticKernel/SemanticKernelChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.SemanticKernel/SemanticKernelChatCompletionProvider.cs index 1b5298fc..13bca1e8 100644 --- a/src/Plugins/BotSharp.Plugin.SemanticKernel/SemanticKernelChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.SemanticKernel/SemanticKernelChatCompletionProvider.cs @@ -18,7 +18,7 @@ namespace BotSharp.Plugin.SemanticKernel /// public class SemanticKernelChatCompletionProvider : IChatCompletion { - private IKernel _kernel; + private Microsoft.SemanticKernel.AI.ChatCompletion.IChatCompletion _kernelChatCompletion; private IServiceProvider _services; private ITokenStatistics _tokenStatistics; private string? _model = null; @@ -29,14 +29,14 @@ namespace BotSharp.Plugin.SemanticKernel /// /// Create a new instance of /// - /// + /// /// /// - public SemanticKernelChatCompletionProvider(IKernel kernel, + public SemanticKernelChatCompletionProvider(Microsoft.SemanticKernel.AI.ChatCompletion.IChatCompletion chatCompletion, IServiceProvider services, ITokenStatistics tokenStatistics) { - this._kernel = kernel; + this._kernelChatCompletion = chatCompletion; this._services = services; this._tokenStatistics = tokenStatistics; } @@ -49,7 +49,7 @@ namespace BotSharp.Plugin.SemanticKernel Task.WaitAll(hooks.Select(hook => hook.BeforeGenerating(agent, conversations)).ToArray()); - var completion = _kernel.GetService(_model); + var completion = this._kernelChatCompletion; var agentService = _services.GetRequiredService(); var instruction = agentService.RenderedInstruction(agent); diff --git a/src/Plugins/BotSharp.Plugin.SemanticKernel/SemanticKernelMemoryStoreProvider.cs b/src/Plugins/BotSharp.Plugin.SemanticKernel/SemanticKernelMemoryStoreProvider.cs new file mode 100644 index 00000000..3f34e76b --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.SemanticKernel/SemanticKernelMemoryStoreProvider.cs @@ -0,0 +1,52 @@ +using BotSharp.Abstraction.VectorStorage; +using Microsoft.SemanticKernel.Memory; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace BotSharp.Plugin.SemanticKernel +{ + internal class SemanticKernelMemoryStoreProvider : IVectorDb + { + private readonly IMemoryStore _memoryStore; + + public SemanticKernelMemoryStoreProvider(IMemoryStore memoryStore) + { + this._memoryStore = memoryStore; + } + public async Task CreateCollection(string collectionName, int dim) + { + await _memoryStore.CreateCollectionAsync(collectionName); + } + + public async Task> GetCollections() + { + var result = new List(); + await foreach (var collection in _memoryStore.GetCollectionsAsync()) + { + result.Add(collection); + } + return result; + } + + public async Task> Search(string collectionName, float[] vector, int limit = 5) + { + var results = _memoryStore.GetNearestMatchesAsync(collectionName, vector, limit); + + var resultTexts = new List(); + await foreach (var (record, _) in results) + { + resultTexts.Add(record.Metadata.Text); + } + + return resultTexts; + + } + + public async Task Upsert(string collectionName, int id, float[] vector, string text) + { + await _memoryStore.UpsertAsync(collectionName, MemoryRecord.LocalRecord(id.ToString(), text, null, vector)); + } + } +} diff --git a/src/Plugins/BotSharp.Plugin.SemanticKernel/SemanticKernelPlugin.cs b/src/Plugins/BotSharp.Plugin.SemanticKernel/SemanticKernelPlugin.cs index 1494bf56..45afa52f 100644 --- a/src/Plugins/BotSharp.Plugin.SemanticKernel/SemanticKernelPlugin.cs +++ b/src/Plugins/BotSharp.Plugin.SemanticKernel/SemanticKernelPlugin.cs @@ -1,19 +1,47 @@ using BotSharp.Abstraction.MLTasks; using BotSharp.Abstraction.Plugins; +using BotSharp.Abstraction.VectorStorage; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace BotSharp.Plugin.SemanticKernel { + /// + /// Use Semantic Kernel as BotSharp plugin + /// public class SemanticKernelPlugin : IBotSharpPlugin { + /// public string Name => "Semantic Kernel"; + + /// public string Description => "Semantic Kernel Service"; + /// public void RegisterDI(IServiceCollection services, IConfiguration config) { - services.AddScoped(); - services.AddScoped(); + + var provider = services.BuildServiceProvider().CreateScope().ServiceProvider; + + if (provider.GetService() != null) + { + services.AddScoped(); + } + + if (provider.GetService() != null) + { + services.AddScoped(); + } + + if (provider.GetService() != null) + { + services.AddScoped(); + } + + if (provider.GetService() != null) + { + services.AddScoped(); + } } } } \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.SemanticKernel/SemanticKernelTextCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.SemanticKernel/SemanticKernelTextCompletionProvider.cs index d222e850..ee38d6f2 100644 --- a/src/Plugins/BotSharp.Plugin.SemanticKernel/SemanticKernelTextCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.SemanticKernel/SemanticKernelTextCompletionProvider.cs @@ -19,7 +19,7 @@ namespace BotSharp.Plugin.SemanticKernel /// public class SemanticKernelTextCompletionProvider : Abstraction.MLTasks.ITextCompletion { - private readonly IKernel _kernel; + private readonly Microsoft.SemanticKernel.AI.TextCompletion.ITextCompletion _kernelTextCompletion; private readonly IServiceProvider _services; private readonly ITokenStatistics _tokenStatistics; private string? _model = null; @@ -30,16 +30,14 @@ namespace BotSharp.Plugin.SemanticKernel /// /// Create a new instance of /// - /// + /// /// /// - public SemanticKernelTextCompletionProvider(IKernel kernel, + public SemanticKernelTextCompletionProvider(Microsoft.SemanticKernel.AI.TextCompletion.ITextCompletion textCompletion, IServiceProvider services, ITokenStatistics tokenStatistics) { - Requires.NotNull(kernel, nameof(IKernel)); - - this._kernel = kernel; + this._kernelTextCompletion = textCompletion; this._services = services; this._tokenStatistics = tokenStatistics; } @@ -61,7 +59,7 @@ namespace BotSharp.Plugin.SemanticKernel Task.WaitAll(hooks.Select(hook => hook.BeforeGenerating(agent, new List { userMessage })).ToArray()); - var completion = _kernel.GetService(_model); + var completion = this._kernelTextCompletion; _tokenStatistics.StartTimer(); var result = await completion.CompleteAsync(text); _tokenStatistics.StopTimer(); diff --git a/src/Plugins/BotSharp.Plugin.SemanticKernel/SemanticKernelTextEmbeddingProvider.cs b/src/Plugins/BotSharp.Plugin.SemanticKernel/SemanticKernelTextEmbeddingProvider.cs new file mode 100644 index 00000000..0b2fa22c --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.SemanticKernel/SemanticKernelTextEmbeddingProvider.cs @@ -0,0 +1,50 @@ +using BotSharp.Abstraction.MLTasks; +using Microsoft.Extensions.Configuration; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.AI.Embeddings; +using Microsoft.SemanticKernel.Memory; +using Microsoft.SemanticKernel.Plugins.Memory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BotSharp.Plugin.SemanticKernel +{ + /// + /// Use Semantic Kernel Memory as text embedding provider + /// + public class SemanticKernelTextEmbeddingProvider : ITextEmbedding + { + private readonly ITextEmbeddingGeneration _embedding; + private readonly IConfiguration _configuration; + + /// + /// Constructor of + /// + public SemanticKernelTextEmbeddingProvider(ITextEmbeddingGeneration embedding, IConfiguration configuration) + { + this._embedding = embedding; + this._configuration = configuration; + this.Dimension = configuration.GetValue("SemanticKernel:Dimension"); + } + + /// + public int Dimension { get; set; } + + /// + public async Task GetVectorAsync(string text) + { + return (await this._embedding.GenerateEmbeddingAsync(text)).ToArray(); + } + + /// + public async Task> GetVectorsAsync(List texts) + { + var embeddings = await this._embedding.GenerateEmbeddingsAsync(texts); + return embeddings.Select(_ => _.ToArray()) + .ToList(); + } + } +} diff --git a/src/Plugins/BotSharp.Plugin.SemanticKernel/readme.md b/src/Plugins/BotSharp.Plugin.SemanticKernel/readme.md new file mode 100644 index 00000000..d740012a --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.SemanticKernel/readme.md @@ -0,0 +1,2 @@ +# Semantic Kernel For BotSharp + diff --git a/tests/BotSharp.Plugin.SemanticKernel.UnitTests/Helpers/ResultHelper.cs b/tests/BotSharp.Plugin.SemanticKernel.UnitTests/Helpers/ResultHelper.cs index 84d9bbcc..4a818310 100644 --- a/tests/BotSharp.Plugin.SemanticKernel.UnitTests/Helpers/ResultHelper.cs +++ b/tests/BotSharp.Plugin.SemanticKernel.UnitTests/Helpers/ResultHelper.cs @@ -15,7 +15,7 @@ namespace BotSharp.Plugin.SemanticKernel.UnitTests.Helpers _response = response; } - public async Task GetChatMessageAsync(CancellationToken cancellationToken = default) + public async Task GetChatMessageAsync(CancellationToken cancellationToken = default) { return await Task.FromResult(new MockModelResult(_response)); } @@ -25,7 +25,7 @@ namespace BotSharp.Plugin.SemanticKernel.UnitTests.Helpers return Task.FromResult(_response); } - public class MockModelResult : ChatMessageBase + public class MockModelResult : ChatMessage { public MockModelResult(string content) : base(AuthorRole.Assistant, content, null) { diff --git a/tests/BotSharp.Plugin.SemanticKernel.UnitTests/Helpers/SemanticKernelHelper.cs b/tests/BotSharp.Plugin.SemanticKernel.UnitTests/Helpers/SemanticKernelHelper.cs index ef775f76..1245a4d2 100644 --- a/tests/BotSharp.Plugin.SemanticKernel.UnitTests/Helpers/SemanticKernelHelper.cs +++ b/tests/BotSharp.Plugin.SemanticKernel.UnitTests/Helpers/SemanticKernelHelper.cs @@ -12,6 +12,7 @@ namespace BotSharp.Plugin.SemanticKernel.UnitTests.Helpers { internal class SemanticKernelHelper : IChatCompletion, ITextCompletion, IAIService { + private Dictionary _attributes = new(); private readonly string _excepted; public SemanticKernelHelper(string excepted) @@ -19,6 +20,8 @@ namespace BotSharp.Plugin.SemanticKernel.UnitTests.Helpers this._excepted = excepted; } + public IReadOnlyDictionary Attributes => _attributes; + public ChatHistory CreateNewChat(string? instructions = null) { return new ChatHistory(); @@ -26,7 +29,7 @@ namespace BotSharp.Plugin.SemanticKernel.UnitTests.Helpers public Task> GetChatCompletionsAsync(ChatHistory chat, AIRequestSettings? requestSettings = null, CancellationToken cancellationToken = default) { - return Task.FromResult>( new List { new ResultHelper(_excepted) }); + return Task.FromResult>(new List { new ResultHelper(_excepted) }); } public Task> GetCompletionsAsync(string text, AIRequestSettings? requestSettings = null, CancellationToken cancellationToken = default) diff --git a/tests/BotSharp.Plugin.SemanticKernel.UnitTests/SemanticKernelChatCompletionProviderTests.cs b/tests/BotSharp.Plugin.SemanticKernel.UnitTests/SemanticKernelChatCompletionProviderTests.cs index 5be823fa..777192ed 100644 --- a/tests/BotSharp.Plugin.SemanticKernel.UnitTests/SemanticKernelChatCompletionProviderTests.cs +++ b/tests/BotSharp.Plugin.SemanticKernel.UnitTests/SemanticKernelChatCompletionProviderTests.cs @@ -24,17 +24,17 @@ namespace BotSharp.Plugin.SemanticKernel.Tests { public class SemanticKernelChatCompletionProviderTests { - private readonly Mock _kernelMock; + private readonly Mock _chatCompletionMock; private readonly Mock _servicesMock; private readonly Mock _tokenStatisticsMock; private readonly SemanticKernelChatCompletionProvider _provider; public SemanticKernelChatCompletionProviderTests() { - _kernelMock = new Mock(); + _chatCompletionMock = new Mock(); _servicesMock = new Mock(); _tokenStatisticsMock = new Mock(); - _provider = new SemanticKernelChatCompletionProvider(_kernelMock.Object, _servicesMock.Object, _tokenStatisticsMock.Object); + _provider = new SemanticKernelChatCompletionProvider(_chatCompletionMock.Object, _servicesMock.Object, _tokenStatisticsMock.Object); } [Fact] @@ -55,15 +55,13 @@ namespace BotSharp.Plugin.SemanticKernel.Tests .Returns(agentService.Object); var chatHistoryMock = new Mock(); - var chatCompletionMock = new Mock(); - chatCompletionMock.Setup(x => x.CreateNewChat(It.IsAny())).Returns(chatHistoryMock.Object); - chatCompletionMock.Setup(x => x.GetChatCompletionsAsync(chatHistoryMock.Object, It.IsAny(), It.IsAny())) + _chatCompletionMock.Setup(x => x.CreateNewChat(It.IsAny())).Returns(chatHistoryMock.Object); + _chatCompletionMock.Setup(x => x.GetChatCompletionsAsync(chatHistoryMock.Object, It.IsAny(), It.IsAny())) .ReturnsAsync(new List { new ResultHelper("How can I help you?") }); - _kernelMock.Setup(x => x.GetService(null)).Returns(chatCompletionMock.Object); // Act var result = _provider.GetChatCompletions(agent, conversations); diff --git a/tests/BotSharp.Plugin.SemanticKernel.UnitTests/SemanticKernelPluginTests.cs b/tests/BotSharp.Plugin.SemanticKernel.UnitTests/SemanticKernelPluginTests.cs index a1ba7214..a1ac3963 100644 --- a/tests/BotSharp.Plugin.SemanticKernel.UnitTests/SemanticKernelPluginTests.cs +++ b/tests/BotSharp.Plugin.SemanticKernel.UnitTests/SemanticKernelPluginTests.cs @@ -1,5 +1,7 @@ using BotSharp.Abstraction.Conversations; using BotSharp.Abstraction.MLTasks; +using BotSharp.Abstraction.VectorStorage; +using BotSharp.Plugin.SemanticKernel.UnitTests.Helpers; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.SemanticKernel; @@ -14,22 +16,25 @@ namespace BotSharp.Plugin.SemanticKernel.Tests { var services = new ServiceCollection(); var config = new ConfigurationBuilder().Build(); + + services.AddSingleton(config); + var plugin = new SemanticKernelPlugin(); - services.AddScoped(x => - { - return new KernelBuilder() - .WithAzureOpenAIChatCompletionService("test", "test", "test") - .Build(); - }); - services.AddScoped(x=> Mock.Of()); + services.AddScoped(x => Mock.Of()); + services.AddScoped(x => Mock.Of()); + services.AddScoped(x => Mock.Of()); + services.AddScoped(x => Mock.Of()); + services.AddScoped(x => Mock.Of()); plugin.RegisterDI(services, config); - var provider = services.BuildServiceProvider(); + var provider = services.BuildServiceProvider().CreateScope().ServiceProvider; Assert.NotNull(provider.GetService()); Assert.NotNull(provider.GetService()); + Assert.NotNull(provider.GetService()); + Assert.NotNull(provider.GetService()); } } } \ No newline at end of file diff --git a/tests/BotSharp.Plugin.SemanticKernel.UnitTests/SemanticKernelTextCompletionProviderTests.cs b/tests/BotSharp.Plugin.SemanticKernel.UnitTests/SemanticKernelTextCompletionProviderTests.cs index 1e463cdf..89188973 100644 --- a/tests/BotSharp.Plugin.SemanticKernel.UnitTests/SemanticKernelTextCompletionProviderTests.cs +++ b/tests/BotSharp.Plugin.SemanticKernel.UnitTests/SemanticKernelTextCompletionProviderTests.cs @@ -37,10 +37,7 @@ namespace BotSharp.Plugin.SemanticKernel.Tests var text = "Hello"; var expected = "Hello, world!"; - var _kernel = new KernelBuilder() - .WithAIService("", new SemanticKernelHelper(expected)) - .Build(); - var provider = new SemanticKernelTextCompletionProvider(_kernel, _services, _tokenStatistics); + var provider = new SemanticKernelTextCompletionProvider(new SemanticKernelHelper(expected), _services, _tokenStatistics); // Act var result = await provider.GetCompletion(text, "agent1", "message1");