Skip to content

Commit 33e8414

Browse files
authored
Merge pull request #2 from BLaZeKiLL/feat/cypher-plugin
Neo4j Cypher Code Gen Plugin
2 parents e12389a + 6d9311f commit 33e8414

File tree

7 files changed

+216
-89
lines changed

7 files changed

+216
-89
lines changed

Codeblaze.SemanticKernel.Connectors.Ollama/README.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ Supports
77
> :warning: **Embedding generation**: Is experimental in the semantic kernel.
88
99
### Quick Start
10-
- Install from [nuget](https://www.nuget.org/packages/Codeblaze.SemanticKernel.Connectors.AI.Ollama)
10+
- Install from [nuget](https://www.nuget.org/packages/Codeblaze.SemanticKernel.Connectors.Ollama)
1111
```
12-
dotnet add package Codeblaze.SemanticKernel.Connectors.AI.Ollama
12+
dotnet add package Codeblaze.SemanticKernel.Connectors.Ollama
1313
```
1414
- Text Generation
1515
1616
Configure the kernel
17-
```cs
18-
var builder = new KernelBuilder();
17+
```csharp
18+
var builder = Kernel.CreateBuilder();
1919
2020
// provide the HTTP client used to interact with Ollama API
2121
builder.Services.AddTransient<HttpClient>();
@@ -29,7 +29,7 @@ Supports
2929
```
3030
3131
Usage
32-
```cs
32+
```csharp
3333
const string prompt = """
3434
Bot: How can I help you?
3535
User: {{$input}}
@@ -50,7 +50,7 @@ Supports
5050
- Chat Completion
5151
5252
Configure the kernel
53-
```cs
53+
```csharp
5454
var builder = new KernelBuilder();
5555
5656
// provide the HTTP client used to interact with Ollama API
@@ -65,7 +65,7 @@ Supports
6565
```
6666
6767
Usage
68-
```cs
68+
```csharp
6969
var chat = _Kernel.GetRequiredService<IChatCompletionService>();
7070
7171
var history = new ChatHistory();
@@ -86,7 +86,7 @@ Supports
8686
- Embedding Generation (Experimental)
8787
8888
Configure the kernel
89-
```cs
89+
```csharp
9090
var builder = new KernelBuilder();
9191
9292
// provide the HTTP client used to interact with Ollama API
@@ -103,7 +103,7 @@ Supports
103103
```
104104
105105
Usage
106-
```cs
106+
```csharp
107107
var memory = _Kernel.GetRequiredService<ISemanticTextMemory>();
108108
109109
// This will internally call Ollama embedding service to generate embeddings

Codeblaze.SemanticKernel.Console/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ async Task Prompt()
5858
{
5959
var prompt = AnsiConsole.Prompt(new TextPrompt<string>("What are you looking to do today ?").PromptStyle("teal"));
6060

61-
NeoResult result = null;
61+
Neo4jResult result = null;
6262

6363
await AnsiConsole.Status().StartAsync("Processing...", async ctx =>
6464
{
Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,37 @@
1+
using Codeblaze.SemanticKernel.Connectors.Ollama;
12
using Codeblaze.SemanticKernel.Plugins.Neo4j;
23
using Microsoft.Extensions.Configuration;
4+
using Microsoft.Extensions.DependencyInjection;
35
using Microsoft.SemanticKernel;
46

57
namespace Codeblaze.SemanticKernel.Console.Services;
68

79
public class NeoKernelService
810
{
911
private readonly Kernel _Kernel;
10-
private readonly Neo4jPlugin _plugin;
1112

1213
public NeoKernelService(IConfiguration config)
1314
{
1415
var builder = Kernel.CreateBuilder();
1516

17+
// builder.Services.AddTransient<HttpClient>();
18+
1619
builder.AddOpenAIChatCompletion(config["OpenAI:Model"], config["OpenAI:Key"]);
17-
20+
// builder.AddOllamaChatCompletion(config["Ollama:Model"], config["Ollama:BaseUrlGeneration"]);
21+
1822
_Kernel = builder.Build();
19-
20-
_plugin = new Neo4jPlugin(_Kernel, config["Neo4j:Url"], config["Neo4j:Username"], config["Neo4j:Password"]);
21-
}
22-
23-
public string GetSchema()
24-
{
25-
return _plugin.Schema;
26-
}
27-
28-
public Task<string> GenerateCypher(string prompt)
29-
{
30-
return _plugin.GenerateCypher(prompt);
23+
24+
_Kernel.AddNeo4jCypherGenPlugin(config["Neo4j:Url"], config["Neo4j:Username"], config["Neo4j:Password"]);
3125
}
3226

33-
public Task<NeoResult> Run(string prompt)
27+
public Task<Neo4jResult?> Run(string prompt)
3428
{
35-
return _plugin.Run(prompt);
29+
return _Kernel.InvokeAsync<Neo4jResult>(
30+
nameof(Neo4jCypherGenPlugin), "Query",
31+
new KernelArguments
32+
{
33+
{ "prompt", prompt }
34+
}
35+
);
3636
}
3737
}
Lines changed: 91 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,32 @@
1+
using System.ComponentModel;
12
using System.Text.Json;
23
using Microsoft.SemanticKernel;
34
using Microsoft.SemanticKernel.ChatCompletion;
45
using Neo4j.Driver;
56

67
namespace Codeblaze.SemanticKernel.Plugins.Neo4j;
78

8-
public class NeoResult
9+
/// <summary>
10+
/// Cypher gen query execution result
11+
/// </summary>
12+
public class Neo4jResult
913
{
1014
public bool Success { get; set; }
1115
public string? Cypher { get; set; }
1216
public List<IRecord>? Result { get; set; }
1317
}
1418

15-
// Extend from IKernelPlugin
16-
public class Neo4jPlugin
19+
/// <summary>
20+
/// Cypher code gen plugin
21+
/// </summary>
22+
public class Neo4jCypherGenPlugin
1723
{
1824
private readonly IDriver _driver;
1925
private readonly IChatCompletionService _chat;
2026

27+
/// <summary>
28+
/// Text bases representation of schema for the current database
29+
/// </summary>
2130
public string Schema { get; }
2231

2332
private const string NODE_PROPS_QUERY = """
@@ -43,67 +52,49 @@ CALL apoc.meta.data()
4352
RETURN {source: label, relationship: property, target: other} AS output
4453
""";
4554

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)
4763
{
4864
_driver = GraphDatabase.Driver(url, AuthTokens.Basic(username, password));
49-
_chat = kernel.GetRequiredService<IChatCompletionService>();
65+
_chat = chat;
5066

5167
Schema = GetSchema().GetAwaiter().GetResult();
5268
}
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-
}
7269

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)
7476
{
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;
9479

95-
return result[0].Content;
80+
Schema = GetSchema().GetAwaiter().GetResult();
9681
}
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)
9990
{
10091
var cypher = await GenerateCypher(prompt);
10192

10293
try
10394
{
104-
var result = await Query(cypher);
95+
var result = await NeoQuery(cypher);
10596

106-
return new NeoResult
97+
return new Neo4jResult
10798
{
10899
Success = true,
109100
Cypher = cypher,
@@ -112,11 +103,60 @@ public async Task<NeoResult> Run(string prompt)
112103
}
113104
catch
114105
{
115-
return new NeoResult { Success = false, Cypher = cypher };
106+
return new Neo4jResult { Success = false, Cypher = cypher };
116107
}
117108
}
118109

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)
120160
{
121161
await using var session = _driver.AsyncSession(o => o.WithDatabase("neo4j"));
122162

@@ -127,6 +167,4 @@ private async Task<List<IRecord>> Query(string query)
127167
return await cursor.ToListAsync();
128168
});
129169
}
130-
131-
132170
}
Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,26 @@
1-
using Microsoft.Extensions.DependencyInjection;
1+
using Microsoft.SemanticKernel;
2+
using Microsoft.SemanticKernel.ChatCompletion;
3+
using Neo4j.Driver;
24

35
namespace Codeblaze.SemanticKernel.Plugins.Neo4j;
46

5-
// Extend from IKernelBuilderPlugins
67
public static class Neo4jPluginBuilderExtension
78
{
8-
public static void AddNeo4jPlugin(this IServiceCollection services, string url, string username, string password)
9+
public static void AddNeo4jCypherGenPlugin(this Kernel kernel, string url, string username, string password)
910
{
11+
var chat = kernel.GetRequiredService<IChatCompletionService>();
1012

13+
var plugin = new Neo4jCypherGenPlugin(chat, url, username, password);
14+
15+
kernel.ImportPluginFromObject(plugin, nameof(Neo4jCypherGenPlugin));
16+
}
17+
18+
public static void AddNeo4jCypherGenPlugin(this Kernel kernel, IDriver driver)
19+
{
20+
var chat = kernel.GetRequiredService<IChatCompletionService>();
21+
22+
var plugin = new Neo4jCypherGenPlugin(chat, driver);
23+
24+
kernel.ImportPluginFromObject(plugin, nameof(Neo4jCypherGenPlugin));
1125
}
1226
}

0 commit comments

Comments
 (0)