Knowledge base initial.

This commit is contained in:
Haiping Chen 2023-06-16 21:42:35 -05:00
parent 578ba15ef5
commit e9817af1a7
27 changed files with 565 additions and 22 deletions

View file

@ -0,0 +1,9 @@
using BotSharp.Abstraction.Knowledges.Models;
namespace BotSharp.Abstraction.Knowledges;
public interface IKnowledgeService
{
Task Feed(KnowledgeFeedModel knowledge);
Task<string> GetAnswer(string question);
}

View file

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

View file

@ -0,0 +1,6 @@
namespace BotSharp.Abstraction.MLTasks;
public interface ITextCompletion
{
Task<string> GetCompletion(string text);
}

View file

@ -0,0 +1,6 @@
namespace BotSharp.Abstraction.MLTasks;
public interface ITextEmbedding
{
float[] GetVector(string text);
}

View file

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

View file

@ -0,0 +1,6 @@
namespace BotSharp.Abstraction.Plugins;
public class PluginLoaderSettings
{
public string[] Assemblies { get; set; }
}

View file

@ -0,0 +1,9 @@
namespace BotSharp.Abstraction.Users;
public interface IUserIdentity
{
string Id { get; }
string Email { get; }
string FirstName { get; }
string LastName { get; }
}

View file

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

View file

@ -66,8 +66,11 @@
<PackageReference Include="EntityFrameworkCore.BootKit" Version="6.2.1" />
<PackageReference Include="LLamaSharp" Version="0.3.0" />
<PackageReference Include="LLamaSharp.Backend.Cuda11" Version="0.3.0" />
<PackageReference Include="PdfPig" Version="0.1.8" />
<PackageReference Include="Qdrant.Client" Version="0.1.0" />
<PackageReference Include="TensorFlow.Keras" Version="0.10.5" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
<PackageReference Include="FastText.NetWrapper" Version="1.3.0" />
</ItemGroup>
<ItemGroup>

View file

@ -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<ICurrentUser, CurrentUser>();
services.AddScoped<IUserIdentity, UserIdentity>();
services.AddScoped<IUserService, UserService>();
services.AddScoped<IAgentService, AgentService>();
services.AddScoped<ISessionService, SessionService>();
services.AddScoped<IConversationService, ConversationService>();
services.AddScoped<IKnowledgeService, KnowledgeService>();
services.AddScoped<IContentTransfer, ContentTransfer>();
@ -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<IServiceZone, ChatCompletionProvider>();
var loader = new PluginLoader(services, config, pluginSettings);
loader.Load();
}
}

View file

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

View file

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

View file

@ -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<string> GetAnswer([FromQuery(Name = "q")] string question)
{
return await _knowledgeService.GetAnswer(question);
}
[HttpPost("/knowledge/feed/{agentId}")]
public async Task<IActionResult> FeedKnowledge([FromRoute] string agentId, List<IFormFile> 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 });
}
}

View file

@ -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<PointStruct>
{
new PointStruct(id: idStart, vector: _textEmbedding.GetVector(line))
});*/
}
}
public async Task<string> 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;
}
}

View file

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

View file

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

View file

@ -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<IBotSharpPlugin> _modules = new List<IBotSharpPlugin>();
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}.");
}
});
}
}

View file

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

View file

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

View file

@ -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<Claim> _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;
}

View file

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

View file

@ -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<ITextCompletion, TextCompletionProvider>();
services.AddScoped<IServiceZone, ChatCompletionProvider>();
}
}

View file

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

View file

@ -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<RoleDialogModel> conversations,
Func<string, Task> 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<RoleDialogModel> GetChatSamples()
{
var samples = new List<RoleDialogModel>();
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<RoleDialogModel> 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;
}
}

View file

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

View file

@ -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 =>
{

View file

@ -42,7 +42,10 @@
"Assemblies": [ "BotSharp.Core" ]
},
"Providers": {
"ChatCompletionProvider": "BotSharp.Plugins.LLamaSharp.ChatCompletionProvider"
"PluginLoader": {
"Assemblies": [
"BotSharp.Core",
"BotSharp.Plugin.AzureOpenAI"
]
}
}