[!NOTE] Bilingual source
This module's comments and docstrings are written primarily in Arabic in the source code. This page translates and documents the described behavior in English; code identifiers (class/method/field names) are unchanged from the source.
Overview
Loads knowledge graphs from JSON (node-link format), GraphML, GEXF, or a simple edge-list CSV, producing one LoadedDocument per node and/or edge. This is purpose-built for GraphRAG-style pipelines, where graph structure (entities and relationships) needs to become independently retrievable, embeddable text units.
GraphLoader and TableLoader are not wired into EXTENSION_MAP or AutoLoader/DirectoryLoader's automatic dispatch — both must be imported and used explicitly.
after setting self.config. This means even if you pass a fully customized GraphLoaderConfig(include_nodes=False, ...), it gets silently overwritten back to the constructor's include_nodes default of True unless you also explicitly pass include_nodes=False as a direct keyword argument. This is different from the pattern used by PDFLoader/CSVLoader/etc., where config= takes full precedence when provided.
Returns
List[LoadedDocument] — one per included node/edge, in source-file order.
Raises
FileNotFoundError / ValueError — at construction
ValueError — for an unrecognized extension reaching lazy_load() (defensive; SUPPORTED_EXTENSIONS validation at construction should normally prevent this)
xml.etree.ElementTree.ParseError — for malformed GraphML/GEXF XML
json.JSONDecodeError — for malformed JSON
Internal workflow — format dispatch
Python · 5 lines
ext = self.file_path.suffix.lower()
if ext in (".json", ".graphjson"): yieldfromself._parse_json()
elif ext == ".graphml": yieldfromself._parse_graphml()
elif ext == ".gexf": yieldfromself._parse_gexf()
elif ext in (".csv", ".tsv"): yieldfromself._parse_edge_csv(delimiter=("\t"if tsv else","))
Format: JSON node-link — _parse_json()
Supports three input shapes:
{"nodes": [...], "links": [...]} — NetworkX's standard node-link export format
{"nodes": [...], "edges": [...]} — a custom alias (edges is checked if links is absent)
A bare list [...] — treated as nodes-only ({"nodes": data, "edges": []})
Each node dict and edge dict is converted via _node_to_doc/_edge_to_doc (see below).
Format: GraphML — _parse_graphml()
Parses standard GraphML XML. Builds a key_id → attr_name map from <key> elements (handling attr.name and <default> values), then for each <node>/<edge> collects its <data key="..."> values, resolved through the key map, applying any unset defaults. Iterates over every <graph> element found (a GraphML file can contain multiple graphs).
Format: GEXF — _parse_gexf()
Parses GEXF (Gephi Exchange Format), trying several namespace URI candidates in order (1.3, 1.2, the legacy 1.2draft, 1.1, and no namespace) since GEXF's namespace varies by exporting tool/version. Builds an attribute id → title map from <attributes><attribute> elements, then extracts <nodes><node> and <edges><edge> elements with their <attvalues>.
Format: edge CSV — _parse_edge_csv(delimiter)
Treats every row as one edge, reading source_col/target_col/relation_col/weight_col per GraphLoaderConfig, with any columns not matching those four added directly into the edge dict (and subsequently into the resulting document's metadata).
_node_to_doc(node, source_fmt) -> LoadedDocument
text: first non-empty value found by trying config.node_text_keys in order; falls back to f"node:{node_id}".
metadata: source (file path), element_type: "node", node_id, graph_format (which parser produced it), plus every other key from the node dict (excluding id) spread directly into metadata.
metadata: source, element_type: "edge", source_node, target_node, relation, weight (stringified), graph_format, plus any other edge fields (excluding the template-consumed ones) spread in.
Examples
Basic — JSON node-link graph:
Python · 6 lines
from zerda.document_loaders import GraphLoader
loader = GraphLoader("knowledge_graph.json")
docs = loader.load()
for doc in docs:
print(doc.metadata["element_type"], doc.page_content[:80])
loader = GraphLoader("large_graph.graphml")
for doc in loader.lazy_load():
index_in_vector_store(doc)
Metadata produced
See _node_to_doc/_edge_to_doc above. Common keys across all formats: source, element_type ("node" or "edge"), graph_format ("json", "graphml", "gexf", or "csv").
Complexity
Time: O(nodes + edges), single pass per format.
Space: GraphML/GEXF parsing uses ElementTree.parse(), which loads the entire XML document tree into memory; JSON parsing similarly loads the whole file via json.load(). There is no true streaming XML/JSON parser in use, even though lazy_load() yields documents incrementally after the full underlying file has been parsed into memory.
Thread safety
No shared mutable state between instances; safe for concurrent use across different GraphLoader instances on different files.
Best practices
Always pass include_nodes/include_edges as direct keyword arguments to GraphLoader(...) rather than only setting them on a GraphLoaderConfig you pass via config= — per the caveat above, the constructor keyword arguments always win.
Customize node_text_keys to match your graph's actual schema — the defaults (label, name, title, content, description, text) cover common conventions but won't match arbitrary custom field names.
Common mistakes
Passing GraphLoaderConfig(include_nodes=False) via config= and expecting nodes to be excluded — without also passing include_nodes=False directly to GraphLoader(...), the config value is overwritten back to the default True.
Assuming GEXF/GraphML parsing is memory-efficient for very large graphs — the whole file is parsed into an in-memory XML tree first.
InMemoryGraphLoader
Extends BaseDocumentLoader directly
Overview
Loads graph data passed directly as Python dict/list structures — no file required. Useful when building a graph programmatically (e.g. from an NLP entity-extraction pipeline) without persisting it to disk first.
Honored as given here — unlike GraphLoader, there is no constructor-kwarg override for include_nodes/include_edges
source_name
str
"in_memory_graph"
Logical source identifier stored in metadata
Internal workflow
Delegates to a private _GraphHelper class (a lightweight, file-independent reimplementation of GraphLoader._node_to_doc/_edge_to_doc) rather than reusing GraphLoader directly, since GraphLoader requires a real file path for construction.
Building a graph programmatically from extracted entities:
Python · 9 lines
entities = extract_entities(text) # your own NLP pipeline
relations = extract_relations(text)
loader = InMemoryGraphLoader(
nodes=[{"id": e.id, "label": e.text, "type": e.entity_type} for e in entities],
edges=[{"source": r.head, "target": r.tail, "relation": r.label} for r in relations],
source_name="extracted_from_doc_42",
)
docs = loader.load()
Use InMemoryGraphLoader instead of writing a temporary JSON file and using GraphLoader when your graph data already exists as Python objects — it avoids unnecessary serialization round-trips.
Common mistakes
Expecting InMemoryGraphLoader's config handling to match GraphLoader's override behavior — it doesn't; here, config.include_nodes/include_edges are respected as-is with no constructor-kwarg override mechanism.
Related APIs
TableLoader — the analogous module for tabular (not graph) data