Merge pull request #596 from iceljc/bugfix/refine-knowledge-base
unite knowledge search model
This commit is contained in:
commit
d5daa92b79
|
|
@ -3,8 +3,8 @@ namespace BotSharp.Abstraction.Knowledges;
|
|||
public interface IKnowledgeService
|
||||
{
|
||||
Task<IEnumerable<string>> GetKnowledgeCollections();
|
||||
Task<IEnumerable<KnowledgeRetrievalResult>> SearchKnowledge(string collectionName, KnowledgeRetrievalOptions options);
|
||||
Task<IEnumerable<KnowledgeSearchResult>> SearchKnowledge(string collectionName, KnowledgeSearchOptions options);
|
||||
Task FeedKnowledge(string collectionName, KnowledgeCreationModel model);
|
||||
Task<StringIdPagedItems<KnowledgeCollectionData>> GetKnowledgeCollectionData(string collectionName, KnowledgeFilter filter);
|
||||
Task<StringIdPagedItems<KnowledgeSearchResult>> GetKnowledgeCollectionData(string collectionName, KnowledgeFilter filter);
|
||||
Task<bool> DeleteKnowledgeCollectionData(string collectionName, string id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ namespace BotSharp.Abstraction.Knowledges.Models;
|
|||
public class KnowledgeCollectionData
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Question { get; set; }
|
||||
public string Answer { get; set; }
|
||||
public Dictionary<string, string> Data { get; set; } = new();
|
||||
public double? Score { get; set; }
|
||||
public float[]? Vector { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ using BotSharp.Abstraction.Knowledges.Enums;
|
|||
|
||||
namespace BotSharp.Abstraction.Knowledges.Models;
|
||||
|
||||
public class KnowledgeRetrievalOptions
|
||||
public class KnowledgeSearchOptions
|
||||
{
|
||||
public string Text { get; set; } = string.Empty;
|
||||
public IEnumerable<string>? Fields { get; set; } = new List<string> { KnowledgePayloadName.Text, KnowledgePayloadName.Answer };
|
||||
|
|
@ -1,12 +1,20 @@
|
|||
namespace BotSharp.Abstraction.Knowledges.Models;
|
||||
|
||||
public class KnowledgeSearchResult
|
||||
public class KnowledgeSearchResult : KnowledgeCollectionData
|
||||
{
|
||||
public Dictionary<string, string> Data { get; set; } = new();
|
||||
public double Score { get; set; }
|
||||
public float[]? Vector { get; set; }
|
||||
}
|
||||
public KnowledgeSearchResult()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class KnowledgeRetrievalResult : KnowledgeSearchResult
|
||||
{
|
||||
public static KnowledgeSearchResult CopyFrom(KnowledgeCollectionData data)
|
||||
{
|
||||
return new KnowledgeSearchResult
|
||||
{
|
||||
Id = data.Id,
|
||||
Data = data.Data,
|
||||
Score = data.Score,
|
||||
Vector = data.Vector
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -3,11 +3,11 @@ namespace BotSharp.Abstraction.VectorStorage;
|
|||
public interface IVectorDb
|
||||
{
|
||||
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<IEnumerable<KnowledgeSearchResult>> Search(string collectionName, float[] vector, IEnumerable<string> fields, int limit = 5, float confidence = 0.5f, bool withVector = false);
|
||||
Task<IEnumerable<KnowledgeCollectionData>> 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,29 +23,29 @@ public class KnowledgeBaseController : ControllerBase
|
|||
}
|
||||
|
||||
[HttpPost("/knowledge/{collection}/search")]
|
||||
public async Task<IEnumerable<KnowledgeRetrivalViewModel>> SearchKnowledge([FromRoute] string collection, [FromBody] SearchKnowledgeModel model)
|
||||
public async Task<IEnumerable<KnowledgeSearchResultViewModel>> SearchKnowledge([FromRoute] string collection, [FromBody] SearchKnowledgeRequest request)
|
||||
{
|
||||
var options = new KnowledgeRetrievalOptions
|
||||
var options = new KnowledgeSearchOptions
|
||||
{
|
||||
Text = model.Text,
|
||||
Fields = model.Fields,
|
||||
Limit = model.Limit ?? 5,
|
||||
Confidence = model.Confidence ?? 0.5f,
|
||||
WithVector = model.WithVector
|
||||
Text = request.Text,
|
||||
Fields = request.Fields,
|
||||
Limit = request.Limit ?? 5,
|
||||
Confidence = request.Confidence ?? 0.5f,
|
||||
WithVector = request.WithVector
|
||||
};
|
||||
|
||||
var results = await _knowledgeService.SearchKnowledge(collection, options);
|
||||
return results.Select(x => KnowledgeRetrivalViewModel.From(x)).ToList();
|
||||
return results.Select(x => KnowledgeSearchResultViewModel.From(x)).ToList();
|
||||
}
|
||||
|
||||
[HttpPost("/knowledge/{collection}/data")]
|
||||
public async Task<StringIdPagedItems<KnowledgeCollectionDataViewModel>> GetKnowledgeCollectionData([FromRoute] string collection, [FromBody] KnowledgeFilter filter)
|
||||
public async Task<StringIdPagedItems<KnowledgeSearchResultViewModel>> GetKnowledgeCollectionData([FromRoute] string collection, [FromBody] KnowledgeFilter filter)
|
||||
{
|
||||
var data = await _knowledgeService.GetKnowledgeCollectionData(collection, filter);
|
||||
var items = data.Items?.Select(x => KnowledgeCollectionDataViewModel.From(x))?
|
||||
.ToList() ?? new List<KnowledgeCollectionDataViewModel>();
|
||||
var items = data.Items?.Select(x => KnowledgeSearchResultViewModel.From(x))?
|
||||
.ToList() ?? new List<KnowledgeSearchResultViewModel>();
|
||||
|
||||
return new StringIdPagedItems<KnowledgeCollectionDataViewModel>
|
||||
return new StringIdPagedItems<KnowledgeSearchResultViewModel>
|
||||
{
|
||||
Count = data.Count,
|
||||
NextId = data.NextId,
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
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,33 @@
|
|||
using BotSharp.Abstraction.Knowledges.Models;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BotSharp.OpenAPI.ViewModels.Knowledges;
|
||||
|
||||
public class KnowledgeSearchResultViewModel
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; set; }
|
||||
|
||||
[JsonPropertyName("data")]
|
||||
public IDictionary<string, string> Data { get; set; }
|
||||
|
||||
[JsonPropertyName("score")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public double? Score { get; set; }
|
||||
|
||||
[JsonPropertyName("vector")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public float[]? Vector { get; set; }
|
||||
|
||||
|
||||
public static KnowledgeSearchResultViewModel From(KnowledgeSearchResult result)
|
||||
{
|
||||
return new KnowledgeSearchResultViewModel
|
||||
{
|
||||
Id = result.Id,
|
||||
Data = result.Data,
|
||||
Score = result.Score,
|
||||
Vector = result.Vector
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ using System.Text.Json.Serialization;
|
|||
|
||||
namespace BotSharp.OpenAPI.ViewModels.Knowledges;
|
||||
|
||||
public class SearchKnowledgeModel
|
||||
public class SearchKnowledgeRequest
|
||||
{
|
||||
[JsonPropertyName("text")]
|
||||
public string Text { get; set; } = string.Empty;
|
||||
|
|
@ -27,12 +27,12 @@ public class MemoryVectorDb : IVectorDb
|
|||
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)
|
||||
public async Task<IEnumerable<KnowledgeCollectionData>> 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>();
|
||||
return new List<KnowledgeCollectionData>();
|
||||
}
|
||||
|
||||
var similarities = VectorUtility.CalCosineSimilarity(vector, _vectors[collectionName]);
|
||||
|
|
@ -41,7 +41,7 @@ public class MemoryVectorDb : IVectorDb
|
|||
var results = np.argsort(similarities).ToArray<int>()
|
||||
.Reverse()
|
||||
.Take(limit)
|
||||
.Select(i => new KnowledgeSearchResult
|
||||
.Select(i => new KnowledgeCollectionData
|
||||
{
|
||||
Data = new Dictionary<string, string> { { "text", _vectors[collectionName][i].Text } },
|
||||
Score = similarities[i],
|
||||
|
|
@ -64,8 +64,8 @@ public class MemoryVectorDb : IVectorDb
|
|||
return true;
|
||||
}
|
||||
|
||||
public Task<bool> DeleteCollectionData(string collectionName, string id)
|
||||
public async Task<bool> DeleteCollectionData(string collectionName, string id)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return await Task.FromResult(false);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,21 +16,27 @@ public partial class KnowledgeService
|
|||
}
|
||||
}
|
||||
|
||||
public async Task<StringIdPagedItems<KnowledgeCollectionData>> GetKnowledgeCollectionData(string collectionName, KnowledgeFilter filter)
|
||||
public async Task<StringIdPagedItems<KnowledgeSearchResult>> GetKnowledgeCollectionData(string collectionName, KnowledgeFilter filter)
|
||||
{
|
||||
try
|
||||
{
|
||||
var db = GetVectorDb();
|
||||
return await db.GetCollectionData(collectionName, filter);
|
||||
var pagedResult = await db.GetCollectionData(collectionName, filter);
|
||||
return new StringIdPagedItems<KnowledgeSearchResult>
|
||||
{
|
||||
Count = pagedResult.Count,
|
||||
Items = pagedResult.Items.Select(x => KnowledgeSearchResult.CopyFrom(x)),
|
||||
NextId = pagedResult.NextId,
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning($"Error when getting knowledge collection data ({collectionName}). {ex.Message}\r\n{ex.InnerException}");
|
||||
return new StringIdPagedItems<KnowledgeCollectionData>();
|
||||
return new StringIdPagedItems<KnowledgeSearchResult>();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<KnowledgeRetrievalResult>> SearchKnowledge(string collectionName, KnowledgeRetrievalOptions options)
|
||||
public async Task<IEnumerable<KnowledgeSearchResult>> SearchKnowledge(string collectionName, KnowledgeSearchOptions options)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -39,21 +45,15 @@ public partial class KnowledgeService
|
|||
|
||||
// Vector search
|
||||
var db = GetVectorDb();
|
||||
var fields = !options.Fields.IsNullOrEmpty() ? options.Fields : new List<string> { KnowledgePayloadName.Text, KnowledgePayloadName.Answer };
|
||||
var found = await db.Search(collectionName, vector, 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 => new KnowledgeRetrievalResult
|
||||
{
|
||||
Data = x.Data,
|
||||
Score = x.Score,
|
||||
Vector = x.Vector
|
||||
}).ToList();
|
||||
var results = found.Select(x => KnowledgeSearchResult.CopyFrom(x)).ToList();
|
||||
return results;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning($"Error when searching knowledge ({collectionName}). {ex.Message}\r\n{ex.InnerException}");
|
||||
return new List<KnowledgeRetrievalResult>();
|
||||
return new List<KnowledgeSearchResult>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@ public class FaissDb : IVectorDb
|
|||
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)
|
||||
public Task<IEnumerable<KnowledgeCollectionData>> Search(string collectionName, float[] vector,
|
||||
IEnumerable<string>? fields, int limit = 10, float confidence = 0.5f, bool withVector = false)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,8 +57,7 @@ public class QdrantDb : IVectorDb
|
|||
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,
|
||||
Data = x.Payload.ToDictionary(x => x.Key, x => x.Value.StringValue),
|
||||
Vector = filter.WithVector ? x.Vectors?.Vector?.Data?.ToArray() : null
|
||||
})?.ToList() ?? new List<KnowledgeCollectionData>();
|
||||
|
||||
|
|
@ -125,10 +124,10 @@ public class QdrantDb : IVectorDb
|
|||
return result.Status == UpdateStatus.Completed;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<KnowledgeSearchResult>> Search(string collectionName, float[] vector,
|
||||
IEnumerable<string> fields, int limit = 5, float confidence = 0.5f, bool withVector = false)
|
||||
public async Task<IEnumerable<KnowledgeCollectionData>> Search(string collectionName, float[] vector,
|
||||
IEnumerable<string>? fields, int limit = 5, float confidence = 0.5f, bool withVector = false)
|
||||
{
|
||||
var results = new List<KnowledgeSearchResult>();
|
||||
var results = new List<KnowledgeCollectionData>();
|
||||
|
||||
var client = GetClient();
|
||||
var exist = await DoesCollectionExist(client, collectionName);
|
||||
|
|
@ -138,24 +137,33 @@ public class QdrantDb : IVectorDb
|
|||
}
|
||||
|
||||
var points = await client.SearchAsync(collectionName, vector, limit: (ulong)limit, scoreThreshold: confidence);
|
||||
|
||||
|
||||
var pickFields = fields != null;
|
||||
foreach (var point in points)
|
||||
{
|
||||
var data = new Dictionary<string, string>();
|
||||
foreach (var field in fields)
|
||||
if (pickFields)
|
||||
{
|
||||
if (point.Payload.ContainsKey(field))
|
||||
foreach (var field in fields)
|
||||
{
|
||||
data[field] = point.Payload[field].StringValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
data[field] = "";
|
||||
if (point.Payload.ContainsKey(field))
|
||||
{
|
||||
data[field] = point.Payload[field].StringValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
data[field] = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results.Add(new KnowledgeSearchResult
|
||||
else
|
||||
{
|
||||
data = point.Payload.ToDictionary(k => k.Key, v => v.Value.StringValue);
|
||||
}
|
||||
|
||||
results.Add(new KnowledgeCollectionData
|
||||
{
|
||||
Id = point.Id.Uuid,
|
||||
Data = data,
|
||||
Score = point.Score,
|
||||
Vector = withVector ? point.Vectors?.Vector?.Data?.ToArray() : null
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ 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.Threading.Tasks;
|
||||
|
||||
|
|
@ -44,15 +43,15 @@ namespace BotSharp.Plugin.SemanticKernel
|
|||
return result;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<KnowledgeSearchResult>> Search(string collectionName, float[] vector,
|
||||
IEnumerable<string> fields, int limit = 5, float confidence = 0.5f, bool withVector = false)
|
||||
public async Task<IEnumerable<KnowledgeCollectionData>> 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<KnowledgeSearchResult>();
|
||||
var resultTexts = new List<KnowledgeCollectionData>();
|
||||
await foreach (var (record, score) in results)
|
||||
{
|
||||
resultTexts.Add(new KnowledgeSearchResult
|
||||
resultTexts.Add(new KnowledgeCollectionData
|
||||
{
|
||||
Data = new Dictionary<string, string> { { "text", record.Metadata.Text } },
|
||||
Score = score,
|
||||
|
|
|
|||
Loading…
Reference in a new issue