Merge branch 'master' into features/add-audio-handler

This commit is contained in:
Wenbo Cao 2024-08-16 22:44:12 -05:00
commit 1f047c3d85
39 changed files with 496 additions and 98 deletions

View file

@ -107,7 +107,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.TencentCos"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Interpreters", "Interpreters", "{C4C59872-3C8A-450D-83D5-2BE402D610D5}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Interpreters", "Interpreters", "{C4C59872-3C8A-450D-83D5-2BE402D610D5}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.PythonInterpreter", "src\Plugins\BotSharp.Plugin.PythonInterpreter\BotSharp.Plugin.PythonInterpreter.csproj", "{05E6E405-5021-406E-8A5E-0A7CEC881F6D}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.PythonInterpreter", "src\Plugins\BotSharp.Plugin.PythonInterpreter\BotSharp.Plugin.PythonInterpreter.csproj", "{05E6E405-5021-406E-8A5E-0A7CEC881F6D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Graph", "Graph", "{97A0B191-64D7-4F8A-BFE8-1BFCC5E247E1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.Graph", "src\Plugins\BotSharp.Plugin.Graph\BotSharp.Plugin.Graph.csproj", "{EBFE97DA-D0BA-48BA-8B5D-083B60348D1D}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -437,6 +441,14 @@ Global
{05E6E405-5021-406E-8A5E-0A7CEC881F6D}.Release|Any CPU.Build.0 = Release|Any CPU {05E6E405-5021-406E-8A5E-0A7CEC881F6D}.Release|Any CPU.Build.0 = Release|Any CPU
{05E6E405-5021-406E-8A5E-0A7CEC881F6D}.Release|x64.ActiveCfg = Release|Any CPU {05E6E405-5021-406E-8A5E-0A7CEC881F6D}.Release|x64.ActiveCfg = Release|Any CPU
{05E6E405-5021-406E-8A5E-0A7CEC881F6D}.Release|x64.Build.0 = Release|Any CPU {05E6E405-5021-406E-8A5E-0A7CEC881F6D}.Release|x64.Build.0 = Release|Any CPU
{EBFE97DA-D0BA-48BA-8B5D-083B60348D1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EBFE97DA-D0BA-48BA-8B5D-083B60348D1D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EBFE97DA-D0BA-48BA-8B5D-083B60348D1D}.Debug|x64.ActiveCfg = Debug|Any CPU
{EBFE97DA-D0BA-48BA-8B5D-083B60348D1D}.Debug|x64.Build.0 = Debug|Any CPU
{EBFE97DA-D0BA-48BA-8B5D-083B60348D1D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EBFE97DA-D0BA-48BA-8B5D-083B60348D1D}.Release|Any CPU.Build.0 = Release|Any CPU
{EBFE97DA-D0BA-48BA-8B5D-083B60348D1D}.Release|x64.ActiveCfg = Release|Any CPU
{EBFE97DA-D0BA-48BA-8B5D-083B60348D1D}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -489,6 +501,8 @@ Global
{BF029B0A-768B-43A1-8D91-E70B95505716} = {38B37C0D-1930-4D47-BCBF-E358EC1096B1} {BF029B0A-768B-43A1-8D91-E70B95505716} = {38B37C0D-1930-4D47-BCBF-E358EC1096B1}
{C4C59872-3C8A-450D-83D5-2BE402D610D5} = {2635EC9B-2E5F-4313-AC21-0B847F31F36C} {C4C59872-3C8A-450D-83D5-2BE402D610D5} = {2635EC9B-2E5F-4313-AC21-0B847F31F36C}
{05E6E405-5021-406E-8A5E-0A7CEC881F6D} = {C4C59872-3C8A-450D-83D5-2BE402D610D5} {05E6E405-5021-406E-8A5E-0A7CEC881F6D} = {C4C59872-3C8A-450D-83D5-2BE402D610D5}
{97A0B191-64D7-4F8A-BFE8-1BFCC5E247E1} = {2635EC9B-2E5F-4313-AC21-0B847F31F36C}
{EBFE97DA-D0BA-48BA-8B5D-083B60348D1D} = {97A0B191-64D7-4F8A-BFE8-1BFCC5E247E1}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19} SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19}

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>$(TargetFramework)</TargetFramework> <TargetFramework>$(TargetFramework)</TargetFramework>

View file

@ -0,0 +1,10 @@
using BotSharp.Abstraction.Graph.Models;
namespace BotSharp.Abstraction.Graph;
public interface IGraphDb
{
public string Name { get; }
Task<GraphSearchData> Search(string query, GraphSearchOptions options);
}

View file

@ -0,0 +1,6 @@
namespace BotSharp.Abstraction.Graph.Models;
public class GraphSearchData
{
public string Result { get; set; }
}

View file

@ -0,0 +1,6 @@
namespace BotSharp.Abstraction.Graph.Models;
public class GraphSearchOptions
{
public string Method { get; set; }
}

View file

@ -0,0 +1,6 @@
namespace BotSharp.Abstraction.Graph.Models;
public class GraphSearchResult
{
public string Result { get; set; }
}

View file

@ -1,10 +1,15 @@
using BotSharp.Abstraction.Graph.Models;
using BotSharp.Abstraction.VectorStorage.Models;
namespace BotSharp.Abstraction.Knowledges; namespace BotSharp.Abstraction.Knowledges;
public interface IKnowledgeService public interface IKnowledgeService
{ {
Task<IEnumerable<string>> GetKnowledgeCollections(); Task<IEnumerable<string>> GetVectorCollections();
Task<IEnumerable<KnowledgeSearchResult>> SearchKnowledge(string collectionName, KnowledgeSearchOptions options); Task<IEnumerable<VectorSearchResult>> SearchVectorKnowledge(string query, string collectionName, VectorSearchOptions options);
Task FeedKnowledge(string collectionName, KnowledgeCreationModel model); Task FeedVectorKnowledge(string collectionName, KnowledgeCreationModel model);
Task<StringIdPagedItems<KnowledgeSearchResult>> GetKnowledgeCollectionData(string collectionName, KnowledgeFilter filter); Task<StringIdPagedItems<VectorSearchResult>> GetVectorCollectionData(string collectionName, VectorFilter filter);
Task<bool> DeleteKnowledgeCollectionData(string collectionName, string id); Task<bool> DeleteVectorCollectionData(string collectionName, string id);
Task<GraphSearchResult> SearchGraphKnowledge(string query, GraphSearchOptions options);
Task<KnowledgeSearchResult> SearchKnowledge(string query, string collectionName, VectorSearchOptions vectorOptions, GraphSearchOptions graphOptions);
} }

View file

@ -1,7 +0,0 @@
namespace BotSharp.Abstraction.Knowledges.Models;
public class KnowledgeFilter : StringIdPagination
{
[JsonPropertyName("with_vector")]
public bool WithVector { get; set; }
}

View file

@ -1,20 +1,10 @@
using BotSharp.Abstraction.Graph.Models;
using BotSharp.Abstraction.VectorStorage.Models;
namespace BotSharp.Abstraction.Knowledges.Models; namespace BotSharp.Abstraction.Knowledges.Models;
public class KnowledgeSearchResult : KnowledgeCollectionData public class KnowledgeSearchResult
{ {
public KnowledgeSearchResult() public IEnumerable<VectorSearchResult> VectorResult { get; set; }
{ public GraphSearchResult GraphResult { get; set; }
}
public static KnowledgeSearchResult CopyFrom(KnowledgeCollectionData data)
{
return new KnowledgeSearchResult
{
Id = data.Id,
Data = data.Data,
Score = data.Score,
Vector = data.Vector
};
}
} }

View file

@ -4,8 +4,9 @@ namespace BotSharp.Abstraction.Knowledges.Settings;
public class KnowledgeBaseSettings public class KnowledgeBaseSettings
{ {
public string VectorDb { get; set; }
public string DefaultCollection { get; set; } = KnowledgeCollectionName.BotSharp; public string DefaultCollection { get; set; } = KnowledgeCollectionName.BotSharp;
public string VectorDb { get; set; }
public string GraphDb { get; set; }
public KnowledgeModelSetting TextEmbedding { get; set; } public KnowledgeModelSetting TextEmbedding { get; set; }
} }

View file

@ -1,3 +1,5 @@
using BotSharp.Abstraction.VectorStorage.Models;
namespace BotSharp.Abstraction.VectorStorage; namespace BotSharp.Abstraction.VectorStorage;
public interface IVectorDb public interface IVectorDb
@ -5,9 +7,9 @@ public interface IVectorDb
string Name { get; } string Name { get; }
Task<IEnumerable<string>> GetCollections(); Task<IEnumerable<string>> GetCollections();
Task<StringIdPagedItems<KnowledgeCollectionData>> GetCollectionData(string collectionName, KnowledgeFilter filter); Task<StringIdPagedItems<VectorCollectionData>> GetCollectionData(string collectionName, VectorFilter filter);
Task CreateCollection(string collectionName, int dim); Task CreateCollection(string collectionName, int dim);
Task<bool> Upsert(string collectionName, string id, float[] vector, string text, Dictionary<string, string>? payload = null); Task<bool> Upsert(string collectionName, string id, float[] vector, string text, Dictionary<string, string>? payload = null);
Task<IEnumerable<KnowledgeCollectionData>> Search(string collectionName, float[] vector, IEnumerable<string>? fields, int limit = 5, float confidence = 0.5f, bool withVector = false); Task<IEnumerable<VectorCollectionData>> 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); Task<bool> DeleteCollectionData(string collectionName, string id);
} }

View file

@ -1,6 +1,6 @@
namespace BotSharp.Abstraction.Knowledges.Models; namespace BotSharp.Abstraction.VectorStorage.Models;
public class KnowledgeCollectionData public class VectorCollectionData
{ {
public string Id { get; set; } public string Id { get; set; }
public Dictionary<string, string> Data { get; set; } = new(); public Dictionary<string, string> Data { get; set; } = new();

View file

@ -0,0 +1,7 @@
namespace BotSharp.Abstraction.VectorStorage.Models;
public class VectorFilter : StringIdPagination
{
[JsonPropertyName("with_vector")]
public bool WithVector { get; set; }
}

View file

@ -1,10 +1,9 @@
using BotSharp.Abstraction.Knowledges.Enums; using BotSharp.Abstraction.Knowledges.Enums;
namespace BotSharp.Abstraction.Knowledges.Models; namespace BotSharp.Abstraction.VectorStorage.Models;
public class KnowledgeSearchOptions public class VectorSearchOptions
{ {
public string Text { get; set; } = string.Empty;
public IEnumerable<string>? Fields { get; set; } = new List<string> { KnowledgePayloadName.Text, KnowledgePayloadName.Answer }; public IEnumerable<string>? Fields { get; set; } = new List<string> { KnowledgePayloadName.Text, KnowledgePayloadName.Answer };
public int? Limit { get; set; } = 5; public int? Limit { get; set; } = 5;
public float? Confidence { get; set; } = 0.5f; public float? Confidence { get; set; } = 0.5f;

View file

@ -0,0 +1,20 @@
namespace BotSharp.Abstraction.VectorStorage.Models;
public class VectorSearchResult : VectorCollectionData
{
public VectorSearchResult()
{
}
public static VectorSearchResult CopyFrom(VectorCollectionData data)
{
return new VectorSearchResult
{
Id = data.Id,
Data = data.Data,
Score = data.Score,
Vector = data.Vector
};
}
}

View file

@ -1,4 +1,6 @@
using BotSharp.Abstraction.Graph.Models;
using BotSharp.Abstraction.Knowledges.Models; using BotSharp.Abstraction.Knowledges.Models;
using BotSharp.Abstraction.VectorStorage.Models;
using BotSharp.OpenAPI.ViewModels.Knowledges; using BotSharp.OpenAPI.ViewModels.Knowledges;
namespace BotSharp.OpenAPI.Controllers; namespace BotSharp.OpenAPI.Controllers;
@ -16,36 +18,35 @@ public class KnowledgeBaseController : ControllerBase
_services = services; _services = services;
} }
[HttpGet("knowledge/collections")] [HttpGet("knowledge/vector/collections")]
public async Task<IEnumerable<string>> GetKnowledgeCollections() public async Task<IEnumerable<string>> GetVectorCollections()
{ {
return await _knowledgeService.GetKnowledgeCollections(); return await _knowledgeService.GetVectorCollections();
} }
[HttpPost("/knowledge/{collection}/search")] [HttpPost("/knowledge/vector/{collection}/search")]
public async Task<IEnumerable<KnowledgeSearchResultViewModel>> SearchKnowledge([FromRoute] string collection, [FromBody] SearchKnowledgeRequest request) public async Task<IEnumerable<VectorKnowledgeViewModel>> SearchVectorKnowledge([FromRoute] string collection, [FromBody] SearchVectorKnowledgeRequest request)
{ {
var options = new KnowledgeSearchOptions var options = new VectorSearchOptions
{ {
Text = request.Text,
Fields = request.Fields, Fields = request.Fields,
Limit = request.Limit ?? 5, Limit = request.Limit ?? 5,
Confidence = request.Confidence ?? 0.5f, Confidence = request.Confidence ?? 0.5f,
WithVector = request.WithVector WithVector = request.WithVector
}; };
var results = await _knowledgeService.SearchKnowledge(collection, options); var results = await _knowledgeService.SearchVectorKnowledge(request.Text, collection, options);
return results.Select(x => KnowledgeSearchResultViewModel.From(x)).ToList(); return results.Select(x => VectorKnowledgeViewModel.From(x)).ToList();
} }
[HttpPost("/knowledge/{collection}/data")] [HttpPost("/knowledge/vector/{collection}/data")]
public async Task<StringIdPagedItems<KnowledgeSearchResultViewModel>> GetKnowledgeCollectionData([FromRoute] string collection, [FromBody] KnowledgeFilter filter) public async Task<StringIdPagedItems<VectorKnowledgeViewModel>> GetVectorCollectionData([FromRoute] string collection, [FromBody] VectorFilter filter)
{ {
var data = await _knowledgeService.GetKnowledgeCollectionData(collection, filter); var data = await _knowledgeService.GetVectorCollectionData(collection, filter);
var items = data.Items?.Select(x => KnowledgeSearchResultViewModel.From(x))? var items = data.Items?.Select(x => VectorKnowledgeViewModel.From(x))?
.ToList() ?? new List<KnowledgeSearchResultViewModel>(); .ToList() ?? new List<VectorKnowledgeViewModel>();
return new StringIdPagedItems<KnowledgeSearchResultViewModel> return new StringIdPagedItems<VectorKnowledgeViewModel>
{ {
Count = data.Count, Count = data.Count,
NextId = data.NextId, NextId = data.NextId,
@ -53,14 +54,14 @@ public class KnowledgeBaseController : ControllerBase
}; };
} }
[HttpDelete("/knowledge/{collection}/data/{id}")] [HttpDelete("/knowledge/vector/{collection}/data/{id}")]
public async Task<bool> DeleteKnowledgeCollectionData([FromRoute] string collection, [FromRoute] string id) public async Task<bool> DeleteVectorCollectionData([FromRoute] string collection, [FromRoute] string id)
{ {
return await _knowledgeService.DeleteKnowledgeCollectionData(collection, id); return await _knowledgeService.DeleteVectorCollectionData(collection, id);
} }
[HttpPost("/knowledge/{collection}/upload")] [HttpPost("/knowledge/vector/{collection}/upload")]
public async Task<IActionResult> UploadKnowledge([FromRoute] string collection, IFormFile file, [FromForm] int? startPageNum, [FromForm] int? endPageNum) public async Task<IActionResult> UploadVectorKnowledge([FromRoute] string collection, IFormFile file, [FromForm] int? startPageNum, [FromForm] int? endPageNum)
{ {
var setttings = _services.GetRequiredService<FileCoreSettings>(); var setttings = _services.GetRequiredService<FileCoreSettings>();
var textConverter = _services.GetServices<IPdf2TextConverter>().FirstOrDefault(x => x.Name == setttings.Pdf2TextConverter); var textConverter = _services.GetServices<IPdf2TextConverter>().FirstOrDefault(x => x.Name == setttings.Pdf2TextConverter);
@ -73,7 +74,7 @@ public class KnowledgeBaseController : ControllerBase
} }
var content = await textConverter.ConvertPdfToText(filePath, startPageNum, endPageNum); var content = await textConverter.ConvertPdfToText(filePath, startPageNum, endPageNum);
await _knowledgeService.FeedKnowledge(collection, new KnowledgeCreationModel await _knowledgeService.FeedVectorKnowledge(collection, new KnowledgeCreationModel
{ {
Content = content Content = content
}); });
@ -81,4 +82,43 @@ public class KnowledgeBaseController : ControllerBase
System.IO.File.Delete(filePath); System.IO.File.Delete(filePath);
return Ok(new { count = 1, file.Length }); return Ok(new { count = 1, file.Length });
} }
[HttpPost("/knowledge/graph/search")]
public async Task<GraphKnowledgeViewModel> SearchGraphKnowledge([FromBody] SearchGraphKnowledgeRequest request)
{
var options = new GraphSearchOptions
{
Method = request.Method
};
var result = await _knowledgeService.SearchGraphKnowledge(request.Query, options);
return new GraphKnowledgeViewModel
{
Result = result.Result
};
}
[HttpPost("/knowledge/search")]
public async Task<KnowledgeSearchViewModel> SearchKnowledge([FromBody] SearchKnowledgeRequest request)
{
var vectorOptions = new VectorSearchOptions
{
Fields = request.VectorParams.Fields,
Limit = request.VectorParams.Limit ?? 5,
Confidence = request.VectorParams.Confidence ?? 0.5f,
WithVector = request.VectorParams.WithVector
};
var graphOptions = new GraphSearchOptions
{
Method = request.GraphParams.Method
};
var result = await _knowledgeService.SearchKnowledge(request.Text, request.VectorParams.Collection, vectorOptions, graphOptions);
return new KnowledgeSearchViewModel
{
VectorResult = result?.VectorResult?.Select(x => VectorKnowledgeViewModel.From(x)),
GraphResult = result?.GraphResult != null ? new GraphKnowledgeViewModel { Result = result.GraphResult.Result } : null
};
}
} }

View file

@ -0,0 +1,9 @@
using System.Text.Json.Serialization;
namespace BotSharp.OpenAPI.ViewModels.Knowledges;
public class GraphKnowledgeViewModel
{
[JsonPropertyName("result")]
public string Result { get; set; }
}

View file

@ -0,0 +1,12 @@
using System.Text.Json.Serialization;
namespace BotSharp.OpenAPI.ViewModels.Knowledges;
public class KnowledgeSearchViewModel
{
[JsonPropertyName("vector_result")]
public IEnumerable<VectorKnowledgeViewModel>? VectorResult { get; set; }
[JsonPropertyName("graph_result")]
public GraphKnowledgeViewModel? GraphResult { get; set; }
}

View file

@ -0,0 +1,12 @@
using System.Text.Json.Serialization;
namespace BotSharp.OpenAPI.ViewModels.Knowledges;
public class SearchGraphKnowledgeRequest
{
[JsonPropertyName("query")]
public string Query { get; set; } = string.Empty;
[JsonPropertyName("method")]
public string Method { get; set; } = string.Empty;
}

View file

@ -1,4 +1,3 @@
using BotSharp.Abstraction.Knowledges.Enums;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace BotSharp.OpenAPI.ViewModels.Knowledges; namespace BotSharp.OpenAPI.ViewModels.Knowledges;
@ -8,6 +7,22 @@ public class SearchKnowledgeRequest
[JsonPropertyName("text")] [JsonPropertyName("text")]
public string Text { get; set; } = string.Empty; public string Text { get; set; } = string.Empty;
#region Vector
[JsonPropertyName("vector_params")]
public VectorParam VectorParams { get; set; }
#endregion
#region Graph
[JsonPropertyName("graph_params")]
public GraphParam GraphParams { get; set; }
#endregion
}
public class VectorParam
{
[JsonPropertyName("collection")]
public string Collection { get; set; }
[JsonPropertyName("fields")] [JsonPropertyName("fields")]
public IEnumerable<string>? Fields { get; set; } public IEnumerable<string>? Fields { get; set; }
@ -20,3 +35,9 @@ public class SearchKnowledgeRequest
[JsonPropertyName("with_vector")] [JsonPropertyName("with_vector")]
public bool WithVector { get; set; } public bool WithVector { get; set; }
} }
public class GraphParam
{
[JsonPropertyName("method")]
public string Method { get; set; } = string.Empty;
}

View file

@ -0,0 +1,21 @@
using System.Text.Json.Serialization;
namespace BotSharp.OpenAPI.ViewModels.Knowledges;
public class SearchVectorKnowledgeRequest
{
[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; }
}

View file

@ -1,9 +1,9 @@
using BotSharp.Abstraction.Knowledges.Models; using BotSharp.Abstraction.VectorStorage.Models;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace BotSharp.OpenAPI.ViewModels.Knowledges; namespace BotSharp.OpenAPI.ViewModels.Knowledges;
public class KnowledgeSearchResultViewModel public class VectorKnowledgeViewModel
{ {
[JsonPropertyName("id")] [JsonPropertyName("id")]
public string Id { get; set; } public string Id { get; set; }
@ -20,9 +20,9 @@ public class KnowledgeSearchResultViewModel
public float[]? Vector { get; set; } public float[]? Vector { get; set; }
public static KnowledgeSearchResultViewModel From(KnowledgeSearchResult result) public static VectorKnowledgeViewModel From(VectorSearchResult result)
{ {
return new KnowledgeSearchResultViewModel return new VectorKnowledgeViewModel
{ {
Id = result.Id, Id = result.Id,
Data = result.Data, Data = result.Data,

View file

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(TargetFramework)</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>$(LangVersion)</LangVersion>
<VersionPrefix>$(BotSharpVersion)</VersionPrefix>
<GeneratePackageOnBuild>$(GeneratePackageOnBuild)</GeneratePackageOnBuild>
<GenerateDocumentationFile>$(GenerateDocumentationFile)</GenerateDocumentationFile>
<OutputPath>$(SolutionDir)packages</OutputPath>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Infrastructure\BotSharp.Abstraction\BotSharp.Abstraction.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,95 @@
using BotSharp.Plugin.Graph.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using System.Net.Http;
using System.Net.Mime;
using System.Text;
using System.Text.Json;
namespace BotSharp.Plugin.Graph;
public class GraphDb : IGraphDb
{
private readonly IServiceProvider _services;
private readonly IHttpContextAccessor _context;
private readonly GraphDbSettings _settings;
private readonly ILogger<GraphDb> _logger;
private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true,
WriteIndented = true,
AllowTrailingCommas = true,
};
public GraphDb(
IServiceProvider services,
IHttpContextAccessor context,
ILogger<GraphDb> logger,
GraphDbSettings settings)
{
_services = services;
_context = context;
_logger = logger;
_settings = settings;
}
public string Name => "Default";
public async Task<GraphSearchData> Search(string query, GraphSearchOptions options)
{
if (string.IsNullOrWhiteSpace(_settings.BaseUrl))
{
return new GraphSearchData();
}
var url = $"{_settings.BaseUrl}/query";
var request = new GraphQueryRequest
{
Query = query,
Method = options.Method
};
return await SendRequest(url, request);
}
private async Task<GraphSearchData> SendRequest(string url, GraphQueryRequest request)
{
var result = new GraphSearchData();
var http = _services.GetRequiredService<IHttpClientFactory>();
using (var client = http.CreateClient())
{
var uri = new Uri(url);
try
{
var data = JsonSerializer.Serialize(request, _jsonOptions);
var message = new HttpRequestMessage
{
Method = HttpMethod.Post,
RequestUri = uri,
Content = new StringContent(data, Encoding.UTF8, MediaTypeNames.Application.Json)
};
AddHeaders(client);
var rawResponse = await client.SendAsync(message);
rawResponse.EnsureSuccessStatusCode();
var responseStr = await rawResponse.Content.ReadAsStringAsync();
result = JsonSerializer.Deserialize<GraphSearchData>(responseStr, _jsonOptions);
return result;
}
catch (Exception ex)
{
_logger.LogError($"Error when fetching Lessen GLM response (Endpoint: {url}). {ex.Message}\r\n{ex.InnerException}");
return result;
}
}
}
private void AddHeaders(HttpClient client)
{
client.DefaultRequestHeaders.Add("Authorization", $"{_context.HttpContext.Request.Headers["Authorization"]}");
client.DefaultRequestHeaders.Add("Origin", $"{_context.HttpContext.Request.Headers["Origin"]}");
}
}

View file

@ -0,0 +1,6 @@
namespace BotSharp.Plugin.Graph;
public class GraphDbSettings
{
public string BaseUrl { get; set; }
}

View file

@ -0,0 +1,24 @@
using BotSharp.Abstraction.Plugins;
using BotSharp.Abstraction.Settings;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace BotSharp.Plugin.Graph;
public class GraphPlugin : IBotSharpPlugin
{
public string Id => "74497c25-5e8d-4ee9-b6a8-ce8fe4dabea9";
public string Name => "Graph";
public string Description => "Graph Database";
public string IconUrl => "https://www.microsoft.com/en-us/research/uploads/prodnew/2024/06/GraphRag2024-BlogHeroFeature-1400x788-1.png";
public void RegisterDI(IServiceCollection services, IConfiguration config)
{
services.AddScoped(provider =>
{
var settingService = provider.GetRequiredService<ISettingService>();
return settingService.Bind<GraphDbSettings>("Graph");
});
services.AddScoped<IGraphDb, GraphDb>();
}
}

View file

@ -0,0 +1,13 @@
using System.Text.Json.Serialization;
namespace BotSharp.Plugin.Graph.Models;
public class GraphQueryRequest
{
[JsonPropertyName("query")]
public string Query { get; set; }
[JsonPropertyName("method")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Method { get; set; }
}

View file

@ -0,0 +1,7 @@
global using System;
global using System.Collections.Generic;
global using System.Linq;
global using System.Threading.Tasks;
global using Microsoft.Extensions.Logging;
global using BotSharp.Abstraction.Graph;
global using BotSharp.Abstraction.Graph.Models;

View file

@ -1,3 +1,4 @@
using BotSharp.Abstraction.VectorStorage.Models;
using BotSharp.Plugin.KnowledgeBase.Utilities; using BotSharp.Plugin.KnowledgeBase.Utilities;
using Tensorflow.NumPy; using Tensorflow.NumPy;
@ -22,17 +23,17 @@ public class MemoryVectorDb : IVectorDb
return _collections.Select(x => x.Key).ToList(); return _collections.Select(x => x.Key).ToList();
} }
public Task<StringIdPagedItems<KnowledgeCollectionData>> GetCollectionData(string collectionName, KnowledgeFilter filter) public Task<StringIdPagedItems<VectorCollectionData>> GetCollectionData(string collectionName, VectorFilter filter)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public async Task<IEnumerable<KnowledgeCollectionData>> Search(string collectionName, float[] vector, public async Task<IEnumerable<VectorCollectionData>> Search(string collectionName, float[] vector,
IEnumerable<string>? fields, int limit = 5, float confidence = 0.5f, bool withVector = false) IEnumerable<string>? fields, int limit = 5, float confidence = 0.5f, bool withVector = false)
{ {
if (!_vectors.ContainsKey(collectionName)) if (!_vectors.ContainsKey(collectionName))
{ {
return new List<KnowledgeCollectionData>(); return new List<VectorCollectionData>();
} }
var similarities = VectorUtility.CalCosineSimilarity(vector, _vectors[collectionName]); var similarities = VectorUtility.CalCosineSimilarity(vector, _vectors[collectionName]);
@ -41,7 +42,7 @@ public class MemoryVectorDb : IVectorDb
var results = np.argsort(similarities).ToArray<int>() var results = np.argsort(similarities).ToArray<int>()
.Reverse() .Reverse()
.Take(limit) .Take(limit)
.Select(i => new KnowledgeCollectionData .Select(i => new VectorCollectionData
{ {
Data = new Dictionary<string, string> { { "text", _vectors[collectionName][i].Text } }, Data = new Dictionary<string, string> { { "text", _vectors[collectionName][i].Text } },
Score = similarities[i], Score = similarities[i],

View file

@ -2,7 +2,7 @@ namespace BotSharp.Plugin.KnowledgeBase.Services;
public partial class KnowledgeService public partial class KnowledgeService
{ {
public async Task FeedKnowledge(string collectionName, KnowledgeCreationModel knowledge) public async Task FeedVectorKnowledge(string collectionName, KnowledgeCreationModel knowledge)
{ {
var index = 0; var index = 0;
var lines = _textChopper.Chop(knowledge.Content, new ChunkOption var lines = _textChopper.Chop(knowledge.Content, new ChunkOption

View file

@ -2,7 +2,7 @@ namespace BotSharp.Plugin.KnowledgeBase.Services;
public partial class KnowledgeService public partial class KnowledgeService
{ {
public async Task<bool> DeleteKnowledgeCollectionData(string collectionName, string id) public async Task<bool> DeleteVectorCollectionData(string collectionName, string id)
{ {
try try
{ {

View file

@ -1,8 +1,11 @@
using BotSharp.Abstraction.Graph.Models;
using BotSharp.Abstraction.VectorStorage.Models;
namespace BotSharp.Plugin.KnowledgeBase.Services; namespace BotSharp.Plugin.KnowledgeBase.Services;
public partial class KnowledgeService public partial class KnowledgeService
{ {
public async Task<IEnumerable<string>> GetKnowledgeCollections() public async Task<IEnumerable<string>> GetVectorCollections()
{ {
try try
{ {
@ -16,44 +19,88 @@ public partial class KnowledgeService
} }
} }
public async Task<StringIdPagedItems<KnowledgeSearchResult>> GetKnowledgeCollectionData(string collectionName, KnowledgeFilter filter) public async Task<StringIdPagedItems<VectorSearchResult>> GetVectorCollectionData(string collectionName, VectorFilter filter)
{ {
try try
{ {
var db = GetVectorDb(); var db = GetVectorDb();
var pagedResult = await db.GetCollectionData(collectionName, filter); var pagedResult = await db.GetCollectionData(collectionName, filter);
return new StringIdPagedItems<KnowledgeSearchResult> return new StringIdPagedItems<VectorSearchResult>
{ {
Count = pagedResult.Count, Count = pagedResult.Count,
Items = pagedResult.Items.Select(x => KnowledgeSearchResult.CopyFrom(x)), Items = pagedResult.Items.Select(x => VectorSearchResult.CopyFrom(x)),
NextId = pagedResult.NextId, NextId = pagedResult.NextId,
}; };
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogWarning($"Error when getting knowledge collection data ({collectionName}). {ex.Message}\r\n{ex.InnerException}"); _logger.LogWarning($"Error when getting knowledge collection data ({collectionName}). {ex.Message}\r\n{ex.InnerException}");
return new StringIdPagedItems<KnowledgeSearchResult>(); return new StringIdPagedItems<VectorSearchResult>();
} }
} }
public async Task<IEnumerable<KnowledgeSearchResult>> SearchKnowledge(string collectionName, KnowledgeSearchOptions options) public async Task<IEnumerable<VectorSearchResult>> SearchVectorKnowledge(string query, string collectionName, VectorSearchOptions options)
{ {
try try
{ {
var textEmbedding = GetTextEmbedding(); var textEmbedding = GetTextEmbedding();
var vector = await textEmbedding.GetVectorAsync(options.Text); var vector = await textEmbedding.GetVectorAsync(query);
// Vector search // Vector search
var db = GetVectorDb(); var db = GetVectorDb();
var found = await db.Search(collectionName, vector, options.Fields, limit: options.Limit ?? 5, confidence: options.Confidence ?? 0.5f, withVector: options.WithVector); var found = await db.Search(collectionName, vector, options.Fields, limit: options.Limit ?? 5, confidence: options.Confidence ?? 0.5f, withVector: options.WithVector);
var results = found.Select(x => KnowledgeSearchResult.CopyFrom(x)).ToList(); var results = found.Select(x => VectorSearchResult.CopyFrom(x)).ToList();
return results; return results;
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogWarning($"Error when searching knowledge ({collectionName}). {ex.Message}\r\n{ex.InnerException}"); _logger.LogWarning($"Error when searching knowledge ({collectionName}). {ex.Message}\r\n{ex.InnerException}");
return new List<KnowledgeSearchResult>(); return new List<VectorSearchResult>();
}
}
public async Task<GraphSearchResult> SearchGraphKnowledge(string query, GraphSearchOptions options)
{
try
{
var db = GetGraphDb();
var found = await db.Search(query, options);
return new GraphSearchResult
{
Result = found.Result
};
}
catch (Exception ex)
{
_logger.LogWarning($"Error when searching graph {query}. {ex.Message}\r\n{ex.InnerException}");
return new GraphSearchResult();
}
}
public async Task<KnowledgeSearchResult> SearchKnowledge(string query, string collectionName, VectorSearchOptions vectorOptions, GraphSearchOptions graphOptions)
{
try
{
var textEmbedding = GetTextEmbedding();
var vector = await textEmbedding.GetVectorAsync(query);
var vectorDb = GetVectorDb();
var vectorRes = await vectorDb.Search(collectionName, vector, vectorOptions.Fields, limit: vectorOptions.Limit ?? 5,
confidence: vectorOptions.Confidence ?? 0.5f, withVector: vectorOptions.WithVector);
var graphDb = GetGraphDb();
var graphRes = await graphDb.Search(query, graphOptions);
return new KnowledgeSearchResult
{
VectorResult = vectorRes.Select(x => VectorSearchResult.CopyFrom(x)),
GraphResult = new GraphSearchResult { Result = graphRes.Result }
};
}
catch (Exception ex)
{
_logger.LogWarning($"Error when searching knowledge (vector collection: {collectionName}) {query}. {ex.Message}\r\n{ex.InnerException}");
return new KnowledgeSearchResult();
} }
} }
} }

View file

@ -25,6 +25,12 @@ public partial class KnowledgeService : IKnowledgeService
return db; return db;
} }
private IGraphDb GetGraphDb()
{
var db = _services.GetServices<IGraphDb>().FirstOrDefault(x => x.Name == _settings.GraphDb);
return db;
}
private ITextEmbedding GetTextEmbedding() private ITextEmbedding GetTextEmbedding()
{ {
var embedding = _services.GetServices<ITextEmbedding>().FirstOrDefault(x => x.Provider == _settings.TextEmbedding.Provider); var embedding = _services.GetServices<ITextEmbedding>().FirstOrDefault(x => x.Provider == _settings.TextEmbedding.Provider);

View file

@ -15,7 +15,7 @@ global using BotSharp.Abstraction.Users;
global using BotSharp.Abstraction.Utilities; global using BotSharp.Abstraction.Utilities;
global using BotSharp.Abstraction.Conversations.Models; global using BotSharp.Abstraction.Conversations.Models;
global using BotSharp.Abstraction.Agents.Settings; global using BotSharp.Abstraction.Agents.Settings;
global using BotSharp.Abstraction.Conversations.Settings; global using BotSharp.Abstraction.Graph;
global using BotSharp.Abstraction.Knowledges.Settings; global using BotSharp.Abstraction.Knowledges.Settings;
global using BotSharp.Abstraction.Knowledges.Enums; global using BotSharp.Abstraction.Knowledges.Enums;
global using BotSharp.Abstraction.VectorStorage; global using BotSharp.Abstraction.VectorStorage;

View file

@ -1,6 +1,6 @@
using BotSharp.Abstraction.Knowledges.Models;
using BotSharp.Abstraction.Utilities; using BotSharp.Abstraction.Utilities;
using BotSharp.Abstraction.VectorStorage; using BotSharp.Abstraction.VectorStorage;
using BotSharp.Abstraction.VectorStorage.Models;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -16,7 +16,7 @@ public class FaissDb : IVectorDb
throw new NotImplementedException(); throw new NotImplementedException();
} }
public Task<StringIdPagedItems<KnowledgeCollectionData>> GetCollectionData(string collectionName, KnowledgeFilter filter) public Task<StringIdPagedItems<VectorCollectionData>> GetCollectionData(string collectionName, VectorFilter filter)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
@ -26,7 +26,7 @@ public class FaissDb : IVectorDb
throw new NotImplementedException(); throw new NotImplementedException();
} }
public Task<IEnumerable<KnowledgeCollectionData>> Search(string collectionName, float[] vector, public Task<IEnumerable<VectorCollectionData>> Search(string collectionName, float[] vector,
IEnumerable<string>? fields, int limit = 10, float confidence = 0.5f, bool withVector = false) IEnumerable<string>? fields, int limit = 10, float confidence = 0.5f, bool withVector = false)
{ {
throw new NotImplementedException(); throw new NotImplementedException();

View file

@ -1,4 +1,5 @@
using BotSharp.Abstraction.Utilities; using BotSharp.Abstraction.Utilities;
using BotSharp.Abstraction.VectorStorage.Models;
using Qdrant.Client; using Qdrant.Client;
using Qdrant.Client.Grpc; using Qdrant.Client.Grpc;
@ -41,27 +42,27 @@ public class QdrantDb : IVectorDb
return collections.ToList(); return collections.ToList();
} }
public async Task<StringIdPagedItems<KnowledgeCollectionData>> GetCollectionData(string collectionName, KnowledgeFilter filter) public async Task<StringIdPagedItems<VectorCollectionData>> GetCollectionData(string collectionName, VectorFilter filter)
{ {
var client = GetClient(); var client = GetClient();
var exist = await DoesCollectionExist(client, collectionName); var exist = await DoesCollectionExist(client, collectionName);
if (!exist) if (!exist)
{ {
return new StringIdPagedItems<KnowledgeCollectionData>(); return new StringIdPagedItems<VectorCollectionData>();
} }
var totalPointCount = await client.CountAsync(collectionName); var totalPointCount = await client.CountAsync(collectionName);
var response = await client.ScrollAsync(collectionName, limit: (uint)filter.Size, var response = await client.ScrollAsync(collectionName, limit: (uint)filter.Size,
offset: !string.IsNullOrWhiteSpace(filter.StartId) ? new PointId { Uuid = filter.StartId } : 0, offset: !string.IsNullOrWhiteSpace(filter.StartId) ? new PointId { Uuid = filter.StartId } : 0,
vectorsSelector: filter.WithVector); vectorsSelector: filter.WithVector);
var points = response?.Result?.Select(x => new KnowledgeCollectionData var points = response?.Result?.Select(x => new VectorCollectionData
{ {
Id = x.Id?.Uuid ?? string.Empty, Id = x.Id?.Uuid ?? string.Empty,
Data = x.Payload.ToDictionary(x => x.Key, x => x.Value.StringValue), Data = x.Payload.ToDictionary(x => x.Key, x => x.Value.StringValue),
Vector = filter.WithVector ? x.Vectors?.Vector?.Data?.ToArray() : null Vector = filter.WithVector ? x.Vectors?.Vector?.Data?.ToArray() : null
})?.ToList() ?? new List<KnowledgeCollectionData>(); })?.ToList() ?? new List<VectorCollectionData>();
return new StringIdPagedItems<KnowledgeCollectionData> return new StringIdPagedItems<VectorCollectionData>
{ {
Count = totalPointCount, Count = totalPointCount,
NextId = response?.NextPageOffset?.Uuid, NextId = response?.NextPageOffset?.Uuid,
@ -124,10 +125,10 @@ public class QdrantDb : IVectorDb
return result.Status == UpdateStatus.Completed; return result.Status == UpdateStatus.Completed;
} }
public async Task<IEnumerable<KnowledgeCollectionData>> Search(string collectionName, float[] vector, public async Task<IEnumerable<VectorCollectionData>> Search(string collectionName, float[] vector,
IEnumerable<string>? fields, int limit = 5, float confidence = 0.5f, bool withVector = false) IEnumerable<string>? fields, int limit = 5, float confidence = 0.5f, bool withVector = false)
{ {
var results = new List<KnowledgeCollectionData>(); var results = new List<VectorCollectionData>();
var client = GetClient(); var client = GetClient();
var exist = await DoesCollectionExist(client, collectionName); var exist = await DoesCollectionExist(client, collectionName);
@ -161,7 +162,7 @@ public class QdrantDb : IVectorDb
data = point.Payload.ToDictionary(k => k.Key, v => v.Value.StringValue); data = point.Payload.ToDictionary(k => k.Key, v => v.Value.StringValue);
} }
results.Add(new KnowledgeCollectionData results.Add(new VectorCollectionData
{ {
Id = point.Id.Uuid, Id = point.Id.Uuid,
Data = data, Data = data,

View file

@ -1,6 +1,6 @@
using BotSharp.Abstraction.Knowledges.Models;
using BotSharp.Abstraction.Utilities; using BotSharp.Abstraction.Utilities;
using BotSharp.Abstraction.VectorStorage; using BotSharp.Abstraction.VectorStorage;
using BotSharp.Abstraction.VectorStorage.Models;
using Microsoft.SemanticKernel.Memory; using Microsoft.SemanticKernel.Memory;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -28,7 +28,7 @@ namespace BotSharp.Plugin.SemanticKernel
await _memoryStore.CreateCollectionAsync(collectionName); await _memoryStore.CreateCollectionAsync(collectionName);
} }
public Task<StringIdPagedItems<KnowledgeCollectionData>> GetCollectionData(string collectionName, KnowledgeFilter filter) public Task<StringIdPagedItems<VectorCollectionData>> GetCollectionData(string collectionName, VectorFilter filter)
{ {
throw new System.NotImplementedException(); throw new System.NotImplementedException();
} }
@ -43,15 +43,15 @@ namespace BotSharp.Plugin.SemanticKernel
return result; return result;
} }
public async Task<IEnumerable<KnowledgeCollectionData>> Search(string collectionName, float[] vector, public async Task<IEnumerable<VectorCollectionData>> Search(string collectionName, float[] vector,
IEnumerable<string>? fields, int limit = 5, float confidence = 0.5f, bool withVector = false) IEnumerable<string>? fields, int limit = 5, float confidence = 0.5f, bool withVector = false)
{ {
var results = _memoryStore.GetNearestMatchesAsync(collectionName, vector, limit); var results = _memoryStore.GetNearestMatchesAsync(collectionName, vector, limit);
var resultTexts = new List<KnowledgeCollectionData>(); var resultTexts = new List<VectorCollectionData>();
await foreach (var (record, score) in results) await foreach (var (record, score) in results)
{ {
resultTexts.Add(new KnowledgeCollectionData resultTexts.Add(new VectorCollectionData
{ {
Data = new Dictionary<string, string> { { "text", record.Metadata.Text } }, Data = new Dictionary<string, string> { { "text", record.Metadata.Text } },
Score = score, Score = score,

View file

@ -48,6 +48,7 @@
<ProjectReference Include="..\Plugins\BotSharp.Plugin.ChatbotUI\BotSharp.Plugin.ChatbotUI.csproj" /> <ProjectReference Include="..\Plugins\BotSharp.Plugin.ChatbotUI\BotSharp.Plugin.ChatbotUI.csproj" />
<ProjectReference Include="..\Plugins\BotSharp.Plugin.HuggingFace\BotSharp.Plugin.HuggingFace.csproj" /> <ProjectReference Include="..\Plugins\BotSharp.Plugin.HuggingFace\BotSharp.Plugin.HuggingFace.csproj" />
<ProjectReference Include="..\Plugins\BotSharp.Plugin.KnowledgeBase\BotSharp.Plugin.KnowledgeBase.csproj" /> <ProjectReference Include="..\Plugins\BotSharp.Plugin.KnowledgeBase\BotSharp.Plugin.KnowledgeBase.csproj" />
<ProjectReference Include="..\Plugins\BotSharp.Plugin.Graph\BotSharp.Plugin.Graph.csproj" />
<ProjectReference Include="..\Plugins\BotSharp.Plugin.MetaAI\BotSharp.Plugin.MetaAI.csproj" /> <ProjectReference Include="..\Plugins\BotSharp.Plugin.MetaAI\BotSharp.Plugin.MetaAI.csproj" />
<ProjectReference Include="..\Plugins\BotSharp.Plugin.MetaMessenger\BotSharp.Plugin.MetaMessenger.csproj" /> <ProjectReference Include="..\Plugins\BotSharp.Plugin.MetaMessenger\BotSharp.Plugin.MetaMessenger.csproj" />
<ProjectReference Include="..\Plugins\BotSharp.Plugin.Qdrant\BotSharp.Plugin.Qdrant.csproj" /> <ProjectReference Include="..\Plugins\BotSharp.Plugin.Qdrant\BotSharp.Plugin.Qdrant.csproj" />

View file

@ -249,6 +249,10 @@
"ApiKey": "" "ApiKey": ""
}, },
"Graph": {
"BaseUrl": ""
},
"WeChat": { "WeChat": {
"AgentId": "437bed34-1169-4833-95ce-c24b8b56154a", "AgentId": "437bed34-1169-4833-95ce-c24b8b56154a",
"Token": "#{Token}#", "Token": "#{Token}#",
@ -259,6 +263,7 @@
"KnowledgeBase": { "KnowledgeBase": {
"VectorDb": "Qdrant", "VectorDb": "Qdrant",
"GraphDb": "Default",
"DefaultCollection": "BotSharp", "DefaultCollection": "BotSharp",
"TextEmbedding": { "TextEmbedding": {
"Provider": "openai", "Provider": "openai",
@ -309,6 +314,7 @@
"BotSharp.Plugin.HuggingFace", "BotSharp.Plugin.HuggingFace",
"BotSharp.Plugin.KnowledgeBase", "BotSharp.Plugin.KnowledgeBase",
"BotSharp.Plugin.Planner", "BotSharp.Plugin.Planner",
"BotSharp.Plugin.Graph",
"BotSharp.Plugin.Qdrant", "BotSharp.Plugin.Qdrant",
"BotSharp.Plugin.ChatHub", "BotSharp.Plugin.ChatHub",
"BotSharp.Plugin.WeChat", "BotSharp.Plugin.WeChat",