This is my second RAG project, built after Traditional_RAG. That one was the basic version — load docs, chunk them, embed, store, retrieve, answer. This one I built to actually learn what people mean when they say "advanced RAG," instead of just taking the term at face value.
Every technique in here I added because it fixes something specific that's actually wrong with basic RAG, not just to pad the project. I've listed the reasoning below so future-me (or anyone reading this) knows why each piece exists.
Question
-> Query Expansion (LLM rewrites the question so retrieval isn't stuck on my exact wording)
-> Hybrid Retrieval (BM25 keyword search + dense vector search, combined)
-> MMR on the dense side (stops it from returning 3 chunks that all say the same thing)
-> Re-ranking (LLM looks at the retrieved chunks again and reorders them by actual relevance)
-> Answer
Before any of that can happen, there's a one-time setup step:
Load .txt files -> semantic chunking -> embed -> store in Astra DB
Advance_rag/
├── data/
│ └── txt_file/ my two test docs (cloud computing + database indexing)
├── Load_file.py loads the txt files
├── chunking.py semantic chunking with SemanticChunker
├── vectordb.py embeds the chunks and stores them in Astra DB
├── retrieval.py hybrid retriever - BM25 + dense with MMR
├── query_expansion.py LLM rewrites my question before it hits the retriever
├── re_ranker.py LLM reorders whatever retrieval gave back
├── qa_chain.py the file that actually wires all of this together
└── .env keys, not committed (learned this the hard way)
I'm using uv, Python 3.13.
uv venv
uv python pin 3.13
uv add langchain langchain-text-splitters langchain-openai langchain-experimental langchain-community langchain-astradb astrapy langchain-groq rank_bm25 python-dotenv numpy pandas openpyxl.env file needs:
RAG_TOKEN=github models token
GROQ_API_KEY=groq key
ASTRADB_API_ENDPOINT=astra endpoint
ASTRADB_APPLICATION_TOKEN=astra token
Embeddings go through GitHub Models (text-embedding-3-small), everything else (expansion, re-ranking, final answer) goes through Groq (openai/gpt-oss-120b) since this pipeline makes several LLM calls per question and Groq is fast.
Build the index once, in order:
python Load_file.py
python chunking.py
python vectordb.pyThen ask something:
python qa_chain.pyChange the question variable at the bottom of qa_chain.py to try other questions. retrieval.py, query_expansion.py, and re_ranker.py also run standalone if I want to check what one stage is doing on its own without running the whole thing.
- Semantic chunking — splits at meaning boundaries instead of just cutting every N characters, so a chunk doesn't get sliced in the middle of an idea
- Hybrid retrieval — vector search alone can miss exact keywords/terms since it's matching on meaning, not words. BM25 catches those. Combining both covers more ground than either alone.
- MMR — without it, retrieval sometimes returns 2-3 chunks that basically repeat each other. MMR trades a bit of pure relevance for diversity so the LLM gets more varied context instead of the same point three times.
- Query expansion — my questions are often short and vague. Expanding them with synonyms/related terms before searching helps catch chunks that use different wording than I did.
- Re-ranking — the retriever's similarity score is fast but shallow. Having the LLM actually look at the question and each chunk together and re-rank them catches relevance the raw score misses.
- This makes several LLM calls per question now (expand, rerank, answer) so it's slower and costs more than
Traditional_RAGper query - I've only tested this on two small text files I wrote myself — no idea how it holds up on messier, larger real data yet
- Still single-turn, no memory across questions
- Add conversational memory so follow-up questions actually work
- Test on a bigger, messier dataset instead of my two clean sample files
- Actually compare this against
Traditional_RAGside by side on the same questions and see if it's really better, not just more complicated