How the Graph Export Feature in code-graph-rag Works for External Analysis Tools
The graph export feature in code-graph-rag converts an in-memory code graph into a portable JSON file that any external tool can ingest, using Cypher queries to extract nodes and relationships from Memgraph and serializing them through GraphService.export_graph_to_dict() and export_graph_to_file().
The code-graph-rag repository by vitali87 builds rich knowledge graphs from codebases and stores them in Memgraph. A critical capability is exporting these graphs for external analysis—whether for visualization, static analysis benchmarks, or feeding structured code relationships into LLM pipelines. This article explains the complete export architecture, from Cypher query execution to JSON file generation.
Core Export Architecture
The export pipeline consists of five coordinated components that transform database state into a self-contained JSON artifact.
1. MemgraphIngestor Triggers Export
The MemgraphIngestor class manages the lifecycle of graph data in Memgraph. When export is requested, it delegates to GraphService.export_graph_to_dict() to materialize the entire graph into Python data structures.
In codebase_rag/graph_updater.py, the ingestor maintains the database connection handle that GraphService uses for query execution.
2. GraphService Executes Cypher Queries
The export_graph_to_dict() method in codebase_rag/services/graph_service.py (lines 60–78) performs the heavy lifting:
# From codebase_rag/services/graph_service.py
def export_graph_to_dict(self) -> Dict[str, Any]:
"""Export the entire graph as a dictionary for external use."""
nodes = self._fetch_nodes()
relationships = self._fetch_relationships()
metadata = self._build_metadata(len(nodes), len(relationships))
return {
"nodes": nodes,
"relationships": relationships,
"metadata": metadata
}
Two pre-defined Cypher queries retrieve the raw graph data:
CYPHER_EXPORT_NODES– Returns every node with its ID, labels, and property mapCYPHER_EXPORT_RELATIONSHIPS– Returns every relationship with source ID, target ID, type, and properties
These queries use Memgraph's map projection to ensure JSON-serializable output formats directly from the database.
3. Metadata and Timestamp Generation
The _build_metadata() helper constructs a GraphMetadata dataclass containing:
| Field | Description |
|---|---|
total_nodes |
Count of nodes exported |
total_relationships |
Count of relationships exported |
exported_at |
ISO 8601 timestamp from _get_current_timestamp() (UTC) |
The timestamp is generated once per export, ensuring consistency across all serialised components.
4. export_graph_to_file Serializes to Disk
The export_graph_to_file() function in codebase_rag/main.py (lines 1400–1410) wraps the dictionary export with file I/O:
# From codebase_rag/main.py
def export_graph_to_file(ingestor, output_path: str) -> bool:
"""Export graph to a JSON file for external analysis tools."""
try:
graph_data = ingestor.export_graph_to_dict()
with open(output_path, 'w', encoding='utf-8') as f:
json.dump(graph_data, f, indent=2, ensure_ascii=False)
logger.info(f"Graph exported successfully to {output_path}")
return True
except Exception as e:
logger.error(f"Failed to export graph: {e}")
return False
Error handling is explicit: exceptions are logged and converted to boolean status, allowing CLI and programmatic callers to implement retry logic or fallback behavior.
5. CLI Entry Point
The --export flag in codebase_rag/cli.py (lines 320–340) exposes this functionality to shell users:
# From codebase_rag/cli.py
if args.export:
success = export_graph_to_file(ingestor, args.export)
sys.exit(0 if success else 1)
JSON Output Schema
Exported files follow a predictable structure that external tools can rely on:
{
"nodes": [
{
"id": "func://src/utils.py::calculate_hash",
"labels": ["Function", "Callable"],
"properties": {
"name": "calculate_hash",
"file_path": "src/utils.py",
"line_number": 45,
"docstring": "Compute BLAKE2b hash of input data."
}
}
],
"relationships": [
{
"source_id": "func://src/utils.py::calculate_hash",
"target_id": "func://src/crypto.py::blake2b",
"type": "CALLS",
"properties": {
"confidence": 1.0,
"static": true
}
}
],
"metadata": {
"total_nodes": 1247,
"total_relationships": 3892,
"exported_at": "2024-01-15T09:23:17.342Z"
}
}
This schema is self-describing: no external schema registry or Memgraph connection is required to interpret the file.
Practical Usage Examples
Export via Command Line
# Basic export to current directory
python -m codebase_rag.main --export ./my_project_graph.json
# Verify export succeeded
jq '.metadata' my_project_graph.json
Programmatic Export in Python
from codebase_rag.graph_updater import MemgraphIngestor
from codebase_rag.main import export_graph_to_file
# Initialize with existing Memgraph connection
ingestor = MemgraphIngestor(uri="bolt://localhost:7687")
# Export to file
success = export_graph_to_file(ingestor, "code_graph.json")
assert success, "Export failed"
# Or work with the dictionary directly
graph_dict = ingestor.export_graph_to_dict()
print(f"Exported {graph_dict['metadata']['total_nodes']} nodes")
Loading into NetworkX for Analysis
import json
import networkx as nx
# Load exported graph
with open("code_graph.json") as f:
data = json.load(f)
# Build NetworkX directed graph
G = nx.DiGraph()
# Add nodes with all properties
for node in data["nodes"]:
G.add_node(node["id"], labels=node["labels"], **node["properties"])
# Add edges with relationship type and properties
for rel in data["relationships"]:
G.add_edge(
rel["source_id"],
rel["target_id"],
relation_type=rel["type"],
**rel["properties"]
)
# Run external analysis: find most called functions
in_degree = dict(G.in_degree())
most_called = max(in_degree, key=in_degree.get)
print(f"Most called function: {most_called} ({in_degree[most_called]} callers)")
Importing into Neo4j for Visualization
from py2neo import Graph, Node, Relationship
import json
g = Graph("bolt://localhost:7687", auth=("neo4j", "password"))
with open("code_graph.json") as f:
data = json.load(f)
# Create nodes
node_map = {}
for n in data["nodes"]:
node = Node(*n["labels"], **n["properties"])
g.create(node)
node_map[n["id"]] = node
# Create relationships
for r in data["relationships"]:
src = node_map[r["source_id"]]
tgt = node_map[r["target_id"]]
g.create(Relationship(src, r["type"], tgt, **r.get("properties", {})))
Reliability and Performance Considerations
Memory Limits for Large Graphs
GraphService applies settings.QUERY_MEMORY_LIMIT_MB before executing Cypher queries, preventing unbounded memory consumption when exporting massive codebases.
Consistent Error Handling
Export failures are logged with structured messages (ls.MG_EXPORTING, ls.MG_EXPORTED from localization strings) and propagated as boolean return values. This supports:
- Retry logic with exponential backoff
- Fallback to partial exports
- Pipeline failure notifications in CI/CD systems
Timestamp Consistency
The UTC timestamp is captured once at export start, not per-query. This ensures that metadata.exported_at accurately represents a single logical snapshot, even if node and relationship queries execute sequentially.
Key Source Files
| File | Purpose | Direct Link |
|---|---|---|
codebase_rag/services/graph_service.py |
Core export logic: export_graph_to_dict(), Cypher query execution, metadata construction |
View source |
codebase_rag/main.py |
File serialization wrapper: export_graph_to_file() with error handling |
View source |
codebase_rag/cli.py |
Command-line interface exposing --export flag |
View source |
codebase_rag/graph_updater.py |
MemgraphIngestor class that orchestrates database operations |
View source |
evals/cgr_graph.py |
Evaluation harness consuming exported graphs for RAG benchmarks | View source |
codebase_rag/tests/test_main_helpers_unit.py |
Unit tests verifying export functionality and edge cases | View source |
Summary
export_graph_to_dict()ingraph_service.pyqueries Memgraph usingCYPHER_EXPORT_NODESandCYPHER_EXPORT_RELATIONSHIPS, returning a structured dictionary with nodes, relationships, and metadataexport_graph_to_file()inmain.pyhandles JSON serialization and file I/O with explicit error handling- CLI integration via
--exportflag makes the feature accessible without writing code - Self-describing JSON output requires no live database connection for external tools to consume
- Memory limits and consistent timestamps ensure reliable operation on large codebases
Frequently Asked Questions
What format does the graph export produce?
The export produces JSON with a stable three-part structure: nodes (array of node objects), relationships (array of relationship objects), and metadata (export statistics and timestamp). This format is natively supported by Python's json module, JavaScript, and most graph analysis libraries.
Can I export without running the full ingestion pipeline?
No—the export requires an active MemgraphIngestor instance with a populated database. The export queries read from Memgraph, not from cached intermediate files. To export a codebase, you must first complete ingestion, then call the export function.
How large can exported graphs be?
The system handles large graphs through two mechanisms: query-level memory limits (settings.QUERY_MEMORY_LIMIT_MB) prevent database-side overload, and streaming JSON serialization avoids loading the entire export into RAM before writing. For multi-million node graphs, consider exporting subgraphs using filtered Cypher queries directly against Memgraph.
Which external tools are compatible with the export format?
Any tool that ingests JSON works directly. Verified use cases include NetworkX (Python graph algorithms), Neo4j (import via apoc.import.json or py2neo), Graphistry (visualization), Gephi (via JSON → GEXF conversion), and custom LLM pipelines that parse the structure for retrieval-augmented generation.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →