diff --git a/examples/src/graph_db_falkordb.ts b/examples/src/graph_db_falkordb.ts new file mode 100644 index 000000000000..b4cf9826aafb --- /dev/null +++ b/examples/src/graph_db_falkordb.ts @@ -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, +}); + +const res = await chain.run("Who played in Pulp Fiction?"); +console.log(res); +// Bruce Willis played in Pulp Fiction. \ No newline at end of file diff --git a/langchain/.env.example b/langchain/.env.example index 2eda74311a41..036e06e9c0fe 100644 --- a/langchain/.env.example +++ b/langchain/.env.example @@ -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 diff --git a/libs/langchain-community/langchain.config.js b/libs/langchain-community/langchain.config.js index b0207b8612ab..a6b1b299f83c 100644 --- a/libs/langchain-community/langchain.config.js +++ b/libs/langchain-community/langchain.config.js @@ -221,6 +221,7 @@ export const config = { // graphs "graphs/neo4j_graph": "graphs/neo4j_graph", "graphs/memgraph_graph": "graphs/memgraph_graph", + "graphs/memgraph_graph": "graphs/falkordb_graph", // document_compressors "document_compressors/ibm": "document_compressors/ibm", // document transformers @@ -450,6 +451,7 @@ export const config = { "cache/upstash_redis", "graphs/neo4j_graph", "graphs/memgraph_graph", + "graphs/falkordb_graph", // document_compressors "document_compressors/ibm", // document_transformers diff --git a/libs/langchain-community/src/graphs/falkordb_graph.ts b/libs/langchain-community/src/graphs/falkordb_graph.ts new file mode 100644 index 000000000000..9e3900eecf64 --- /dev/null +++ b/libs/langchain-community/src/graphs/falkordb_graph.ts @@ -0,0 +1,154 @@ +import { createClient } from "redis"; +import { Graph } from "redisgraph.js"; + +// 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 { + 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 { + 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 { + await this.driver.connect(); // Ensure the Redis client is connected + } + + async refreshSchema(): Promise { + 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 { + 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 { + await this.driver.quit(); + } +} \ No newline at end of file diff --git a/libs/langchain-community/src/graphs/tests/falkordb_graph.int.test.ts b/libs/langchain-community/src/graphs/tests/falkordb_graph.int.test.ts new file mode 100644 index 000000000000..a6103b757950 --- /dev/null +++ b/libs/langchain-community/src/graphs/tests/falkordb_graph.int.test.ts @@ -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); + }); + }); +}); \ No newline at end of file diff --git a/libs/langchain-community/src/load/import_constants.ts b/libs/langchain-community/src/load/import_constants.ts index 722dd82e678b..fdef37989705 100644 --- a/libs/langchain-community/src/load/import_constants.ts +++ b/libs/langchain-community/src/load/import_constants.ts @@ -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",