Merge pull request #82 from hchen2020/master
Enable knowledge in chatbot.
This commit is contained in:
commit
92dc6b6e4c
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -3,4 +3,5 @@ namespace BotSharp.Abstraction.Conversations.Settings;
|
|||
public class ConversationSetting
|
||||
{
|
||||
public string ChatCompletion { get; set; }
|
||||
public bool EnableKnowledgeBase { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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": ""
|
||||
|
|
|
|||
Loading…
Reference in a new issue