
From 17 node types to 6: my 11-step GraphRAG pipeline, what worked, and what's still broken
While building a financial assistant for an SF start-up, we learned that AI frameworks add complexity without value. When I started building a personal assistant with GraphRAG, I carried that lesson but still tried LangChain's MongoDBGraphStore. It gave me a working knowledge graph in 10 minutes.
Then I looked at the data. I had 17 node types and 34 relationship types from just 5 documents, including three versions of "part of". GraphRAG is a data modeling problem, not a retrieval problem.
The attached diagram shows the full 11-step pipeline I ended up with. Here is a walkthrough of what you can learn from each step.
So basically, in steps 1 and 2 of the data pipeline, raw sources go through an Extract, Transform, Load (ETL) process. They land as documents in a MongoDB data warehouse. Each document stores the source type, URI, content, and metadata.
Then in step 3, we clean the documents and split them into token-bounded chunks. We started with 512 tokens with a 64-token overlap. Still, we have to run more tests on this.
The thing is, step 4 handles graph extraction. We defined a strict ontology. An ontology is just a formal contract defining exactly what categories and relationships exist in your data. We used 6 node types and 8 edge types. The LLM can only extract what this ontology allows.
For example, if it outputs a PERSON to TASK connection with an EXPERIENCED edge, the pipeline rejects it. EXPERIENCED must connect a PERSON to an EPISODE.
We also split LLM extraction from deterministic extraction. We create structural entries like Document or Chunk nodes without LLM calls.
Turns out, step 5 for normalization is the hardest part. We use a three-phase deduplication process. We do in-memory fuzzy matching, cross-document resolution against MongoDB, and edge remapping.
Anyway, in step 6, we batch embed the nodes. The system uses a mock for tests, Sentence Transformers for development, and the Voyage API for production.
Ultimately, in steps 7 and 8, nodes and edges are stored in a single MongoDB collection as unified memory. We use deterministic string IDs like "person:alice" to prevent duplicates. MongoDB handles documents, $vectorSearch, $text, and $graphLookup in one aggregation pipeline. The $graphLookup function natively traverses connected graph data directly in the database. You don't need Neo4j + Pinecone + Postgres for most agent use cases. A single database like MongoDB gets the job done really well. Through sharding, you can scale it up to a billion records.
To wrap it up, steps 9 through 11 cover retrieval. The agent calls tools through an MCP server. It uses search memory with hybrid vector, text, and graph expansion, alongside query memory for natural language to MongoDB aggregation. The agent also uses ingest tools to write back to the database for continual learning.
Here are a few things I am still struggling with and would love your opinion on:
- How are you handling entity/relationship resolution across documents?
- What helped you the most to optimize the extraction of entities/relationships using LLMs?
- How do you keep embeddings in sync after graph updates?
Also, while building my personal assistant, I have been writing about this system on LinkedIn over the past few months. Here are the posts that go deeper into each piece:
- 3 ways to run embedding models
- LangChain gave me a knowledge graph in 10 minutes
- Palantir built a $400B empire on ontology-first AI
- Ingestion architecture for Digital Twin agent
- Most AI agents don't need three databases
- CLI tools > MCP servers for DB access during dev
P.S. I am also planning to open-source the full repo soon.
TL;DR: Frameworks create messy graphs. Define a strict ontology, extract deterministically where possible, use a unified database, and accept that entity resolution will be painful.