Overview
The knowledge graph you created is solely based on unstructured data extracted from documents.
You may have access to structured data sources such as databases, CSV files, or APIs that contain valuable information relevant to your domain.
Combining the structured and unstructured data can enhance the knowledge graph’s richness and usefulness.
Lexical and Domain Graphs
The unstructured part of your graph is known as the Lexical Graph, while the structured part is known as the Domain Graph.Structured data source
The repository contains a sample CSV file workshop-genai/data/docs.csv which contains metadata about the lessons the documents were created from.
filename,course,module,lesson,url
genai-fundamentals_1-generative-ai_1-what-is-genai.pdf,genai-fundamentals,1-generative-ai,1-what-is-genai,https://graphacademy.neo4j.com/courses/genai-fundamentals/1-generative-ai/1-what-is-genai
genai-fundamentals_1-generative-ai_2-considerations.pdf,genai-fundamentals,1-generative-ai,2-considerations,https://graphacademy.neo4j.com/courses/genai-fundamentals/1-generative-ai/2-considerations
...You can use the CSV file as input and a structured data source when creating the knowledge graph.
Continue with the lesson to load the structured data.
Load from CSV file
Open workshop-genai/kg_structured_builder.py and review the code.
import os
from dotenv import load_dotenv
load_dotenv()
import asyncio
import csv
from neo4j import GraphDatabase
from neo4j_graphrag.llm import OpenAILLM
from neo4j_graphrag.embeddings import OpenAIEmbeddings
from neo4j_graphrag.experimental.pipeline.kg_builder import SimpleKGPipeline
from neo4j_graphrag.experimental.components.text_splitters.fixed_size_splitter import FixedSizeSplitter
neo4j_driver = GraphDatabase.driver(
os.getenv("NEO4J_URI"),
auth=(os.getenv("NEO4J_USERNAME"), os.getenv("NEO4J_PASSWORD"))
)
neo4j_driver.verify_connectivity()
llm = OpenAILLM(
model_name="gpt-4o",
model_params={
"temperature": 0,
"response_format": {"type": "json_object"},
}
)
embedder = OpenAIEmbeddings(
model="text-embedding-ada-002"
)
text_splitter = FixedSizeSplitter(chunk_size=500, chunk_overlap=100)
NODE_TYPES = [
"Technology",
"Concept",
"Example",
"Process",
"Challenge",
{"label": "Benefit", "description": "A benefit or advantage of using a technology or approach."},
{
"label": "Resource",
"description": "A related learning resource such as a book, article, video, or course.",
"properties": [
{"name": "name", "type": "STRING", "required": True},
{"name": "type", "type": "STRING"}
]
},
]
RELATIONSHIP_TYPES = [
"RELATED_TO",
"PART_OF",
"USED_IN",
"LEADS_TO",
"HAS_CHALLENGE",
"LEADS_TO",
"CITES"
]
PATTERNS = [
("Technology", "RELATED_TO", "Technology"),
("Concept", "RELATED_TO", "Technology"),
("Example", "USED_IN", "Technology"),
("Process", "PART_OF", "Technology"),
("Technology", "HAS_CHALLENGE", "Challenge"),
("Concept", "HAS_CHALLENGE", "Challenge"),
("Technology", "LEADS_TO", "Benefit"),
("Process", "LEADS_TO", "Benefit"),
("Resource", "CITES", "Technology"),
]
kg_builder = SimpleKGPipeline(
llm=llm,
driver=neo4j_driver,
neo4j_database=os.getenv("NEO4J_DATABASE"),
embedder=embedder,
from_pdf=True,
text_splitter=text_splitter,
schema={
"node_types": NODE_TYPES,
"relationship_types": RELATIONSHIP_TYPES,
"patterns": PATTERNS
},
)
data_path = "./workshop-genai/data/"
docs_csv = csv.DictReader(
open(os.path.join(data_path, "docs.csv"), encoding="utf8", newline='')
)
cypher = """
MATCH (d:Document {path: $pdf_path})
MERGE (l:Lesson {url: $url})
SET l.name = $lesson,
l.module = $module,
l.course = $course
MERGE (d)-[:PDF_OF]->(l)
"""
for doc in docs_csv:
# Create the complete PDF path
doc["pdf_path"] = os.path.join(data_path, doc["filename"])
print(f"Processing document: {doc['pdf_path']}")
# Entity extraction and KG population
result = asyncio.run(
kg_builder.run_async(
file_path=os.path.join(doc["pdf_path"])
)
)
# Create structured graph
records, summary, keys = neo4j_driver.execute_query(
cypher,
parameters_=doc,
database_=os.getenv("NEO4J_DATABASE")
)
print(result, summary.counters)The key differences are:
-
The
docs.csvfile is loaded usingcsv.DictReaderto read each row as a dictionary:pythonLoad docs.csvdata_path = "./workshop-genai/data/" docs_csv = csv.DictReader( open(os.path.join(data_path, "docs.csv"), encoding="utf8", newline='') ) -
The path of the PDF document is constructed using the
filenamefield from the CSV:pythonPDF path# Create the complete PDF path doc["pdf_path"] = os.path.join(data_path, doc["filename"]) print(f"Processing document: {doc['pdf_path']}") -
A
cypherstatement is defined to createLessonnodes with properties from the CSV data:pythonCypher statementcypher = """ MATCH (d:Document {path: $pdf_path}) MERGE (l:Lesson {url: $url}) SET l.name = $lesson, l.module = $module, l.course = $course MERGE (d)-[:PDF_OF]->(l) """The
pdf_pathis used as the key to match theDocumentnodes created from the PDF files. -
A
Lessonnode is created for each document using thecypherstatement and the CSV data:pythonLesson nodes# Create structured graph records, summary, keys = neo4j_driver.execute_query( cypher, parameters_=doc, database_=os.getenv("NEO4J_DATABASE") )
The resulting knowledge graph will now contain Lesson nodes connected to the Document nodes created from the PDF files:
Run the program to create the knowledge graph with the structured data.
Remember to delete the existing graph before re-running the pipeline
MATCH (n) DETACH DELETE nOpenAI Rate Limiting?
When using a free OpenAI API key, you may encounter rate limiting issues when processing multiple documents. You can add a sleep between document processing to mitigate this.
Explore the structured data
The structured data allows you to query the knowledge graph in new ways.
You can find all lessons that cover a specific technology or concept:
MATCH (kg:Technology)
MATCH (kg)-[:FROM_CHUNK]->(c)-[:FROM_DOCUMENT]-(d)-[:PDF_OF]-(l)
WHERE toLower(kg.name) CONTAINS "knowledge graph"
RETURN DISTINCT toLower(kg.name), l.name, l.urlExplore the knowledge graph
The knowledge graph allows you to summarize the content of each lesson by specific categories such as technologies and concepts:
MATCH (lesson:Lesson)<-[:PDF_OF]-(:Document)<-[:FROM_DOCUMENT]-(c:Chunk)
RETURN
lesson.name,
lesson.url,
[ (c)<-[:FROM_CHUNK]-(tech:Technology) | tech.name ] AS technologies,
[ (c)<-[:FROM_CHUNK]-(concept:Concept) | concept.name ] AS conceptsSpend some time exploring the knowledge graph and experiment with adding additional data.
Lesson Summary
In this lesson, you learned:
-
About benefits of adding structured data to a knowledge graph.
-
How to load structured data from a CSV file.
-
How to create nodes from structured data and connect them to unstructured data nodes.
In the next module, you will create retrievers to query the knowledge graph.