forked from microsoft/semantic-kernel
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathQdrantVectorStoreCollectionSearchMapping.cs
120 lines (106 loc) · 5.86 KB
/
QdrantVectorStoreCollectionSearchMapping.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using Microsoft.Extensions.VectorData;
using Qdrant.Client.Grpc;
namespace Microsoft.SemanticKernel.Connectors.Qdrant;
/// <summary>
/// Contains mapping helpers to use when searching for documents using Qdrant.
/// </summary>
internal static class QdrantVectorStoreCollectionSearchMapping
{
#pragma warning disable CS0618 // Type or member is obsolete
/// <summary>
/// Build a Qdrant <see cref="Filter"/> from the provided <see cref="VectorSearchFilter"/>.
/// </summary>
/// <param name="basicVectorSearchFilter">The <see cref="VectorSearchFilter"/> to build a Qdrant <see cref="Filter"/> from.</param>
/// <param name="storagePropertyNames">A mapping of data model property names to the names under which they are stored.</param>
/// <returns>The Qdrant <see cref="Filter"/>.</returns>
/// <exception cref="InvalidOperationException">Thrown when the provided filter contains unsupported types, values or unknown properties.</exception>
public static Filter BuildFromLegacyFilter(VectorSearchFilter basicVectorSearchFilter, IReadOnlyDictionary<string, string> storagePropertyNames)
{
var filter = new Filter();
foreach (var filterClause in basicVectorSearchFilter.FilterClauses)
{
string fieldName;
object filterValue;
// In Qdrant, tag list contains is handled using a keyword match, which is the same as a string equality check.
// We can therefore just extract the field name and value from each clause and handle them the same.
if (filterClause is EqualToFilterClause equalityFilterClause)
{
fieldName = equalityFilterClause.FieldName;
filterValue = equalityFilterClause.Value;
}
else if (filterClause is AnyTagEqualToFilterClause tagListContainsClause)
{
fieldName = tagListContainsClause.FieldName;
filterValue = tagListContainsClause.Value;
}
else
{
throw new InvalidOperationException($"Unsupported filter clause type '{filterClause.GetType().Name}'.");
}
// Get the storage name for the field.
if (!storagePropertyNames.TryGetValue(fieldName, out var storagePropertyName))
{
throw new InvalidOperationException($"Property name '{fieldName}' provided as part of the filter clause is not a valid property name.");
}
// Map DateTimeOffset equality.
if (filterValue is DateTimeOffset dateTimeOffset)
{
var range = new global::Qdrant.Client.Grpc.DatetimeRange
{
Gte = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTimeOffset(dateTimeOffset),
Lte = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTimeOffset(dateTimeOffset),
};
filter.Must.Add(new Condition() { Field = new FieldCondition() { Key = storagePropertyName, DatetimeRange = range } });
continue;
}
// Map each type of filter value to the appropriate Qdrant match type.
var match = filterValue switch
{
string stringValue => new Match { Keyword = stringValue },
int intValue => new Match { Integer = intValue },
long longValue => new Match { Integer = longValue },
bool boolValue => new Match { Boolean = boolValue },
_ => throw new InvalidOperationException($"Unsupported filter value type '{filterValue.GetType().Name}'.")
};
filter.Must.Add(new Condition() { Field = new FieldCondition() { Key = storagePropertyName, Match = match } });
}
return filter;
}
#pragma warning restore CS0618 // Type or member is obsolete
/// <summary>
/// Map the given <see cref="ScoredPoint"/> to a <see cref="VectorSearchResult{TRecord}"/>.
/// </summary>
/// <typeparam name="TRecord">The type of the record to map to.</typeparam>
/// <param name="point">The point to map to a <see cref="VectorSearchResult{TRecord}"/>.</param>
/// <param name="mapper">The mapper to perform the main mapping operation with.</param>
/// <param name="includeVectors">A value indicating whether to include vectors in the mapped result.</param>
/// <param name="databaseSystemName">The name of the database system the operation is being run on.</param>
/// <param name="collectionName">The name of the collection the operation is being run on.</param>
/// <param name="operationName">The type of database operation being run.</param>
/// <returns>The mapped <see cref="VectorSearchResult{TRecord}"/>.</returns>
public static VectorSearchResult<TRecord> MapScoredPointToVectorSearchResult<TRecord>(ScoredPoint point, IVectorStoreRecordMapper<TRecord, PointStruct> mapper, bool includeVectors, string databaseSystemName, string collectionName, string operationName)
{
// Since the mapper doesn't know about scored points, we need to convert the scored point to a point struct first.
var pointStruct = new PointStruct
{
Id = point.Id,
Vectors = point.Vectors,
Payload = { }
};
foreach (KeyValuePair<string, Value> payloadEntry in point.Payload)
{
pointStruct.Payload.Add(payloadEntry.Key, payloadEntry.Value);
}
// Do the mapping with error handling.
return new VectorSearchResult<TRecord>(
VectorStoreErrorHandler.RunModelConversion(
databaseSystemName,
collectionName,
operationName,
() => mapper.MapFromStorageToDataModel(pointStruct, new() { IncludeVectors = includeVectors })),
point.Score);
}
}