define vector payload value

This commit is contained in:
Jicheng Lu 2025-08-14 23:40:15 -05:00
parent 15978470bf
commit bcd1e2ca35
16 changed files with 144 additions and 81 deletions

View file

@ -43,7 +43,7 @@ public interface IKnowledgeService
/// <param name="refData"></param>
/// <returns></returns>
Task<bool> ImportDocumentContentToKnowledge(string collectionName, string fileName, string fileSource, IEnumerable<string> contents,
DocMetaRefData? refData = null, Dictionary<string, object>? payload = null);
DocMetaRefData? refData = null, Dictionary<string, VectorPayloadValue>? payload = null);
/// <summary>
/// Delete one document and its related knowledge in the collection
/// </summary>

View file

@ -0,0 +1,11 @@
namespace BotSharp.Abstraction.VectorStorage.Enums;
public enum VectorPayloadDataType
{
Unknown = 0,
String = 1,
Boolean = 2,
Integer = 3,
Double = 4,
Datetime = 5
}

View file

@ -20,7 +20,7 @@ public interface IVectorDb
=> throw new NotImplementedException();
Task<bool> DeleteCollection(string collectionName)
=> throw new NotImplementedException();
Task<bool> Upsert(string collectionName, Guid id, float[] vector, string text, Dictionary<string, object>? payload = null)
Task<bool> Upsert(string collectionName, Guid id, float[] vector, string text, Dictionary<string, VectorPayloadValue>? payload = null)
=> throw new NotImplementedException();
Task<IEnumerable<VectorCollectionData>> Search(string collectionName, float[] vector, VectorSearchOptions? options = null)
=> throw new NotImplementedException();

View file

@ -3,7 +3,7 @@ namespace BotSharp.Abstraction.VectorStorage.Models;
public class VectorCollectionData
{
public string Id { get; set; }
public Dictionary<string, object> Data { get; set; } = new();
public Dictionary<string, VectorPayloadValue> Data { get; set; } = new();
public double? Score { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]

View file

@ -6,5 +6,5 @@ public class VectorCreateModel
{
public string Text { get; set; }
public string DataSource { get; set; } = VectorDataSource.Api;
public Dictionary<string, object>? Payload { get; set; }
public Dictionary<string, VectorPayloadValue>? Payload { get; set; }
}

View file

@ -0,0 +1,58 @@
using BotSharp.Abstraction.VectorStorage.Enums;
namespace BotSharp.Abstraction.VectorStorage.Models;
public class VectorPayloadValue
{
[JsonPropertyName("data")]
public object Data { get; set; } = null!;
[JsonPropertyName("data_type")]
public VectorPayloadDataType DataType { get; set; } = VectorPayloadDataType.String;
public VectorPayloadValue()
{
}
public VectorPayloadValue(object data, VectorPayloadDataType dataType)
{
Data = data;
DataType = dataType;
}
public override string ToString()
{
return $"{Data} ({DataType})";
}
public static VectorPayloadValue BuildStringValue(string data)
{
return new(data, VectorPayloadDataType.String);
}
public static VectorPayloadValue BuildIntegerValue(long data)
{
return new(data, VectorPayloadDataType.Integer);
}
public static VectorPayloadValue BuildDoubleValue(double data)
{
return new(data, VectorPayloadDataType.Double);
}
public static VectorPayloadValue BuildBooleanValue(bool data)
{
return new(data, VectorPayloadDataType.Boolean);
}
public static VectorPayloadValue BuildDatetimeValue(DateTime data)
{
return new(data, VectorPayloadDataType.Datetime);
}
public static VectorPayloadValue BuildUnkownValue(object data)
{
return new(data, VectorPayloadDataType.Unknown);
}
}

View file

@ -1,3 +1,4 @@
using BotSharp.Abstraction.VectorStorage.Models;
using System.Text.Json.Serialization;
namespace BotSharp.OpenAPI.ViewModels.Knowledges;
@ -11,5 +12,5 @@ public class VectorKnowledgeCreateRequest
public string DataSource { get; set; } = string.Empty;
[JsonPropertyName("payload")]
public Dictionary<string, object>? Payload { get; set; }
public Dictionary<string, VectorPayloadValue>? Payload { get; set; }
}

View file

@ -9,7 +9,7 @@ public class VectorKnowledgeViewModel
public string Id { get; set; }
[JsonPropertyName("data")]
public IDictionary<string, object> Data { get; set; }
public IDictionary<string, VectorPayloadValue> Data { get; set; }
[JsonPropertyName("score")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]

View file

@ -26,9 +26,9 @@ public class MemorizeKnowledgeFn : IFunctionCallback
var result = await knowledgeService.CreateVectorCollectionData(collectionName, new VectorCreateModel
{
Text = args.Question,
Payload = new Dictionary<string, object>
Payload = new Dictionary<string, VectorPayloadValue>
{
{ KnowledgePayloadName.Answer, args.Answer }
{ KnowledgePayloadName.Answer, VectorPayloadValue.BuildStringValue(args.Answer) }
}
});

View file

@ -1,3 +1,4 @@
using BotSharp.Abstraction.VectorStorage.Enums;
using System.Linq;
using Tensorflow.NumPy;
@ -59,7 +60,10 @@ public class MemoryVectorDb : IVectorDb
.Take(options.Limit.GetValueOrDefault())
.Select(i => new VectorCollectionData
{
Data = new Dictionary<string, object> { { "text", _vectors[collectionName][i].Text } },
Data = new Dictionary<string, VectorPayloadValue>
{
{ "text", new(_vectors[collectionName][i].Text, VectorPayloadDataType.String) }
},
Score = similarities[i],
Vector = options.WithVector ? _vectors[collectionName][i].Vector : null,
})
@ -68,7 +72,7 @@ public class MemoryVectorDb : IVectorDb
return await Task.FromResult(results);
}
public async Task<bool> Upsert(string collectionName, Guid id, float[] vector, string text, Dictionary<string, object>? payload = null)
public async Task<bool> Upsert(string collectionName, Guid id, float[] vector, string text, Dictionary<string, VectorPayloadValue>? payload = null)
{
_vectors[collectionName].Add(new VecRecord
{

View file

@ -61,17 +61,17 @@ public partial class KnowledgeService
}
// Save to vector db
var payload = new Dictionary<string, object>()
var payload = new Dictionary<string, VectorPayloadValue>()
{
{ KnowledgePayloadName.DataSource, VectorDataSource.File },
{ KnowledgePayloadName.FileId, fileId.ToString() },
{ KnowledgePayloadName.FileName, file.FileName },
{ KnowledgePayloadName.FileSource, file.FileSource }
{ KnowledgePayloadName.DataSource, VectorPayloadValue.BuildStringValue(VectorDataSource.File) },
{ KnowledgePayloadName.FileId, VectorPayloadValue.BuildStringValue(fileId.ToString()) },
{ KnowledgePayloadName.FileName, VectorPayloadValue.BuildStringValue(file.FileName) },
{ KnowledgePayloadName.FileSource, VectorPayloadValue.BuildStringValue(file.FileSource) }
};
if (!string.IsNullOrWhiteSpace(file.FileUrl))
{
payload[KnowledgePayloadName.FileUrl] = file.FileUrl;
payload[KnowledgePayloadName.FileUrl] = VectorPayloadValue.BuildStringValue(file.FileUrl);
}
var dataIds = await SaveToVectorDb(collectionName, contents, payload);
@ -113,7 +113,7 @@ public partial class KnowledgeService
public async Task<bool> ImportDocumentContentToKnowledge(string collectionName, string fileName, string fileSource,
IEnumerable<string> contents, DocMetaRefData? refData = null, Dictionary<string, object>? payload = null)
IEnumerable<string> contents, DocMetaRefData? refData = null, Dictionary<string, VectorPayloadValue>? payload = null)
{
if (string.IsNullOrWhiteSpace(collectionName)
|| string.IsNullOrWhiteSpace(fileName)
@ -133,7 +133,7 @@ public partial class KnowledgeService
var fileId = Guid.NewGuid();
var contentType = FileUtility.GetFileContentType(fileName);
var innerPayload = new Dictionary<string, object>();
var innerPayload = new Dictionary<string, VectorPayloadValue>();
if (payload != null)
{
foreach (var item in payload)
@ -142,14 +142,14 @@ public partial class KnowledgeService
}
}
innerPayload[KnowledgePayloadName.DataSource] = VectorDataSource.File;
innerPayload[KnowledgePayloadName.FileId] = fileId.ToString();
innerPayload[KnowledgePayloadName.FileName] = fileName;
innerPayload[KnowledgePayloadName.FileSource] = fileSource;
innerPayload[KnowledgePayloadName.DataSource] = VectorPayloadValue.BuildStringValue(VectorDataSource.File);
innerPayload[KnowledgePayloadName.FileId] = VectorPayloadValue.BuildStringValue(fileId.ToString());
innerPayload[KnowledgePayloadName.FileName] = VectorPayloadValue.BuildStringValue(fileName);
innerPayload[KnowledgePayloadName.FileSource] = VectorPayloadValue.BuildStringValue(fileSource);
if (!string.IsNullOrWhiteSpace(refData?.Url))
{
innerPayload[KnowledgePayloadName.FileUrl] = refData.Url;
innerPayload[KnowledgePayloadName.FileUrl] = VectorPayloadValue.BuildStringValue(refData.Url);
}
var dataIds = await SaveToVectorDb(collectionName, contents, innerPayload);
@ -410,7 +410,7 @@ public partial class KnowledgeService
return saved;
}
private async Task<IEnumerable<string>> SaveToVectorDb(string collectionName, IEnumerable<string> contents, Dictionary<string, object>? payload = null)
private async Task<IEnumerable<string>> SaveToVectorDb(string collectionName, IEnumerable<string> contents, Dictionary<string, VectorPayloadValue>? payload = null)
{
if (contents.IsNullOrEmpty())
{
@ -426,7 +426,7 @@ public partial class KnowledgeService
var content = contents.ElementAt(i);
var vector = await textEmbedding.GetVectorAsync(content);
var dataId = Guid.NewGuid();
var saved = await vectorDb.Upsert(collectionName, dataId, vector, content, payload ?? new Dictionary<string, object>());
var saved = await vectorDb.Upsert(collectionName, dataId, vector, content, payload ?? []);
if (!saved) continue;

View file

@ -1,6 +1,6 @@
using BotSharp.Abstraction.Files;
using BotSharp.Abstraction.VectorStorage.Enums;
using BotSharp.Core.Infrastructures;
using Microsoft.AspNetCore.Http.HttpResults;
namespace BotSharp.Plugin.KnowledgeBase.Services;
@ -169,8 +169,8 @@ public partial class KnowledgeService
if (!payload.TryGetValue(KnowledgePayloadName.DataSource, out _))
{
payload[KnowledgePayloadName.DataSource] = !string.IsNullOrWhiteSpace(create.DataSource) ?
create.DataSource : VectorDataSource.Api;
var dataSource = !string.IsNullOrWhiteSpace(create.DataSource) ? create.DataSource : VectorDataSource.Api;
payload[KnowledgePayloadName.DataSource] = VectorPayloadValue.BuildStringValue(dataSource);
}
return await db.Upsert(collectionName, guid, vector, create.Text, payload);
@ -206,8 +206,8 @@ public partial class KnowledgeService
if (!payload.TryGetValue(KnowledgePayloadName.DataSource, out _))
{
payload[KnowledgePayloadName.DataSource] = !string.IsNullOrWhiteSpace(update.DataSource) ?
update.DataSource : VectorDataSource.Api;
var dataSource = !string.IsNullOrWhiteSpace(update.DataSource) ? update.DataSource : VectorDataSource.Api;
payload[KnowledgePayloadName.DataSource] = VectorPayloadValue.BuildStringValue(dataSource);
}
return await db.Upsert(collectionName, guid, vector, update.Text, payload);
@ -247,8 +247,8 @@ public partial class KnowledgeService
if (!payload.TryGetValue(KnowledgePayloadName.DataSource, out _))
{
payload[KnowledgePayloadName.DataSource] = !string.IsNullOrWhiteSpace(update.DataSource) ?
update.DataSource : VectorDataSource.Api;
var dataSource = !string.IsNullOrWhiteSpace(update.DataSource) ? update.DataSource : VectorDataSource.Api;
payload[KnowledgePayloadName.DataSource] = VectorPayloadValue.BuildStringValue(dataSource);
}
return await db.Upsert(collectionName, guid, vector, update.Text, payload);

View file

@ -1,7 +1,5 @@
using BotSharp.Abstraction.Models;
using BotSharp.Abstraction.Options;
using BotSharp.Abstraction.Utilities;
using BotSharp.Abstraction.VectorStorage.Models;
using BotSharp.Plugin.Qdrant.Models;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@ -176,11 +174,11 @@ public class QdrantDb : IVectorDb
Id = x.Id?.Uuid ?? string.Empty,
Data = x.Payload.ToDictionary(p => p.Key, p => p.Value.KindCase switch
{
Value.KindOneofCase.StringValue => p.Value.StringValue,
Value.KindOneofCase.BoolValue => p.Value.BoolValue,
Value.KindOneofCase.IntegerValue => p.Value.IntegerValue,
Value.KindOneofCase.DoubleValue => p.Value.DoubleValue,
_ => new object()
Value.KindOneofCase.StringValue => VectorPayloadValue.BuildStringValue(p.Value.StringValue),
Value.KindOneofCase.BoolValue => VectorPayloadValue.BuildBooleanValue(p.Value.BoolValue),
Value.KindOneofCase.IntegerValue => VectorPayloadValue.BuildIntegerValue(p.Value.IntegerValue),
Value.KindOneofCase.DoubleValue => VectorPayloadValue.BuildDoubleValue(p.Value.DoubleValue),
_ => VectorPayloadValue.BuildUnkownValue(string.Empty)
}),
Vector = filter.WithVector ? x.Vectors?.Vector?.Data?.ToArray() : null
})?.ToList() ?? new List<VectorCollectionData>();
@ -213,19 +211,19 @@ public class QdrantDb : IVectorDb
return points.Select(x => new VectorCollectionData
{
Id = x.Id?.Uuid ?? string.Empty,
Data = x.Payload?.ToDictionary(p => p.Key, p => p.Value.KindCase switch
{
Value.KindOneofCase.StringValue => p.Value.StringValue,
Value.KindOneofCase.BoolValue => p.Value.BoolValue,
Value.KindOneofCase.IntegerValue => p.Value.IntegerValue,
Value.KindOneofCase.DoubleValue => p.Value.DoubleValue,
_ => new object()
Data = x.Payload?.ToDictionary(p => p.Key, p => p.Value.KindCase switch
{
Value.KindOneofCase.StringValue => VectorPayloadValue.BuildStringValue(p.Value.StringValue),
Value.KindOneofCase.BoolValue => VectorPayloadValue.BuildBooleanValue(p.Value.BoolValue),
Value.KindOneofCase.IntegerValue => VectorPayloadValue.BuildIntegerValue(p.Value.IntegerValue),
Value.KindOneofCase.DoubleValue => VectorPayloadValue.BuildDoubleValue(p.Value.DoubleValue),
_ => VectorPayloadValue.BuildUnkownValue(string.Empty)
}) ?? new(),
Vector = x.Vectors?.Vector?.Data?.ToArray()
});
}
public async Task<bool> Upsert(string collectionName, Guid id, float[] vector, string text, Dictionary<string, object>? payload = null)
public async Task<bool> Upsert(string collectionName, Guid id, float[] vector, string text, Dictionary<string, VectorPayloadValue>? payload = null)
{
// Insert vectors
var point = new PointStruct()
@ -245,41 +243,25 @@ public class QdrantDb : IVectorDb
{
foreach (var item in payload)
{
var value = item.Value?.ToString();
var value = item.Value.Data?.ToString();
if (value == null || item.Key.IsEqualTo(KnowledgePayloadName.Text))
{
continue;
}
if (bool.TryParse(value, out var b))
if (item.Value.DataType == VectorPayloadDataType.Boolean && bool.TryParse(value, out var b))
{
point.Payload[item.Key] = b;
}
else if (byte.TryParse(value, out var int8))
else if (item.Value.DataType == VectorPayloadDataType.Integer && long.TryParse(value, out var longVal))
{
point.Payload[item.Key] = int8;
point.Payload[item.Key] = longVal;
}
else if (short.TryParse(value, out var int16))
else if (item.Value.DataType == VectorPayloadDataType.Double && double.TryParse(value, out var doubleVal))
{
point.Payload[item.Key] = int16;
point.Payload[item.Key] = doubleVal;
}
else if (int.TryParse(value, out var int32))
{
point.Payload[item.Key] = int32;
}
else if (long.TryParse(value, out var int64))
{
point.Payload[item.Key] = int64;
}
else if (float.TryParse(value, out var f32))
{
point.Payload[item.Key] = f32;
}
else if (double.TryParse(value, out var f64))
{
point.Payload[item.Key] = f64;
}
else if (DateTime.TryParse(value, out var dt))
else if (item.Value.DataType == VectorPayloadDataType.Datetime && DateTime.TryParse(value, out var dt))
{
point.Payload[item.Key] = dt.ToUniversalTime().ToString("o");
}
@ -325,14 +307,14 @@ public class QdrantDb : IVectorDb
results = points.Select(x => new VectorCollectionData
{
Id = x.Id.Uuid,
Data = x.Payload.ToDictionary(p => p.Key, p => p.Value.KindCase switch
Data = x.Payload?.ToDictionary(p => p.Key, p => p.Value.KindCase switch
{
Value.KindOneofCase.StringValue => p.Value.StringValue,
Value.KindOneofCase.BoolValue => p.Value.BoolValue,
Value.KindOneofCase.IntegerValue => p.Value.IntegerValue,
Value.KindOneofCase.DoubleValue => p.Value.DoubleValue,
_ => new object()
}),
Value.KindOneofCase.StringValue => VectorPayloadValue.BuildStringValue(p.Value.StringValue),
Value.KindOneofCase.BoolValue => VectorPayloadValue.BuildBooleanValue(p.Value.BoolValue),
Value.KindOneofCase.IntegerValue => VectorPayloadValue.BuildIntegerValue(p.Value.IntegerValue),
Value.KindOneofCase.DoubleValue => VectorPayloadValue.BuildDoubleValue(p.Value.DoubleValue),
_ => VectorPayloadValue.BuildUnkownValue(string.Empty)
}) ?? new(),
Score = x.Score,
Vector = x.Vectors?.Vector?.Data?.ToArray()
}).ToList();
@ -659,6 +641,7 @@ public class QdrantDb : IVectorDb
res = PayloadSchemaType.Float;
break;
case "bool":
case "boolean":
res = PayloadSchemaType.Bool;
break;
case "geo":

View file

@ -3,5 +3,7 @@ global using System.Collections.Generic;
global using System.Linq;
global using System.Threading.Tasks;
global using BotSharp.Abstraction.VectorStorage;
global using BotSharp.Abstraction.VectorStorage.Enums;
global using BotSharp.Abstraction.VectorStorage.Models;
global using BotSharp.Abstraction.Knowledges.Enums;
global using BotSharp.Abstraction.Knowledges.Models;

View file

@ -1,5 +1,6 @@
using BotSharp.Abstraction.Utilities;
using BotSharp.Abstraction.VectorStorage;
using BotSharp.Abstraction.VectorStorage.Enums;
using BotSharp.Abstraction.VectorStorage.Models;
using Microsoft.SemanticKernel.Memory;
using System;
@ -73,7 +74,10 @@ namespace BotSharp.Plugin.SemanticKernel
{
resultTexts.Add(new VectorCollectionData
{
Data = new Dictionary<string, object> { { "text", record.Metadata.Text } },
Data = new Dictionary<string, VectorPayloadValue>
{
{ "text", new(record.Metadata.Text, VectorPayloadDataType.String) }
},
Score = score,
Vector = options.WithVector ? record.Embedding.ToArray() : null
});
@ -82,7 +86,7 @@ namespace BotSharp.Plugin.SemanticKernel
return resultTexts;
}
public async Task<bool> Upsert(string collectionName, Guid id, float[] vector, string text, Dictionary<string, object>? payload)
public async Task<bool> Upsert(string collectionName, Guid id, float[] vector, string text, Dictionary<string, VectorPayloadValue>? payload)
{
#pragma warning disable SKEXP0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
await _memoryStore.UpsertAsync(collectionName, MemoryRecord.LocalRecord(id.ToString(), text, null, vector));

View file

@ -71,9 +71,9 @@ public class DbKnowledgeService
await knowledgeService.CreateVectorCollectionData(collectionName, new VectorCreateModel
{
Text = item.Question,
Payload = new Dictionary<string, object>
Payload = new Dictionary<string, VectorPayloadValue>
{
{ KnowledgePayloadName.Answer, item.Answer }
{ KnowledgePayloadName.Answer, VectorPayloadValue.BuildStringValue(item.Answer) },
}
});