Build Knowledge Graph from Documents: LLM Extraction Pipeline with Neo4j and LlamaIndex
TL;DR — Build knowledge graphs from documents with LLM extraction. Neo4j: "SimpleKGPipeline with llm, driver, embedder, entities, relations, potential_schema. Schema builder grounds extraction. Lexical graph builds Document/Chunk relationships. LangChain LLMGraphTransformer and LlamaIndex SchemaLLMPathExtractor." LlamaIndex Docs: "SchemaLLMPathExtractor with Literal entities/relations, validation_schema, strict=True. PropertyGraphIndex.from_documents. Neo4j + Ollama + HuggingFace embeddings." MarkAICode: "Documents → LLM extractor → property graph → Neo4j → retriever → answer. Three retrievers: LLMSynonym, VectorContext, CypherTemplate. Combine all three." ASOasis: "Ingest → chunk → extract → upsert to Neo4j → embed → vector + graph retrieval → re-rank → citations → answer." KindaTechnical: "Pipeline: ingest → extract → store (Cypher MERGE) → embed → query (vector + graph) → generate. MERGE for idempotent upserts." Learn more with knowledge graph basics, RAG pipeline, function calling, and Ollama tutorial.
KindaTechnical describes the pipeline: "GraphRAG grounds LLM responses in a knowledge graph instead of a flat vector store. Where vanilla RAG retrieves chunks by semantic similarity, GraphRAG retrieves connected subgraphs: entities, their relationships, and the multi-hop context surrounding them."
MarkAICode adds: "End-to-end flow: documents → LLM entity extractor → property graph → Neo4j → graph retriever → LLM answer. How PropertyGraphIndex extracts entities and relations, connects to Neo4j for persistent storage, and switches between keyword, vector, and Cypher graph retrievers."
Knowledge Graph Construction Pipeline
PDFs, text, web pages
YouTube transcripts
Office documents"] end subgraph Ingest["1. Ingestion"] Parse["Parse Documents
SimpleDirectoryReader
extract text from PDFs"] Chunk["Chunk Text
split into chunks
512-1500 tokens
overlap for context"] end subgraph Extract["2. LLM Extraction"] Schema["Define Schema
entity types: PERSON, ORG, TOPIC
relation types: WORKS_AT, MENTIONS
validation_schema dict"] LLM["LLM Extractor
SchemaLLMPathExtractor
LLMGraphTransformer
extracts (entity, relation, entity) triples
max_triplets_per_chunk=10"] end subgraph Store["3. Storage"] Neo4j["Neo4j Graph
MERGE nodes (idempotent)
SET properties
MERGE relationships
Document→Chunk→Entity
store embeddings on chunks"] Vector["Vector Index
embed chunks
FAISS or Neo4j native
map chunk_id ↔ vector_id"] end subgraph Retrieve["4. Hybrid Retrieval"] Semantic["Vector Recall
top-k chunks by
embedding similarity"] Graph["Graph Expansion
Cypher traversal
entity neighborhoods
multi-hop paths"] Rerank["Re-ranking
cross-encoder scoring
select final N chunks
assemble citations"] end subgraph Generate["5. Generation"] Context["Context Assembly
retrieved subgraph
+ source chunks
+ provenance"] Answer["LLM Answer
multi-hop reasoning
explicit reasoning paths
fewer hallucinations"] end Docs --> Parse Parse --> Chunk Chunk --> Schema Schema --> LLM LLM --> Neo4j LLM --> Vector Semantic --> Rerank Graph --> Rerank Rerank --> Context Context --> Answer Neo4j --> Graph Vector --> Semantic style Ingest fill:#4169E1,color:#fff style Extract fill:#39FF14,color:#000 style Store fill:#2D1B69,color:#fff style Retrieve fill:#FF6B6B,color:#fff
Framework Comparison
| Framework | Extractor | Schema Support | Storage | Retrievers | Best For |
|---|---|---|---|---|---|
| LlamaIndex | SchemaLLMPathExtractor | ✅ Literal types, validation_schema | Neo4jPropertyGraphStore | LLMSynonym, VectorContext, CypherTemplate | Full pipeline, schema control |
| LangChain | LLMGraphTransformer | ✅ allowed_nodes, allowed_relationships | Neo4jGraph | GraphCypherQAChain | LangChain ecosystem |
| Neo4j Python | SimpleKGPipeline | ✅ entities, relations, potential_schema | Neo4j native | Custom Cypher | Direct Neo4j control |
| Custom | Direct LLM prompt | ✅ Custom JSON schema | Any graph DB | Custom | Full control, no framework |
Implementation
from dataclasses import dataclass
from typing import Optional
from enum import Enum
class Framework(Enum):
LLAMAINDEX = "llamaindex"
LANGCHAIN = "langchain"
NEO4J_PYTHON = "neo4j_python"
CUSTOM = "custom"
@dataclass
class BuildKnowledgeGraphGuide:
"""Build knowledge graph from documents guide."""
def get_llamaindex_pipeline(self) -> str:
"""LlamaIndex SchemaLLMPathExtractor pipeline."""
return (
"# === LLAMAINDEX PIPELINE ===\n"
"from typing import Literal\n"
"from llama_index.core import\n"
" PropertyGraphIndex,\n"
" SimpleDirectoryReader\n"
"from llama_index.core.indices.\n"
" property_graph import (\n"
" SchemaLLMPathExtractor,\n"
" LLMSynonymRetriever,\n"
" VectorContextRetriever,\n"
" CypherTemplateRetriever,\n"
")\n"
"from llama_index.llms.ollama\n"
" import Ollama\n"
"from llama_index.embeddings.\n"
" huggingface import\n"
" HuggingFaceEmbedding\n"
"from llama_index.graph_stores.\n"
" neo4j import\n"
" Neo4jPropertyGraphStore\n"
"\n"
"# 1. DEFINE SCHEMA\n"
"entities = Literal[\n"
" 'PERSON', 'ORGANIZATION',\n"
" 'TOPIC', 'EVENT', 'PLACE'\n"
"]\n"
"relations = Literal[\n"
" 'WORKS_AT', 'MENTIONS',\n"
" 'CITES', 'COLLABORATED_WITH',\n"
" 'AFFILIATED_WITH', 'ABOUT'\n"
"]\n"
"validation_schema = {\n"
" 'PERSON': [\n"
" 'WORKS_AT',\n"
" 'COLLABORATED_WITH',\n"
" 'AFFILIATED_WITH'],\n"
" 'ORGANIZATION': [\n"
" 'COLLABORATED_WITH',\n"
" 'AFFILIATED_WITH'],\n"
" 'TOPIC': ['ABOUT'],\n"
" 'EVENT': ['MENTIONS'],\n"
" 'PLACE': ['ABOUT'],\n"
"}\n"
"\n"
"# 2. CREATE EXTRACTOR\n"
"kg_extractor = (\n"
" SchemaLLMPathExtractor(\n"
" llm=Ollama(\n"
" model='llama3.2',\n"
" json_mode=True,\n"
" request_timeout=3600,\n"
" context_window=8000,\n"
" ),\n"
" possible_entities=entities,\n"
" possible_relations=relations,\n"
" kg_validation_schema=\n"
" validation_schema,\n"
" strict=True,\n"
" num_workers=4,\n"
" max_triplets_per_chunk=10,\n"
"))\n"
"\n"
"# 3. SETUP GRAPH STORE\n"
"graph_store = (\n"
" Neo4jPropertyGraphStore(\n"
" username='neo4j',\n"
" password='password',\n"
" url='bolt://\n"
" localhost:7687',\n"
"))\n"
"\n"
"# 4. BUILD INDEX\n"
"documents = (\n"
" SimpleDirectoryReader(\n"
" './docs').load_data())\n"
"index = (\n"
" PropertyGraphIndex\n"
" .from_documents(\n"
" documents,\n"
" kg_extractors=[\n"
" kg_extractor],\n"
" property_graph_store=\n"
" graph_store,\n"
" embed_model=\n"
" HuggingFaceEmbedding(\n"
" model_name=\n"
" 'BAAI/\n"
" bge-small-en-v1.5'),\n"
" show_progress=True,\n"
"))\n"
"\n"
"# 5. HYBRID RETRIEVAL\n"
"retriever = index.as_retriever(\n"
" sub_retrievers=[\n"
" LLMSynonymRetriever(\n"
" index.property_graph_store,\n"
" llm=Ollama(\n"
" model='llama3.2'),\n"
" include_text=True,\n"
" ),\n"
" VectorContextRetriever(\n"
" index.property_graph_store,\n"
" embed_model=\n"
" HuggingFaceEmbedding(\n"
" model_name=\n"
" 'BAAI/\n"
" bge-small-en-v1.5'),\n"
" include_text=True,\n"
" ),\n"
" CypherTemplateRetriever(\n"
" index.property_graph_store,\n"
" ),\n"
" ]\n"
")\n"
"\n"
"# 6. QUERY\n"
"nodes = retriever.retrieve(\n"
" 'Who works at AI Lab?')\n"
"for node in nodes:\n"
" print(node.text)"
)
def get_langchain_pipeline(self) -> str:
"""LangChain LLMGraphTransformer pipeline."""
return (
"# === LANGCHAIN PIPELINE ===\n"
"from langchain_experimental.\n"
" graph_transformers import\n"
" LLMGraphTransformer\n"
"from langchain_community.\n"
" graphs import Neo4jGraph\n"
"from langchain_text_splitters\n"
" import RecursiveCharacterText\n"
" Splitter\n"
"from langchain_openai\n"
" import ChatOpenAI\n"
"\n"
"# 1. SETUP LLM\n"
"llm = ChatOpenAI(\n"
" base_url=\n"
" 'http://localhost:11434/v1',\n"
" api_key='ollama',\n"
" model='llama3.2',\n"
" temperature=0,\n"
")\n"
"\n"
"# 2. DEFINE SCHEMA\n"
"allowed_nodes = [\n"
" 'Person', 'Organization',\n"
" 'Topic', 'Document',\n"
"]\n"
"allowed_relationships = [\n"
" 'WORKS_AT', 'MENTIONS',\n"
" 'CITES', 'COLLABORATED_WITH',\n"
"]\n"
"\n"
"# 3. CREATE TRANSFORMER\n"
"transformer = (\n"
" LLMGraphTransformer(\n"
" llm=llm,\n"
" allowed_nodes=allowed_nodes,\n"
" allowed_relationships=\n"
" allowed_relationships,\n"
" # Optional: specify\n"
" # properties to extract\n"
" node_properties=True,\n"
" relationship_properties=True,\n"
"))\n"
"\n"
"# 4. LOAD & SPLIT DOCUMENTS\n"
"from langchain_community.\n"
" document_loaders import\n"
" DirectoryLoader\n"
"loader = DirectoryLoader(\n"
" './docs', glob='**/*.pdf')\n"
"docs = loader.load()\n"
"splitter = (\n"
" RecursiveCharacterText\n"
" Splitter(\n"
" chunk_size=1000,\n"
" chunk_overlap=200))\n"
"chunks = splitter.\n"
" split_documents(docs)\n"
"\n"
"# 5. EXTRACT GRAPH\n"
"graph_documents = (\n"
" transformer\n"
" .convert_to_graph_documents(\n"
" chunks))\n"
"# graph_documents[0].nodes\n"
"# graph_documents[0].relationships\n"
"\n"
"# 6. STORE IN NEO4J\n"
"graph = Neo4jGraph(\n"
" url='bolt://localhost:7687',\n"
" username='neo4j',\n"
" password='password',\n"
")\n"
"graph.add_graph_documents(\n"
" graph_documents,\n"
" include_source=True,\n"
")\n"
"\n"
"# 7. QUERY WITH CYPHER\n"
"result = graph.query(\n"
" '''MATCH (p:Person)-\n"
" [:WORKS_AT]->(o:Organization)\n"
" WHERE o.name = 'AI Lab'\n"
" RETURN p.name''')\n"
"print(result)"
)
def get_neo4j_upsert(self) -> str:
"""Direct Neo4j Cypher upsert pattern."""
return (
"# === NEO4J UPSERT ===\n"
"from neo4j import GraphDatabase\n"
"import uuid\n"
"\n"
"driver = GraphDatabase.driver(\n"
" 'bolt://localhost:7687',\n"
" auth=('neo4j', 'password'))\n"
"\n"
"def upsert(tx, query, **params):\n"
" tx.run(query, **params)\n"
"\n"
"def ingest_document(doc):\n"
" '''Ingest document with\n"
" chunks and entities.'''\n"
" chunks = chunk_text(\n"
" doc['text'])\n"
" with driver.session() as sess:\n"
" # Create Document node\n"
" sess.execute_write(\n"
" upsert,\n"
" '''MERGE (d:Document\n"
" {id:$id})\n"
" SET d.title=$title,\n"
" d.url=$url''',\n"
" id=doc['id'],\n"
" title=doc['title'],\n"
" url=doc.get('url'))\n"
" \n"
" for order, text in \\\n"
" enumerate(chunks):\n"
" cid = str(\n"
" uuid.uuid4())\n"
" # Create Chunk\n"
" sess.execute_write(\n"
" upsert,\n"
" '''MERGE (c:Chunk\n"
" {id:$id})\n"
" SET c.text=$text,\n"
" c.order=$order,\n"
" c.doc_id=$did''',\n"
" id=cid, text=text,\n"
" order=order,\n"
" did=doc['id'])\n"
" # Link Doc→Chunk\n"
" sess.execute_write(\n"
" upsert,\n"
" '''MATCH (d:Document\n"
" {id:$did}),\n"
" (c:Chunk {id:$cid})\n"
" MERGE (d)-\n"
" [:CONTAINS]->(c)''',\n"
" did=doc['id'],\n"
" cid=cid)\n"
" # Extract & upsert\n"
" # entities\n"
" ents = extract(\n"
" text) # LLM call\n"
" for e in ents:\n"
" sess.execute_write(\n"
" upsert,\n"
" f'''MERGE\n"
" (n:{e['type']}\n"
" {{name:$name}})''',\n"
" name=e['name'])\n"
" sess.execute_write(\n"
" upsert,\n"
" '''MATCH (c:Chunk\n"
" {id:$cid}),\n"
" (n {name:$name})\n"
" MERGE (c)-\n"
" [:MENTIONS]->(n)''',\n"
" cid=cid,\n"
" name=e['name'])\n"
"\n"
"driver.close()"
)
def get_hybrid_retrieval(self) -> str:
"""Hybrid vector + graph retrieval with Cypher."""
return (
"# === HYBRID RETRIEVAL ===\n"
"from sentence_transformers\n"
" import CrossEncoder\n"
"\n"
"cross = CrossEncoder(\n"
" 'cross-encoder/ms-marco-\n"
" MiniLM-L-6-v2')\n"
"\n"
"def hybrid_retrieve(\n"
" question, top_k=5):\n"
" '''1. Vector seeds\n"
" 2. Graph expansion\n"
" 3. Re-rank''' \n"
" \n"
" with driver.session() as sess:\n"
" # Step 1: Vector recall\n"
" # (semantic seeds)\n"
" q_emb = embed(\n"
" question)\n"
" seeds = sess.run(\n"
" '''CALL db.index.\n"
" vector.\n"
" queryNodes(\n"
" 'chunk_embeddings',\n"
" $k, $emb)\n"
" YIELD node, score\n"
" RETURN node, score''',\n"
" k=top_k, emb=q_emb\n"
" ).data()\n"
" \n"
" # Step 2: Graph expansion\n"
" # (Cypher traversal)\n"
" expanded = sess.run(\n"
" '''MATCH (c:Chunk)\n"
" WHERE c.id IN $ids\n"
" MATCH (c)-\n"
" [:MENTIONS]->(e)\n"
" MATCH (e)<-\n"
" [:MENTIONS]-(c2:Chunk)\n"
" RETURN DISTINCT\n"
" c2.id, c2.text\n"
" LIMIT 20''',\n"
" ids=[s['node']['id']\n"
" for s in seeds]\n"
" ).data()\n"
" \n"
" # Step 3: Re-rank\n"
" # (cross-encoder)\n"
" pairs = [(question,\n"
" c['c2.text'])\n"
" for c in expanded]\n"
" scores = cross.predict(\n"
" pairs)\n"
" ranked = sorted(\n"
" zip(expanded, scores),\n"
" key=lambda x: x[1],\n"
" reverse=True)\n"
" return [\n"
" r[0] for r in\n"
" ranked[:top_k]]\n"
"\n"
"# Usage\n"
"results = hybrid_retrieve(\n"
" 'Who works at AI Lab?')\n"
"for r in results:\n"
" print(r['c2.text'])"
)
def get_pipeline_steps(self) -> dict:
"""Pipeline steps summary."""
return {
"1_ingest": "Parse documents (PDFs, text, web) with SimpleDirectoryReader or DirectoryLoader",
"2_chunk": "Split into chunks (512-1500 tokens, overlap 200) with RecursiveCharacterTextSplitter",
"3_schema": "Define entity types (PERSON, ORG, TOPIC) and relation types (WORKS_AT, MENTIONS) as schema",
"4_extract": "LLM extracts (entity, relation, entity) triples from each chunk via SchemaLLMPathExtractor or LLMGraphTransformer",
"5_store": "Upsert nodes and relationships to Neo4j with MERGE (idempotent), SET properties, store embeddings on chunks",
"6_embed": "Compute vector embeddings for chunks, store in Neo4j native vector index or FAISS",
"7_retrieve": "Hybrid retrieval: vector semantic seeds → graph expansion (Cypher) → cross-encoder re-ranking",
"8_generate": "Feed retrieved subgraph + source chunks to LLM for multi-hop reasoning answer with provenance",
}
Build Knowledge Graph Checklist
- [ ] Pipeline: ingest → chunk → extract → store → embed → retrieve → generate
- [ ] Step 1 Ingest: parse documents (PDFs, text, web pages, YouTube transcripts) with SimpleDirectoryReader or DirectoryLoader
- [ ] Step 2 Chunk: split into chunks (512-1500 tokens, overlap 200) with RecursiveCharacterTextSplitter
- [ ] Step 3 Schema: define entity types (PERSON, ORGANIZATION, TOPIC, EVENT, PLACE) and relation types (WORKS_AT, MENTIONS, CITES)
- [ ] Step 3 Schema: validation_schema dict defines which entities can have which relations
- [ ] Step 4 Extract: LLM extracts (entity, relation, entity) triples from each chunk
- [ ] Step 4 Extract: LlamaIndex SchemaLLMPathExtractor with Literal entities/relations, strict=True, num_workers=4, max_triplets_per_chunk=10
- [ ] Step 4 Extract: LangChain LLMGraphTransformer with allowed_nodes, allowed_relationships, convert_to_graph_documents
- [ ] Step 4 Extract: Neo4j SimpleKGPipeline with llm, driver, embedder, entities, relations, potential_schema
- [ ] Step 4 Extract: extraction takes 1-3 sec per chunk — show_progress=True for visibility
- [ ] Step 5 Store: MERGE for idempotent node creation (no duplicates on re-run)
- [ ] Step 5 Store: SET for properties on nodes and relationships
- [ ] Step 5 Store: MATCH + MERGE for relationships between existing nodes
- [ ] Step 5 Store: session.execute_write for Neo4j transactions
- [ ] Step 5 Store: store embeddings on chunk nodes for vector retrieval
- [ ] Step 5 Store: lexical graph: (Document)-[:CONTAINS]->(Chunk), (Chunk)-[:MENTIONS]->(Entity)
- [ ] Step 6 Embed: compute vector embeddings for chunks using BAAI/bge-small-en-v1.5 or nomic-embed-text
- [ ] Step 6 Embed: store in Neo4j native vector index or external FAISS
- [ ] Step 6 Embed: map chunk_id ↔ vector_id for retrieval
- [ ] Step 7 Retrieve: hybrid retrieval — vector semantic seeds + graph Cypher expansion + cross-encoder re-ranking
- [ ] Step 7 Retrieve: LLMSynonymRetriever expands query terms, matches entity names
- [ ] Step 7 Retrieve: VectorContextRetriever finds semantic neighbors
- [ ] Step 7 Retrieve: CypherTemplateRetriever translates natural language to Cypher, handles 3+ hop queries
- [ ] Step 7 Retrieve: combine all three retrievers for entity matches + semantic + graph paths
- [ ] Step 7 Retrieve: cross-encoder re-ranking with cross-encoder/ms-marco-MiniLM-L-6-v2
- [ ] Step 8 Generate: feed retrieved subgraph + source chunks to LLM for answer
- [ ] Step 8 Generate: multi-hop reasoning with explicit reasoning paths
- [ ] Step 8 Generate: provenance-rich answers with citations
- [ ] Step 8 Generate: fewer hallucinations than vanilla RAG
- [ ] LlamaIndex: PropertyGraphIndex.from_documents with kg_extractors, property_graph_store, embed_model
- [ ] LlamaIndex: Neo4jPropertyGraphStore for persistent graph storage
- [ ] LangChain: LLMGraphTransformer.convert_to_graph_documents
- [ ] LangChain: Neo4jGraph.add_graph_documents with include_source=True
- [ ] Neo4j: SimpleKGPipeline with from_pdf, entities, relations, potential_schema
- [ ] Neo4j: optional community summaries and embeddings enrichment
- [ ] Schema constrains extraction to specified types — improves quality and navigability
- [ ] strict=True enforces schema; strict=False allows triplets outside schema as suggestions
- [ ] Batch upserts for performance on large document sets
- [ ] Optional: enrich graph with community summaries and node embeddings
- [ ] Optional: temporal versioning with validFrom/validTo properties for point-in-time queries
- [ ] Read knowledge graph basics for concepts
- [ ] Read RAG pipeline for vector RAG
- [ ] Read function calling for tool use
- [ ] Read Ollama tutorial for local LLM
- [ ] Test: entities and relationships extracted correctly from sample documents
- [ ] Test: MERGE upserts are idempotent (re-run doesn't create duplicates)
- [ ] Test: hybrid retrieval returns relevant multi-hop results
- [ ] Test: cross-encoder re-ranking improves precision over vector-only
- [ ] Test: schema constraint prevents off-schema extractions
- [ ] Test: graph query performance acceptable for your dataset size
- [ ] Document schema, framework choice, extraction pipeline, storage config, retrieval strategy
FAQ
How do you build a knowledge graph from documents using LLMs?
Pipeline: chunk documents → LLM extracts entities/relations → upsert to graph database → embed chunks → hybrid retrieval. Neo4j: "SimpleKGBuilder provides easy way to get started. SimpleKGPipeline with llm, driver, embedder, entities, relations, potential_schema. Schema builder grounds LLM extracted entities and relations. Lexical graph builder builds Document, Chunk and relationships." ASOasis: "Ingestion: parse documents → chunk text → extract entities/relations → upsert to Neo4j. Indexing: embed chunks → store vectors → map chunk_id ↔ vector_id. Retrieval: semantic recall (vector) → graph expansion (Cypher) → re-rank → assemble citations → generate answer." KindaTechnical: "Pipeline: ingest (split into chunks) → extract (LLM pulls entities/relationships) → store (write to Cypher graph) → embed (vector embeddings) → query (combine semantic + graph traversal) → generate (feed subgraph to LLM)." Build: (1) Parse documents → chunk text. (2) LLM extracts entities and relationships. (3) Upsert nodes and edges to Neo4j. (4) Embed chunks for vector retrieval. (5) Hybrid retrieval: vector + graph.
How do you use LlamaIndex SchemaLLMPathExtractor to build a knowledge graph?
Define entity and relation types as Literal enums, pass to SchemaLLMPathExtractor, create PropertyGraphIndex. LlamaIndex Docs: "SchemaLLMPathExtractor allows specifying exact schema with possible entity types, relation types, and how they connect. entities = Literal[PERSON, PLACE, ORGANIZATION], relations = Literal[HAS, PART_OF, WORKED_AT]. validation_schema defines which entities can have which relations. strict=True enforces schema." MarkAICode: "PropertyGraphIndex.from_documents with kg_extractors=[kg_extractor], property_graph_store=graph_store. End-to-end: documents → LLM entity extractor → property graph → Neo4j → graph retriever → LLM answer. num_workers=4 for parallel extraction, max_triplets_per_chunk=10." Usage: (1) Define entities/relations as Literal. (2) Create validation_schema dict. (3) SchemaLLMPathExtractor(llm, possible_entities, possible_relations, kg_validation_schema, strict=True). (4) PropertyGraphIndex.from_documents(documents, kg_extractors=[extractor]). (5) Retriever: LLMSynonymRetriever + VectorContextRetriever + CypherTemplateRetriever.
How do you use LangChain LLMGraphTransformer for knowledge graph construction?
LLMGraphTransformer converts LangChain documents into graph documents with nodes and relationships. Neo4j: "LangChain LLMGraphTransformer under the hood. convert_to_graph_documents method converts LangChain documents into graph documents (nodes and relationships). Specify schema with allowed nodes and relationships, properties, and extraction instructions. Extracts lexical graph (Document, Chunk) then uses LLM for entities and relations. Optionally enriches with community summaries and embeddings." Usage: (1) from langchain_experimental.graph_transformers import LLMGraphTransformer. (2) Define allowed_nodes and allowed_relationships. (3) transformer = LLMGraphTransformer(llm=llm, allowed_nodes=nodes, allowed_relationships=rels). (4) graph_documents = transformer.convert_to_graph_documents(documents). (5) Upsert to Neo4j with Neo4jGraph.add_graph_documents. (6) Optional: specify properties for extraction.
How do you store extracted entities and relationships in Neo4j?
Use MERGE for idempotent upserts of nodes and relationships via Cypher queries. ASOasis: "Upsert with MERGE: MERGE (d:Document {id:$id}) SET d.title=$title. MERGE (c:Chunk {id:$id}) SET c.text=$text, c.embedding=$emb. MATCH (d:Document), (c:Chunk) MERGE (d)-[:CONTAINS]->(c). MERGE (n:Person {name:$name}). MATCH (c:Chunk), (n) MERGE (c)-[:MENTIONS]->(n)." KindaTechnical: "Write extraction: MERGE (e:Entity {id:$id}) SET e.name=$name, e.label=$label. MATCH (s:Entity {id:$src}), (t:Entity {id:$tgt}) MERGE (s)-[r:RELATED {type:$type}]->(t) SET r.source=$doc." Storage: (1) MERGE for idempotent node creation (no duplicates). (2) SET for properties. (3) MATCH + MERGE for relationships. (4) session.execute_write for transactions. (5) Batch upserts for performance. (6) Store embeddings on chunk nodes for vector retrieval.
How do you retrieve from a knowledge graph for RAG?
Combine vector semantic recall with graph traversal expansion and re-ranking. ASOasis: "Retrieval: semantic seeds (top-k chunks by embedding) → graph expansion (pull neighbors via Cypher) → candidate assembly → re-ranking (cross-encoder) → final N chunks. Multi-index: blend BM25, vector, graph paths, learn weights via logistic regression." MarkAICode: "Three retrievers: LLMSynonymRetriever (expands query terms, matches entity names), VectorContextRetriever (semantic neighbors), CypherTemplateRetriever (translates NL to Cypher, 3+ hop queries). Combine all three for entity matches + semantic + graph paths." KindaTechnical: "Hybrid: CALL db.index.vector.queryNodes for semantic seeds, MATCH (seed)-[r]-(neighbor) for graph expansion, collect context, feed to LLM." Retrieval: (1) Vector: top-k semantic seeds. (2) Graph: expand neighbors via Cypher. (3) Re-rank: cross-encoder scoring. (4) Combine: LLMSynonym + VectorContext + CypherTemplate.
Want a self-hosted AI company brain that does all of this out of the box?
Book a demo →