From b64a4597b1d6f242a5d198f94b263e83a0501984 Mon Sep 17 00:00:00 2001
From: hchen2020 <101423@smsassist.com>
Date: Thu, 29 Jun 2023 18:14:57 -0500
Subject: [PATCH] Enable knowledge in chatbot.
---
.../Agents/Models/Agent.cs | 5 +++
.../Settings/ConversationSetting.cs | 1 +
.../Knowledges/IKnowledgeService.cs | 1 +
.../Knowledges/Models/ChunkOption.cs | 6 ++-
.../VectorStorage/IVectorDb.cs | 2 +-
.../Agents/Services/AgentService.GetAgents.cs | 13 ++++++-
.../Services/AgentService.UpdateAgent.cs | 9 +++++
.../Services/ConversationService.cs | 17 ++++++++-
.../Knowledges/Services/KnowledgeService.cs | 22 ++++++-----
.../Knowledges/Services/TextChopperService.cs | 31 +++++++++++++++
.../Plugins/LLamaSharp/LlamaSharpSettings.cs | 2 -
.../Plugins/MemVecDb/MemVectorDatabase.cs | 38 +++++++++++++++----
.../Providers/ChatCompletionProvider.cs | 18 +++++----
.../Settings/AzureOpenAiSettings.cs | 2 -
.../BotSharp.Plugin.Qdrant/QdrantDb.cs | 8 ++--
src/WebStarter/appsettings.json | 4 --
16 files changed, 135 insertions(+), 44 deletions(-)
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 3a23b97b..ed480529 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": ""