Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add FalkorDB support #7367

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions examples/src/graph_db_falkordb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { FalkorDBGraph } from "@langchain/community/graphs/falkordb_graph";
import { OpenAI } from "@langchain/llms/openai";
import { GraphCypherQAChain } from "langchain/chains/graph_qa/cypher";

/**
* This example uses FalkorDB database, which is native graph database.
* To set it up follow the instructions on https://docs.falkordb.com/.
*/

const url = "bolt://localhost:6379";

const graph = await FalkorDBGraph.initialize({ url });
const model = new OpenAI({ temperature: 0 });

// Populate the database with two nodes and a relationship
await graph.query(
"CREATE (a:Actor {name:'Bruce Willis'})" +
"-[:ACTED_IN]->(:Movie {title: 'Pulp Fiction'})"
);

const chain = GraphCypherQAChain.fromLLM({
llm: model,
graph,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we'll need to change the GraphCypherQAChain typing here?

});

const res = await chain.run("Who played in Pulp Fiction?");
console.log(res);
// Bruce Willis played in Pulp Fiction.
1 change: 1 addition & 0 deletions langchain/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ NEO4J_PASSWORD=ADD_YOURS_HERE
MEMGRAPH_URI=ADD_YOURS_HERE
MEMGRAPH_USERNAME=ADD_YOURS_HERE
MEMGRAPH_PASSWORD=ADD_YOURS_HERE
FALKORDB_URI=ADD_YOURS_HERE
CLOSEVECTOR_API_KEY=ADD_YOURS_HERE
CLOSEVECTOR_API_SECRET=ADD_YOURS_HERE
GPLACES_API_KEY=ADD_YOURS_HERE
Expand Down
2 changes: 2 additions & 0 deletions libs/langchain-community/langchain.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ export const config = {
// graphs
"graphs/neo4j_graph": "graphs/neo4j_graph",
"graphs/memgraph_graph": "graphs/memgraph_graph",
"graphs/memgraph_graph": "graphs/falkordb_graph",
Naseem77 marked this conversation as resolved.
Show resolved Hide resolved
// document_compressors
"document_compressors/ibm": "document_compressors/ibm",
// document transformers
Expand Down Expand Up @@ -453,6 +454,7 @@ export const config = {
"cache/upstash_redis",
"graphs/neo4j_graph",
"graphs/memgraph_graph",
"graphs/falkordb_graph",
// document_compressors
"document_compressors/ibm",
// document_transformers
Expand Down
154 changes: 154 additions & 0 deletions libs/langchain-community/src/graphs/falkordb_graph.ts
Naseem77 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { createClient } from "redis";
import { Graph } from "redisgraph.js";
Naseem77 marked this conversation as resolved.
Show resolved Hide resolved

// eslint-disable-next-line @typescript-eslint/no-explicit-any

interface FalkorDBGraphConfig {
url: string;
graph?: string;
enhancedSchema?: boolean;
}

interface StructuredSchema {
nodeProps: { [key: string]: string[] };
relProps: { [key: string]: string[] };
relationships: { start: string; type: string; end: string }[];
}

export class FalkorDBGraph {
private driver;
private graph: Graph;
private schema = "";
private structuredSchema: StructuredSchema = {
nodeProps: {},
relProps: {},
relationships: [],
};
private enhancedSchema: boolean;

constructor({ url, graph = "falkordb", enhancedSchema = false }: FalkorDBGraphConfig) {
try {
this.driver = createClient({ url });
this.graph = new Graph(graph); // Initialize the Graph instance
this.enhancedSchema = enhancedSchema;
} catch (error) {
throw new Error(
"Could not create a FalkorDB driver instance. Please check the connection details."
);
}
}

static async initialize(config: FalkorDBGraphConfig): Promise<FalkorDBGraph> {
const graph = new FalkorDBGraph(config);
await graph.verifyConnectivity();
await graph.refreshSchema();
return graph;
}

getSchema(): string {
return this.schema;
}

getStructuredSchema(): StructuredSchema {
return this.structuredSchema;
}

async query(query: string): Promise<any[]> {
const resultSet = await this.graph.query(query); // Run the query
const rows = [];

// Iterate through the ResultSet
while (resultSet.hasNext()) {
const record = resultSet.next(); // Get the next record
const keys = record.keys(); // Get column names
const values = record.values(); // Get values
const obj = Object.fromEntries(keys.map((key, i) => [key, values[i]])); // Map keys to values
rows.push(obj); // Add the object to rows
}

return rows;
}

async verifyConnectivity(): Promise<void> {
await this.driver.connect(); // Ensure the Redis client is connected
}

async refreshSchema(): Promise<void> {
const nodePropertiesQuery = `
MATCH (n)
WITH keys(n) as keys, labels(n) AS labels
UNWIND labels AS label
UNWIND keys AS key
WITH label, collect(DISTINCT key) AS properties
RETURN {label: label, properties: properties} AS output
`;

const relPropertiesQuery = `
MATCH ()-[r]->()
WITH keys(r) as keys, type(r) AS type
UNWIND keys AS key
WITH type, collect(DISTINCT key) AS properties
RETURN {type: type, properties: properties} AS output
`;

const relQuery = `
MATCH (n)-[r]->(m)
UNWIND labels(n) as src_label
UNWIND labels(m) as dst_label
RETURN DISTINCT {start: src_label, type: type(r), end: dst_label} AS output
`;

const nodeProperties = await this.query(nodePropertiesQuery);
const relationshipsProperties = await this.query(relPropertiesQuery);
const relationships = await this.query(relQuery);

this.structuredSchema = {
nodeProps: Object.fromEntries(
nodeProperties.map((el: { output: { label: string; properties: string[] } }) => [el.output.label, el.output.properties])
),
relProps: Object.fromEntries(
relationshipsProperties.map((el: { output: { type: string; properties: string[] } }) => [el.output.type, el.output.properties])
),
relationships: relationships.map((el: { output: { start: string; type: string; end: string } }) => el.output),
};

if (this.enhancedSchema) {
this.enhanceSchemaDetails();
}

this.schema = this.formatSchema();
}

private async enhanceSchemaDetails(): Promise<void> {
console.log("Enhanced schema details not yet implemented for FalkorDB.");
}

private formatSchema(): string {
const { nodeProps, relProps, relationships } = this.structuredSchema;

const formattedNodeProps = Object.entries(nodeProps)
.map(([label, props]) => `${label}: {${props.join(", ")}}`)
.join("\n");

const formattedRelProps = Object.entries(relProps)
.map(([type, props]) => `${type}: {${props.join(", ")}}`)
.join("\n");

const formattedRelationships = relationships
.map((rel) => `(:${rel.start}) -[:${rel.type}]-> (:${rel.end})`)
.join("\n");

return [
"Node properties are the following:",
formattedNodeProps,
"Relationship properties are the following:",
formattedRelProps,
"The relationships are the following:",
formattedRelationships,
].join("\n");
}

async close(): Promise<void> {
await this.driver.quit();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/* eslint-disable no-process-env */

import { test } from "@jest/globals";
import { FalkorDBGraph } from "../falkordb_graph.js";

describe("FalkorDB Graph Tests", () => {
const url = process.env.FALKORDB_URI as string;
let graph: FalkorDBGraph;

beforeEach(async () => {
graph = await FalkorDBGraph.initialize({ url });
await graph.query("MATCH (n) DETACH DELETE n");
});

afterEach(async () => {
await graph.close();
});

test("Test that FalkorDN database is correctly instantiated and connected", async () => {
expect(url).toBeDefined();

// eslint-disable-next-line @typescript-eslint/no-explicit-any
return graph.query('RETURN "test" AS output').then((output: any) => {
const expectedOutput = [{ output: "test" }];
expect(output).toEqual(expectedOutput);
});
});
});
1 change: 1 addition & 0 deletions libs/langchain-community/src/load/import_constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export const optionalImportEntrypoints: string[] = [
"langchain_community/retrievers/zep_cloud",
"langchain_community/graphs/neo4j_graph",
"langchain_community/graphs/memgraph_graph",
"langchain_community/graphs/falkordb_graph",
"langchain_community/document_compressors/ibm",
"langchain_community/document_transformers/html_to_text",
"langchain_community/document_transformers/mozilla_readability",
Expand Down