Merge pull request #576 from iceljc/features/refine-knowledge-base
Features/refine knowledge base
This commit is contained in:
commit
2d1cf3e77e
|
|
@ -2,6 +2,8 @@ namespace BotSharp.Abstraction.Files.Converters;
|
|||
|
||||
public interface IPdf2ImageConverter
|
||||
{
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Convert pdf pages to images, and return a list of image file paths
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
using BotSharp.Abstraction.Repositories.Enums;
|
||||
|
||||
namespace BotSharp.Abstraction.Files;
|
||||
|
||||
public class FileCoreSettings
|
||||
{
|
||||
public string Storage { get; set; } = FileStorageEnum.LocalFileStorage;
|
||||
public string Pdf2TextConverter { get; set; }
|
||||
public string Pdf2ImageConverter { get; set; }
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
using BotSharp.Abstraction.Repositories.Enums;
|
||||
|
||||
namespace BotSharp.Abstraction.Files;
|
||||
|
||||
public class FileStorageSettings
|
||||
{
|
||||
public string Default { get; set; } = FileStorageEnum.LocalFileStorage;
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
namespace BotSharp.Abstraction.Knowledges.Enums;
|
||||
|
||||
public static class KnowledgeCollectionName
|
||||
{
|
||||
public static string BotSharp = nameof(BotSharp);
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
namespace BotSharp.Abstraction.Knowledges.Enums;
|
||||
|
||||
public static class KnowledgePayloadName
|
||||
{
|
||||
public static string Text = "text";
|
||||
public static string Question = "question";
|
||||
public static string Answer = "answer";
|
||||
public static string Request = "request";
|
||||
public static string Response = "response";
|
||||
}
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
using BotSharp.Abstraction.Knowledges.Models;
|
||||
|
||||
namespace BotSharp.Abstraction.Knowledges;
|
||||
|
||||
public interface IKnowledgeHook
|
||||
|
|
|
|||
|
|
@ -4,11 +4,8 @@ namespace BotSharp.Abstraction.Knowledges;
|
|||
|
||||
public interface IKnowledgeService
|
||||
{
|
||||
Task<List<KnowledgeChunk>> CollectChunkedKnowledge();
|
||||
Task EmbedKnowledge(List<KnowledgeChunk> chunks);
|
||||
|
||||
Task Feed(KnowledgeFeedModel knowledge);
|
||||
Task EmbedKnowledge(KnowledgeCreationModel knowledge);
|
||||
Task<string> GetKnowledges(KnowledgeRetrievalModel retrievalModel);
|
||||
Task<List<RetrievedResult>> GetAnswer(KnowledgeRetrievalModel retrievalModel);
|
||||
Task<IEnumerable<KnowledgeRetrievalResult>> SearchKnowledge(KnowledgeRetrievalModel model);
|
||||
Task FeedKnowledge(KnowledgeCreationModel model);
|
||||
Task<StringIdPagedItems<KnowledgeCollectionData>> GetKnowledgeCollectionData(string collectionName, KnowledgeFilter filter);
|
||||
Task<bool> DeleteKnowledgeCollectionData(string collectionName, string id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,8 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace BotSharp.Abstraction.Knowledges
|
||||
{
|
||||
public interface IPdf2TextConverter
|
||||
{
|
||||
public string Name { get; }
|
||||
Task<string> ConvertPdfToText(string filePath, int? startPageNum, int? endPageNum);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
namespace BotSharp.Abstraction.Knowledges.Models;
|
||||
|
||||
public class KnowledgeCollectionData
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Question { get; set; }
|
||||
public string Answer { get; set; }
|
||||
public float[]? Vector { get; set; }
|
||||
}
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
using BotSharp.Abstraction.Knowledges.Enums;
|
||||
|
||||
namespace BotSharp.Abstraction.Knowledges.Models;
|
||||
|
||||
public class KnowledgeCreationModel
|
||||
{
|
||||
public string Collection { get; set; } = KnowledgeCollectionName.BotSharp;
|
||||
public string Content { get; set; } = string.Empty;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
namespace BotSharp.Abstraction.Knowledges.Models;
|
||||
|
||||
public class KnowledgeFeedModel
|
||||
{
|
||||
public string AgentId { get; set; } = string.Empty;
|
||||
public string Content { get; set; } = string.Empty;
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
namespace BotSharp.Abstraction.Knowledges.Models;
|
||||
|
||||
public class KnowledgeFilter : StringIdPagination
|
||||
{
|
||||
[JsonPropertyName("with_vector")]
|
||||
public bool WithVector { get; set; }
|
||||
}
|
||||
|
|
@ -1,7 +1,13 @@
|
|||
using BotSharp.Abstraction.Knowledges.Enums;
|
||||
|
||||
namespace BotSharp.Abstraction.Knowledges.Models;
|
||||
|
||||
public class KnowledgeRetrievalModel
|
||||
{
|
||||
public string AgentId { get; set; } = string.Empty;
|
||||
public string Question { get; set; } = string.Empty;
|
||||
public string Collection { get; set; } = KnowledgeCollectionName.BotSharp;
|
||||
public string Text { get; set; } = string.Empty;
|
||||
public IEnumerable<string>? Fields { get; set; } = new List<string> { KnowledgePayloadName.Text, KnowledgePayloadName.Answer };
|
||||
public int? Limit { get; set; } = 5;
|
||||
public float? Confidence { get; set; } = 0.5f;
|
||||
public bool WithVector { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
namespace BotSharp.Abstraction.Knowledges.Models;
|
||||
|
||||
public class KnowledgeSearchResult
|
||||
{
|
||||
public IDictionary<string, string> Data { get; set; } = new Dictionary<string, string>();
|
||||
public double Score { get; set; }
|
||||
public float[]? Vector { get; set; }
|
||||
}
|
||||
|
||||
public class KnowledgeRetrievalResult : KnowledgeSearchResult
|
||||
{
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BotSharp.Abstraction.Knowledges.Models;
|
||||
|
||||
public class RetrievedResult
|
||||
{
|
||||
public int Paragraph { get; set; }
|
||||
|
||||
[JsonPropertyName("cite_source")]
|
||||
public string CiteSource { get; set; } = "related text";
|
||||
|
||||
[JsonPropertyName("reasoning")]
|
||||
public string Reasoning { get; set; } = "";
|
||||
}
|
||||
|
|
@ -3,7 +3,11 @@ namespace BotSharp.Abstraction.Knowledges.Settings;
|
|||
public class KnowledgeBaseSettings
|
||||
{
|
||||
public string VectorDb { get; set; }
|
||||
public string TextEmbedding { get; set; }
|
||||
public string TextCompletion { get; set; }
|
||||
public string Pdf2TextConverter { get; set; }
|
||||
public KnowledgeModelSetting TextEmbedding { get; set; }
|
||||
}
|
||||
|
||||
public class KnowledgeModelSetting
|
||||
{
|
||||
public string Provider { get; set; }
|
||||
public string Model { get; set; }
|
||||
}
|
||||
|
|
@ -17,4 +17,5 @@ global using BotSharp.Abstraction.Templating;
|
|||
global using BotSharp.Abstraction.Translation.Attributes;
|
||||
global using BotSharp.Abstraction.Messaging.Enums;
|
||||
global using BotSharp.Abstraction.Files.Models;
|
||||
global using BotSharp.Abstraction.Files.Enums;
|
||||
global using BotSharp.Abstraction.Files.Enums;
|
||||
global using BotSharp.Abstraction.Knowledges.Models;
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
namespace BotSharp.Abstraction.Utilities;
|
||||
|
||||
public class StringIdPagination : Pagination
|
||||
{
|
||||
[JsonPropertyName("start_id")]
|
||||
public string? StartId { get; set; }
|
||||
}
|
||||
|
||||
public class StringIdPagedItems<T> : PagedItems<T>
|
||||
{
|
||||
public new ulong Count { get; set; }
|
||||
|
||||
[JsonPropertyName("next_id")]
|
||||
public string? NextId { get; set; }
|
||||
}
|
||||
|
|
@ -2,8 +2,12 @@ namespace BotSharp.Abstraction.VectorStorage;
|
|||
|
||||
public interface IVectorDb
|
||||
{
|
||||
Task<List<string>> GetCollections();
|
||||
string Name { get; }
|
||||
|
||||
Task<IEnumerable<string>> GetCollections();
|
||||
Task<StringIdPagedItems<KnowledgeCollectionData>> GetCollectionData(string collectionName, KnowledgeFilter filter);
|
||||
Task CreateCollection(string collectionName, int dim);
|
||||
Task<bool> Upsert(string collectionName, string id, float[] vector, string text, Dictionary<string, string>? payload = null);
|
||||
Task<List<string>> Search(string collectionName, float[] vector, string returnFieldName, int limit = 5, float confidence = 0.5f);
|
||||
Task<IEnumerable<KnowledgeSearchResult>> Search(string collectionName, float[] vector, IEnumerable<string> fields, int limit = 5, float confidence = 0.5f, bool withVector = false);
|
||||
Task<bool> DeleteCollectionData(string collectionName, string id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,22 +4,22 @@ using Microsoft.Extensions.Configuration;
|
|||
|
||||
namespace BotSharp.Core.Files;
|
||||
|
||||
public class FilePlugin : IBotSharpPlugin
|
||||
public class FileCorePlugin : IBotSharpPlugin
|
||||
{
|
||||
public string Id => "6a8473c0-04eb-4346-be32-24755ce5973d";
|
||||
|
||||
public string Name => "File";
|
||||
public string Name => "File Core";
|
||||
|
||||
public string Description => "Provides file storage and analysis.";
|
||||
|
||||
|
||||
public void RegisterDI(IServiceCollection services, IConfiguration config)
|
||||
{
|
||||
var myFileStorageSettings = new FileStorageSettings();
|
||||
config.Bind("FileStorage", myFileStorageSettings);
|
||||
services.AddSingleton(myFileStorageSettings);
|
||||
var fileCoreSettings = new FileCoreSettings();
|
||||
config.Bind("FileCore", fileCoreSettings);
|
||||
services.AddSingleton(fileCoreSettings);
|
||||
|
||||
if (myFileStorageSettings.Default == FileStorageEnum.LocalFileStorage)
|
||||
if (fileCoreSettings.Storage == FileStorageEnum.LocalFileStorage)
|
||||
{
|
||||
services.AddScoped<IFileStorageService, LocalFileStorageService>();
|
||||
}
|
||||
|
|
@ -97,7 +97,8 @@ public partial class FileInstructService
|
|||
private async Task<IEnumerable<string>> ConvertPdfToImages(IEnumerable<string> files)
|
||||
{
|
||||
var images = new List<string>();
|
||||
var converter = _services.GetServices<IPdf2ImageConverter>().FirstOrDefault();
|
||||
var settings = _services.GetRequiredService<FileCoreSettings>();
|
||||
var converter = _services.GetServices<IPdf2ImageConverter>().FirstOrDefault(x => x.Name == settings.Pdf2ImageConverter);
|
||||
if (converter == null || files.IsNullOrEmpty())
|
||||
{
|
||||
return images;
|
||||
|
|
|
|||
|
|
@ -275,8 +275,9 @@ public partial class LocalFileStorageService
|
|||
|
||||
private IPdf2ImageConverter? GetPdf2ImageConverter()
|
||||
{
|
||||
var converters = _services.GetServices<IPdf2ImageConverter>();
|
||||
return converters.FirstOrDefault();
|
||||
var settings = _services.GetRequiredService<FileCoreSettings>();
|
||||
var converter = _services.GetServices<IPdf2ImageConverter>().FirstOrDefault(x => x.Name == settings.Pdf2ImageConverter);
|
||||
return converter;
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<MessageFileModel>> GetScreenshots(string file, string parentDir, string messageId, string source)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using BotSharp.Abstraction.Knowledges.Enums;
|
||||
using BotSharp.Abstraction.Knowledges.Models;
|
||||
using BotSharp.Abstraction.Knowledges.Settings;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using BotSharp.OpenAPI.ViewModels.Knowledges;
|
||||
|
||||
namespace BotSharp.OpenAPI.Controllers;
|
||||
|
||||
|
|
@ -17,77 +18,65 @@ public class KnowledgeBaseController : ControllerBase
|
|||
_services = services;
|
||||
}
|
||||
|
||||
[HttpGet("/knowledge/{agentId}")]
|
||||
public async Task<List<RetrievedResult>> RetrieveKnowledge([FromRoute] string agentId, [FromQuery(Name = "q")] string question)
|
||||
[HttpPost("/knowledge/search")]
|
||||
public async Task<IEnumerable<KnowledgeRetrivalViewModel>> SearchKnowledge([FromBody] SearchKnowledgeModel model)
|
||||
{
|
||||
return await _knowledgeService.GetAnswer(new KnowledgeRetrievalModel
|
||||
var searchModel = new KnowledgeRetrievalModel
|
||||
{
|
||||
AgentId = agentId,
|
||||
Question = question
|
||||
});
|
||||
Collection = model.Collection,
|
||||
Text = model.Text,
|
||||
Fields = model.Fields,
|
||||
Limit = model.Limit ?? 5,
|
||||
Confidence = model.Confidence ?? 0.5f,
|
||||
WithVector = model.WithVector
|
||||
};
|
||||
|
||||
var results = await _knowledgeService.SearchKnowledge(searchModel);
|
||||
return results.Select(x => KnowledgeRetrivalViewModel.From(x)).ToList();
|
||||
}
|
||||
|
||||
[HttpPost("/knowledge-base/upload")]
|
||||
public async Task<IActionResult> UploadKnowledge(IFormFile file, [FromQuery] int? startPageNum, [FromQuery] int? endPageNum)
|
||||
[HttpPost("/knowledge/{collection}/data")]
|
||||
public async Task<StringIdPagedItems<KnowledgeCollectionDataViewModel>> GetKnowledgeCollectionData([FromRoute] string collection, [FromBody] KnowledgeFilter filter)
|
||||
{
|
||||
var setttings = _services.GetRequiredService<KnowledgeBaseSettings>();
|
||||
var textConverter = _services.GetServices<IPdf2TextConverter>()
|
||||
.First(x => x.GetType().FullName.EndsWith(setttings.Pdf2TextConverter));
|
||||
var data = await _knowledgeService.GetKnowledgeCollectionData(collection, filter);
|
||||
var items = data.Items?.Select(x => KnowledgeCollectionDataViewModel.From(x))?
|
||||
.ToList() ?? new List<KnowledgeCollectionDataViewModel>();
|
||||
|
||||
return new StringIdPagedItems<KnowledgeCollectionDataViewModel>
|
||||
{
|
||||
Count = data.Count,
|
||||
NextId = data.NextId,
|
||||
Items = items
|
||||
};
|
||||
}
|
||||
|
||||
[HttpDelete("/knowledge/{collection}/data/{id}")]
|
||||
public async Task<bool> DeleteKnowledgeCollectionData([FromRoute] string collection, [FromRoute] string id)
|
||||
{
|
||||
return await _knowledgeService.DeleteKnowledgeCollectionData(collection, id);
|
||||
}
|
||||
|
||||
[HttpPost("/knowledge/upload")]
|
||||
public async Task<IActionResult> UploadKnowledge(IFormFile file, [FromQuery] string? collection, [FromQuery] int? startPageNum, [FromQuery] int? endPageNum)
|
||||
{
|
||||
var setttings = _services.GetRequiredService<FileCoreSettings>();
|
||||
var textConverter = _services.GetServices<IPdf2TextConverter>().FirstOrDefault(x => x.Name == setttings.Pdf2TextConverter);
|
||||
|
||||
var filePath = Path.GetTempFileName();
|
||||
using (var stream = System.IO.File.Create(filePath))
|
||||
using (var stream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
await file.CopyToAsync(stream);
|
||||
await stream.FlushAsync();
|
||||
}
|
||||
|
||||
var content = await textConverter.ConvertPdfToText(filePath, startPageNum, endPageNum);
|
||||
|
||||
// Process uploaded files
|
||||
// Don't rely on or trust the FileName property without validation.
|
||||
|
||||
// Add FeedWithMetaData
|
||||
await _knowledgeService.EmbedKnowledge(new KnowledgeCreationModel
|
||||
await _knowledgeService.FeedKnowledge(new KnowledgeCreationModel
|
||||
{
|
||||
Collection = collection ?? KnowledgeCollectionName.BotSharp,
|
||||
Content = content
|
||||
});
|
||||
|
||||
System.IO.File.Delete(filePath);
|
||||
return Ok(new { count = 1, file.Length });
|
||||
}
|
||||
|
||||
[HttpPost("/knowledge/{agentId}")]
|
||||
public async Task<IActionResult> FeedKnowledge([FromRoute] string agentId, List<IFormFile> files, [FromQuery] int? startPageNum, [FromQuery] int? endPageNum, [FromQuery] bool? paddleModel)
|
||||
{
|
||||
var setttings = _services.GetRequiredService<KnowledgeBaseSettings>();
|
||||
var textConverter = _services.GetServices<IPdf2TextConverter>().First(x => x.GetType().FullName.EndsWith(setttings.Pdf2TextConverter));
|
||||
long size = files.Sum(f => f.Length);
|
||||
|
||||
foreach (var formFile in files)
|
||||
{
|
||||
var filePath = Path.GetTempFileName();
|
||||
|
||||
|
||||
using (var stream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
await formFile.CopyToAsync(stream);
|
||||
await stream.FlushAsync(); // Ensure all data is written to the file
|
||||
}
|
||||
|
||||
var content = await textConverter.ConvertPdfToText(filePath, startPageNum, endPageNum);
|
||||
|
||||
// Process uploaded files
|
||||
// Don't rely on or trust the FileName property without validation.
|
||||
|
||||
// Add FeedWithMetaData
|
||||
await _knowledgeService.Feed(new KnowledgeFeedModel
|
||||
{
|
||||
AgentId = agentId,
|
||||
Content = content
|
||||
});
|
||||
|
||||
// Delete the temp file after processing to clean up
|
||||
System.IO.File.Delete(filePath);
|
||||
}
|
||||
|
||||
return Ok(new { count = files.Count, size });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
using BotSharp.Abstraction.Knowledges.Models;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BotSharp.OpenAPI.ViewModels.Knowledges;
|
||||
|
||||
public class KnowledgeCollectionDataViewModel
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; set; }
|
||||
|
||||
[JsonPropertyName("question")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string Question { get; set; }
|
||||
|
||||
[JsonPropertyName("answer")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string Answer { get; set; }
|
||||
|
||||
[JsonPropertyName("vector")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public float[]? Vector { get; set; }
|
||||
|
||||
public static KnowledgeCollectionDataViewModel From(KnowledgeCollectionData data)
|
||||
{
|
||||
return new KnowledgeCollectionDataViewModel
|
||||
{
|
||||
Id = data.Id,
|
||||
Question = data.Question,
|
||||
Answer = data.Answer,
|
||||
Vector = data.Vector
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
using BotSharp.Abstraction.Knowledges.Models;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BotSharp.OpenAPI.ViewModels.Knowledges;
|
||||
|
||||
public class KnowledgeRetrivalViewModel
|
||||
{
|
||||
[JsonPropertyName("data")]
|
||||
public IDictionary<string, string> Data { get; set; }
|
||||
|
||||
[JsonPropertyName("score")]
|
||||
public double Score { get; set; }
|
||||
|
||||
[JsonPropertyName("vector")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public float[]? Vector { get; set; }
|
||||
|
||||
public static KnowledgeRetrivalViewModel From(KnowledgeRetrievalResult model)
|
||||
{
|
||||
return new KnowledgeRetrivalViewModel
|
||||
{
|
||||
Data = model.Data,
|
||||
Score = model.Score,
|
||||
Vector = model.Vector
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
using BotSharp.Abstraction.Knowledges.Enums;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BotSharp.OpenAPI.ViewModels.Knowledges;
|
||||
|
||||
public class SearchKnowledgeModel
|
||||
{
|
||||
[JsonPropertyName("collection")]
|
||||
public string Collection { get; set; } = KnowledgeCollectionName.BotSharp;
|
||||
|
||||
[JsonPropertyName("text")]
|
||||
public string Text { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("fields")]
|
||||
public IEnumerable<string>? Fields { get; set; }
|
||||
|
||||
[JsonPropertyName("limit")]
|
||||
public int? Limit { get; set; } = 5;
|
||||
|
||||
[JsonPropertyName("confidence")]
|
||||
public float? Confidence { get; set; } = 0.5f;
|
||||
|
||||
[JsonPropertyName("with_vector")]
|
||||
public bool WithVector { get; set; }
|
||||
}
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
using BotSharp.Abstraction.Files.Utilities;
|
||||
using BotSharp.Abstraction.Templating;
|
||||
using System.IO;
|
||||
|
||||
namespace BotSharp.Plugin.FileHandler.Functions;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,3 @@
|
|||
using BotSharp.Abstraction.Functions;
|
||||
using BotSharp.Abstraction.Messaging.Enums;
|
||||
using BotSharp.Abstraction.Messaging.Models.RichContent.Template;
|
||||
using BotSharp.Abstraction.Messaging.Models.RichContent;
|
||||
using BotSharp.Abstraction.Messaging;
|
||||
|
||||
namespace BotSharp.Plugin.KnowledgeBase.Functions;
|
||||
|
||||
public class ConfirmKnowledgePersistenceFn : IFunctionCallback
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
using BotSharp.Abstraction.Functions;
|
||||
using BotSharp.Core.Infrastructures;
|
||||
|
||||
namespace BotSharp.Plugin.KnowledgeBase.Functions;
|
||||
|
||||
public class KnowledgeRetrievalFn : IFunctionCallback
|
||||
|
|
@ -20,20 +17,14 @@ public class KnowledgeRetrievalFn : IFunctionCallback
|
|||
{
|
||||
var args = JsonSerializer.Deserialize<ExtractedKnowledge>(message.FunctionArgs ?? "{}");
|
||||
|
||||
var embedding = _services.GetServices<ITextEmbedding>()
|
||||
.FirstOrDefault(x => x.GetType().FullName.EndsWith(_settings.TextEmbedding));
|
||||
|
||||
var vector = await embedding.GetVectorsAsync(new List<string>
|
||||
{
|
||||
args.Question
|
||||
});
|
||||
var embedding = _services.GetServices<ITextEmbedding>().FirstOrDefault(x => x.Provider == _settings.TextEmbedding.Provider);
|
||||
embedding.SetModelName(_settings.TextEmbedding.Model);
|
||||
|
||||
var vector = await embedding.GetVectorAsync(args.Question);
|
||||
var vectorDb = _services.GetRequiredService<IVectorDb>();
|
||||
var knowledges = await vectorDb.Search(KnowledgeCollectionName.BotSharp, vector, new List<string> { KnowledgePayloadName.Answer });
|
||||
|
||||
var id = Utilities.HashTextMd5(args.Question);
|
||||
var knowledges = await vectorDb.Search("lessen", vector[0], "answer");
|
||||
|
||||
if (knowledges.Count > 0)
|
||||
if (!knowledges.IsNullOrEmpty())
|
||||
{
|
||||
message.Content = string.Join("\r\n\r\n=====\r\n", knowledges);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
using BotSharp.Abstraction.Functions;
|
||||
using BotSharp.Core.Infrastructures;
|
||||
|
||||
namespace BotSharp.Plugin.KnowledgeBase.Functions;
|
||||
|
||||
public class MemorizeKnowledgeFn : IFunctionCallback
|
||||
|
|
@ -20,8 +17,8 @@ public class MemorizeKnowledgeFn : IFunctionCallback
|
|||
{
|
||||
var args = JsonSerializer.Deserialize<ExtractedKnowledge>(message.FunctionArgs ?? "{}");
|
||||
|
||||
var embedding = _services.GetServices<ITextEmbedding>()
|
||||
.First(x => x.GetType().FullName.EndsWith(_settings.TextEmbedding));
|
||||
var embedding = _services.GetServices<ITextEmbedding>().FirstOrDefault(x => x.Provider == _settings.TextEmbedding.Provider);
|
||||
embedding.SetModelName(_settings.TextEmbedding.Model);
|
||||
|
||||
var vector = await embedding.GetVectorsAsync(new List<string>
|
||||
{
|
||||
|
|
@ -29,15 +26,14 @@ public class MemorizeKnowledgeFn : IFunctionCallback
|
|||
});
|
||||
|
||||
var vectorDb = _services.GetRequiredService<IVectorDb>();
|
||||
await vectorDb.CreateCollection(KnowledgeCollectionName.BotSharp, vector[0].Length);
|
||||
|
||||
await vectorDb.CreateCollection("lessen", vector[0].Length);
|
||||
|
||||
var id = Utilities.HashTextMd5(args.Question);
|
||||
var result = await vectorDb.Upsert("lessen", id, vector[0],
|
||||
var id = Guid.NewGuid().ToString();
|
||||
var result = await vectorDb.Upsert(KnowledgeCollectionName.BotSharp, id, vector[0],
|
||||
args.Question,
|
||||
new Dictionary<string, string>
|
||||
{
|
||||
{ "answer", args.Answer }
|
||||
{ KnowledgePayloadName.Answer, args.Answer }
|
||||
});
|
||||
|
||||
message.Content = result ? "Saved to my brain" : "I forgot it";
|
||||
|
|
|
|||
|
|
@ -1,9 +1,3 @@
|
|||
using BotSharp.Abstraction.Agents.Enums;
|
||||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Repositories;
|
||||
using BotSharp.Plugin.KnowledgeBase.Enum;
|
||||
|
||||
namespace BotSharp.Plugin.KnowledgeBase.Hooks;
|
||||
|
||||
public class KnowledgeBaseAgentHook : AgentHookBase, IAgentHook
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
using BotSharp.Plugin.KnowledgeBase.Enum;
|
||||
|
||||
namespace BotSharp.Plugin.KnowledgeBase.Hooks;
|
||||
|
||||
public class KnowledgeBaseUtilityHook : IAgentUtilityHook
|
||||
|
|
|
|||
|
|
@ -9,6 +9,6 @@ public class MemVecDbPlugin : IBotSharpPlugin
|
|||
public string Description => "Store text embedding, search similar text from memory.";
|
||||
public void RegisterDI(IServiceCollection services, IConfiguration config)
|
||||
{
|
||||
services.AddSingleton<IVectorDb, MemVectorDatabase>();
|
||||
services.AddSingleton<IVectorDb, MemoryVectorDb>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
using BotSharp.Plugin.KnowledgeBase.Utilities;
|
||||
using Tensorflow.NumPy;
|
||||
|
||||
namespace BotSharp.Plugin.KnowledgeBase.MemVecDb;
|
||||
|
||||
public class MemoryVectorDb : IVectorDb
|
||||
{
|
||||
private readonly Dictionary<string, int> _collections = new Dictionary<string, int>();
|
||||
private readonly Dictionary<string, List<VecRecord>> _vectors = new Dictionary<string, List<VecRecord>>();
|
||||
|
||||
|
||||
public string Name => "MemoryVector";
|
||||
|
||||
public async Task CreateCollection(string collectionName, int dim)
|
||||
{
|
||||
_collections[collectionName] = dim;
|
||||
_vectors[collectionName] = new List<VecRecord>();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<string>> GetCollections()
|
||||
{
|
||||
return _collections.Select(x => x.Key).ToList();
|
||||
}
|
||||
|
||||
public Task<StringIdPagedItems<KnowledgeCollectionData>> GetCollectionData(string collectionName, KnowledgeFilter filter)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<KnowledgeSearchResult>> Search(string collectionName, float[] vector,
|
||||
IEnumerable<string> fields, int limit = 5, float confidence = 0.5f, bool withVector = false)
|
||||
{
|
||||
if (!_vectors.ContainsKey(collectionName))
|
||||
{
|
||||
return new List<KnowledgeSearchResult>();
|
||||
}
|
||||
|
||||
var similarities = VectorUtility.CalCosineSimilarity(vector, _vectors[collectionName]);
|
||||
// var similarities = VectorUtility.CalEuclideanDistance(vector, _vectors[collectionName]);
|
||||
|
||||
var results = np.argsort(similarities).ToArray<int>()
|
||||
.Reverse()
|
||||
.Take(limit)
|
||||
.Select(i => new KnowledgeSearchResult
|
||||
{
|
||||
Data = new Dictionary<string, string> { { "text", _vectors[collectionName][i].Text } },
|
||||
Score = similarities[i],
|
||||
Vector = withVector ? _vectors[collectionName][i].Vector : null,
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return await Task.FromResult(results);
|
||||
}
|
||||
|
||||
public async Task<bool> Upsert(string collectionName, string id, float[] vector, string text, Dictionary<string, string>? payload = null)
|
||||
{
|
||||
_vectors[collectionName].Add(new VecRecord
|
||||
{
|
||||
Id = id,
|
||||
Vector = vector,
|
||||
Text = text
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public Task<bool> DeleteCollectionData(string collectionName, string id)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
namespace BotSharp.Plugin.KnowledgeBase.Services;
|
||||
|
||||
public partial class KnowledgeService
|
||||
{
|
||||
public async Task FeedKnowledge(KnowledgeCreationModel knowledge)
|
||||
{
|
||||
var index = 0;
|
||||
var lines = _textChopper.Chop(knowledge.Content, new ChunkOption
|
||||
{
|
||||
Size = 1024,
|
||||
Conjunction = 32,
|
||||
SplitByWord = true,
|
||||
});
|
||||
|
||||
var db = GetVectorDb();
|
||||
var textEmbedding = GetTextEmbedding();
|
||||
|
||||
await db.CreateCollection(knowledge.Collection, textEmbedding.Dimension);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var vec = await textEmbedding.GetVectorAsync(line);
|
||||
var id = Guid.NewGuid().ToString();
|
||||
await db.Upsert(knowledge.Collection, id, vec, line);
|
||||
index++;
|
||||
Console.WriteLine($"Saved vector {index}/{lines.Count}: {line}\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
namespace BotSharp.Plugin.KnowledgeBase.Services;
|
||||
|
||||
public partial class KnowledgeService
|
||||
{
|
||||
public async Task<bool> DeleteKnowledgeCollectionData(string collectionName, string id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var db = GetVectorDb();
|
||||
return await db.DeleteCollectionData(collectionName, id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning($"Error when deleting knowledge collection data ({collectionName}-{id}). {ex.Message}\r\n{ex.InnerException}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
namespace BotSharp.Plugin.KnowledgeBase.Services;
|
||||
|
||||
public partial class KnowledgeService
|
||||
{
|
||||
public async Task<StringIdPagedItems<KnowledgeCollectionData>> GetKnowledgeCollectionData(string collectionName, KnowledgeFilter filter)
|
||||
{
|
||||
try
|
||||
{
|
||||
var db = GetVectorDb();
|
||||
return await db.GetCollectionData(collectionName, filter);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning($"Error when getting knowledge collection data ({collectionName}). {ex.Message}\r\n{ex.InnerException}");
|
||||
return new StringIdPagedItems<KnowledgeCollectionData>();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<KnowledgeRetrievalResult>> SearchKnowledge(KnowledgeRetrievalModel model)
|
||||
{
|
||||
var textEmbedding = GetTextEmbedding();
|
||||
var vector = await textEmbedding.GetVectorAsync(model.Text);
|
||||
|
||||
// Vector search
|
||||
var db = GetVectorDb();
|
||||
var collection = !string.IsNullOrWhiteSpace(model.Collection) ? model.Collection : KnowledgeCollectionName.BotSharp;
|
||||
var fields = !model.Fields.IsNullOrEmpty() ? model.Fields : new List<string> { KnowledgePayloadName.Text, KnowledgePayloadName.Answer };
|
||||
var found = await db.Search(collection, vector, fields, limit: model.Limit ?? 5, confidence: model.Confidence ?? 0.5f, withVector: model.WithVector);
|
||||
|
||||
var results = found.Select(x => new KnowledgeRetrievalResult
|
||||
{
|
||||
Data = x.Data,
|
||||
Score = x.Score,
|
||||
Vector = x.Vector
|
||||
}).ToList();
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
|
@ -5,114 +5,33 @@ public partial class KnowledgeService : IKnowledgeService
|
|||
private readonly IServiceProvider _services;
|
||||
private readonly KnowledgeBaseSettings _settings;
|
||||
private readonly ITextChopper _textChopper;
|
||||
private readonly ILogger<KnowledgeService> _logger;
|
||||
|
||||
public KnowledgeService(IServiceProvider services,
|
||||
public KnowledgeService(
|
||||
IServiceProvider services,
|
||||
KnowledgeBaseSettings settings,
|
||||
ITextChopper textChopper)
|
||||
ITextChopper textChopper,
|
||||
ILogger<KnowledgeService> logger)
|
||||
{
|
||||
_services = services;
|
||||
_settings = settings;
|
||||
_textChopper = textChopper;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task EmbedKnowledge(KnowledgeCreationModel knowledge)
|
||||
private IVectorDb GetVectorDb()
|
||||
{
|
||||
var idStart = 0;
|
||||
var lines = _textChopper.Chop(knowledge.Content, new ChunkOption
|
||||
{
|
||||
Size = 1024,
|
||||
Conjunction = 32,
|
||||
SplitByWord = true,
|
||||
});
|
||||
|
||||
var db = GetVectorDb();
|
||||
var textEmbedding = GetTextEmbedding();
|
||||
|
||||
await db.CreateCollection("shared", textEmbedding.Dimension);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var vec = await textEmbedding.GetVectorAsync(line);
|
||||
await db.Upsert("shared", idStart.ToString(), vec, line);
|
||||
idStart++;
|
||||
Console.WriteLine($"Saved vector {idStart}/{lines.Count}: {line}\n");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Feed(KnowledgeFeedModel knowledge)
|
||||
{
|
||||
var idStart = 0;
|
||||
var lines = _textChopper.Chop(knowledge.Content, new ChunkOption
|
||||
{
|
||||
Size = 1024,
|
||||
Conjunction = 32,
|
||||
SplitByWord = true,
|
||||
});
|
||||
|
||||
var db = GetVectorDb();
|
||||
var textEmbedding = GetTextEmbedding();
|
||||
|
||||
await db.CreateCollection(knowledge.AgentId, textEmbedding.Dimension);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var vec = await textEmbedding.GetVectorAsync(line);
|
||||
await db.Upsert(knowledge.AgentId, idStart.ToString(), vec, line);
|
||||
idStart++;
|
||||
Console.WriteLine($"Saved vector {idStart}/{lines.Count}: {line}\n");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> GetKnowledges(KnowledgeRetrievalModel retrievalModel)
|
||||
{
|
||||
var textEmbedding = GetTextEmbedding();
|
||||
var vector = await textEmbedding.GetVectorAsync(retrievalModel.Question);
|
||||
|
||||
// Vector search
|
||||
var db = GetVectorDb();
|
||||
var result = await db.Search("shared", vector, "answer", limit: 10);
|
||||
|
||||
// Restore
|
||||
return string.Join("\n\n", result.Select((x, i) => $"### Paragraph {i + 1} ###\n{x.Trim()}"));
|
||||
}
|
||||
|
||||
public async Task<List<RetrievedResult>> GetAnswer(KnowledgeRetrievalModel retrievalModel)
|
||||
{
|
||||
// Restore
|
||||
var prompt = await GetKnowledges(retrievalModel);
|
||||
|
||||
var sb = new StringBuilder(prompt);
|
||||
sb.AppendLine();
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("------");
|
||||
sb.AppendLine("Answer question based on the given information above. Keep your answers concise. Please response with paragraph number, cite sources and reasoning in JSON format, if multiple paragraphs are found, put them in a JSON array. make sure the paragraph number is real. If you don't know the answer just output empty.");
|
||||
sb.AppendLine("[" + JsonSerializer.Serialize(new RetrievedResult()) + "]");
|
||||
sb.AppendLine("------");
|
||||
sb.AppendLine($"QUESTION: \"{retrievalModel.Question}\"");
|
||||
sb.AppendLine("Which paragraphs are relevant in order to answer the above question?");
|
||||
sb.AppendLine("ANSWER: ");
|
||||
prompt = sb.ToString().Trim();
|
||||
|
||||
var completion = await GetTextCompletion().GetCompletion(prompt, Guid.Empty.ToString(), Guid.Empty.ToString());
|
||||
return JsonSerializer.Deserialize<List<RetrievedResult>>(completion);
|
||||
}
|
||||
|
||||
public IVectorDb GetVectorDb()
|
||||
{
|
||||
var db = _services.GetServices<IVectorDb>()
|
||||
.FirstOrDefault(x => x.GetType().FullName.EndsWith(_settings.VectorDb));
|
||||
var db = _services.GetServices<IVectorDb>().FirstOrDefault(x => x.Name == _settings.VectorDb);
|
||||
return db;
|
||||
}
|
||||
|
||||
public ITextEmbedding GetTextEmbedding()
|
||||
private ITextEmbedding GetTextEmbedding()
|
||||
{
|
||||
var embedding = _services.GetServices<ITextEmbedding>()
|
||||
.FirstOrDefault(x => x.GetType().FullName.EndsWith(_settings.TextEmbedding));
|
||||
var embedding = _services.GetServices<ITextEmbedding>().FirstOrDefault(x => x.Provider == _settings.TextEmbedding.Provider);
|
||||
if (embedding != null)
|
||||
{
|
||||
embedding.SetModelName(_settings.TextEmbedding.Model);
|
||||
}
|
||||
return embedding;
|
||||
}
|
||||
|
||||
public ITextCompletion GetTextCompletion()
|
||||
{
|
||||
var textCompletion = _services.GetServices<ITextCompletion>()
|
||||
.FirstOrDefault(x => x.GetType().FullName.EndsWith(_settings.TextCompletion));
|
||||
return textCompletion;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
namespace BotSharp.Plugin.KnowledgeBase.Services;
|
||||
|
||||
public partial class KnowledgeService
|
||||
{
|
||||
public async Task<List<KnowledgeChunk>> CollectChunkedKnowledge()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task EmbedKnowledge(List<KnowledgeChunk> chunks)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,8 @@ namespace BotSharp.Plugin.KnowledgeBase.Services;
|
|||
|
||||
public class PigPdf2TextConverter : IPdf2TextConverter
|
||||
{
|
||||
public string Name => "Pig";
|
||||
|
||||
public Task<string> ConvertPdfToText(string filePath, int? startPageNum, int? endPageNum)
|
||||
{
|
||||
// since PdfDocument.Open is not async, we dont need to make this method async
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ public class TextChopperService : ITextChopper
|
|||
var chunks = new List<string>();
|
||||
|
||||
var words = content.Split(' ')
|
||||
.Where(x => !string.IsNullOrEmpty(x))
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||||
.ToList();
|
||||
|
||||
var chunk = "";
|
||||
|
|
|
|||
|
|
@ -17,7 +17,18 @@ global using BotSharp.Abstraction.Conversations.Models;
|
|||
global using BotSharp.Abstraction.Agents.Settings;
|
||||
global using BotSharp.Abstraction.Conversations.Settings;
|
||||
global using BotSharp.Abstraction.Knowledges.Settings;
|
||||
global using BotSharp.Abstraction.Knowledges.Enums;
|
||||
global using BotSharp.Abstraction.VectorStorage;
|
||||
global using BotSharp.Abstraction.Knowledges.Models;
|
||||
global using BotSharp.Abstraction.MLTasks;
|
||||
global using BotSharp.Plugin.KnowledgeBase.Services;
|
||||
global using BotSharp.Abstraction.Functions;
|
||||
global using BotSharp.Abstraction.Messaging.Enums;
|
||||
global using BotSharp.Abstraction.Messaging.Models.RichContent.Template;
|
||||
global using BotSharp.Abstraction.Messaging.Models.RichContent;
|
||||
global using BotSharp.Abstraction.Messaging;
|
||||
global using BotSharp.Abstraction.Agents.Enums;
|
||||
global using BotSharp.Abstraction.Agents.Models;
|
||||
global using BotSharp.Abstraction.Functions.Models;
|
||||
global using BotSharp.Abstraction.Repositories;
|
||||
global using BotSharp.Plugin.KnowledgeBase.Services;
|
||||
global using BotSharp.Plugin.KnowledgeBase.Enum;
|
||||
|
|
@ -1,55 +1,12 @@
|
|||
using BotSharp.Plugin.KnowledgeBase.MemVecDb;
|
||||
using Tensorflow.NumPy;
|
||||
using static Tensorflow.Binding;
|
||||
|
||||
namespace BotSharp.Plugin.KnowledgeBase.MemVecDb;
|
||||
namespace BotSharp.Plugin.KnowledgeBase.Utilities;
|
||||
|
||||
public class MemVectorDatabase : IVectorDb
|
||||
public static class VectorUtility
|
||||
{
|
||||
private readonly Dictionary<string, int> _collections = new Dictionary<string, int>();
|
||||
private readonly Dictionary<string, List<VecRecord>> _vectors = new Dictionary<string, List<VecRecord>>();
|
||||
public async Task CreateCollection(string collectionName, int dim)
|
||||
{
|
||||
_collections[collectionName] = dim;
|
||||
_vectors[collectionName] = new List<VecRecord>();
|
||||
}
|
||||
|
||||
public async Task<List<string>> GetCollections()
|
||||
{
|
||||
return _collections.Select(x => x.Key).ToList();
|
||||
}
|
||||
|
||||
public async Task<List<string>> Search(string collectionName, float[] vector, string returnFieldName, int limit = 5, float confidence = 0.5f)
|
||||
{
|
||||
if (!_vectors.ContainsKey(collectionName))
|
||||
{
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
var similarities = CalCosineSimilarity(vector, _vectors[collectionName]);
|
||||
// var similarities2 = CalEuclideanDistance(vector, _vectors[collectionName]);
|
||||
|
||||
var texts = np.argsort(similarities).ToArray<int>()
|
||||
.Reverse()
|
||||
.Take(limit)
|
||||
.Select(i => _vectors[collectionName][i].Text)
|
||||
.ToList();
|
||||
|
||||
return texts;
|
||||
}
|
||||
|
||||
public async Task<bool> Upsert(string collectionName, string id, float[] vector, string text, Dictionary<string, string>? payload = null)
|
||||
{
|
||||
_vectors[collectionName].Add(new VecRecord
|
||||
{
|
||||
Id = id,
|
||||
Vector = vector,
|
||||
Text = text
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private float[] CalEuclideanDistance(float[] vec, List<VecRecord> records)
|
||||
public static float[] CalEuclideanDistance(float[] vec, List<VecRecord> records)
|
||||
{
|
||||
var a = np.zeros((records.Count, vec.Length), np.float32);
|
||||
var b = np.zeros((records.Count, vec.Length), np.float32);
|
||||
|
|
@ -64,7 +21,7 @@ public class MemVectorDatabase : IVectorDb
|
|||
return c.ToArray<float>();
|
||||
}
|
||||
|
||||
public NDArray CalCosineSimilarity(float[] vec, List<VecRecord> records)
|
||||
public static NDArray CalCosineSimilarity(float[] vec, List<VecRecord> records)
|
||||
{
|
||||
var recordsArray = np.zeros((records.Count, records[0].Vector.Length), dtype: np.float32);
|
||||
|
||||
|
|
@ -85,7 +42,7 @@ public class MemVectorDatabase : IVectorDb
|
|||
return simiMatix;
|
||||
}
|
||||
|
||||
public (int, float)[] CalCosineSimilarityTopK(float[] vec, List<VecRecord> records, int topK = 10, float filterProb = 0.75f)
|
||||
public static (int, float)[] CalCosineSimilarityTopK(float[] vec, List<VecRecord> records, int topK = 10, float filterProb = 0.75f)
|
||||
{
|
||||
var simiMatix = CalCosineSimilarity(vec, records);
|
||||
|
||||
|
|
@ -108,7 +65,7 @@ public class MemVectorDatabase : IVectorDb
|
|||
return resIndex.ToArray();
|
||||
}
|
||||
|
||||
public (NDArray, NDArray) SafeNormalize(NDArray x, double eps = 2.223E-15)
|
||||
private static (NDArray, NDArray) SafeNormalize(NDArray x, double eps = 2.223E-15)
|
||||
{
|
||||
var squaredX = np.sum(np.multiply(x, x), axis: 1);
|
||||
var normX = np.sqrt(squaredX);
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
using BotSharp.Abstraction.Knowledges.Models;
|
||||
using BotSharp.Abstraction.Utilities;
|
||||
using BotSharp.Abstraction.VectorStorage;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
|
@ -7,17 +9,25 @@ namespace BotSharp.Plugin.MetaAI.Providers;
|
|||
|
||||
public class FaissDb : IVectorDb
|
||||
{
|
||||
public string Name => "Faiss";
|
||||
|
||||
public Task CreateCollection(string collectionName, int dim)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<List<string>> GetCollections()
|
||||
public Task<StringIdPagedItems<KnowledgeCollectionData>> GetCollectionData(string collectionName, KnowledgeFilter filter)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<List<string>> Search(string collectionName, float[] vector, string returnFieldName, int limit = 10, float confidence = 0.5f)
|
||||
public Task<IEnumerable<string>> GetCollections()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IEnumerable<KnowledgeSearchResult>> Search(string collectionName, float[] vector,
|
||||
IEnumerable<string> fields, int limit = 10, float confidence = 0.5f, bool withVector = false)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
|
@ -26,4 +36,9 @@ public class FaissDb : IVectorDb
|
|||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<bool> DeleteCollectionData(string collectionName, string id)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,15 +19,18 @@ using BotSharp.Plugin.PaddleSharp.Settings;
|
|||
namespace BotSharp.Plugin.PaddleSharp.Providers;
|
||||
|
||||
public class Pdf2TextConverter : IPdf2TextConverter
|
||||
{
|
||||
{
|
||||
private Dictionary<int, string> _mappings = new Dictionary<int, string>();
|
||||
private FullOcrModel _model;
|
||||
private PaddleSharpSettings _paddleSharpSettings;
|
||||
|
||||
public Pdf2TextConverter(PaddleSharpSettings paddleSharpSettings)
|
||||
{
|
||||
_paddleSharpSettings = paddleSharpSettings;
|
||||
}
|
||||
|
||||
public string Name => "Paddle";
|
||||
|
||||
public async Task<string> ConvertPdfToText(string filePath, int? startPageNum, int? endPageNum)
|
||||
{
|
||||
await ConvertPdfToLocalImagesAsync(filePath, startPageNum, endPageNum);
|
||||
|
|
|
|||
|
|
@ -1,13 +1,6 @@
|
|||
using BotSharp.Abstraction.Agents;
|
||||
using BotSharp.Abstraction.VectorStorage;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using BotSharp.Abstraction.Utilities;
|
||||
using Qdrant.Client;
|
||||
using Qdrant.Client.Grpc;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BotSharp.Plugin.Qdrant;
|
||||
|
||||
|
|
@ -17,14 +10,16 @@ public class QdrantDb : IVectorDb
|
|||
private readonly QdrantSetting _setting;
|
||||
private readonly IServiceProvider _services;
|
||||
|
||||
public QdrantDb(QdrantSetting setting,
|
||||
public QdrantDb(
|
||||
QdrantSetting setting,
|
||||
IServiceProvider services)
|
||||
{
|
||||
_setting = setting;
|
||||
_services = services;
|
||||
|
||||
}
|
||||
|
||||
public string Name => "Qdrant";
|
||||
|
||||
private QdrantClient GetClient()
|
||||
{
|
||||
if (_client == null)
|
||||
|
|
@ -39,20 +34,50 @@ public class QdrantDb : IVectorDb
|
|||
return _client;
|
||||
}
|
||||
|
||||
public async Task<List<string>> GetCollections()
|
||||
public async Task<IEnumerable<string>> GetCollections()
|
||||
{
|
||||
// List all the collections
|
||||
var collections = await GetClient().ListCollectionsAsync();
|
||||
return collections.ToList();
|
||||
}
|
||||
|
||||
public async Task<StringIdPagedItems<KnowledgeCollectionData>> GetCollectionData(string collectionName, KnowledgeFilter filter)
|
||||
{
|
||||
var client = GetClient();
|
||||
var exist = await DoesCollectionExist(client, collectionName);
|
||||
if (!exist)
|
||||
{
|
||||
return new StringIdPagedItems<KnowledgeCollectionData>();
|
||||
}
|
||||
|
||||
var totalPointCount = await client.CountAsync(collectionName);
|
||||
var response = await client.ScrollAsync(collectionName, limit: (uint)filter.Size,
|
||||
offset: !string.IsNullOrWhiteSpace(filter.StartId) ? new PointId { Uuid = filter.StartId } : 0,
|
||||
vectorsSelector: filter.WithVector);
|
||||
var points = response?.Result?.Select(x => new KnowledgeCollectionData
|
||||
{
|
||||
Id = x.Id?.Uuid ?? string.Empty,
|
||||
Question = x.Payload.ContainsKey(KnowledgePayloadName.Text) ? x.Payload[KnowledgePayloadName.Text].StringValue : string.Empty,
|
||||
Answer = x.Payload.ContainsKey(KnowledgePayloadName.Answer) ? x.Payload[KnowledgePayloadName.Answer].StringValue : string.Empty,
|
||||
Vector = filter.WithVector ? x.Vectors?.Vector?.Data?.ToArray() : null
|
||||
})?.ToList() ?? new List<KnowledgeCollectionData>();
|
||||
|
||||
return new StringIdPagedItems<KnowledgeCollectionData>
|
||||
{
|
||||
Count = totalPointCount,
|
||||
NextId = response?.NextPageOffset?.Uuid,
|
||||
Items = points
|
||||
};
|
||||
}
|
||||
|
||||
public async Task CreateCollection(string collectionName, int dim)
|
||||
{
|
||||
var collections = await GetCollections();
|
||||
if (!collections.Contains(collectionName))
|
||||
var client = GetClient();
|
||||
var exist = await DoesCollectionExist(client, collectionName);
|
||||
if (!exist)
|
||||
{
|
||||
// Create a new collection
|
||||
await GetClient().CreateCollectionAsync(collectionName, new VectorParams()
|
||||
await client.CreateCollectionAsync(collectionName, new VectorParams()
|
||||
{
|
||||
Size = (ulong)dim,
|
||||
Distance = Distance.Cosine
|
||||
|
|
@ -60,7 +85,7 @@ public class QdrantDb : IVectorDb
|
|||
}
|
||||
|
||||
// Get collection info
|
||||
var collectionInfo = await _client.GetCollectionInfoAsync(collectionName);
|
||||
var collectionInfo = await client.GetCollectionInfoAsync(collectionName);
|
||||
if (collectionInfo == null)
|
||||
{
|
||||
throw new Exception($"Create {collectionName} failed.");
|
||||
|
|
@ -77,10 +102,9 @@ public class QdrantDb : IVectorDb
|
|||
Uuid = id
|
||||
},
|
||||
Vectors = vector,
|
||||
|
||||
Payload =
|
||||
Payload =
|
||||
{
|
||||
{ "text", text }
|
||||
{ KnowledgePayloadName.Text, text }
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -93,7 +117,6 @@ public class QdrantDb : IVectorDb
|
|||
}
|
||||
|
||||
var client = GetClient();
|
||||
|
||||
var result = await client.UpsertAsync(collectionName, points: new List<PointStruct>
|
||||
{
|
||||
point
|
||||
|
|
@ -102,13 +125,54 @@ public class QdrantDb : IVectorDb
|
|||
return result.Status == UpdateStatus.Completed;
|
||||
}
|
||||
|
||||
public async Task<List<string>> Search(string collectionName, float[] vector, string returnFieldName, int limit = 5, float confidence = 0.5f)
|
||||
public async Task<IEnumerable<KnowledgeSearchResult>> Search(string collectionName, float[] vector,
|
||||
IEnumerable<string> fields, int limit = 5, float confidence = 0.5f, bool withVector = false)
|
||||
{
|
||||
var client = GetClient();
|
||||
var points = await client.SearchAsync(collectionName, vector,
|
||||
limit: (ulong)limit,
|
||||
scoreThreshold: confidence);
|
||||
var points = await client.SearchAsync(collectionName, vector, limit: (ulong)limit, scoreThreshold: confidence);
|
||||
|
||||
return points.Select(x => x.Payload[returnFieldName].StringValue).ToList();
|
||||
var results = new List<KnowledgeSearchResult>();
|
||||
foreach (var point in points)
|
||||
{
|
||||
var data = new Dictionary<string, string>();
|
||||
foreach (var field in fields)
|
||||
{
|
||||
if (point.Payload.ContainsKey(field))
|
||||
{
|
||||
data[field] = point.Payload[field].StringValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
data[field] = "";
|
||||
}
|
||||
}
|
||||
|
||||
results.Add(new KnowledgeSearchResult
|
||||
{
|
||||
Data = data,
|
||||
Score = point.Score,
|
||||
Vector = withVector ? point.Vectors?.Vector?.Data?.ToArray() : null
|
||||
});
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteCollectionData(string collectionName, string id)
|
||||
{
|
||||
if (!Guid.TryParse(id, out var guid))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var client = GetClient();
|
||||
var result = await client.DeleteAsync(collectionName, guid);
|
||||
return result.Status == UpdateStatus.Completed;
|
||||
}
|
||||
|
||||
|
||||
private async Task<bool> DoesCollectionExist(QdrantClient client, string collectionName)
|
||||
{
|
||||
return await client.CollectionExistsAsync(collectionName);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using BotSharp.Abstraction.Plugins;
|
||||
using BotSharp.Abstraction.Settings;
|
||||
using BotSharp.Abstraction.VectorStorage;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
|
|
|
|||
7
src/Plugins/BotSharp.Plugin.Qdrant/Using.cs
Normal file
7
src/Plugins/BotSharp.Plugin.Qdrant/Using.cs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
global using System.Linq;
|
||||
global using System.Threading.Tasks;
|
||||
global using BotSharp.Abstraction.VectorStorage;
|
||||
global using BotSharp.Abstraction.Knowledges.Enums;
|
||||
global using BotSharp.Abstraction.Knowledges.Models;
|
||||
|
|
@ -57,8 +57,8 @@ public class IntentClassifier
|
|||
return;
|
||||
}
|
||||
|
||||
var vector = _services.GetServices<ITextEmbedding>()
|
||||
.FirstOrDefault(x => x.GetType().FullName.EndsWith(_knowledgeBaseSettings.TextEmbedding));
|
||||
var vector = _services.GetServices<ITextEmbedding>().FirstOrDefault(x => x.Provider == _knowledgeBaseSettings.TextEmbedding.Provider);
|
||||
vector.SetModelName(_knowledgeBaseSettings.TextEmbedding.Model);
|
||||
|
||||
var layers = new List<ILayer>
|
||||
{
|
||||
|
|
@ -136,8 +136,8 @@ public class IntentClassifier
|
|||
public NDArray GetTextEmbedding(string text)
|
||||
{
|
||||
var knowledgeSettings = _services.GetRequiredService<KnowledgeBaseSettings>();
|
||||
var embedding = _services.GetServices<ITextEmbedding>()
|
||||
.FirstOrDefault(x => x.GetType().FullName.EndsWith(knowledgeSettings.TextEmbedding));
|
||||
var embedding = _services.GetServices<ITextEmbedding>() .FirstOrDefault(x => x.Provider == knowledgeSettings.TextEmbedding.Provider);
|
||||
embedding.SetModelName(knowledgeSettings.TextEmbedding.Model);
|
||||
|
||||
var x = np.zeros((1, embedding.Dimension), dtype: np.float32);
|
||||
x[0] = embedding.GetVectorAsync(text).GetAwaiter().GetResult();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
using BotSharp.Abstraction.Knowledges.Models;
|
||||
using BotSharp.Abstraction.Utilities;
|
||||
using BotSharp.Abstraction.VectorStorage;
|
||||
using Microsoft.SemanticKernel.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BotSharp.Plugin.SemanticKernel
|
||||
|
|
@ -19,12 +20,21 @@ namespace BotSharp.Plugin.SemanticKernel
|
|||
{
|
||||
this._memoryStore = memoryStore;
|
||||
}
|
||||
|
||||
|
||||
public string Name => "SemanticKernel";
|
||||
|
||||
public async Task CreateCollection(string collectionName, int dim)
|
||||
{
|
||||
await _memoryStore.CreateCollectionAsync(collectionName);
|
||||
}
|
||||
|
||||
public async Task<List<string>> GetCollections()
|
||||
public Task<StringIdPagedItems<KnowledgeCollectionData>> GetCollectionData(string collectionName, KnowledgeFilter filter)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<string>> GetCollections()
|
||||
{
|
||||
var result = new List<string>();
|
||||
await foreach (var collection in _memoryStore.GetCollectionsAsync())
|
||||
|
|
@ -34,18 +44,23 @@ namespace BotSharp.Plugin.SemanticKernel
|
|||
return result;
|
||||
}
|
||||
|
||||
public async Task<List<string>> Search(string collectionName, float[] vector, string returnFieldName, int limit = 5, float confidence = 0.5f)
|
||||
public async Task<IEnumerable<KnowledgeSearchResult>> Search(string collectionName, float[] vector,
|
||||
IEnumerable<string> fields, int limit = 5, float confidence = 0.5f, bool withVector = false)
|
||||
{
|
||||
var results = _memoryStore.GetNearestMatchesAsync(collectionName, vector, limit);
|
||||
|
||||
var resultTexts = new List<string>();
|
||||
await foreach (var (record, _) in results)
|
||||
var resultTexts = new List<KnowledgeSearchResult>();
|
||||
await foreach (var (record, score) in results)
|
||||
{
|
||||
resultTexts.Add(record.Metadata.Text);
|
||||
resultTexts.Add(new KnowledgeSearchResult
|
||||
{
|
||||
Data = new Dictionary<string, string> { { "text", record.Metadata.Text } },
|
||||
Score = score,
|
||||
Vector = withVector ? record.Embedding.ToArray() : null
|
||||
});
|
||||
}
|
||||
|
||||
return resultTexts;
|
||||
|
||||
}
|
||||
|
||||
public async Task<bool> Upsert(string collectionName, string id, float[] vector, string text, Dictionary<string, string>? payload)
|
||||
|
|
@ -55,5 +70,17 @@ namespace BotSharp.Plugin.SemanticKernel
|
|||
#pragma warning restore SKEXP0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteCollectionData(string collectionName, string id)
|
||||
{
|
||||
var exist = await _memoryStore.DoesCollectionExistAsync(collectionName);
|
||||
|
||||
if (exist)
|
||||
{
|
||||
await _memoryStore.RemoveAsync(collectionName, id);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using BotSharp.Abstraction.Files;
|
||||
using BotSharp.Abstraction.Files.Converters;
|
||||
using BotSharp.Abstraction.Files.Enums;
|
||||
using BotSharp.Abstraction.Files.Utilities;
|
||||
|
|
@ -252,8 +253,9 @@ public partial class TencentCosService
|
|||
|
||||
private IPdf2ImageConverter? GetPdf2ImageConverter()
|
||||
{
|
||||
var converters = _services.GetServices<IPdf2ImageConverter>();
|
||||
return converters.FirstOrDefault();
|
||||
var settings = _services.GetRequiredService<FileCoreSettings>();
|
||||
var converter = _services.GetServices<IPdf2ImageConverter>().FirstOrDefault(x => x.Name == settings.Pdf2ImageConverter);
|
||||
return converter;
|
||||
}
|
||||
|
||||
private string BuilFileUrl(string file)
|
||||
|
|
|
|||
|
|
@ -18,10 +18,10 @@ public class TencentCosPlugin : IBotSharpPlugin
|
|||
|
||||
public void RegisterDI(IServiceCollection services, IConfiguration config)
|
||||
{
|
||||
var myFileStorageSettings = new FileStorageSettings();
|
||||
config.Bind("FileStorage", myFileStorageSettings);
|
||||
var fileCoreSettings = new FileCoreSettings();
|
||||
config.Bind("FileCore", fileCoreSettings);
|
||||
|
||||
if (myFileStorageSettings.Default == FileStorageEnum.TencentCosStorage)
|
||||
if (fileCoreSettings.Storage == FileStorageEnum.TencentCosStorage)
|
||||
{
|
||||
services.AddScoped(provider =>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -230,9 +230,13 @@
|
|||
"FileRepository": "data",
|
||||
"Assemblies": [ "BotSharp.Core" ]
|
||||
},
|
||||
"FileStorage": {
|
||||
"Default": "LocalFileStorage"
|
||||
|
||||
"FileCore": {
|
||||
"Storage": "LocalFileStorage",
|
||||
"Pdf2TextConverter": "",
|
||||
"Pdf2ImageConverter": ""
|
||||
},
|
||||
|
||||
"TencentCos": {
|
||||
"AppId": "",
|
||||
"SecretId": "",
|
||||
|
|
@ -254,10 +258,11 @@
|
|||
},
|
||||
|
||||
"KnowledgeBase": {
|
||||
"VectorDb": "MemVectorDatabase",
|
||||
"TextEmbedding": "fastTextEmbeddingProvider",
|
||||
"TextCompletion": "AzureOpenAI.Providers.TextCompletionProvider",
|
||||
"Pdf2TextConverter": "PigPdf2TextConverter"
|
||||
"VectorDb": "Qdrant",
|
||||
"TextEmbedding": {
|
||||
"Provider": "openai",
|
||||
"Model": "text-embedding-3-small"
|
||||
}
|
||||
},
|
||||
|
||||
"SparkDesk": {
|
||||
|
|
|
|||
Loading…
Reference in a new issue