Skip to content

Commit 3656b97

Browse files
authored
Merge pull request #5 from BLaZeKiLL/wip/neo-memory
Neo4j Memory Store Plugin
2 parents 0b83af7 + 575362b commit 3656b97

17 files changed

+768
-32
lines changed

Codeblaze.SemanticKernel.Api/Codeblaze.SemanticKernel.Api.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
</PropertyGroup>
1010

1111
<ItemGroup>
12-
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.0"/>
13-
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0"/>
12+
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.1" />
13+
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
1414
</ItemGroup>
1515

1616
<ItemGroup>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.0</TargetFramework>
5+
<LangVersion>Latest</LangVersion>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="Microsoft.SemanticKernel" Version="1.1.0" />
12+
<PackageReference Include="Neo4j.Driver" Version="5.16.0" />
13+
</ItemGroup>
14+
15+
<!-- Versioning -->
16+
<ItemGroup>
17+
<PackageReference Include="MinVer" Version="4.3.0">
18+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
19+
<PrivateAssets>all</PrivateAssets>
20+
</PackageReference>
21+
</ItemGroup>
22+
23+
<!-- Nuget Stuff -->
24+
<PropertyGroup>
25+
<PackageId>Codeblaze.SemanticKernel.Connectors.Memory.Neo4j</PackageId>
26+
<Authors>Codeblaze (Devashish Lal)</Authors>
27+
<PackageTags>SemanticKernel;Neo4j;Memory;Embeddings;AI</PackageTags>
28+
<Description>
29+
This package provides a Neo4j Memory store for semantic kernel capable of storing and retriving vector embeddings
30+
</Description>
31+
<RepositoryUrl>https://github.com/BLaZeKiLL/Codeblaze.SemanticKernel</RepositoryUrl>
32+
<RepositoryType>git</RepositoryType>
33+
<PackageReadmeFile>README.md</PackageReadmeFile>
34+
<PackageLicenseFile>LICENSE.md</PackageLicenseFile>
35+
<MinVerTagPrefix>v</MinVerTagPrefix>
36+
</PropertyGroup>
37+
38+
<PropertyGroup>
39+
<IsPackable>true</IsPackable>
40+
<GenerateDocumentationFile>true</GenerateDocumentationFile>
41+
</PropertyGroup>
42+
43+
<PropertyGroup>
44+
<NoWarn>$(NoWarn);1591</NoWarn>
45+
<DocumentationFile>bin/$(Configuration)/$(TargetFramework)/$(AssemblyName).xml</DocumentationFile>
46+
</PropertyGroup>
47+
48+
<ItemGroup>
49+
<None Include="./README.md" Pack="true" PackagePath="/" />
50+
<None Include="../LICENSE.md" Pack="true" PackagePath="$(PackageLicenseFile)" />
51+
</ItemGroup>
52+
</Project>
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
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+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using Microsoft.SemanticKernel.Memory;
2+
3+
namespace Codeblaze.SemanticKernel.Connectors.Memory.Neo4j;
4+
5+
#pragma warning disable SKEXP0003
6+
public static class Neo4jMemoryBuilderExtension
7+
{
8+
public static MemoryBuilder WithNeo4jMemoryStore(this MemoryBuilder builder, string url, string username, string password, INeo4jQueryFactory queryFactory)
9+
{
10+
builder.WithMemoryStore(_ => new Neo4jMemoryStore(url, username, password, queryFactory));
11+
12+
return builder;
13+
}
14+
}
15+
#pragma warning enable SKEXP0003

0 commit comments

Comments
 (0)