const express = require('express');
var bodyParser = require('body-parser')
const neo4j = require('neo4j-driver');
const uri = 'neo4j+s://c51f487a.databases.neo4j.io';
const username = 'neo4j';
const password = 'QwksWJq_DRG8Eg15V9Zm5visciigktn3qZWBvJtgPA8';
const driver = neo4j.driver(uri, require("neo4j-driver").auth.basic(username, password));
const app = express();
const port = 3000;
async function 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 function 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 function 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 function 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();
}
}
app.use(bodyParser.json({
limit: '100mb'
})) // to support JSON-encoded bodies
app.use(bodyParser.urlencoded({ // to support URL-encoded bodies
extended: true,
limit: '100mb'
}))
// Example endpoint for creating parent-child nodes
app.post('/create-nodes', async (req, res) => {
try {
console.log("parent_embedding data type:", typeof req.body.parent_embedding); // Log data type
const nodesCreated = await createParentChildNodes(req.body);
res.json({ message: `Successfully created ${nodesCreated} nodes.` });
} catch (error) {
console.error(error);
res.status(500).json({ message: 'Error creating nodes' });
}
});
// Example endpoint for searching nodes
app.get('/search-nodes', async (req, res) => {
try {
const question = req.query.q; // Expect a query parameter named 'q'
const results = await searchNodes(question);
res.json(results);
} catch (error) {
console.error(error);
res.status(500).json({ message: 'Error searching nodes' });
}
});
app.post('/index-documents', async (req, res) => {
try {
const results = await indexDocuments(req.body.firstParams, req.body.secondParams, req.body.thirdParams);
res.status(200).json({ message: 'Documents indexed successfully.', results });
} catch (error) {
console.error(error);
res.status(500).json({ message: 'Error indexing documents.' });
}
});
app.get('/ping', async (req, res) => {
try {
res.json({ result : "pong"});
} catch (error) {
console.error(error);
res.status(500).json({ message: 'Error searching nodes' });
}
});
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});