Merge pull request #82 from hchen2020/master

Enable knowledge in chatbot.
This commit is contained in:
Haiping 2023-06-29 18:32:18 -05:00 committed by GitHub
commit 92dc6b6e4c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 135 additions and 44 deletions

View file

@ -18,6 +18,11 @@ public class Agent
/// </summary>
public string Samples { get; set; }
/// <summary>
/// Domain knowledges
/// </summary>
public string Knowledges { get; set;}
/// <summary>
/// Owner user id
/// </summary>

View file

@ -3,4 +3,5 @@ namespace BotSharp.Abstraction.Conversations.Settings;
public class ConversationSetting
{
public string ChatCompletion { get; set; }
public bool EnableKnowledgeBase { get; set; }
}

View file

@ -5,5 +5,6 @@ namespace BotSharp.Abstraction.Knowledges;
public interface IKnowledgeService
{
Task Feed(KnowledgeFeedModel knowledge);
Task<string> GetKnowledges(KnowledgeRetrievalModel retrievalModel);
Task<string> GetAnswer(KnowledgeRetrievalModel retrievalModel);
}

View file

@ -3,12 +3,14 @@ namespace BotSharp.Abstraction.Knowledges.Models;
public class ChunkOption
{
/// <summary>
/// Chunk size
/// Max chunk character size
/// </summary>
public int Size { get; set; }
/// <summary>
/// Overlap length in between two chunks
/// Overlap word count in between two chunks
/// </summary>
public int Conjunction { get; set; }
public bool SplitByWord { get; set; }
}

View file

@ -5,5 +5,5 @@ public interface IVectorDb
Task<List<string>> GetCollections();
Task CreateCollection(string collectionName, int dim);
Task Upsert(string collectionName, int id, float[] vector, string text);
Task<List<string>> Search(string collectionName, float[] vector, int limit = 10);
Task<List<string>> Search(string collectionName, float[] vector, int limit = 5);
}

View file

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

View file

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

View file

@ -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<string> SendMessage(string agentId, string conversationId, List<RoleDialogModel> wholeDialogs)
{
var agent = await _services.GetRequiredService<IAgentService>().GetAgent(agentId);
var chat = GetChatCompletion();
var response = await chat.GetChatCompletionsAsync(agent, wholeDialogs);
// Get relevant domain knowledge
if (_settings.EnableKnowledgeBase)
{
var knowledge = _services.GetRequiredService<IKnowledgeService>();
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;
}

View file

@ -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<string> GetAnswer(KnowledgeRetrievalModel retrievalModel)
public async Task<string> 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<string> 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);

View file

@ -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<string> 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<string> ChopByWord(string content, ChunkOption option)
{
var chunks = new List<string>();
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<string> ChopByChar(string content, ChunkOption option)
{
var chunks = new List<string>();
var currentPos = 0;

View file

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

View file

@ -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<List<string>> Search(string collectionName, float[] vector, int limit = 10)
public Task<List<string>> 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<int>()
.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<VecRecord> 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<float>();
}
private float[] CalCosineSimilarity(float[] vec, List<VecRecord> 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;
}
}

View file

@ -93,14 +93,18 @@ public class ChatCompletionProvider : IChatCompletion
private ChatCompletionsOptions PrepareOptions(Agent agent, List<RoleDialogModel> 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));

View file

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

View file

@ -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<PointStruct>
/*await _client.Upsert(collectionName, points: new List<PointStruct>
{
new PointStruct(id: id, vector: vector)
});
});*/
// Store chunks in local file system
var agentService = _services.GetRequiredService<IAgentService>();
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<List<string>> Search(string collectionName, float[] vector, int limit = 10)
public async Task<List<string>> Search(string collectionName, float[] vector, int limit = 5)
{
var result = await _client.Search(collectionName, vector, limit);

View file

@ -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": ""