Skip to content

Commit

Permalink
Neo Plugin Working
Browse files Browse the repository at this point in the history
  • Loading branch information
BLaZeKiLL committed Dec 11, 2023
1 parent 773a115 commit b4b6b3a
Show file tree
Hide file tree
Showing 7 changed files with 234 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

<ItemGroup>
<ProjectReference Include="..\Codeblaze.SemanticKernel.Connectors.AI.Ollama\Codeblaze.SemanticKernel.Connectors.AI.Ollama.csproj" />
<ProjectReference Include="..\Codeblaze.SemanticKernel.Plugins.Neo4j\Codeblaze.SemanticKernel.Plugins.Neo4j.csproj" />
</ItemGroup>

</Project>
42 changes: 31 additions & 11 deletions Codeblaze.SemanticKernel.Console/Program.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
using Codeblaze.SemanticKernel.Console.Services;
using System.Text.Json;
using Codeblaze.SemanticKernel.Console.Services;
using Codeblaze.SemanticKernel.Plugins.Neo4j;
using Microsoft.Extensions.Configuration;
using Spectre.Console;
using Spectre.Console.Json;

var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
Expand All @@ -9,14 +12,14 @@
AnsiConsole.Write(new FigletText($"{config["Name"]!}").Color(Color.Green));
AnsiConsole.WriteLine("");

KernelService kernel = null;
NeoKernelService kernel = null;

AnsiConsole.Status().Start("Initializing...", ctx =>
{
ctx.Spinner(Spinner.Known.Star);
ctx.SpinnerStyle(Style.Parse("green"));
kernel = new KernelService(config);
kernel = new NeoKernelService(config);
ctx.Status("Initialized");
});
Expand Down Expand Up @@ -55,20 +58,37 @@ async Task Prompt()
{
var prompt = AnsiConsole.Prompt(new TextPrompt<string>("What are you looking to do today ?").PromptStyle("teal"));

var result = string.Empty;
NeoResult result = null;

await AnsiConsole.Status().StartAsync("Processing...", async ctx =>
{
ctx.Spinner(Spinner.Known.Star);
ctx.SpinnerStyle(Style.Parse("green"));
ctx.Status($"Processing input to generate Chat Response");
result = await kernel.BasicPrompt(prompt);
ctx.Status($"Processing input to generate cypher");
result = await kernel.Run(prompt);
});

if (result.Success)
{
AnsiConsole.WriteLine("");
AnsiConsole.Write(new Rule("[cyan]Cypher[/]") { Justification = Justify.Center });
AnsiConsole.WriteLine(result.Cypher);
AnsiConsole.Write(new Panel(new JsonText(JsonSerializer.Serialize(result.Result)))
.Header("Cypher Result")
.Expand()
.RoundedBorder()
.BorderColor(Color.Green));
AnsiConsole.WriteLine("");
}
else
{
AnsiConsole.WriteLine("");
AnsiConsole.Write(new Rule("[red]Cypher[/]") { Justification = Justify.Center });
AnsiConsole.WriteLine(result.Cypher);
AnsiConsole.Write(new Rule("[red]Cypher Execution Error[/]") { Justification = Justify.Center });
AnsiConsole.WriteLine("");
}

AnsiConsole.WriteLine("");
AnsiConsole.Write(new Rule($"[silver]AI Assistant Response[/]") { Justification = Justify.Center });
AnsiConsole.WriteLine(result);
AnsiConsole.Write(new Rule($"[yellow]****[/]") { Justification = Justify.Center });
AnsiConsole.WriteLine("");

}
40 changes: 40 additions & 0 deletions Codeblaze.SemanticKernel.Console/Services/NeoKernelService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Azure.AI.OpenAI;
using Codeblaze.SemanticKernel.Connectors.AI.Ollama;
using Codeblaze.SemanticKernel.Plugins.Neo4j;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;

namespace Codeblaze.SemanticKernel.Console.Services;

public class NeoKernelService
{
private readonly Kernel _Kernel;
private readonly Neo4jPlugin _plugin;

public NeoKernelService(IConfiguration config)
{
var builder = new KernelBuilder();

builder.AddOpenAIChatCompletion(config["OpenAI:Model"], config["OpenAI:Key"]);

_Kernel = builder.Build();

_plugin = new Neo4jPlugin(_Kernel, config["Neo4j:Url"], config["Neo4j:Username"], config["Neo4j:Password"]);
}

public string GetSchema()
{
return _plugin.Schema;
}

public Task<string> GenerateCypher(string prompt)
{
return _plugin.GenerateCypher(prompt);
}

public Task<NeoResult> Run(string prompt)
{
return _plugin.Run(prompt);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.SemanticKernel" Version="1.0.0-rc3" />
<PackageReference Include="Neo4j.Driver" Version="5.15.0" />
</ItemGroup>

</Project>
131 changes: 131 additions & 0 deletions Codeblaze.SemanticKernel.Plugins.Neo4j/Neo4jPlugin.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
using System.Text.Json;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.AI.ChatCompletion;
using Neo4j.Driver;

namespace Codeblaze.SemanticKernel.Plugins.Neo4j;

public class NeoResult
{
public bool Success { get; set; }
public string? Cypher { get; set; }
public List<IRecord>? Result { get; set; }
}

public class Neo4jPlugin
{
private readonly IDriver _driver;
private readonly IChatCompletionService _chat;

public string Schema { get; }

private const string NODE_PROPS_QUERY = """
CALL apoc.meta.data()
YIELD label, other, elementType, type, property
WHERE NOT type = "RELATIONSHIP" AND elementType = "node"
WITH label AS nodeLabels, collect(property) AS properties
RETURN {labels: nodeLabels, properties: properties} AS output
""";

private const string REL_PROPS_QUERY = """
CALL apoc.meta.data()
YIELD label, other, elementType, type, property
WHERE NOT type = "RELATIONSHIP" AND elementType = "relationship"
WITH label AS nodeLabels, collect(property) AS properties
RETURN {type: nodeLabels, properties: properties} AS output
""";

private const string REL_QUERY = """
CALL apoc.meta.data()
YIELD label, other, elementType, type, property
WHERE type = "RELATIONSHIP" AND elementType = "node"
RETURN {source: label, relationship: property, target: other} AS output
""";

public Neo4jPlugin(Kernel kernel, string url, string username, string password)
{
_driver = GraphDatabase.Driver(url, AuthTokens.Basic(username, password));
_chat = kernel.GetRequiredService<IChatCompletionService>();

Schema = GetSchema().GetAwaiter().GetResult();
}

private async Task<string> GetSchema()
{

var node_props = JsonSerializer.Serialize((await Query(NODE_PROPS_QUERY)).Select(x => x.Values["output"]).ToList());
var rel_props = JsonSerializer.Serialize((await Query(REL_PROPS_QUERY)).Select(x => x.Values["output"]).ToList());
var rels = JsonSerializer.Serialize((await Query(REL_QUERY)).Select(x => x.Values["output"]).ToList());

return $"""
This is the schema representation of the Neo4j database.
Node properties are the following:
{node_props}
Relationship properties are the following:
{rel_props}
Relationship point from source to target nodes
{rels}
Make sure to respect relationship types and directions
""";
}

public async Task<string> GenerateCypher(string prompt)
{
var system = $"""
Task: Generate Cypher queries to query a Neo4j graph database based on the provided schema definition.
Instructions:
Use only the provided relationship types and properties.
Do not use any other relationship types or properties that are not provided.
If you cannot generate a Cypher statement based on the provided schema, explain the reason to the user.
Schema:
{Schema}

Note: Do not include any explanations or apologies in your responses.
""";

var history = new ChatHistory
{
new(AuthorRole.System, system),
new(AuthorRole.User, prompt)
};

var result = await _chat.GetChatMessageContentsAsync(history);

return result[0].Content;
}

public async Task<NeoResult> Run(string prompt)
{
var cypher = await GenerateCypher(prompt);

try
{
var result = await Query(cypher);

return new NeoResult
{
Success = true,
Cypher = cypher,
Result = result
};
}
catch
{
return new NeoResult { Success = false, Cypher = cypher };
}
}

private async Task<List<IRecord>> Query(string query)
{
await using var session = _driver.AsyncSession(o => o.WithDatabase("neo4j"));

return await session.ExecuteReadAsync(async transaction =>
{
var cursor = await transaction.RunAsync(query);
return await cursor.ToListAsync();
});
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;

namespace Codeblaze.SemanticKernel.Plugins.Neo4j;

public static class Neo4jPluginBuilderExtension
{
public static void AddNeo4jPlugin(this IServiceCollection services, string url, string username, string password)
{
}
}
6 changes: 6 additions & 0 deletions Codeblaze.SemanticKernel.sln
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codeblaze.SemanticKernel.Ap
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codeblaze.SemanticKernel.Console", "Codeblaze.SemanticKernel.Console\Codeblaze.SemanticKernel.Console.csproj", "{AC32C115-684E-408C-9320-8D915FF7E3A1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codeblaze.SemanticKernel.Plugins.Neo4j", "Codeblaze.SemanticKernel.Plugins.Neo4j\Codeblaze.SemanticKernel.Plugins.Neo4j.csproj", "{59813D42-7792-4750-AD89-0548812BAD9E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -24,5 +26,9 @@ Global
{AC32C115-684E-408C-9320-8D915FF7E3A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AC32C115-684E-408C-9320-8D915FF7E3A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AC32C115-684E-408C-9320-8D915FF7E3A1}.Release|Any CPU.Build.0 = Release|Any CPU
{59813D42-7792-4750-AD89-0548812BAD9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{59813D42-7792-4750-AD89-0548812BAD9E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{59813D42-7792-4750-AD89-0548812BAD9E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{59813D42-7792-4750-AD89-0548812BAD9E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

0 comments on commit b4b6b3a

Please sign in to comment.