diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs index 256c2ed9..7330e31b 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs @@ -18,6 +18,11 @@ public class Agent /// public string Samples { get; set; } + /// + /// Domain knowledges + /// + public string Knowledges { get; set;} + /// /// Owner user id /// diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Settings/ConversationSetting.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Settings/ConversationSetting.cs index 8b095147..4f11ed8f 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Settings/ConversationSetting.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Settings/ConversationSetting.cs @@ -3,4 +3,5 @@ namespace BotSharp.Abstraction.Conversations.Settings; public class ConversationSetting { public string ChatCompletion { get; set; } + public bool EnableKnowledgeBase { get; set; } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs b/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs index b707ad6d..65a85745 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs @@ -5,5 +5,6 @@ namespace BotSharp.Abstraction.Knowledges; public interface IKnowledgeService { Task Feed(KnowledgeFeedModel knowledge); + Task GetKnowledges(KnowledgeRetrievalModel retrievalModel); Task GetAnswer(KnowledgeRetrievalModel retrievalModel); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/ChunkOption.cs b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/ChunkOption.cs index 8d6a59eb..936a41fb 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/ChunkOption.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Models/ChunkOption.cs @@ -3,12 +3,14 @@ namespace BotSharp.Abstraction.Knowledges.Models; public class ChunkOption { /// - /// Chunk size + /// Max chunk character size /// public int Size { get; set; } /// - /// Overlap length in between two chunks + /// Overlap word count in between two chunks /// public int Conjunction { get; set; } + + public bool SplitByWord { get; set; } } diff --git a/src/Infrastructure/BotSharp.Abstraction/VectorStorage/IVectorDb.cs b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/IVectorDb.cs index dd8e193f..defe7647 100644 --- a/src/Infrastructure/BotSharp.Abstraction/VectorStorage/IVectorDb.cs +++ b/src/Infrastructure/BotSharp.Abstraction/VectorStorage/IVectorDb.cs @@ -5,5 +5,5 @@ public interface IVectorDb Task> GetCollections(); Task CreateCollection(string collectionName, int dim); Task Upsert(string collectionName, int id, float[] vector, string text); - Task> Search(string collectionName, float[] vector, int limit = 10); + Task> Search(string collectionName, float[] vector, int limit = 5); } diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs index 9793b343..e50426d7 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs @@ -24,8 +24,17 @@ public partial class AgentService var profile = query.FirstOrDefault(); var dir = GetAgentDataDir(id); - profile.Instruction = File.ReadAllText(Path.Combine(dir, "instruction.txt")); - profile.Samples = File.ReadAllText(Path.Combine(dir, "samples.txt")); + var instructionFile = Path.Combine(dir, "instruction.txt"); + if (File.Exists(instructionFile)) + { + profile.Instruction = File.ReadAllText(instructionFile); + } + + var samplesFile = Path.Combine(dir, "samples.txt"); + if (File.Exists(samplesFile)) + { + profile.Samples = File.ReadAllText(Path.Combine(dir, "samples.txt")); + } return profile; } diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs index 9dd50308..4f10598c 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs @@ -1,4 +1,5 @@ using BotSharp.Abstraction.Agents.Models; +using System.IO; namespace BotSharp.Core.Agents.Services; @@ -16,5 +17,13 @@ public partial class AgentService record.Description = agent.Description; record.UpdatedDateTime = DateTime.UtcNow; }); + + // Save instruction to file + var dir = GetAgentDataDir(agent.Id); + var instructionFile = Path.Combine(dir, "instruction.txt"); + File.WriteAllText(instructionFile, agent.Instruction); + + var samplesFile = Path.Combine(dir, "samples.txt"); + File.WriteAllText(samplesFile, agent.Samples); } } diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs index 4363c678..f0a994e9 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs @@ -1,5 +1,6 @@ using BotSharp.Abstraction.Conversations.Models; using BotSharp.Abstraction.Conversations.Settings; +using BotSharp.Abstraction.Knowledges.Models; using BotSharp.Abstraction.MLTasks; namespace BotSharp.Core.Conversations.Services; @@ -76,8 +77,20 @@ public class ConversationService : IConversationService public async Task SendMessage(string agentId, string conversationId, List wholeDialogs) { var agent = await _services.GetRequiredService().GetAgent(agentId); - var chat = GetChatCompletion(); - var response = await chat.GetChatCompletionsAsync(agent, wholeDialogs); + + // Get relevant domain knowledge + if (_settings.EnableKnowledgeBase) + { + var knowledge = _services.GetRequiredService(); + agent.Knowledges = await knowledge.GetKnowledges(new KnowledgeRetrievalModel + { + AgentId = agentId, + Question = string.Join("\n", wholeDialogs.Select(x => x.Text)) + }); + } + + var chatCompletion = GetChatCompletion(); + var response = await chatCompletion.GetChatCompletionsAsync(agent, wholeDialogs); return response; } diff --git a/src/Infrastructure/BotSharp.Core/Plugins/Knowledges/Services/KnowledgeService.cs b/src/Infrastructure/BotSharp.Core/Plugins/Knowledges/Services/KnowledgeService.cs index 1e0d8a31..bde02fee 100644 --- a/src/Infrastructure/BotSharp.Core/Plugins/Knowledges/Services/KnowledgeService.cs +++ b/src/Infrastructure/BotSharp.Core/Plugins/Knowledges/Services/KnowledgeService.cs @@ -25,7 +25,8 @@ public class KnowledgeService : IKnowledgeService var lines = _textChopper.Chop(knowledge.Content, new ChunkOption { Size = 256, - Conjunction = 32 + Conjunction = 5, + SplitByWord = true, }); var db = GetVectorDb(); @@ -40,23 +41,24 @@ public class KnowledgeService : IKnowledgeService } } - public async Task GetAnswer(KnowledgeRetrievalModel retrievalModel) + public async Task GetKnowledges(KnowledgeRetrievalModel retrievalModel) { var textEmbedding = GetTextEmbedding(); var vector = textEmbedding.GetVector(retrievalModel.Question); // Vector search - var result = await GetVectorDb().Search(retrievalModel.AgentId, vector); + var result = await GetVectorDb().Search(retrievalModel.AgentId, vector, limit: 10); // Restore - var prompt = ""; - foreach (var knowledge in result) - { - prompt += knowledge + "\n"; - } + return "### Helpful domain knowledges:\r\n" + string.Join("\n", result.Select((x, i) => $"{i + 1}: {x}")); + } - prompt += "\r\n###\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"; + public async Task GetAnswer(KnowledgeRetrievalModel retrievalModel) + { + // Restore + var prompt = await GetKnowledges(retrievalModel); + + prompt += "\r\n### Answer user's question by utilizing the helpful domain knowledges above.\r\n"; prompt += $"\r\nQuestion: {retrievalModel.Question}\r\nAnswer: "; var completion = await GetTextCompletion().GetCompletion(prompt); diff --git a/src/Infrastructure/BotSharp.Core/Plugins/Knowledges/Services/TextChopperService.cs b/src/Infrastructure/BotSharp.Core/Plugins/Knowledges/Services/TextChopperService.cs index 44554136..6033e66e 100644 --- a/src/Infrastructure/BotSharp.Core/Plugins/Knowledges/Services/TextChopperService.cs +++ b/src/Infrastructure/BotSharp.Core/Plugins/Knowledges/Services/TextChopperService.cs @@ -1,10 +1,41 @@ using BotSharp.Abstraction.Knowledges.Models; +using System.Text.RegularExpressions; namespace BotSharp.Core.Plugins.Knowledges.Services; public class TextChopperService : ITextChopper { public List Chop(string content, ChunkOption option) + { + content = Regex.Replace(content, @"\.{2,}", " "); + content = Regex.Replace(content, @"_{2,}", " "); + return option.SplitByWord ? ChopByWord(content, option) : ChopByChar(content, option); + } + + private List ChopByWord(string content, ChunkOption option) + { + var chunks = new List(); + + var words = content.Split(' ') + .Where(x => !string.IsNullOrEmpty(x)) + .ToList(); + + var chunk = ""; + for (int i = 0; i < words.Count; i++) + { + chunk += words[i] + " "; + if (chunk.Length > option.Size) + { + chunks.Add(chunk.Trim()); + chunk = ""; + i -= option.Conjunction; + } + } + + return chunks; + } + + private List ChopByChar(string content, ChunkOption option) { var chunks = new List(); var currentPos = 0; diff --git a/src/Infrastructure/BotSharp.Core/Plugins/LLamaSharp/LlamaSharpSettings.cs b/src/Infrastructure/BotSharp.Core/Plugins/LLamaSharp/LlamaSharpSettings.cs index 26f51cff..01507188 100644 --- a/src/Infrastructure/BotSharp.Core/Plugins/LLamaSharp/LlamaSharpSettings.cs +++ b/src/Infrastructure/BotSharp.Core/Plugins/LLamaSharp/LlamaSharpSettings.cs @@ -3,8 +3,6 @@ namespace BotSharp.Core.Plugins.LLamaSharp; public class LlamaSharpSettings { public string ModelPath { get; set; } = string.Empty; - public string InstructionFile { get; set; } = string.Empty; - public string ChatSampleFile { get; set; } = string.Empty; public int MaxContextLength { get; set; } = 512; public float RepeatPenalty { get; set; } = 1.0f; public bool VerbosePrompt { get; set; } diff --git a/src/Infrastructure/BotSharp.Core/Plugins/MemVecDb/MemVectorDatabase.cs b/src/Infrastructure/BotSharp.Core/Plugins/MemVecDb/MemVectorDatabase.cs index 0d8a8c75..2bd188c3 100644 --- a/src/Infrastructure/BotSharp.Core/Plugins/MemVecDb/MemVectorDatabase.cs +++ b/src/Infrastructure/BotSharp.Core/Plugins/MemVecDb/MemVectorDatabase.cs @@ -1,5 +1,8 @@ using BotSharp.Abstraction.VectorStorage; +using System.Collections; using System.IO; +using System.Numerics; +using Tensorflow; using Tensorflow.NumPy; namespace BotSharp.Core.Plugins.MemVecDb; @@ -20,13 +23,10 @@ public class MemVectorDatabase : IVectorDb return Task.FromResult(_collections.Select(x => x.Key).ToList()); } - public Task> Search(string collectionName, float[] vector, int limit = 10) + public Task> Search(string collectionName, float[] vector, int limit = 5) { - var similarities = new float[_vectors[collectionName].Count]; - for (int i = 0; i < _vectors[collectionName].Count; i++) - { - similarities[i] = CalCosineSimilarity(vector, _vectors[collectionName][i].Vector); - } + var similarities = CalCosineSimilarity(vector, _vectors[collectionName]); + // var similarities2 = CalEuclideanDistance(vector, _vectors[collectionName]); var texts = np.argsort(similarities).ToArray() .Reverse() @@ -49,8 +49,30 @@ public class MemVectorDatabase : IVectorDb return Task.CompletedTask; } - private float CalCosineSimilarity(float[] a, float[] b) + private float[] CalEuclideanDistance(float[] vec, List records) { - return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)); + var a = np.zeros((records.Count, vec.Length), np.float32); + var b = np.zeros((records.Count, vec.Length), np.float32); + for (var i = 0; i < records.Count; i++) + { + a[i] = vec; + b[i] = records[i].Vector; + } + + var c = np.sqrt(np.sum(np.square(a - b), axis: 1)); + // var c = -np.prod(np.linalg.norm(a, axis: 1) * np.linalg.norm(b, axis: 1), axis: 1); + return c.ToArray(); + } + + private float[] CalCosineSimilarity(float[] vec, List records) + { + var similarities = new float[records.Count]; + for (int i = 0; i < records.Count; i++) + { + var a = vec; + var b = records[i].Vector; + similarities[i] = np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)); + } + return similarities; } } diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs index b7fa4c7a..cb11d9a5 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs @@ -93,14 +93,18 @@ public class ChatCompletionProvider : IChatCompletion private ChatCompletionsOptions PrepareOptions(Agent agent, List conversations) { - var chatCompletionsOptions = new ChatCompletionsOptions() - { - Messages = - { - new ChatMessage(ChatRole.System, agent.Instruction) - } - }; + var chatCompletionsOptions = new ChatCompletionsOptions(); + if (!string.IsNullOrEmpty(agent.Instruction)) + { + chatCompletionsOptions.Messages.Add(new ChatMessage(ChatRole.System, agent.Instruction)); + } + + if (!string.IsNullOrEmpty(agent.Knowledges)) + { + chatCompletionsOptions.Messages.Add(new ChatMessage(ChatRole.System, agent.Knowledges)); + } + foreach (var message in GetChatSamples(agent.Samples)) { chatCompletionsOptions.Messages.Add(new ChatMessage(message.Role, message.Text)); diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Settings/AzureOpenAiSettings.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Settings/AzureOpenAiSettings.cs index 950c549e..2a3cbf6e 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Settings/AzureOpenAiSettings.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Settings/AzureOpenAiSettings.cs @@ -6,6 +6,4 @@ public class AzureOpenAiSettings public string Endpoint { get; set; } = string.Empty; public DeploymentModelSetting DeploymentModel { get; set; } = new DeploymentModelSetting(); - public string InstructionFile { get; set; } = string.Empty; - public string ChatSampleFile { get; set; } = string.Empty; } diff --git a/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs b/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs index 62feb4f2..632ad1ac 100644 --- a/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs +++ b/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs @@ -62,19 +62,19 @@ public class QdrantDb : IVectorDb public async Task Upsert(string collectionName, int id, float[] vector, string text) { // Insert vectors - await _client.Upsert(collectionName, points: new List + /*await _client.Upsert(collectionName, points: new List { new PointStruct(id: id, vector: vector) - }); + });*/ // Store chunks in local file system var agentService = _services.GetRequiredService(); var agentDataDir = agentService.GetAgentDataDir(collectionName); var knowledgePath = Path.Combine(agentDataDir, "knowledge.txt"); - File.WriteAllLines(knowledgePath, new string[] { text }); + File.AppendAllLines(knowledgePath, new[] { text }); } - public async Task> Search(string collectionName, float[] vector, int limit = 10) + public async Task> Search(string collectionName, float[] vector, int limit = 5) { var result = await _client.Search(collectionName, vector, limit); diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json index 75e0948a..6fb9390f 100644 --- a/src/WebStarter/appsettings.json +++ b/src/WebStarter/appsettings.json @@ -21,8 +21,6 @@ "LlamaSharp": { "Interactive": true, "ModelPath": "C:\\Users\\haipi\\Downloads\\wizard-vicuna-13B.ggmlv3.q8_0.bin", - "InstructionFile": "Prompts\\chat-with-bob.txt", - "ChatSampleFile": "Prompts\\chat-samples.txt", "MaxContextLength": 1024, "NumberOfGpuLayer": 10 }, @@ -30,8 +28,6 @@ "AzureOpenAi": { "ApiKey": "", "Endpoint": "", - "InstructionFile": "Prompts\\chat-with-bob.txt", - "ChatSampleFile": "Prompts\\chat-samples.txt", "DeploymentModel": { "ChatCompletionModel": "", "TextCompletionModel": ""