refine vector filter

This commit is contained in:
Jicheng Lu 2025-08-08 15:27:38 -05:00
parent 5bb5d69894
commit 0e2fb847a7
8 changed files with 73 additions and 43 deletions

View file

@ -6,16 +6,10 @@ public class VectorFilter : StringIdPagination
public bool WithVector { get; set; } public bool WithVector { get; set; }
/// <summary> /// <summary>
/// For keyword search /// Filter group: each item contains a logical operator and a list of key-value pairs
/// </summary> /// </summary>
[JsonPropertyName("filters")] [JsonPropertyName("filter_groups")]
public IEnumerable<KeyValue>? Filters { get; set; } public IEnumerable<VectorFilterGroup>? FilterGroups { get; set; }
/// <summary>
/// Filter operator
/// </summary>
[JsonPropertyName("filter_operator")]
public string FilterOperator { get; set; } = "or";
/// <summary> /// <summary>
/// Included payload fields /// Included payload fields

View file

@ -0,0 +1,10 @@
namespace BotSharp.Abstraction.VectorStorage.Models;
public class VectorFilterGroup
{
[JsonPropertyName("filters")]
public IEnumerable<KeyValue>? Filters { get; set; }
[JsonPropertyName("filter_operator")]
public string FilterOperator { get; set; } = "or";
}

View file

@ -5,8 +5,7 @@ namespace BotSharp.Abstraction.VectorStorage.Models;
public class VectorSearchOptions public class VectorSearchOptions
{ {
public IEnumerable<string>? Fields { get; set; } = [KnowledgePayloadName.Text, KnowledgePayloadName.Answer]; public IEnumerable<string>? Fields { get; set; } = [KnowledgePayloadName.Text, KnowledgePayloadName.Answer];
public IEnumerable<KeyValue>? Filters { get; set; } public IEnumerable<VectorFilterGroup>? FilterGroups { get; set; }
public string FilterOperator { get; set; } = "or";
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;
public bool WithVector { get; set; } public bool WithVector { get; set; }
@ -16,8 +15,7 @@ public class VectorSearchOptions
return new() return new()
{ {
Fields = [KnowledgePayloadName.Text, KnowledgePayloadName.Answer], Fields = [KnowledgePayloadName.Text, KnowledgePayloadName.Answer],
Filters = null, FilterGroups = null,
FilterOperator = "or",
Limit = 5, Limit = 5,
Confidence = 0.5f, Confidence = 0.5f,
WithVector = false WithVector = false

View file

@ -60,8 +60,7 @@ public class KnowledgeBaseController : ControllerBase
var options = new VectorSearchOptions var options = new VectorSearchOptions
{ {
Fields = request.Fields, Fields = request.Fields,
Filters = request.Filters, FilterGroups = request.FilterGroups,
FilterOperator = request.FilterOperator,
Limit = request.Limit ?? 5, Limit = request.Limit ?? 5,
Confidence = request.Confidence ?? 0.5f, Confidence = request.Confidence ?? 0.5f,
WithVector = request.WithVector WithVector = request.WithVector

View file

@ -1,3 +1,4 @@
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;
@ -10,11 +11,8 @@ public class SearchVectorKnowledgeRequest
[JsonPropertyName("fields")] [JsonPropertyName("fields")]
public IEnumerable<string>? Fields { get; set; } public IEnumerable<string>? Fields { get; set; }
[JsonPropertyName("filters")] [JsonPropertyName("filter_groups")]
public IEnumerable<KeyValue>? Filters { get; set; } public IEnumerable<VectorFilterGroup>? FilterGroups { get; set; }
[JsonPropertyName("filter_operator")]
public string FilterOperator { get; set; } = "or";
[JsonPropertyName("limit")] [JsonPropertyName("limit")]
public int? Limit { get; set; } = 5; public int? Limit { get; set; } = 5;

View file

@ -8,7 +8,7 @@ public class VectorKnowledgeCreateRequest
public string Text { get; set; } public string Text { get; set; }
[JsonPropertyName("data_source")] [JsonPropertyName("data_source")]
public string DataSource { get; set; } = VectorDataSource.Api; public string DataSource { get; set; } = string.Empty;
[JsonPropertyName("payload")] [JsonPropertyName("payload")]
public Dictionary<string, object>? Payload { get; set; } public Dictionary<string, object>? Payload { get; set; }

View file

@ -1,5 +1,6 @@
using BotSharp.Abstraction.Files; using BotSharp.Abstraction.Files;
using BotSharp.Abstraction.VectorStorage.Enums; using BotSharp.Abstraction.VectorStorage.Enums;
using static Microsoft.EntityFrameworkCore.DbLoggerCategory;
namespace BotSharp.Plugin.KnowledgeBase.Services; namespace BotSharp.Plugin.KnowledgeBase.Services;
@ -165,7 +166,12 @@ public partial class KnowledgeService
var db = GetVectorDb(); var db = GetVectorDb();
var guid = Guid.NewGuid(); var guid = Guid.NewGuid();
var payload = create.Payload ?? new(); var payload = create.Payload ?? new();
payload[KnowledgePayloadName.DataSource] = !string.IsNullOrWhiteSpace(create.DataSource) ? create.DataSource : VectorDataSource.Api;
if (!payload.TryGetValue(KnowledgePayloadName.DataSource, out _))
{
payload[KnowledgePayloadName.DataSource] = !string.IsNullOrWhiteSpace(create.DataSource) ?
create.DataSource : VectorDataSource.Api;
}
return await db.Upsert(collectionName, guid, vector, create.Text, payload); return await db.Upsert(collectionName, guid, vector, create.Text, payload);
} }
@ -198,7 +204,12 @@ public partial class KnowledgeService
var textEmbedding = GetTextEmbedding(collectionName); var textEmbedding = GetTextEmbedding(collectionName);
var vector = await textEmbedding.GetVectorAsync(update.Text); var vector = await textEmbedding.GetVectorAsync(update.Text);
var payload = update.Payload ?? new(); var payload = update.Payload ?? new();
payload[KnowledgePayloadName.DataSource] = !string.IsNullOrWhiteSpace(update.DataSource) ? update.DataSource : VectorDataSource.Api;
if (!payload.TryGetValue(KnowledgePayloadName.DataSource, out _))
{
payload[KnowledgePayloadName.DataSource] = !string.IsNullOrWhiteSpace(update.DataSource) ?
update.DataSource : VectorDataSource.Api;
}
return await db.Upsert(collectionName, guid, vector, update.Text, payload); return await db.Upsert(collectionName, guid, vector, update.Text, payload);
} }

View file

@ -142,7 +142,7 @@ public class QdrantDb : IVectorDb
return new StringIdPagedItems<VectorCollectionData>(); return new StringIdPagedItems<VectorCollectionData>();
} }
Filter? queryFilter = BuildQueryFilter(filter.Filters, filter.FilterOperator); Filter? queryFilter = BuildQueryFilter(filter.FilterGroups);
WithPayloadSelector? payloadSelector = BuildPayloadSelector(filter.Fields); WithPayloadSelector? payloadSelector = BuildPayloadSelector(filter.Fields);
var client = GetClient(); var client = GetClient();
@ -237,7 +237,10 @@ public class QdrantDb : IVectorDb
foreach (var item in payload) foreach (var item in payload)
{ {
var value = item.Value?.ToString(); var value = item.Value?.ToString();
if (value == null) continue; if (value == null || item.Key.IsEqualTo(KnowledgePayloadName.Text))
{
continue;
}
if (bool.TryParse(value, out var b)) if (bool.TryParse(value, out var b))
{ {
@ -298,7 +301,7 @@ public class QdrantDb : IVectorDb
} }
options ??= VectorSearchOptions.Default(); options ??= VectorSearchOptions.Default();
Filter? queryFilter = BuildQueryFilter(options.Filters, options.FilterOperator); Filter? queryFilter = BuildQueryFilter(options.FilterGroups);
WithPayloadSelector? payloadSelector = BuildPayloadSelector(options.Fields); WithPayloadSelector? payloadSelector = BuildPayloadSelector(options.Fields);
var client = GetClient(); var client = GetClient();
@ -533,49 +536,66 @@ public class QdrantDb : IVectorDb
#region Private methods #region Private methods
private Filter? BuildQueryFilter(IEnumerable<KeyValue>? keyValues, string op) private Filter? BuildQueryFilter(IEnumerable<VectorFilterGroup>? filterGroups)
{ {
Filter? queryFilter = null; Filter? queryFilter = null;
if (!keyValues.IsNullOrEmpty())
if (filterGroups.IsNullOrEmpty())
{ {
var conditions = keyValues.Select(x => return queryFilter;
}
var conditions = filterGroups.Where(x => !x.Filters.IsNullOrEmpty()).Select(x =>
{
Filter filter;
var innerConditions = x.Filters.Select(f =>
{ {
var field = new FieldCondition var field = new FieldCondition
{ {
Key = x.Key, Key = f.Key,
Match = new Match { Text = x.Value }, Match = new Match { Text = f.Value },
}; };
if (bool.TryParse(x.Value, out var boolVal)) if (bool.TryParse(f.Value, out var boolVal))
{ {
field.Match = new Match { Boolean = boolVal }; field.Match = new Match { Boolean = boolVal };
} }
else if (long.TryParse(x.Value, out var intVal)) else if (long.TryParse(f.Value, out var intVal))
{ {
field.Match = new Match { Integer = intVal }; field.Match = new Match { Integer = intVal };
} }
return new Condition return new Condition { Field = field };
{
Field = field
};
}); });
if (op.IsEqualTo("and")) if (x.FilterOperator.IsEqualTo("and"))
{ {
queryFilter = new Filter filter = new Filter
{ {
Must = { conditions } Must = { innerConditions }
}; };
} }
else else
{ {
queryFilter = new Filter filter = new Filter
{ {
Should = { conditions } Should = { innerConditions }
}; };
} }
}
return new Condition
{
Filter = filter
};
});
queryFilter = new Filter
{
Must =
{
conditions
}
};
return queryFilter; return queryFilter;
} }