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; }
/// <summary>
/// For keyword search
/// Filter group: each item contains a logical operator and a list of key-value pairs
/// </summary>
[JsonPropertyName("filters")]
public IEnumerable<KeyValue>? Filters { get; set; }
/// <summary>
/// Filter operator
/// </summary>
[JsonPropertyName("filter_operator")]
public string FilterOperator { get; set; } = "or";
[JsonPropertyName("filter_groups")]
public IEnumerable<VectorFilterGroup>? FilterGroups { get; set; }
/// <summary>
/// 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 IEnumerable<string>? Fields { get; set; } = [KnowledgePayloadName.Text, KnowledgePayloadName.Answer];
public IEnumerable<KeyValue>? Filters { get; set; }
public string FilterOperator { get; set; } = "or";
public IEnumerable<VectorFilterGroup>? FilterGroups { get; set; }
public int? Limit { get; set; } = 5;
public float? Confidence { get; set; } = 0.5f;
public bool WithVector { get; set; }
@ -16,8 +15,7 @@ public class VectorSearchOptions
return new()
{
Fields = [KnowledgePayloadName.Text, KnowledgePayloadName.Answer],
Filters = null,
FilterOperator = "or",
FilterGroups = null,
Limit = 5,
Confidence = 0.5f,
WithVector = false

View file

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

View file

@ -1,3 +1,4 @@
using BotSharp.Abstraction.VectorStorage.Models;
using System.Text.Json.Serialization;
namespace BotSharp.OpenAPI.ViewModels.Knowledges;
@ -10,11 +11,8 @@ public class SearchVectorKnowledgeRequest
[JsonPropertyName("fields")]
public IEnumerable<string>? Fields { get; set; }
[JsonPropertyName("filters")]
public IEnumerable<KeyValue>? Filters { get; set; }
[JsonPropertyName("filter_operator")]
public string FilterOperator { get; set; } = "or";
[JsonPropertyName("filter_groups")]
public IEnumerable<VectorFilterGroup>? FilterGroups { get; set; }
[JsonPropertyName("limit")]
public int? Limit { get; set; } = 5;

View file

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

View file

@ -1,5 +1,6 @@
using BotSharp.Abstraction.Files;
using BotSharp.Abstraction.VectorStorage.Enums;
using static Microsoft.EntityFrameworkCore.DbLoggerCategory;
namespace BotSharp.Plugin.KnowledgeBase.Services;
@ -165,7 +166,12 @@ public partial class KnowledgeService
var db = GetVectorDb();
var guid = Guid.NewGuid();
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);
}
@ -198,7 +204,12 @@ public partial class KnowledgeService
var textEmbedding = GetTextEmbedding(collectionName);
var vector = await textEmbedding.GetVectorAsync(update.Text);
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);
}

View file

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