const neo4j = require('neo4j-driver');
const uri = process.env.NEO4J_URI || 'neo4j+s://c51f487a.databases.neo4j.io';
const username = process.env.NEO4J_USERNAME || 'neo4j';
const password = process.env.NEO4J_PASSWORD || 'QwksWJq_DRG8Eg15V9Zm5visciigktn3qZWBvJtgPA8';
const driver = neo4j.driver(uri, neo4j.auth.basic(username, password));
class Neo4jService {
constructor() {}
async createParentChildNodes(params) {
const session = driver.session();
try {
const result = await session.run(
`MERGE (p:Parent {id: $parent_id})
SET p.text = $parent_text, p.embedding = $parent_embedding
WITH p
UNWIND $children AS child
MERGE (c:Child {id: child.id})
SET c.text = child.text, c.embedding = child.embedding
MERGE (c)-[:HAS_PARENT]->(p)
RETURN count(*)`,
params
);
return result.records[0].get(0).toInt(); // Get the count of created nodes
} catch (error) {
console.error(error);
throw error; // Re-throw for proper error handling
} finally {
await session.close();
}
}
async searchNodes(question) {
const session = driver.session();
try {
const result = await session.run(
`CALL db.index.fulltext.queryNodes("nodeSearchIndex", $question)
YIELD node, score
RETURN node.text AS text, score
ORDER BY score DESC
LIMIT 5`,
{ question }
);
return result.records.map((record) => ({
text: record.get('text'),
score: record.get('score'),
}));
} catch (error) {
console.error(error);
throw error; // Re-throw for proper error handling
} finally {
await session.close();
}
}
async indexDocuments(firstParams, secondParams, thirdParams) {
const session = driver.session();
if(firstParams) {
try {
await session.run(`
MERGE (p:Parent {id: $parent_id})
SET p.text = $parent_text
WITH p
CALL apoc.create.setProperty(p, 'embedding', $parent_embedding)
YIELD node
WITH p
UNWIND $children AS child
MERGE (c:Child {id: child.id})
SET c.text = child.text
MERGE (c)<-[:HAS_CHILD]-(p)
WITH c, child
CALL apoc.create.setProperty(c, 'embedding', child.embedding)
YIELD node
RETURN count(*)
`, firstParams);
// Create vector index for child (assuming it's not already created)
try {
await session.run(`
CALL db.index.fulltext.createNodeIndex('parent_document', ['Child'], ['embedding'], {indexConfig: {provider: 'lucene+native-3.0', similarity: 'cosine'}})
`);
} catch (e) {
// Index already exists, ignore the error
}
// Create vector index for parents (assuming it's not already created)
try {
await session.run(`
CALL db.index.fulltext.createNodeIndex('typical_rag', ['Parent'], ['embedding'], {indexConfig: {provider: 'lucene+native-3.0', similarity: 'cosine'}})
`);
} catch (e) {
// Index already exists, ignore the error
}
} catch (error) {
console.error(error);
throw error;
} finally {
await session.close();
}
}
// Ingest hypothetical questions
if(secondParams) {
await session.run(`
MERGE (p:Parent {id: $parent_id})
WITH p
UNWIND $questions AS question
CREATE (q:Question {id: question.id})
SET q.text = question.text
MERGE (q)<-[:HAS_QUESTION]-(p)
WITH q, question
CALL apoc.create.setProperty(q, 'embedding', question.embedding)
YIELD node
RETURN count(*)
`, secondParams);
// Create vector index for questions
try {
await session.run(`
CALL db.index.fulltext.createNodeIndex('hypothetical_questions', ['Question'], ['embedding'], {indexConfig: {provider: 'lucene+native-3.0', similarity: 'cosine'}})
`);
} catch (e) {
// Index already exists, ignore the error
}
}
if(thirdParams) {
// Ingest summaries
await session.run(`
MERGE (p:Parent {id: $parent_id})
MERGE (p)-[:HAS_SUMMARY]->(s:Summary)
SET s.text = $summary
WITH s
CALL apoc.create.setProperty(s, 'embedding', $embedding)
YIELD node
RETURN count(*)
`, thirdParams);
// Create vector index for summaries
try {
await session.run(`
CALL db.index.fulltext.createNodeIndex('summary', ['Summary'], ['embedding'], {indexConfig: {provider: 'lucene+native-3.0', similarity: 'cosine'}})
`);
} catch (e) {
// Index already exists, ignore the error
}
}
}
async getAnswers(question) {
const questionEmbedding = await this.openAIService.generateEmbedding(question);
const session = this.driver.session();
try {
const result = await session.run(`
CALL db.index.fulltext.queryNodes('parent_document', $question)
YIELD node, score
RETURN node.text AS text, score
ORDER BY score DESC
LIMIT 5
`, { question: questionEmbedding });
return result.records.map(record => record.toObject());
} catch (error) {
console.error(error);
throw error;
} finally {
await session.close();
}
}
}
module.exports = Neo4jService;