Property graphs let retrieval applications represent named entities and the relationships between them instead of relying only on text similarity. A LlamaIndex PropertyGraphIndex keeps those graph paths connected to their source text, which makes service dependencies and other linked facts available for graph-aware retrieval.
The default extraction path can ask an LLM to infer triples from unstructured text. This local build uses explicit EntityNode and Relation objects with ImplicitPathExtractor, so every expected relationship is known before indexing and can be checked without an external model or API key.
The completed script stores three entities and two directed relationships, persists the index, then reloads and checks both paths from the recovered graph store. MockLLM and MockEmbedding keep the smoke test offline; production applications can retain the same graph construction flow while selecting their own models and property graph store.
$ cat > property_graph_index.py <<'PY' from pathlib import Path from llama_index.core import ( PropertyGraphIndex, Settings, StorageContext, load_index_from_storage, ) from llama_index.core.embeddings import MockEmbedding from llama_index.core.graph_stores import EntityNode, Relation from llama_index.core.graph_stores.types import KG_NODES_KEY, KG_RELATIONS_KEY from llama_index.core.indices.property_graph import ImplicitPathExtractor from llama_index.core.llms import MockLLM from llama_index.core.schema import TextNode storage_dir = Path("property_graph_storage") Settings.llm = MockLLM(max_tokens=32) Settings.embed_model = MockEmbedding(embed_dim=8) PY
$ cat >> property_graph_index.py <<'PY' service = EntityNode(name="CheckoutService", label="service") database = EntityNode(name="PostgreSQL", label="database") cache = EntityNode(name="Redis", label="cache") PY
$ cat >> property_graph_index.py <<'PY' source_node = TextNode( text="CheckoutService writes orders to PostgreSQL and reads carts from Redis.", id_="checkout-service-note", metadata={ KG_NODES_KEY: [service, database, cache], KG_RELATIONS_KEY: [ Relation(label="WRITES_TO", source_id=service.id, target_id=database.id), Relation(label="READS_FROM", source_id=service.id, target_id=cache.id), ], }, ) PY
ImplicitPathExtractor reads graph metadata already attached to the source node. SimpleLLMPathExtractor or a schema-based extractor is appropriate when relationships must be inferred from unstructured text.
$ cat >> property_graph_index.py <<'PY' index = PropertyGraphIndex( nodes=[source_node], kg_extractors=[ImplicitPathExtractor()], embed_model=Settings.embed_model, llm=Settings.llm, show_progress=False, ) PY
$ cat >> property_graph_index.py <<'PY' index.storage_context.persist(persist_dir=str(storage_dir)) loaded_index = load_index_from_storage( StorageContext.from_defaults(persist_dir=str(storage_dir)) ) PY
$ cat >> property_graph_index.py <<'PY' loaded_store = loaded_index.property_graph_store loaded_nodes = loaded_store.get() loaded_entities = sorted( graph_node.name for graph_node in loaded_nodes if graph_node.label != "text_chunk" ) loaded_service = next( graph_node for graph_node in loaded_nodes if isinstance(graph_node, EntityNode) and graph_node.name == "CheckoutService" ) loaded_paths = sorted( f"{source.name} -[{relation.label}]-> {target.name}" for source, relation, target in loaded_store.get_rel_map( [loaded_service], depth=1 ) ) assert loaded_entities == ["CheckoutService", "PostgreSQL", "Redis"] assert loaded_paths == [ "CheckoutService -[READS_FROM]-> Redis", "CheckoutService -[WRITES_TO]-> PostgreSQL", ] assert len(loaded_nodes) == 4 print(f"reloaded entity nodes: {', '.join(loaded_entities)}") print("reloaded service paths:") for path in loaded_paths: print(f"- {path}") print(f"reloaded graph nodes: {len(loaded_nodes)}") PY
$ python3 property_graph_index.py reloaded entity nodes: CheckoutService, PostgreSQL, Redis reloaded service paths: - CheckoutService -[READS_FROM]-> Redis - CheckoutService -[WRITES_TO]-> PostgreSQL reloaded graph nodes: 4
The reloaded count includes the source text chunk and the three entity nodes. A missing entity, relationship, or persisted node raises AssertionError instead of printing the success transcript.