Merge pull request #213 from xbotter/sk/memory

Refactor the injection method of the SemanticKernel component.
This commit is contained in:
Haiping 2023-11-20 09:23:12 -06:00 committed by GitHub
commit 4c1af4c710
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 192 additions and 54 deletions

View file

@ -1,8 +1,10 @@
using System.Threading;
namespace BotSharp.Abstraction.MLTasks;
public interface ITextEmbedding
{
int Dimension { get; }
float[] GetVector(string text);
List<float[]> GetVectors(List<string> texts);
Task<float[]> GetVectorAsync(string text);
Task<List<float[]>> GetVectorsAsync(List<string> texts);
}

View file

@ -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<string> 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);

View file

@ -15,7 +15,7 @@ public class TextEmbeddingProvider : ITextEmbedding
_settings = settings;
}
public float[] GetVector(string text)
public Task<float[]> 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<float[]> GetVectors(List<string> texts)
public Task<List<float[]>> GetVectorsAsync(List<string> texts)
{
throw new NotImplementedException();
}

View file

@ -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<float[]> GetVectorAsync(string text)
{
LoadModel();
return _fastText.GetSentenceVector(text);
return Task.FromResult(_fastText.GetSentenceVector(text));
}
public List<float[]> GetVectors(List<string> texts)
public async Task<List<float[]>> GetVectorsAsync(List<string> texts)
{
LoadModel();
var vectors = new List<float[]>();
for (int i = 0; i < texts.Count; i++)
{
vectors.Add(GetVector(texts[i]));
vectors.Add(await GetVectorAsync(texts[i]));
}
return vectors;
}

View file

@ -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());
}

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
@ -10,8 +10,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SemanticKernel.Abstractions" Version="1.0.0-beta6" />
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="17.6.11" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Microsoft.SemanticKernel.Abstractions" Version="1.0.0-beta8" />
<PackageReference Include="Microsoft.SemanticKernel.Plugins.Memory" Version="1.0.0-beta8" />
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="17.8.8" />
</ItemGroup>
<ItemGroup>

View file

@ -18,7 +18,7 @@ namespace BotSharp.Plugin.SemanticKernel
/// </summary>
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
/// <summary>
/// Create a new instance of <see cref="SemanticKernelChatCompletionProvider"/>
/// </summary>
/// <param name="kernel"></param>
/// <param name="chatCompletion"></param>
/// <param name="services"></param>
/// <param name="tokenStatistics"></param>
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<Microsoft.SemanticKernel.AI.ChatCompletion.IChatCompletion>(_model);
var completion = this._kernelChatCompletion;
var agentService = _services.GetRequiredService<IAgentService>();
var instruction = agentService.RenderedInstruction(agent);

View file

@ -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<List<string>> GetCollections()
{
var result = new List<string>();
await foreach (var collection in _memoryStore.GetCollectionsAsync())
{
result.Add(collection);
}
return result;
}
public async Task<List<string>> Search(string collectionName, float[] vector, int limit = 5)
{
var results = _memoryStore.GetNearestMatchesAsync(collectionName, vector, limit);
var resultTexts = new List<string>();
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));
}
}
}

View file

@ -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
{
/// <summary>
/// Use Semantic Kernel as BotSharp plugin
/// </summary>
public class SemanticKernelPlugin : IBotSharpPlugin
{
/// <inheritdoc/>
public string Name => "Semantic Kernel";
/// <inheritdoc/>
public string Description => "Semantic Kernel Service";
/// <inheritdoc/>
public void RegisterDI(IServiceCollection services, IConfiguration config)
{
services.AddScoped<ITextCompletion, SemanticKernelTextCompletionProvider>();
services.AddScoped<IChatCompletion, SemanticKernelChatCompletionProvider>();
var provider = services.BuildServiceProvider().CreateScope().ServiceProvider;
if (provider.GetService<Microsoft.SemanticKernel.AI.TextCompletion.ITextCompletion>() != null)
{
services.AddScoped<ITextCompletion, SemanticKernelTextCompletionProvider>();
}
if (provider.GetService<Microsoft.SemanticKernel.AI.ChatCompletion.IChatCompletion>() != null)
{
services.AddScoped<IChatCompletion, SemanticKernelChatCompletionProvider>();
}
if (provider.GetService<Microsoft.SemanticKernel.Memory.IMemoryStore>() != null)
{
services.AddScoped<IVectorDb, SemanticKernelMemoryStoreProvider>();
}
if (provider.GetService<Microsoft.SemanticKernel.AI.Embeddings.ITextEmbeddingGeneration>() != null)
{
services.AddScoped<ITextEmbedding, SemanticKernelTextEmbeddingProvider>();
}
}
}
}

View file

@ -19,7 +19,7 @@ namespace BotSharp.Plugin.SemanticKernel
/// </summary>
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
/// <summary>
/// Create a new instance of <see cref="SemanticKernelTextCompletionProvider"/>
/// </summary>
/// <param name="kernel"></param>
/// <param name="textCompletion"></param>
/// <param name="services"></param>
/// <param name="tokenStatistics"></param>
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<RoleDialogModel> { userMessage })).ToArray());
var completion = _kernel.GetService<Microsoft.SemanticKernel.AI.TextCompletion.ITextCompletion>(_model);
var completion = this._kernelTextCompletion;
_tokenStatistics.StartTimer();
var result = await completion.CompleteAsync(text);
_tokenStatistics.StopTimer();

View file

@ -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
{
/// <summary>
/// Use Semantic Kernel Memory as text embedding provider
/// </summary>
public class SemanticKernelTextEmbeddingProvider : ITextEmbedding
{
private readonly ITextEmbeddingGeneration _embedding;
private readonly IConfiguration _configuration;
/// <summary>
/// Constructor of <see cref="SemanticKernelTextEmbeddingProvider"/>
/// </summary>
public SemanticKernelTextEmbeddingProvider(ITextEmbeddingGeneration embedding, IConfiguration configuration)
{
this._embedding = embedding;
this._configuration = configuration;
this.Dimension = configuration.GetValue<int>("SemanticKernel:Dimension");
}
/// <inheritdoc/>
public int Dimension { get; set; }
/// <inheritdoc/>
public async Task<float[]> GetVectorAsync(string text)
{
return (await this._embedding.GenerateEmbeddingAsync(text)).ToArray();
}
/// <inheritdoc/>
public async Task<List<float[]>> GetVectorsAsync(List<string> texts)
{
var embeddings = await this._embedding.GenerateEmbeddingsAsync(texts);
return embeddings.Select(_ => _.ToArray())
.ToList();
}
}
}

View file

@ -0,0 +1,2 @@
# Semantic Kernel For BotSharp

View file

@ -15,7 +15,7 @@ namespace BotSharp.Plugin.SemanticKernel.UnitTests.Helpers
_response = response;
}
public async Task<ChatMessageBase> GetChatMessageAsync(CancellationToken cancellationToken = default)
public async Task<ChatMessage> 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)
{

View file

@ -12,6 +12,7 @@ namespace BotSharp.Plugin.SemanticKernel.UnitTests.Helpers
{
internal class SemanticKernelHelper : IChatCompletion, ITextCompletion, IAIService
{
private Dictionary<string, string> _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<string, string> Attributes => _attributes;
public ChatHistory CreateNewChat(string? instructions = null)
{
return new ChatHistory();
@ -26,7 +29,7 @@ namespace BotSharp.Plugin.SemanticKernel.UnitTests.Helpers
public Task<IReadOnlyList<IChatResult>> GetChatCompletionsAsync(ChatHistory chat, AIRequestSettings? requestSettings = null, CancellationToken cancellationToken = default)
{
return Task.FromResult<IReadOnlyList<IChatResult>>( new List<IChatResult> { new ResultHelper(_excepted) });
return Task.FromResult<IReadOnlyList<IChatResult>>(new List<IChatResult> { new ResultHelper(_excepted) });
}
public Task<IReadOnlyList<ITextResult>> GetCompletionsAsync(string text, AIRequestSettings? requestSettings = null, CancellationToken cancellationToken = default)

View file

@ -24,17 +24,17 @@ namespace BotSharp.Plugin.SemanticKernel.Tests
{
public class SemanticKernelChatCompletionProviderTests
{
private readonly Mock<IKernel> _kernelMock;
private readonly Mock<Microsoft.SemanticKernel.AI.ChatCompletion.IChatCompletion> _chatCompletionMock;
private readonly Mock<IServiceProvider> _servicesMock;
private readonly Mock<ITokenStatistics> _tokenStatisticsMock;
private readonly SemanticKernelChatCompletionProvider _provider;
public SemanticKernelChatCompletionProviderTests()
{
_kernelMock = new Mock<IKernel>();
_chatCompletionMock = new Mock<Microsoft.SemanticKernel.AI.ChatCompletion.IChatCompletion>();
_servicesMock = new Mock<IServiceProvider>();
_tokenStatisticsMock = new Mock<ITokenStatistics>();
_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<ChatHistory>();
var chatCompletionMock = new Mock<Microsoft.SemanticKernel.AI.ChatCompletion.IChatCompletion>();
chatCompletionMock.Setup(x => x.CreateNewChat(It.IsAny<string>())).Returns(chatHistoryMock.Object);
chatCompletionMock.Setup(x => x.GetChatCompletionsAsync(chatHistoryMock.Object, It.IsAny<AIRequestSettings>(), It.IsAny<CancellationToken>()))
_chatCompletionMock.Setup(x => x.CreateNewChat(It.IsAny<string>())).Returns(chatHistoryMock.Object);
_chatCompletionMock.Setup(x => x.GetChatCompletionsAsync(chatHistoryMock.Object, It.IsAny<AIRequestSettings>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new List<IChatResult>
{
new ResultHelper("How can I help you?")
});
_kernelMock.Setup(x => x.GetService<Microsoft.SemanticKernel.AI.ChatCompletion.IChatCompletion>(null)).Returns(chatCompletionMock.Object);
// Act
var result = _provider.GetChatCompletions(agent, conversations);

View file

@ -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<IConfiguration>(config);
var plugin = new SemanticKernelPlugin();
services.AddScoped(x =>
{
return new KernelBuilder()
.WithAzureOpenAIChatCompletionService("test", "test", "test")
.Build();
});
services.AddScoped<ITokenStatistics>(x=> Mock.Of<ITokenStatistics>());
services.AddScoped(x => Mock.Of<Microsoft.SemanticKernel.AI.TextCompletion.ITextCompletion>());
services.AddScoped(x => Mock.Of<Microsoft.SemanticKernel.AI.ChatCompletion.IChatCompletion>());
services.AddScoped(x => Mock.Of<Microsoft.SemanticKernel.Memory.IMemoryStore>());
services.AddScoped(x => Mock.Of<Microsoft.SemanticKernel.AI.Embeddings.ITextEmbeddingGeneration>());
services.AddScoped(x => Mock.Of<ITokenStatistics>());
plugin.RegisterDI(services, config);
var provider = services.BuildServiceProvider();
var provider = services.BuildServiceProvider().CreateScope().ServiceProvider;
Assert.NotNull(provider.GetService<ITextCompletion>());
Assert.NotNull(provider.GetService<IChatCompletion>());
Assert.NotNull(provider.GetService<IVectorDb>());
Assert.NotNull(provider.GetService<ITextEmbedding>());
}
}
}

View file

@ -37,10 +37,7 @@ namespace BotSharp.Plugin.SemanticKernel.Tests
var text = "Hello";
var expected = "Hello, world!";
var _kernel = new KernelBuilder()
.WithAIService<ITextCompletion>("", 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");