1
+ using Microsoft . SemanticKernel ;
2
+
3
+ namespace Codeblaze . SemanticKernel . Connectors . Memory . Neo4j ;
4
+
5
+ public interface INeo4jQueryFactory
6
+ {
7
+ public string TextProperty { get ; }
8
+ public string IndexProperty { get ; }
9
+ ( string , object ) ListIndexQuery ( ) ;
10
+ ( string , object ) CreateIndexQuery ( string collectionName ) ;
11
+ ( string , object ) DropIndexQuery ( string collectionName ) ;
12
+ ( string , object ) UpsertQuery ( string collectionName , int key , ReadOnlyMemory < float > embedding ) ;
13
+ ( string , object ) UpsertBatchQuery ( string collectionName , IEnumerable < int > keys ,
14
+ IEnumerable < ReadOnlyMemory < float > > embeddings ) ;
15
+ ( string , object ) GetQuery ( string collectionName , int keys ) ;
16
+ ( string , object ) GetBatchQuery ( string collectionName , IEnumerable < int > keys ) ;
17
+ ( string , object ) RemoveQuery ( string collectionName , int keys ) ;
18
+ ( string , object ) RemoveBatchQuery ( string collectionName , IEnumerable < int > keys ) ;
19
+ ( string , object ) GetNearestMatchQuery ( string collectionName , ReadOnlyMemory < float > embedding ,
20
+ double minRelevanceScore , int limit = 1 ) ;
21
+ }
22
+
23
+ public abstract class Neo4jVectorQueries
24
+ {
25
+ public const string ListIndexQuery = "SHOW VECTOR INDEXES YIELD name" ;
26
+
27
+ public const string CreateIndexQuery = """
28
+ CREATE VECTOR INDEX `$name`
29
+ FOR (n:$node) ON (n.$indexProperty)
30
+ OPTIONS {indexConfig: {
31
+ `vector.dimensions`: $dimensions,
32
+ `vector.similarity_function`: 'cosine'
33
+ }
34
+ """ ;
35
+
36
+ public const string DropIndexQuery = "DROP INDEX $name" ;
37
+
38
+ public const string GetNearestMatchQuery = """
39
+ CALL db.index.vector.queryNodes($name, $limit, $embedding)
40
+ YIELD node, score
41
+ WHERE score >= $minRelevanceScore
42
+ RETURN node.id AS id, score
43
+ """ ;
44
+
45
+ public const string UpsertQuery = """
46
+ MATCH (n {id: $id})
47
+ CALL db.create.setNodeVectorProperty(n, $indexProperty, $embedding)
48
+ RETURN node.id AS id
49
+ """ ;
50
+
51
+ public const string UpsertBatchQuery = """
52
+ UNWIND $updates AS update,
53
+ MATCH (n {id: update.id})
54
+ CALL db.create.setNodeVectorProperty(n, $indexProperty, update.embedding)
55
+ RETURN node.id AS id
56
+ """ ;
57
+
58
+ public const string GetQuery = """
59
+ MATCH (n {id: $id})
60
+ RETURN n
61
+ """ ;
62
+
63
+ public const string GetBatchQuery = """
64
+ UNWIND $ids AS id
65
+ MATCH (n {id: id})
66
+ RETURN n
67
+ """ ;
68
+
69
+ public const string RemoveQuery = """
70
+ MATCH (n {id: $id})
71
+ DELETE n
72
+ """ ;
73
+
74
+ public const string RemoveBatchQuery = """
75
+ UNWIND $ids AS id
76
+ MATCH (n {id: id})
77
+ DELETE n
78
+ """ ;
79
+ }
80
+
81
+ /// <summary>
82
+ /// Neo4j 5.15 or above is supported by this query factory, vector functionality was introduced in an earlier version of neo4j
83
+ /// If you are using an old version you may need to implement some of the queries below using old cypher, refer neo4j docs
84
+ /// </summary>
85
+ /// <param name="name">Index name</param>
86
+ /// <param name="node">Node on which index is created</param>
87
+ /// <param name="indexProperty">Property of the node on which index is created</param>
88
+ /// <param name="dimensions">Vector index dimensions</param>
89
+ public class Neo4jVectorIndexQueryFactory ( string name , string node , string indexProperty , string textProperty , int dimensions )
90
+ : INeo4jQueryFactory
91
+ {
92
+ /// <summary>
93
+ /// This can be updated at runtime to pass different values to the query factory
94
+ /// To use dynamic properties the default query methods need to be overriden
95
+ /// and the dynamic property must be returned along with the new query
96
+ /// </summary>
97
+ public IDictionary < string , object > DynamicProperties { get ; } = new Dictionary < string , object > ( ) ;
98
+
99
+ public string TextProperty => textProperty ;
100
+ public string IndexProperty => indexProperty ;
101
+
102
+ public virtual ( string , object ) ListIndexQuery ( )
103
+ {
104
+ return ( Neo4jVectorQueries . ListIndexQuery , null ) ;
105
+ }
106
+
107
+ public virtual ( string , object ) CreateIndexQuery ( string collectionName )
108
+ {
109
+ if ( name != collectionName )
110
+ throw new KernelException (
111
+ $ "Kernel passed { collectionName } index but query factory is configured with { name } index") ;
112
+
113
+ return ( Neo4jVectorQueries . CreateIndexQuery , new
114
+ {
115
+ name , node , indexProperty , dimensions
116
+ }
117
+ ) ;
118
+ }
119
+
120
+ public virtual ( string , object ) DropIndexQuery ( string collectionName )
121
+ {
122
+ if ( name != collectionName )
123
+ throw new KernelException (
124
+ $ "Kernel passed { collectionName } index but query factory is configured with { name } index") ;
125
+
126
+ return ( Neo4jVectorQueries . DropIndexQuery , new { name } ) ;
127
+ }
128
+
129
+ public ( string , object ) UpsertQuery ( string collectionName , int key , ReadOnlyMemory < float > embedding )
130
+ {
131
+ if ( name != collectionName )
132
+ throw new KernelException (
133
+ $ "Kernel passed { collectionName } index but query factory is configured with { name } index") ;
134
+
135
+ return ( Neo4jVectorQueries . UpsertQuery , new { id = key , embedding } ) ;
136
+ }
137
+
138
+ public ( string , object ) UpsertBatchQuery ( string collectionName , IEnumerable < int > keys , IEnumerable < ReadOnlyMemory < float > > embeddings )
139
+ {
140
+ if ( name != collectionName )
141
+ throw new KernelException (
142
+ $ "Kernel passed { collectionName } index but query factory is configured with { name } index") ;
143
+
144
+ var updates = keys . Zip ( embeddings , ( id , embedding ) => new { id , embedding } ) . ToArray ( ) ;
145
+
146
+ return ( Neo4jVectorQueries . UpsertBatchQuery , new { updates } ) ;
147
+ }
148
+
149
+ public virtual ( string , object ) GetQuery ( string collectionName , int key )
150
+ {
151
+ if ( name != collectionName )
152
+ throw new KernelException (
153
+ $ "Kernel passed { collectionName } index but query factory is configured with { name } index") ;
154
+
155
+ return ( Neo4jVectorQueries . GetQuery , new { id = key } ) ;
156
+ }
157
+
158
+ public virtual ( string , object ) GetBatchQuery ( string collectionName , IEnumerable < int > keys )
159
+ {
160
+ if ( name != collectionName )
161
+ throw new KernelException (
162
+ $ "Kernel passed { collectionName } index but query factory is configured with { name } index") ;
163
+
164
+ return ( Neo4jVectorQueries . GetBatchQuery , new { ids = keys . ToArray ( ) } ) ;
165
+ }
166
+
167
+ public virtual ( string , object ) RemoveQuery ( string collectionName , int key )
168
+ {
169
+ if ( name != collectionName )
170
+ throw new KernelException (
171
+ $ "Kernel passed { collectionName } index but query factory is configured with { name } index") ;
172
+
173
+ return ( Neo4jVectorQueries . RemoveQuery , new { id = key } ) ;
174
+ }
175
+
176
+ public virtual ( string , object ) RemoveBatchQuery ( string collectionName , IEnumerable < int > keys )
177
+ {
178
+ if ( name != collectionName )
179
+ throw new KernelException (
180
+ $ "Kernel passed { collectionName } index but query factory is configured with { name } index") ;
181
+
182
+ return ( Neo4jVectorQueries . RemoveBatchQuery , new { ids = keys } ) ;
183
+ }
184
+
185
+ public virtual ( string , object ) GetNearestMatchQuery ( string collectionName , ReadOnlyMemory < float > embedding ,
186
+ double minRelevanceScore , int limit = 1 )
187
+ {
188
+ if ( name != collectionName )
189
+ throw new KernelException (
190
+ $ "Kernel passed { collectionName } index but query factory is configured with { name } index") ;
191
+
192
+ return ( Neo4jVectorQueries . GetNearestMatchQuery , new
193
+ {
194
+ name ,
195
+ limit ,
196
+ embedding = embedding . ToArray ( ) ,
197
+ minRelevanceScore
198
+ } ) ;
199
+ }
200
+ }
0 commit comments