1
+ using System . ComponentModel ;
1
2
using System . Text . Json ;
2
3
using Microsoft . SemanticKernel ;
3
4
using Microsoft . SemanticKernel . ChatCompletion ;
4
5
using Neo4j . Driver ;
5
6
6
7
namespace Codeblaze . SemanticKernel . Plugins . Neo4j ;
7
8
8
- public class NeoResult
9
+ /// <summary>
10
+ /// Cypher gen query execution result
11
+ /// </summary>
12
+ public class Neo4jResult
9
13
{
10
14
public bool Success { get ; set ; }
11
15
public string ? Cypher { get ; set ; }
12
16
public List < IRecord > ? Result { get ; set ; }
13
17
}
14
18
15
- // Extend from IKernelPlugin
16
- public class Neo4jPlugin
19
+ /// <summary>
20
+ /// Cypher code gen plugin
21
+ /// </summary>
22
+ public class Neo4jCypherGenPlugin
17
23
{
18
24
private readonly IDriver _driver ;
19
25
private readonly IChatCompletionService _chat ;
20
26
27
+ /// <summary>
28
+ /// Text bases representation of schema for the current database
29
+ /// </summary>
21
30
public string Schema { get ; }
22
31
23
32
private const string NODE_PROPS_QUERY = """
@@ -43,67 +52,49 @@ CALL apoc.meta.data()
43
52
RETURN {source: label, relationship: property, target: other} AS output
44
53
""" ;
45
54
46
- public Neo4jPlugin ( Kernel kernel , string url , string username , string password )
55
+ /// <summary>
56
+ /// Creates a neo4j cypher gen plugin instance
57
+ /// </summary>
58
+ /// <param name="chat">Chat service from semantic kernel to be used as generation backend</param>
59
+ /// <param name="url">Neo4j url, used by the neo4j driver</param>
60
+ /// <param name="username">Neo4j database username, used by the neo4j driver</param>
61
+ /// <param name="password">Neo4j database password, used by the neo4j driver</param>
62
+ public Neo4jCypherGenPlugin ( IChatCompletionService chat , string url , string username , string password )
47
63
{
48
64
_driver = GraphDatabase . Driver ( url , AuthTokens . Basic ( username , password ) ) ;
49
- _chat = kernel . GetRequiredService < IChatCompletionService > ( ) ;
65
+ _chat = chat ;
50
66
51
67
Schema = GetSchema ( ) . GetAwaiter ( ) . GetResult ( ) ;
52
68
}
53
-
54
- private async Task < string > GetSchema ( )
55
- {
56
-
57
- var node_props = JsonSerializer . Serialize ( ( await Query ( NODE_PROPS_QUERY ) ) . Select ( x => x . Values [ "output" ] ) . ToList ( ) ) ;
58
- var rel_props = JsonSerializer . Serialize ( ( await Query ( REL_PROPS_QUERY ) ) . Select ( x => x . Values [ "output" ] ) . ToList ( ) ) ;
59
- var rels = JsonSerializer . Serialize ( ( await Query ( REL_QUERY ) ) . Select ( x => x . Values [ "output" ] ) . ToList ( ) ) ;
60
-
61
- return $ """
62
- This is the schema representation of the Neo4j database.
63
- Node properties are the following:
64
- { node_props }
65
- Relationship properties are the following:
66
- { rel_props }
67
- Relationship point from source to target nodes
68
- { rels }
69
- Make sure to respect relationship types and directions
70
- """ ;
71
- }
72
69
73
- public async Task < string > GenerateCypher ( string prompt )
70
+ /// <summary>
71
+ /// Creates a neo4j cypher gen plugin instance
72
+ /// </summary>
73
+ /// <param name="chat">Chat service from semantic kernel to be used as generation backend</param>
74
+ /// <param name="driver">Neo4j driver to be used for executing cypher</param>
75
+ public Neo4jCypherGenPlugin ( IChatCompletionService chat , IDriver driver )
74
76
{
75
- var system = $ """
76
- Task: Generate Cypher queries to query a Neo4j graph database based on the provided schema definition.
77
- Instructions:
78
- Use only the provided relationship types and properties.
79
- Do not use any other relationship types or properties that are not provided.
80
- If you cannot generate a Cypher statement based on the provided schema, explain the reason to the user.
81
- Schema:
82
- { Schema }
83
-
84
- Note: Do not include any explanations or apologies in your responses.
85
- """ ;
86
-
87
- var history = new ChatHistory
88
- {
89
- new ( AuthorRole . System , system ) ,
90
- new ( AuthorRole . User , prompt )
91
- } ;
92
-
93
- var result = await _chat . GetChatMessageContentsAsync ( history ) ;
77
+ _driver = driver ;
78
+ _chat = chat ;
94
79
95
- return result [ 0 ] . Content ;
80
+ Schema = GetSchema ( ) . GetAwaiter ( ) . GetResult ( ) ;
96
81
}
97
-
98
- public async Task < NeoResult > Run ( string prompt )
82
+
83
+ /// <summary>
84
+ /// SK Function to generate cypher, execute it and return the result
85
+ /// </summary>
86
+ /// <param name="prompt">prompt against which cypher is to be generated</param>
87
+ /// <returns>Result containing, cypher and cypher execution result</returns>
88
+ [ KernelFunction , Description ( "Generates cypher code based on prompt and queries the database" ) ]
89
+ public async Task < Neo4jResult > Query ( string prompt )
99
90
{
100
91
var cypher = await GenerateCypher ( prompt ) ;
101
92
102
93
try
103
94
{
104
- var result = await Query ( cypher ) ;
95
+ var result = await NeoQuery ( cypher ) ;
105
96
106
- return new NeoResult
97
+ return new Neo4jResult
107
98
{
108
99
Success = true ,
109
100
Cypher = cypher ,
@@ -112,11 +103,60 @@ public async Task<NeoResult> Run(string prompt)
112
103
}
113
104
catch
114
105
{
115
- return new NeoResult { Success = false , Cypher = cypher } ;
106
+ return new Neo4jResult { Success = false , Cypher = cypher } ;
116
107
}
117
108
}
118
109
119
- private async Task < List < IRecord > > Query ( string query )
110
+ /// <summary>
111
+ /// SK Function to generate cypher based on the prompt
112
+ /// </summary>
113
+ /// <param name="prompt">prompt against which cypher is to be generated</param>
114
+ /// <returns>Generated cypher</returns>
115
+ [ KernelFunction , Description ( "Generates cypher code based on prompt" ) ]
116
+ public async Task < string > GenerateCypher ( string prompt )
117
+ {
118
+ var system = $ """
119
+ Task: Generate Cypher queries to query a Neo4j graph database based on the provided schema definition.
120
+ Instructions:
121
+ Use only the provided relationship types and properties.
122
+ Do not use any other relationship types or properties that are not provided.
123
+ If you cannot generate a Cypher statement based on the provided schema, explain the reason to the user.
124
+ Schema:
125
+ { Schema }
126
+
127
+ Note: Do not include any explanations or apologies in your responses.
128
+ """ ;
129
+
130
+ var history = new ChatHistory
131
+ {
132
+ new ( AuthorRole . System , system ) ,
133
+ new ( AuthorRole . User , prompt )
134
+ } ;
135
+
136
+ var result = await _chat . GetChatMessageContentsAsync ( history ) ;
137
+
138
+ return result [ 0 ] . Content ;
139
+ }
140
+
141
+ private async Task < string > GetSchema ( )
142
+ {
143
+ var nodeProps = JsonSerializer . Serialize ( ( await NeoQuery ( NODE_PROPS_QUERY ) ) . Select ( x => x . Values [ "output" ] ) . ToList ( ) ) ;
144
+ var relationProps = JsonSerializer . Serialize ( ( await NeoQuery ( REL_PROPS_QUERY ) ) . Select ( x => x . Values [ "output" ] ) . ToList ( ) ) ;
145
+ var relations = JsonSerializer . Serialize ( ( await NeoQuery ( REL_QUERY ) ) . Select ( x => x . Values [ "output" ] ) . ToList ( ) ) ;
146
+
147
+ return $ """
148
+ This is the schema representation of the Neo4j database.
149
+ Node properties are the following:
150
+ { nodeProps }
151
+ Relationship properties are the following:
152
+ { relationProps }
153
+ Relationship point from source to target nodes
154
+ { relations }
155
+ Make sure to respect relationship types and directions
156
+ """ ;
157
+ }
158
+
159
+ private async Task < List < IRecord > > NeoQuery ( string query )
120
160
{
121
161
await using var session = _driver . AsyncSession ( o => o . WithDatabase ( "neo4j" ) ) ;
122
162
@@ -127,6 +167,4 @@ private async Task<List<IRecord>> Query(string query)
127
167
return await cursor . ToListAsync ( ) ;
128
168
} ) ;
129
169
}
130
-
131
-
132
170
}
0 commit comments