From e9817af1a7957f724e60b0152d13970c3175df9e Mon Sep 17 00:00:00 2001 From: Haiping Chen <101423@smsassist.com> Date: Fri, 16 Jun 2023 21:42:35 -0500 Subject: [PATCH] Knowledge base initial. --- .../Knowledges/IKnowledgeService.cs | 9 ++ .../Knowledges/Models/KnowledgeFeedModel.cs | 7 + .../MLTasks/ITextCompletion.cs | 6 + .../MLTasks/ITextEmbedding.cs | 6 + .../Plugins/IBotSharpPlugin.cs | 4 + .../Plugins/PluginLoaderSettings.cs | 6 + .../Users/IUserIdentity.cs | 9 ++ .../Agents/Services/AgentService.cs | 4 +- .../BotSharp.Core/BotSharp.Core.csproj | 3 + .../BotSharpServiceCollectionExtensions.cs | 18 +-- .../Conversations/Services/SessionService.cs | 4 +- .../Infrastructures/ContentTransfer.cs | 4 +- .../Knowledges/KnowledgeController.cs | 67 +++++++++ .../Knowledges/Services/KnowledgeService.cs | 92 +++++++++++++ .../LLamaSharp/ChatCompletionProvider.cs | 2 +- .../Plugins/LLamaSharp/LLamaSharpPlugin.cs | 9 ++ .../BotSharp.Core/Plugins/PluginLoader.cs | 53 +++++++ .../fastText/fastTextEmbeddingProvider.cs | 23 ++++ .../Plugins/fastText/fastTextPlugin.cs | 12 ++ .../Users/Services/UserIdentity.cs | 25 ++++ .../Users/Services/UserService.cs | 4 +- .../AzureOpenAiPlugin.cs | 22 +++ .../AzureOpenAiSettings.cs | 2 +- .../TextTasks/ChatCompletionProvider.cs | 130 ++++++++++++++++++ .../TextTasks/TextCompletionProvider.cs | 56 ++++++++ src/WebStarter/Program.cs | 3 +- src/WebStarter/appsettings.json | 7 +- 27 files changed, 565 insertions(+), 22 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs create mode 100644 src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/KnowledgeFeedModel.cs create mode 100644 src/Infrastructure/BotSharp.Abstraction/MLTasks/ITextCompletion.cs create mode 100644 src/Infrastructure/BotSharp.Abstraction/MLTasks/ITextEmbedding.cs create mode 100644 src/Infrastructure/BotSharp.Abstraction/Plugins/PluginLoaderSettings.cs create mode 100644 src/Infrastructure/BotSharp.Abstraction/Users/IUserIdentity.cs create mode 100644 src/Infrastructure/BotSharp.Core/Knowledges/KnowledgeController.cs create mode 100644 src/Infrastructure/BotSharp.Core/Knowledges/Services/KnowledgeService.cs create mode 100644 src/Infrastructure/BotSharp.Core/Plugins/PluginLoader.cs create mode 100644 src/Infrastructure/BotSharp.Core/Plugins/fastText/fastTextEmbeddingProvider.cs create mode 100644 src/Infrastructure/BotSharp.Core/Plugins/fastText/fastTextPlugin.cs create mode 100644 src/Infrastructure/BotSharp.Core/Users/Services/UserIdentity.cs create mode 100644 src/Plugins/BotSharp.Plugin.AzureOpenAI/AzureOpenAiPlugin.cs create mode 100644 src/Plugins/BotSharp.Plugin.AzureOpenAI/TextTasks/ChatCompletionProvider.cs create mode 100644 src/Plugins/BotSharp.Plugin.AzureOpenAI/TextTasks/TextCompletionProvider.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs b/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs new file mode 100644 index 00000000..ed70bf1c --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs @@ -0,0 +1,9 @@ +using BotSharp.Abstraction.Knowledges.Models; + +namespace BotSharp.Abstraction.Knowledges; + +public interface IKnowledgeService +{ + Task Feed(KnowledgeFeedModel knowledge); + Task GetAnswer(string question); +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/KnowledgeFeedModel.cs b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/KnowledgeFeedModel.cs new file mode 100644 index 00000000..7e3a315e --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/KnowledgeFeedModel.cs @@ -0,0 +1,7 @@ +namespace BotSharp.Abstraction.Knowledges.Models; + +public class KnowledgeFeedModel +{ + public string AgentId { get; set; } = string.Empty; + public string Content { get; set; } = string.Empty; +} diff --git a/src/Infrastructure/BotSharp.Abstraction/MLTasks/ITextCompletion.cs b/src/Infrastructure/BotSharp.Abstraction/MLTasks/ITextCompletion.cs new file mode 100644 index 00000000..f0bc8332 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/MLTasks/ITextCompletion.cs @@ -0,0 +1,6 @@ +namespace BotSharp.Abstraction.MLTasks; + +public interface ITextCompletion +{ + Task GetCompletion(string text); +} diff --git a/src/Infrastructure/BotSharp.Abstraction/MLTasks/ITextEmbedding.cs b/src/Infrastructure/BotSharp.Abstraction/MLTasks/ITextEmbedding.cs new file mode 100644 index 00000000..6c483cdf --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/MLTasks/ITextEmbedding.cs @@ -0,0 +1,6 @@ +namespace BotSharp.Abstraction.MLTasks; + +public interface ITextEmbedding +{ + float[] GetVector(string text); +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Plugins/IBotSharpPlugin.cs b/src/Infrastructure/BotSharp.Abstraction/Plugins/IBotSharpPlugin.cs index b300c140..1b3d21da 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Plugins/IBotSharpPlugin.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Plugins/IBotSharpPlugin.cs @@ -1,5 +1,9 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + namespace BotSharp.Abstraction.Plugins; public interface IBotSharpPlugin { + void RegisterDI(IServiceCollection services, IConfiguration config); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Plugins/PluginLoaderSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Plugins/PluginLoaderSettings.cs new file mode 100644 index 00000000..95b1ea28 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Plugins/PluginLoaderSettings.cs @@ -0,0 +1,6 @@ +namespace BotSharp.Abstraction.Plugins; + +public class PluginLoaderSettings +{ + public string[] Assemblies { get; set; } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/IUserIdentity.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IUserIdentity.cs new file mode 100644 index 00000000..81b0743b --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Users/IUserIdentity.cs @@ -0,0 +1,9 @@ +namespace BotSharp.Abstraction.Users; + +public interface IUserIdentity +{ + string Id { get; } + string Email { get; } + string FirstName { get; } + string LastName { get; } +} diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs index c7fdce39..bd8ad04f 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs @@ -7,9 +7,9 @@ namespace BotSharp.Core.Agents.Services; public class AgentService : IAgentService { private readonly IServiceProvider _services; - private readonly ICurrentUser _user; + private readonly IUserIdentity _user; - public AgentService(IServiceProvider services, ICurrentUser user) + public AgentService(IServiceProvider services, IUserIdentity user) { _services = services; _user = user; diff --git a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj index e2080cfc..04e328d7 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj +++ b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj @@ -66,8 +66,11 @@ + + + diff --git a/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs b/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs index b93ad355..8214df65 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs +++ b/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs @@ -1,10 +1,13 @@ using BotSharp.Abstraction.Agents; using BotSharp.Abstraction.Conversations; using BotSharp.Abstraction.Infrastructures.ContentTransmitters; +using BotSharp.Abstraction.Knowledges; using BotSharp.Abstraction.Users; using BotSharp.Core.Agents.Services; using BotSharp.Core.Conversations.Services; using BotSharp.Core.Infrastructures; +using BotSharp.Core.Knowledges.Services; +using BotSharp.Core.Plugins; using BotSharp.Core.Users.Services; using BotSharp.Plugins.LLamaSharp; using Microsoft.AspNetCore.Builder; @@ -16,11 +19,12 @@ public static class BotSharpServiceCollectionExtensions { public static IServiceCollection AddBotSharp(this IServiceCollection services, IConfiguration config) { - services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); @@ -74,14 +78,10 @@ public static class BotSharpServiceCollectionExtensions public static void RegisterPlugins(IServiceCollection services, IConfiguration config) { - var settings = new LlamaSharpSettings(); - config.Bind("LlamaSharp", settings); - services.AddSingleton(x => - { + var pluginSettings = new PluginLoaderSettings(); + config.Bind("PluginLoader", pluginSettings); - return settings; - }); - - // services.AddScoped(); + var loader = new PluginLoader(services, config, pluginSettings); + loader.Load(); } } diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/SessionService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/SessionService.cs index f950c5f0..071206d3 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/SessionService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/SessionService.cs @@ -7,9 +7,9 @@ namespace BotSharp.Core.Conversations.Services; public class SessionService : ISessionService { private readonly IServiceProvider _services; - private readonly ICurrentUser _user; + private readonly IUserIdentity _user; - public SessionService(IServiceProvider services, ICurrentUser user) + public SessionService(IServiceProvider services, IUserIdentity user) { _services = services; _user = user; diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/ContentTransfer.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/ContentTransfer.cs index bb881088..bb3ea673 100644 --- a/src/Infrastructure/BotSharp.Core/Infrastructures/ContentTransfer.cs +++ b/src/Infrastructure/BotSharp.Core/Infrastructures/ContentTransfer.cs @@ -7,9 +7,9 @@ namespace BotSharp.Core.Infrastructures; public class ContentTransfer : IContentTransfer { private readonly IServiceProvider _services; - private readonly ICurrentUser _user; + private readonly IUserIdentity _user; - public ContentTransfer(IServiceProvider services, ICurrentUser user) + public ContentTransfer(IServiceProvider services, IUserIdentity user) { _services = services; _user = user; diff --git a/src/Infrastructure/BotSharp.Core/Knowledges/KnowledgeController.cs b/src/Infrastructure/BotSharp.Core/Knowledges/KnowledgeController.cs new file mode 100644 index 00000000..ca90d767 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Knowledges/KnowledgeController.cs @@ -0,0 +1,67 @@ +using BotSharp.Abstraction.ApiAdapters; +using BotSharp.Abstraction.Knowledges; +using BotSharp.Abstraction.Knowledges.Models; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using System.IO; +using UglyToad.PdfPig.Content; +using UglyToad.PdfPig; + +namespace BotSharp.Core.Knowledges; + +[Authorize] +[ApiController] +public class KnowledgeController : ControllerBase, IApiAdapter +{ + private readonly IKnowledgeService _knowledgeService; + public KnowledgeController(IKnowledgeService knowledgeService) + { + _knowledgeService = knowledgeService; + } + + [HttpGet("/knowledge")] + public async Task GetAnswer([FromQuery(Name = "q")] string question) + { + return await _knowledgeService.GetAnswer(question); + } + + [HttpPost("/knowledge/feed/{agentId}")] + public async Task FeedKnowledge([FromRoute] string agentId, List files) + { + long size = files.Sum(f => f.Length); + + foreach (var formFile in files) + { + if (formFile.Length <= 0) + { + continue; + } + + var filePath = Path.GetTempFileName(); + + using (var stream = System.IO.File.Create(filePath)) + { + await formFile.CopyToAsync(stream); + } + + var document = PdfDocument.Open(filePath); + var content = ""; + foreach (Page page in document.GetPages()) + { + content += page.Text; + } + + // Process uploaded files + // Don't rely on or trust the FileName property without validation. + + await _knowledgeService.Feed(new KnowledgeFeedModel + { + AgentId = agentId, + Content = content + }); + } + + return Ok(new { count = files.Count, size }); + } +} diff --git a/src/Infrastructure/BotSharp.Core/Knowledges/Services/KnowledgeService.cs b/src/Infrastructure/BotSharp.Core/Knowledges/Services/KnowledgeService.cs new file mode 100644 index 00000000..db2b7378 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Knowledges/Services/KnowledgeService.cs @@ -0,0 +1,92 @@ +using BotSharp.Abstraction.Knowledges; +using BotSharp.Abstraction.Knowledges.Models; +using QdrantCSharp.Enums; +using QdrantCSharp.Models; +using QdrantCSharp; +using System.IO; +using System.Collections; +using BotSharp.Abstraction.MLTasks; + +namespace BotSharp.Core.Knowledges.Services; + +public class KnowledgeService : IKnowledgeService +{ + private readonly ITextEmbedding _textEmbedding; + private readonly ITextCompletion _textCompletion; + string collectionName = "my_collection"; + public KnowledgeService(ITextEmbedding textEmbedding, ITextCompletion textCompletion) + { + _textEmbedding = textEmbedding; + _textCompletion = textCompletion; + } + + public QdrantHttpClient GetClient() + { + var client = new QdrantHttpClient + ( + url: "", + apiKey: "" + ); + return client; + } + + public async Task Feed(KnowledgeFeedModel knowledge) + { + var client = GetClient(); + + // List all the collections + var collections = await client.GetCollections(); + if (!collections.Result.Collections.Select(x => x.Name).Contains(collectionName)) + { + // Create a new collection + await client.CreateCollection(collectionName, new VectorParams(size: 300, distance: Distance.COSINE)); + } + + // Get collection info + var collectionInfo = await client.GetCollection(collectionName); + var idStart = 0; + var lines = knowledge.Content.Split(". "); + lines = lines.Select((x, i) => $"{i+1} {x}").ToArray(); + File.WriteAllLines(collectionName + ".txt", lines); + + foreach (var line in lines) + { + idStart++; + + // Insert vectors + /*await client.Upsert(collectionName, points: new List + { + new PointStruct(id: idStart, vector: _textEmbedding.GetVector(line)) + });*/ + } + } + + public async Task GetAnswer(string question) + { + var client = GetClient(); + var vector = _textEmbedding.GetVector(question); + + // Vector search + var result = await client.Search + ( + collectionName, + vector, + limit: 10 + ); + + var prompt = ""; + var lines = File.ReadAllLines(collectionName + ".txt"); + foreach (var r in result.Result) + { + prompt += lines[r.Id - 1] + "\n"; + } + + prompt += "###\r\n"; + prompt += "Answer the user's question based on the content provided above, and your reply should be as concise and organized as possible.\r\n"; + prompt += "Q: how to turn on Hood Light? \r\nA: Press the Hood Light keypad to turn the light beneath the hood on or off.\r\n"; + prompt += $"Q: {question}\r\nA: "; + + var completion = await _textCompletion.GetCompletion(prompt); + return completion; + } +} diff --git a/src/Infrastructure/BotSharp.Core/Plugins/LLamaSharp/ChatCompletionProvider.cs b/src/Infrastructure/BotSharp.Core/Plugins/LLamaSharp/ChatCompletionProvider.cs index e95d7f13..42767c28 100644 --- a/src/Infrastructure/BotSharp.Core/Plugins/LLamaSharp/ChatCompletionProvider.cs +++ b/src/Infrastructure/BotSharp.Core/Plugins/LLamaSharp/ChatCompletionProvider.cs @@ -6,7 +6,7 @@ using System.IO; namespace BotSharp.Plugins.LLamaSharp; -public class ChatCompletionProvider : IBotSharpPlugin, IServiceZone +public class ChatCompletionProvider : IServiceZone { private readonly IChatModel _model; private readonly LlamaSharpSettings _settings; diff --git a/src/Infrastructure/BotSharp.Core/Plugins/LLamaSharp/LLamaSharpPlugin.cs b/src/Infrastructure/BotSharp.Core/Plugins/LLamaSharp/LLamaSharpPlugin.cs index 6146bc95..bd40cae2 100644 --- a/src/Infrastructure/BotSharp.Core/Plugins/LLamaSharp/LLamaSharpPlugin.cs +++ b/src/Infrastructure/BotSharp.Core/Plugins/LLamaSharp/LLamaSharpPlugin.cs @@ -1,6 +1,15 @@ +using Microsoft.Extensions.Configuration; + namespace BotSharp.Plugins.LLamaSharp; public class LLamaSharpPlugin : IBotSharpPlugin { + public void RegisterDI(IServiceCollection services, IConfiguration config) + { + var llamaSharpSettings = new LlamaSharpSettings(); + config.Bind("LlamaSharp", llamaSharpSettings); + services.AddSingleton(x => llamaSharpSettings); + // services.AddScoped(); + } } diff --git a/src/Infrastructure/BotSharp.Core/Plugins/PluginLoader.cs b/src/Infrastructure/BotSharp.Core/Plugins/PluginLoader.cs new file mode 100644 index 00000000..a6829abb --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Plugins/PluginLoader.cs @@ -0,0 +1,53 @@ +using Microsoft.Extensions.Configuration; +using System.IO; +using System.Reflection; + +namespace BotSharp.Core.Plugins; + +public class PluginLoader +{ + private readonly IServiceCollection _services; + private readonly IConfiguration _config; + private readonly PluginLoaderSettings _settings; + private static List _modules = new List(); + + public PluginLoader(IServiceCollection services, + IConfiguration config, + PluginLoaderSettings settings) + { + _services = services; + _config = config; + _settings = settings; + } + + public void Load() + { + var executingDir = Directory.GetParent(Assembly.GetEntryAssembly().Location).FullName; + + _settings.Assemblies.ToList().ForEach(assemblyName => + { + var assemblyPath = Path.Combine(executingDir, assemblyName + ".dll"); + if (File.Exists(assemblyPath)) + { + var assembly = Assembly.Load(assemblyName); + + var modules = assembly.GetTypes() + .Where(x => x.GetInterface(nameof(IBotSharpPlugin)) != null) + .Select(x => Activator.CreateInstance(x) as IBotSharpPlugin) + .ToList(); + + foreach (var module in modules) + { + module.RegisterDI(_services, _config); + Console.WriteLine($"Loaded plugin {module.GetType().Name} from {assemblyName}."); + } + + _modules.AddRange(modules); + } + else + { + Console.WriteLine($"Can't find assemble {assemblyPath}."); + } + }); + } +} diff --git a/src/Infrastructure/BotSharp.Core/Plugins/fastText/fastTextEmbeddingProvider.cs b/src/Infrastructure/BotSharp.Core/Plugins/fastText/fastTextEmbeddingProvider.cs new file mode 100644 index 00000000..9abd0360 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Plugins/fastText/fastTextEmbeddingProvider.cs @@ -0,0 +1,23 @@ +using BotSharp.Abstraction.MLTasks; +using FastText.NetWrapper; + +namespace BotSharp.Core.Plugins.fastText; + +public class fastTextEmbeddingProvider : ITextEmbedding +{ + FastTextWrapper fastText; + public fastTextEmbeddingProvider() + { + fastText = new FastTextWrapper(); + + if (!fastText.IsModelReady()) + { + fastText.LoadModel(@"D:\Service Mesh\prediction\WebStarter\tmp_data\models\crawl-300d-2M-subword.bin"); + } + } + + public float[] GetVector(string text) + { + return fastText.GetSentenceVector(text); + } +} diff --git a/src/Infrastructure/BotSharp.Core/Plugins/fastText/fastTextPlugin.cs b/src/Infrastructure/BotSharp.Core/Plugins/fastText/fastTextPlugin.cs new file mode 100644 index 00000000..850bf0bc --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Plugins/fastText/fastTextPlugin.cs @@ -0,0 +1,12 @@ +using BotSharp.Abstraction.MLTasks; +using Microsoft.Extensions.Configuration; + +namespace BotSharp.Core.Plugins.fastText; + +public class fastTextPlugin : IBotSharpPlugin +{ + public void RegisterDI(IServiceCollection services, IConfiguration config) + { + services.AddSingleton(); + } +} diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/UserIdentity.cs b/src/Infrastructure/BotSharp.Core/Users/Services/UserIdentity.cs new file mode 100644 index 00000000..436a60c8 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Users/Services/UserIdentity.cs @@ -0,0 +1,25 @@ +using BotSharp.Abstraction.Users; +using Microsoft.AspNetCore.Http; +using System.Security.Claims; + +namespace BotSharp.Core.Users.Services; + +public class UserIdentity : IUserIdentity +{ + private readonly IHttpContextAccessor _contextAccessor; + private IEnumerable _claims => _contextAccessor.HttpContext.User.Claims; + + public UserIdentity(IHttpContextAccessor contextAccessor) + { + _contextAccessor = contextAccessor; + } + + public string Id => _claims.First(x => x.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier").Value; + + + public string Email => _claims.First(x => x.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress").Value; + + public string FirstName => _claims.First(x => x.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname").Value; + + public string LastName => _claims.First(x => x.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname").Value; +} diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs index 96991199..6acdf0f5 100644 --- a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs +++ b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs @@ -13,9 +13,9 @@ namespace BotSharp.Core.Users.Services; public class UserService : IUserService { private readonly IServiceProvider _services; - private readonly ICurrentUser _user; + private readonly IUserIdentity _user; - public UserService(IServiceProvider services, ICurrentUser user) + public UserService(IServiceProvider services, IUserIdentity user) { _services = services; _user = user; diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/AzureOpenAiPlugin.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/AzureOpenAiPlugin.cs new file mode 100644 index 00000000..5053b6da --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/AzureOpenAiPlugin.cs @@ -0,0 +1,22 @@ +using BotSharp.Abstraction.Infrastructures.ContentTransfers; +using BotSharp.Abstraction.MLTasks; +using BotSharp.Abstraction.Plugins; +using BotSharp.Plugin.AzureOpenAI.TextGeneratives; +using BotSharp.Plugin.AzureOpenAI.TextTasks; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace BotSharp.Platform.AzureAi; + +public class AzureOpenAiPlugin : IBotSharpPlugin +{ + public void RegisterDI(IServiceCollection services, IConfiguration config) + { + var settings = new AzureOpenAiSettings(); + config.Bind("AzureOpenAi", settings); + services.AddSingleton(x => settings); + + services.AddSingleton(); + services.AddScoped(); + } +} \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/AzureOpenAiSettings.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/AzureOpenAiSettings.cs index 359cae7d..f94500c4 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/AzureOpenAiSettings.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/AzureOpenAiSettings.cs @@ -4,7 +4,7 @@ public class AzureOpenAiSettings { public string ApiKey { get; set; } = string.Empty; public string Endpoint { get; set; } = string.Empty; - public string DeploymentModel { get; set; } = string.Empty; + public string DeploymentName { get; set; } = string.Empty; public string InstructionFile { get; set; } = string.Empty; public string ChatSampleFile { get; set; } = string.Empty; } diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/TextTasks/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/TextTasks/ChatCompletionProvider.cs new file mode 100644 index 00000000..ffdbfe9a --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/TextTasks/ChatCompletionProvider.cs @@ -0,0 +1,130 @@ +using Azure; +using Azure.AI.OpenAI; +using BotSharp.Abstraction.Infrastructures.ContentTransfers; +using BotSharp.Abstraction.Infrastructures.ContentTransmitters; +using BotSharp.Abstraction.Models; +using BotSharp.Platform.AzureAi; +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +namespace BotSharp.Plugin.AzureOpenAI.TextGeneratives; + +public class ChatCompletionProvider : IServiceZone +{ + private readonly AzureOpenAiSettings _settings; + + public ChatCompletionProvider(AzureOpenAiSettings settings) + { + _settings = settings; + } + + public async Task GetChatCompletionsAsync(List conversations, + Func onChunkReceived) + { + var client = new OpenAIClient(new Uri(_settings.Endpoint), new AzureKeyCredential(_settings.ApiKey)); + var chatCompletionsOptions = PrepareOptions(conversations); + + var response = await client.GetChatCompletionsStreamingAsync(_settings.DeploymentName, chatCompletionsOptions); + using StreamingChatCompletions streaming = response.Value; + + string content = ""; + await foreach (var choice in streaming.GetChoicesStreaming()) + { + await foreach (var message in choice.GetMessageStreaming()) + { + if (message.Content == null) + continue; + Console.Write(message.Content); + content += message.Content; + await onChunkReceived(message.Content); + } + } + + Console.WriteLine(); + } + + public List GetChatSamples() + { + var samples = new List(); + if (!string.IsNullOrEmpty(_settings.ChatSampleFile)) + { + var lines = File.ReadAllLines(_settings.ChatSampleFile); + for (int i = 0; i < lines.Length; i++) + { + var line = lines[i]; + var role = line.Substring(0, line.IndexOf(' ') - 1); + var content = line.Substring(line.IndexOf(' ') + 1); + + samples.Add(new RoleDialogModel + { + Role = role, + Content = content + }); + } + } + return samples; + } + + public string GetInstruction() + { + if (!string.IsNullOrEmpty(_settings.InstructionFile)) + { + return File.ReadAllText(_settings.InstructionFile); + } + return string.Empty; + } + + public async Task Serving(ContentContainer content) + { + var client = new OpenAIClient(new Uri(_settings.Endpoint), new AzureKeyCredential(_settings.ApiKey)); + var chatCompletionsOptions = PrepareOptions(content.Conversations); + + var response = await client.GetChatCompletionsStreamingAsync(_settings.DeploymentName, chatCompletionsOptions); + using StreamingChatCompletions streaming = response.Value; + + string output = ""; + await foreach (var choice in streaming.GetChoicesStreaming()) + { + await foreach (var message in choice.GetMessageStreaming()) + { + if (message.Content == null) + continue; + Console.Write(message.Content); + output += message.Content; + } + } + + Console.WriteLine(); + content.Output = new RoleDialogModel + { + Role = ChatRole.Assistant.ToString(), + Content = output + }; + } + + private ChatCompletionsOptions PrepareOptions(List conversations) + { + var prompt = GetInstruction(); + var chatCompletionsOptions = new ChatCompletionsOptions() + { + Messages = + { + new ChatMessage(ChatRole.System, prompt) + } + }; + + foreach (var message in GetChatSamples()) + { + chatCompletionsOptions.Messages.Add(new ChatMessage(message.Role, message.Content)); + } + + foreach (var message in conversations) + { + chatCompletionsOptions.Messages.Add(new ChatMessage(message.Role, message.Content)); + } + + return chatCompletionsOptions; + } +} diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/TextTasks/TextCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/TextTasks/TextCompletionProvider.cs new file mode 100644 index 00000000..4cca2f24 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/TextTasks/TextCompletionProvider.cs @@ -0,0 +1,56 @@ +using Azure.AI.OpenAI; +using Azure; +using BotSharp.Abstraction.MLTasks; +using BotSharp.Platform.AzureAi; +using System; +using System.Threading.Tasks; + +namespace BotSharp.Plugin.AzureOpenAI.TextTasks; + +public class TextCompletionProvider : ITextCompletion +{ + private readonly AzureOpenAiSettings _settings; + bool _useAzureOpenAI = true; + + public TextCompletionProvider(AzureOpenAiSettings settings) + { + _settings = settings; + } + + public async Task GetCompletion(string text) + { + var client = GetOpenAIClient(); + var completionsOptions = new CompletionsOptions() + { + Prompts = + { + text + }, + Temperature = 0.5f, + MaxTokens = 128 + }; + + var response = await client.GetCompletionsAsync( + deploymentOrModelName: _settings.DeploymentName, + completionsOptions); + + // OpenAI + var completion = ""; + foreach (var t in response.Value.Choices) + { + completion += t.Text; + }; + + return completion; + } + + private OpenAIClient GetOpenAIClient() + { + OpenAIClient client = _useAzureOpenAI + ? new OpenAIClient( + new Uri(_settings.Endpoint), + new AzureKeyCredential(_settings.ApiKey)) + : new OpenAIClient("your-api-key-from-platform.openai.com"); + return client; + } +} diff --git a/src/WebStarter/Program.cs b/src/WebStarter/Program.cs index ae712573..6ee5606e 100644 --- a/src/WebStarter/Program.cs +++ b/src/WebStarter/Program.cs @@ -1,4 +1,6 @@ +using BotSharp.Abstraction.Users; using BotSharp.Core; +using BotSharp.Core.Users.Services; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using System.Text; @@ -36,7 +38,6 @@ builder.Services.AddAuthentication(options => // Add BotSharp builder.Services.AddBotSharp(builder.Configuration); -builder.Services.AddAzureOpenAiPlatform(builder.Configuration); builder.Services.AddCors(options => { diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json index d916bbbb..8211f423 100644 --- a/src/WebStarter/appsettings.json +++ b/src/WebStarter/appsettings.json @@ -42,7 +42,10 @@ "Assemblies": [ "BotSharp.Core" ] }, - "Providers": { - "ChatCompletionProvider": "BotSharp.Plugins.LLamaSharp.ChatCompletionProvider" + "PluginLoader": { + "Assemblies": [ + "BotSharp.Core", + "BotSharp.Plugin.AzureOpenAI" + ] } }