Skip to content

New Graph query changes #586

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

Merged
merged 19 commits into from
Jul 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
67 changes: 3 additions & 64 deletions backend/src/graph_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,11 @@
from neo4j import GraphDatabase
import os
import json
from src.shared.constants import GRAPH_CHUNK_LIMIT,GRAPH_QUERY
# from neo4j.debug import watch

# watch("neo4j")

QUERY_MAP = {
"document" : " + [docs] ",
"chunks" : " + collect { MATCH p=(c)-[:NEXT_CHUNK]-() RETURN p } + collect { MATCH p=(c)-[:SIMILAR]-() RETURN p } ",
"entities" : " + collect { OPTIONAL MATCH (c:Chunk)-[:HAS_ENTITY]->(e), p=(e)-[*0..1]-(:!Chunk) RETURN p }",
"docEntities" : " + [docs] + collect { MATCH (c:Chunk)-[:HAS_ENTITY]->(e), p=(e)--(:!Chunk) RETURN p }",
"docChunks" : " + [chunks] + collect { MATCH p=(c)-[:FIRST_CHUNK]-() RETURN p } + collect { MATCH p=(c)-[:NEXT_CHUNK]-() RETURN p } + collect { MATCH p=(c)-[:SIMILAR]-() RETURN p } ",
"chunksEntities" : " + collect { MATCH p=(c)-[:NEXT_CHUNK]-() RETURN p } + collect { MATCH p=(c)-[:SIMILAR]-() RETURN p } + collect { OPTIONAL MATCH p=(c:Chunk)-[:HAS_ENTITY]->(e)-[*0..1]-(:!Chunk) RETURN p }",
"docChunkEntities" : " + [chunks] + collect { MATCH p=(c)-[:FIRST_CHUNK]-() RETURN p } + collect { MATCH p=(c)-[:NEXT_CHUNK]-() RETURN p } + collect { MATCH p=(c)-[:SIMILAR]-() RETURN p } + collect { OPTIONAL MATCH p=(c:Chunk)-[:HAS_ENTITY]->(e)-[*0..1]-(:!Chunk) RETURN p }"
}

QUERY_WITH_DOCUMENT = """
MATCH docs = (d:Document)
WHERE d.fileName IN $document_names
WITH docs, d ORDER BY d.createdAt DESC
CALL {{ WITH d
OPTIONAL MATCH chunks=(d)<-[:PART_OF]-(c:Chunk)
RETURN chunks, c LIMIT 50
}}
WITH [] {query_to_change} AS paths
CALL {{ WITH paths UNWIND paths AS path UNWIND nodes(path) as node RETURN collect(distinct node) as nodes }}
CALL {{ WITH paths UNWIND paths AS path UNWIND relationships(path) as rel RETURN collect(distinct rel) as rels }}
RETURN nodes, rels
"""

QUERY_WITHOUT_DOCUMENT = """
MATCH docs = (d:Document)
WITH docs, d ORDER BY d.createdAt DESC
LIMIT $doc_limit
CALL {{ WITH d
OPTIONAL MATCH chunks=(d)<-[:PART_OF]-(c:Chunk)
RETURN chunks, c LIMIT 50
}}
WITH [] {query_to_change} AS paths
CALL {{ WITH paths UNWIND paths AS path UNWIND nodes(path) as node RETURN collect(distinct node) as nodes }}
CALL {{ WITH paths UNWIND paths AS path UNWIND relationships(path) as rel RETURN collect(distinct rel) as rels }}
RETURN nodes, rels
"""

def get_graphDB_driver(uri, username, password):
"""
Creates and returns a Neo4j database driver instance configured with the provided credentials.
Expand All @@ -68,29 +31,6 @@ def get_graphDB_driver(uri, username, password):
# raise Exception(error_message) from e


def get_cypher_query(query_map, query_type, document_names):
"""
Generates a Cypher query based on the provided parameters using global templates.

Returns:
str: A Cypher query string ready to be executed.
"""
try:
query_to_change = query_map[query_type].strip()
logging.info(f"Query template retrieved for type {query_type}")

if document_names:
logging.info(f"Generating query for documents: {document_names}")
query = QUERY_WITH_DOCUMENT.format(query_to_change=query_to_change)
else:
logging.info("Generating query without specific document.")
query = QUERY_WITHOUT_DOCUMENT.format(query_to_change=query_to_change)
return query.strip()

except Exception as e:
logging.error("graph_query module: An unexpected error occurred while generating the Cypher query.")


def execute_query(driver, query,document_names,doc_limit=None):
"""
Executes a specified query using the Neo4j driver, with parameters based on the presence of a document name.
Expand Down Expand Up @@ -257,9 +197,8 @@ def get_graph_results(uri, username, password,document_names):
logging.info(f"Starting graph query process")
driver = get_graphDB_driver(uri, username, password)
document_names= list(map(str.strip, json.loads(document_names)))
query_type = "docChunkEntities"
query = get_cypher_query(QUERY_MAP, query_type, document_names)
records, summary , keys = execute_query(driver, query, document_names)
query = GRAPH_QUERY.format(graph_chunk_limit=GRAPH_CHUNK_LIMIT)
records, summary , keys = execute_query(driver, query.strip(), document_names)
document_nodes = extract_node_elements(records)
document_relationships = extract_relationships(records)

Expand Down
48 changes: 48 additions & 0 deletions backend/src/shared/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,54 @@
PROJECT_ID = 'llm-experiments-387609'




##graph viz


GRAPH_CHUNK_LIMIT = 50

#query
GRAPH_QUERY = """
MATCH docs = (d:Document)
WHERE d.fileName IN $document_names
WITH docs, d ORDER BY d.createdAt DESC
// fetch chunks for documents, currently with limit
CALL {{
WITH d
OPTIONAL MATCH chunks=(d)<-[:PART_OF|FIRST_CHUNK]-(c:Chunk)
RETURN c, chunks LIMIT {graph_chunk_limit}
}}

WITH collect([docs, chunks]) as data, collect(distinct c) as selectedChunks
WITH data, selectedChunks
// select relationships between selected chunks
WITH *,
[ c in selectedChunks | [p=(c)-[:NEXT_CHUNK|SIMILAR]-(other) WHERE other IN selectedChunks | p]] as chunkRels

// fetch entities and relationships between entities
CALL {{
WITH selectedChunks
UNWIND selectedChunks as c

OPTIONAL MATCH entities=(c:Chunk)-[:HAS_ENTITY]->(e)
OPTIONAL MATCH entityRels=(e)--(e2:!Chunk) WHERE exists {{
(e2)<-[:HAS_ENTITY]-(other) WHERE other IN selectedChunks
}}
RETURN collect(entities) as entities, collect(entityRels) as entityRels
}}

WITH apoc.coll.flatten(data + chunkRels + entities + entityRels, true) as paths

// distinct nodes and rels
CALL {{ WITH paths UNWIND paths AS path UNWIND nodes(path) as node WITH distinct node
RETURN collect(node /* {{.*, labels:labels(node), elementId:elementId(node), embedding:null, text:null}} */)
AS nodes }}
CALL {{ WITH paths UNWIND paths AS path UNWIND relationships(path) as rel RETURN collect(distinct rel) AS rels }}
RETURN nodes, rels
"""


## CHAT SETUP
CHAT_MAX_TOKENS = 1000
CHAT_SEARCH_KWARG_K = 3
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/Graph/CheckboxSelection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ const CheckboxSelection: React.FC<CheckboxSectionProps> = ({ graphType, loading,
<Checkbox
checked={graphType.includes('DocumentChunk')}
label='Document & Chunk'
disabled={(graphType.includes('DocumentChunk') && graphType.length === 1) || loading}
disabled={loading}
onChange={() => handleChange('DocumentChunk')}
/>
<Checkbox
checked={graphType.includes('Entities')}
label='Entities'
disabled={(graphType.includes('Entities') && graphType.length === 1) || loading}
disabled={loading}
onChange={() => handleChange('Entities')}
/>
</div>
Expand Down
34 changes: 20 additions & 14 deletions frontend/src/components/Graph/GraphViewModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { filterData, processGraphData } from '../../utils/Utils';
import { useCredentials } from '../../context/UserCredentials';
import { LegendsChip } from './LegendsChip';
import graphQueryAPI from '../../services/GraphQuery';
import { intitalGraphType, mouseEventCallbacks, nvlOptions, queryMap } from '../../utils/Constants';
import { graphLabels, intitalGraphType, mouseEventCallbacks, nvlOptions, queryMap } from '../../utils/Constants';
import CheckboxSelection from './CheckboxSelection';
const GraphViewModal: React.FunctionComponent<GraphViewModalProps> = ({
open,
Expand Down Expand Up @@ -69,6 +69,7 @@ const GraphViewModal: React.FunctionComponent<GraphViewModalProps> = ({
);
};

// Unmounting the component
useEffect(() => {
const timeoutId = setTimeout(() => {
handleZoomToFit();
Expand All @@ -88,7 +89,7 @@ const GraphViewModal: React.FunctionComponent<GraphViewModalProps> = ({
const fetchData = useCallback(async () => {
try {
const nodeRelationshipData =
viewPoint === 'showGraphView'
viewPoint === graphLabels.showGraphView
? await graphQueryAPI(
userCredentials as UserCredentials,
graphQuery,
Expand Down Expand Up @@ -150,16 +151,17 @@ const GraphViewModal: React.FunctionComponent<GraphViewModalProps> = ({
}
}, [open]);

// Unmounting the component
if (!open) {
return <></>;
}

const headerTitle =
viewPoint === 'showGraphView' || viewPoint === 'chatInfoView'
? 'Generated Graph'
: `Inspect Generated Graph from ${inspectedName}`;
viewPoint === graphLabels.showGraphView || viewPoint === graphLabels.chatInfoView
? graphLabels.generateGraph
: `${graphLabels.inspectGeneratedGraphFrom} ${inspectedName}`;

const checkBoxView = viewPoint !== 'chatInfoView';
const checkBoxView = viewPoint !== graphLabels.chatInfoView;

const nvlCallbacks = {
onLayoutComputing(isComputing: boolean) {
Expand Down Expand Up @@ -201,13 +203,11 @@ const GraphViewModal: React.FunctionComponent<GraphViewModalProps> = ({
setAllRelationships([]);
};

console.log('nodes', nodes);
console.log('rel', relationships);
// sort the legends in with Chunk and Document always the first two values
const legendCheck = Object.keys(newScheme).sort((a, b) => {
if (a === 'Document' || a === 'Chunk') {
if (a === graphLabels.document || a === graphLabels.chunk) {
return -1;
} else if (b === 'Document' || b === 'Chunk') {
} else if (b === graphLabels.document || b === graphLabels.chunk) {
return 1;
}
return a.localeCompare(b);
Expand Down Expand Up @@ -263,9 +263,13 @@ const GraphViewModal: React.FunctionComponent<GraphViewModalProps> = ({
<div className='my-40 flex items-center justify-center'>
<Banner name='graph banner' description={statusMessage} type={status} />
</div>
) : nodes.length === 0 || relationships.length === 0 ? (
) : nodes.length === 0 && relationships.length === 0 && graphType.length !== 0 ? (
<div className='my-40 flex items-center justify-center'>
<Banner name='graph banner' description='No Entities Found' type='danger' />
<Banner name='graph banner' description={graphLabels.noEntities} type='danger' />
</div>
) : graphType.length === 0 ? (
<div className='my-40 flex items-center justify-center'>
<Banner name='graph banner' description={graphLabels.selectCheckbox} type='danger' />
</div>
) : (
<>
Expand Down Expand Up @@ -331,8 +335,10 @@ const GraphViewModal: React.FunctionComponent<GraphViewModalProps> = ({
>
<div className='legend_div'>
<Flex className='py-4 pt-3 ml-2'>
<Typography variant='h3'>Result Overview</Typography>
<Typography variant='subheading-small'>Total Nodes ({nodes.length})</Typography>
<Typography variant='h3'>{graphLabels.resultOverview}</Typography>
<Typography variant='subheading-small'>
{graphLabels.totalNodes} ({nodes.length})
</Typography>
</Flex>
<div className='flex gap-2 flex-wrap ml-2'>
{legendCheck.map((key, index) => (
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/Graph/LegendsChip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const LegendsChip: React.FunctionComponent<LegendChipProps> = ({ scheme,
const chunkcount = useMemo(
// @ts-ignore
() => [...new Set(nodes?.filter((n) => n?.labels?.includes(title)).map((i) => i.id))].length,
[]
[nodes]
);
return <Legend title={title} chunkCount={chunkcount} bgColor={scheme[title]}></Legend>;
};
18 changes: 15 additions & 3 deletions frontend/src/utils/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,11 +183,23 @@ export const graphView: OptionType[] = [
{ label: 'Knowledge Graph', value: queryMap.DocChunkEntities },
];
export const intitalGraphType: GraphType[] = ['DocumentChunk', 'Entities'];
export const knowledgeGraph = 'Knowledge Graph';
export const lexicalGraph = 'Lexical Graph';
export const entityGraph = 'Entity Graph';

export const appLabels = {
ownSchema: 'Or Define your own Schema',
predefinedSchema: 'Select a Pre-defined Schema',
};

export const graphLabels = {
showGraphView: 'showGraphView',
chatInfoView: 'chatInfoView',
generateGraph: 'Generated Graph',
inspectGeneratedGraphFrom: 'Inspect Generated Graph from',
document: 'Document',
chunk: 'Chunk',
documentChunk: 'DocumentChunk',
entities: 'Entities',
resultOverview: 'Result Overview',
totalNodes: 'Total Nodes',
noEntities: 'No Entities Found',
selectCheckbox: 'Select atleast one checkbox for graph view',
};