add upload vector knowledge files

This commit is contained in:
Jicheng Lu 2024-09-09 17:42:09 -05:00
parent c3e048d1a7
commit 16c50075ee
13 changed files with 182 additions and 28 deletions

View file

@ -61,4 +61,8 @@ public interface IFileStorageService
bool SaveSpeechFile(string conversationId, string fileName, BinaryData data);
BinaryData GetSpeechFile(string conversationId, string fileName);
#endregion
#region Knowledge
bool SaveKnowledgeFiles(string collectionName, string fileId, string fileName, Stream stream);
#endregion
}

View file

@ -22,6 +22,7 @@ public interface IKnowledgeService
#endregion
#region Document
Task<UploadKnowledgeResponse> UploadVectorKnowledge(string collectionName, IEnumerable<InputFileModel> files);
#endregion
#region Common

View file

@ -0,0 +1,10 @@
namespace BotSharp.Abstraction.Knowledges.Models;
public class UploadKnowledgeResponse
{
[JsonPropertyName("success")]
public IEnumerable<string> Success { get; set; } = new List<string>();
[JsonPropertyName("failed")]
public IEnumerable<string> Failed { get; set; } = new List<string>();
}

View file

@ -44,6 +44,13 @@ public static class StringExtensions
return str1.Equals(str2, option);
}
public static string RemoveWhiteSpaces(this string? str)
{
if (string.IsNullOrWhiteSpace(str)) return string.Empty;
return str.Replace(" ", "").Replace("\t", "").Replace("\n", "").Replace("\r", "");
}
public static string JsonContent(this string text)
{
var m = Regex.Match(text, @"\{(?:[^{}]|(?<open>\{)|(?<-open>\}))+(?(open)(?!))\}");

View file

@ -0,0 +1,34 @@
using System.IO;
namespace BotSharp.Core.Files.Services;
public partial class LocalFileStorageService
{
public bool SaveKnowledgeFiles(string collectionName, string fileId, string fileName, Stream stream)
{
if (string.IsNullOrWhiteSpace(collectionName) || string.IsNullOrWhiteSpace(fileId))
{
return false;
}
try
{
var dir = Path.Combine(_baseDir, KNOWLEDGE_FOLDER, KNOWLEDGE_DOC_FOLDER, collectionName, fileId);
if (ExistDirectory(dir))
{
Directory.Delete(dir);
}
Directory.CreateDirectory(dir);
var filePath = Path.Combine(dir, fileName);
using var fs = File.Create(filePath);
stream.CopyTo(fs);
return true;
}
catch (Exception ex)
{
_logger.LogWarning($"Error when saving knowledge file (Collection: {collectionName}, File name: {fileName}). {ex.Message}\r\n{ex.InnerException}");
return false;
}
}
}

View file

@ -19,6 +19,8 @@ public partial class LocalFileStorageService : IFileStorageService
private const string USER_AVATAR_FOLDER = "avatar";
private const string SESSION_FOLDER = "sessions";
private const string TEXT_TO_SPEECH_FOLDER = "speeches";
private const string KNOWLEDGE_FOLDER = "knowledgebase";
private const string KNOWLEDGE_DOC_FOLDER = "document";
public LocalFileStorageService(
BotSharpDatabaseSettings dbSettings,

View file

@ -40,7 +40,7 @@ public partial class FileRepository : IBotSharpRepository
private const string AGENT_RESPONSES_FOLDER = "responses";
private const string AGENT_TASKS_FOLDER = "tasks";
private const string USERS_FOLDER = "users";
private const string KNOWLEDGE_FOLDER = "knowledge";
private const string KNOWLEDGE_FOLDER = "knowledgebase";
private const string VECTOR_FOLDER = "vector";
private const string COLLECTION_CONFIG_FILE = "collection-config.json";

View file

@ -366,8 +366,7 @@ public class ConversationController : ControllerBase
#region Files and attachments
[HttpPost("/conversation/{conversationId}/attachments")]
public IActionResult UploadAttachments([FromRoute] string conversationId,
IFormFile[] files)
public IActionResult UploadAttachments([FromRoute] string conversationId, IFormFile[] files)
{
if (files != null && files.Length > 0)
{

View file

@ -99,29 +99,6 @@ public class KnowledgeBaseController : ControllerBase
{
return await _knowledgeService.DeleteVectorCollectionData(collection, id);
}
[HttpPost("/knowledge/vector/{collection}/upload")]
public async Task<IActionResult> UploadVectorKnowledge([FromRoute] string collection, IFormFile file, [FromForm] int? startPageNum, [FromForm] int? endPageNum)
{
var setttings = _services.GetRequiredService<FileCoreSettings>();
var textConverter = _services.GetServices<IPdf2TextConverter>().FirstOrDefault(x => x.Provider == setttings.Pdf2TextConverter.Provider);
var filePath = Path.GetTempFileName();
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);
await _knowledgeService.FeedVectorKnowledge(collection, new KnowledgeCreationModel
{
Content = content
});
System.IO.File.Delete(filePath);
return Ok(new { count = 1, file.Length });
}
#endregion
@ -144,7 +121,35 @@ public class KnowledgeBaseController : ControllerBase
#region Document
//[HttpPost("/knowledge/vector/{collection}/upload")]
//public async Task<IActionResult> UploadVectorKnowledge([FromRoute] string collection, IFormFile file, [FromForm] int? startPageNum, [FromForm] int? endPageNum)
//{
// var setttings = _services.GetRequiredService<FileCoreSettings>();
// var textConverter = _services.GetServices<IPdf2TextConverter>().FirstOrDefault(x => x.Provider == setttings.Pdf2TextConverter.Provider);
// var filePath = Path.GetTempFileName();
// 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);
// await _knowledgeService.FeedVectorKnowledge(collection, new KnowledgeCreationModel
// {
// Content = content
// });
// System.IO.File.Delete(filePath);
// return Ok(new { count = 1, file.Length });
//}
[HttpPost("/knowledge/vector/{collection}/upload")]
public async Task<UploadKnowledgeResponse> UploadVectorKnowledge([FromRoute] string collection, [FromBody] VectorKnowledgeUploadRequest request)
{
var response = await _knowledgeService.UploadVectorKnowledge(collection, request.Files);
return response;
}
#endregion
#region Common

View file

@ -0,0 +1,6 @@
namespace BotSharp.OpenAPI.ViewModels.Knowledges;
public class VectorKnowledgeUploadRequest
{
public IEnumerable<InputFileModel> Files { get; set; } = new List<InputFileModel>();
}

View file

@ -14,7 +14,7 @@ public class NativeWhisperProvider : IAudioCompletion
private readonly IFileStorageService _fileStorage;
private readonly ILogger<NativeWhisperProvider> _logger;
public string Provider => "native";
public string Provider => "native-whisper";
public NativeWhisperProvider(
BotSharpDatabaseSettings dbSettings,

View file

@ -1,7 +1,89 @@
using BotSharp.Abstraction.Files;
using BotSharp.Abstraction.Files.Models;
using BotSharp.Abstraction.Files.Utilities;
namespace BotSharp.Plugin.KnowledgeBase.Services;
public partial class KnowledgeService
{
public async Task<UploadKnowledgeResponse> UploadVectorKnowledge(string collectionName, IEnumerable<InputFileModel> files)
{
if (string.IsNullOrWhiteSpace(collectionName))
{
return new UploadKnowledgeResponse
{
Success = [],
Failed = files.Select(x => x.FileName)
};
}
var fileStoreage = _services.GetRequiredService<IFileStorageService>();
var cleanCollectionName = collectionName.RemoveWhiteSpaces();
var successFiles = new List<string>();
var failedFiles = new List<string>();
foreach (var file in files)
{
if (string.IsNullOrWhiteSpace(file.FileData) || string.IsNullOrWhiteSpace(file.FileName))
{
continue;
}
var dataIds = new List<string>();
try
{
// Chop text
var (contentType, bytes) = FileUtility.GetFileInfoFromData(file.FileData);
using var stream = new MemoryStream(bytes);
using var reader = new StreamReader(stream);
var content = await reader.ReadToEndAsync();
// Save file
var fileId = Guid.NewGuid().ToString();
var saved = fileStoreage.SaveKnowledgeFiles(cleanCollectionName, fileId, file.FileName, stream);
reader.Close();
stream.Close();
if (!saved)
{
failedFiles.Add(file.FileName);
continue;
}
// Text embedding
var vectorDb = GetVectorDb();
var textEmbedding = GetTextEmbedding(collectionName);
var vector = await textEmbedding.GetVectorAsync(content);
// Save to vector db
var dataId = Guid.NewGuid();
await vectorDb.Upsert(collectionName, dataId, vector, content, new Dictionary<string, string>
{
{ "fileName", file.FileName },
{ "fileId", fileId },
{ "page", "0" }
});
dataIds.Add(dataId.ToString());
successFiles.Add(file.FileName);
}
catch (Exception ex)
{
_logger.LogError($"Error when processing knowledge file ({file.FileName}). {ex.Message}\r\n{ex.InnerException}");
failedFiles.Add(file.FileName);
continue;
}
}
return new UploadKnowledgeResponse
{
Success = successFiles,
Failed = failedFiles
};
}
public async Task FeedVectorKnowledge(string collectionName, KnowledgeCreationModel knowledge)
{
var index = 0;
@ -24,4 +106,7 @@ public partial class KnowledgeService
Console.WriteLine($"Saved vector {index}/{lines.Count}: {line}\n");
}
}
#region Private methods
#endregion
}

View file

@ -141,7 +141,8 @@
"Enable": true,
"BatchSize": 50,
"MessageLimit": 2,
"BufferHours": 12
"BufferHours": 12,
"ExcludeAgentIds": []
},
"RateLimit": {
"MaxConversationPerDay": 100,