Skip to main content

ai-data-engineering-rag-pipeline

Build production-grade local RAG pipelines with BM25 search, hierarchical chunking, and retrieval evaluation from Nahid's hands-on roadmap

الانتقال إلى التثبيت

معلومات المصدر

المستودع
reason-machines/data-skills
آخر نشاط في المصدر
٣١ يوليو ٢٠٢٦ في ١١:٤٩
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٥
التفرعات
١

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
ai-data-engineering-rag-pipeline
description
Build production-grade local RAG pipelines with BM25 search, hierarchical chunking, and retrieval evaluation from Nahid's hands-on roadmap
triggers
["how do I build a RAG pipeline from scratch","implement BM25 baseline search engine","create hierarchical chunking for document retrieval","evaluate retrieval performance with Recall@10","design retrieval contracts and golden datasets","set up local RAG with chunk granularity analysis","build inverted index for document search","implement parent-child metadata linkage for chunks"]
# AI Data Engineering RAG Pipeline > Skill by [ara.so](https://ara.so) — Data Skills collection. This skill helps you build production-grade local RAG (Retrieval-Augmented Generation) pipelines using the architectural patterns and implementations from Nahid Mahmud's AI & Data Engineering Roadmap. The project focuses on baseline search engines, hierarchical chunking strategies, and rigorous evaluation methodologies. ## What This Project Provides - **Day 01**: Retrieval contract design, governed corpus creation, golden dataset (`questions.jsonl`), and evaluation frameworks - **Day 02**: BM25 baseline search engine with inverted index, Okapi BM25 ranking, and Recall@10 evaluation - **Day 03**: RAG pipeline with hierarchical chunking (document, section, paragraph levels), deterministic chunk IDs, parent-child metadata linkage, and failure mode analysis ## Installation & Setup Clone the repository: ```bash git clone https://github.com/Nahid-mahmud555/ai-data-engineering-roadmap.git cd ai-data-engineering-roadmap ``` Install dependencies (each day has its own requirements): ```bash # For Day 02 (BM25) cd Day_02 pip install -r requirements.txt # For Day 03 (RAG Pipeline) cd Day_03 pip install -r requirements.txt ``` Common dependencies across modules: - `numpy` - numerical operations - `rank-bm25` - BM25 implementation - `sentence-transformers` - embeddings (Day 03+) - `faiss-cpu` - vector search (Day 03+) ## Project Structure ``` ai-data-engineering-roadmap/ ├── Day_01/ # Retrieval contracts & golden datasets ├── Day_02/ # BM25 baseline search engine │ ├── baseline_bm25.py │ ├── corpus/ # Document collection │ └── questions.jsonl ├── Day_03/ # RAG pipeline with hierarchical chunking │ ├── pipeline.py │ ├── corpus/ # Multi-domain documents │ └── questions.jsonl ``` ## Day 02: BM25 Baseline Search Engine ### Core Implementation The BM25 baseline provides a deterministic search engine for retrieval evaluation: ```python from rank_bm25 import BM25Okapi import json # Load corpus corpus_docs = [] doc_ids = [] # Assuming corpus stored in text files import os corpus_dir = "corpus" for filename in os.listdir(corpus_dir): if filename.endswith(".txt"): with open(os.path.join(corpus_dir, filename), 'r') as f: content = f.read() corpus_docs.append(content) doc_ids.append(filename) # Tokenize corpus (simple whitespace tokenization) tokenized_corpus = [doc.lower().split() for doc in corpus_docs] # Build BM25 index bm25 = BM25Okapi(tokenized_corpus) # Query retrieval query = "what is machine learning" tokenized_query = query.lower().split() # Get top-k scores and documents doc_scores = bm25.get_scores(tokenized_query) # Retrieve top 10 documents import numpy as np top_n = 10 top_indices = np.argsort(doc_scores)[::-1][:top_n] results = [(doc_ids[i], corpus_docs[i], doc_scores[i]) for i in top_indices] for rank, (doc_id, content, score) in enumerate(results, 1): print(f"Rank {rank}: {doc_id} (Score: {score:.4f})") print(f"Preview: {content[:200]}...\n") ``` ### Recall@10 Evaluation Evaluate retrieval quality against golden dataset: ```python import json # Load golden dataset with open('questions.jsonl', 'r') as f: questions = [json.loads(line) for line in f] def calculate_recall_at_k(bm25_index, tokenized_corpus, doc_ids, questions, k=10): """ Calculate Recall@K for retrieval evaluation questions format: [{"query": "...", "relevant_docs": ["doc1.txt", "doc2.txt"]}] """ total_recall = 0 num_queries = len(questions) for q in questions: query = q['query'] relevant_docs = set(q['relevant_docs']) # Tokenize and search tokenized_query = query.lower().split() scores = bm25_index.get_scores(tokenized_query) # Get top-k top_k_indices = np.argsort(scores)[::-1][:k] retrieved_docs = set([doc_ids[i] for i in top_k_indices]) # Calculate recall relevant_retrieved = len(relevant_docs.intersection(retrieved_docs)) recall = relevant_retrieved / len(relevant_docs) if relevant_docs else 0 total_recall += recall print(f"Query: {query}") print(f"Recall@{k}: {recall:.2%}") print(f"Retrieved: {retrieved_docs}") print(f"Relevant: {relevant_docs}\n") avg_recall = total_recall / num_queries print(f"Average Recall@{k}: {avg_recall:.2%}") return avg_recall # Run evaluation recall = calculate_recall_at_k(bm25, tokenized_corpus, doc_ids, questions, k=10) ``` ## Day 03: Hierarchical Chunking RAG Pipeline ### Chunk Granularity Strategy Implement multi-level chunking with parent-child metadata: ```python import hashlib import uuid def generate_deterministic_chunk_id(content, parent_id=None, level="document"): """ Generate deterministic chunk ID based on content hash """ content_hash = hashlib.sha256(content.encode('utf-8')).hexdigest()[:16] return f"{level}_{content_hash}" def hierarchical_chunking(document, doc_id): """ Create document, section, and paragraph level chunks Returns: List of chunk dictionaries with metadata """ chunks = [] # Document-level chunk doc_chunk_id = generate_deterministic_chunk_id(document, level="doc") chunks.append({ "chunk_id": doc_chunk_id, "content": document, "level": "document", "parent_id": None, "doc_id": doc_id, "metadata": {"granularity": "document"} }) # Section-level chunking (split by double newline or headers) sections = document.split("\n\n") for sec_idx, section in enumerate(sections): if len(section.strip()) < 50: # Skip very short sections continue sec_chunk_id = generate_deterministic_chunk_id(section, level="sec") chunks.append({ "chunk_id": sec_chunk_id, "content": section, "level": "section", "parent_id": doc_chunk_id, "doc_id": doc_id, "metadata": { "granularity": "section", "section_index": sec_idx } }) # Paragraph-level chunking paragraphs = section.split("\n") for para_idx, paragraph in enumerate(paragraphs): if len(paragraph.strip()) < 20: # Skip very short paragraphs continue para_chunk_id = generate_deterministic_chunk_id(paragraph, level="para") chunks.append({ "chunk_id": para_chunk_id, "content": paragraph, "level": "paragraph", "parent_id": sec_chunk_id, "doc_id": doc_id, "metadata": { "granularity": "paragraph", "section_index": sec_idx, "paragraph_index": para_idx } }) return chunks # Example usage sample_doc = """# Machine Learning Basics Machine learning is a subset of artificial intelligence that enables systems to learn from data. ## Supervised Learning Supervised learning uses labeled data to train models. Common algorithms include linear regression and decision trees. ## Unsupervised Learning Unsupervised learning finds patterns in unlabeled data. Clustering and dimensionality reduction are key techniques.""" chunks = hierarchical_chunking(sample_doc, "ml_basics.txt") for chunk in chunks: print(f"Level: {chunk['level']}, ID: {chunk['chunk_id'][:20]}...") print(f"Parent: {chunk['parent_id'][:20] if chunk['parent_id'] else 'None'}...") print(f"Content: {chunk['content'][:80]}...\n") ``` ### RAG Pipeline with Vector Search Combine BM25 with semantic search: ```python from sentence_transformers import SentenceTransformer import faiss import numpy as np class HybridRAGPipeline: def __init__(self, embedding_model="all-MiniLM-L6-v2"): self.embedding_model = SentenceTransformer(embedding_model) self.chunks = [] self.bm25_index = None self.faiss_index = None def index_documents(self, documents): """ Index documents with both BM25 and vector embeddings """ # Create hierarchical chunks all_chunks = [] for doc_id, doc_content in documents.items(): chunks = hierarchical_chunking(doc_content, doc_id) all_chunks.extend(chunks) self.chunks = all_chunks # BM25 indexing tokenized_chunks = [chunk['content'].lower().split() for chunk in all_chunks] self.bm25_index = BM25Okapi(tokenized_chunks) # Vector indexing chunk_texts = [chunk['content'] for chunk in all_chunks] embeddings = self.embedding_model.encode(chunk_texts, show_progress_bar=True) # Build FAISS index dimension = embeddings.shape[1] self.faiss_index = faiss.IndexFlatIP(dimension) # Inner product for cosine similarity # Normalize embeddings for cosine similarity faiss.normalize_L2(embeddings) self.faiss_index.add(embeddings) def retrieve(self, query, top_k=10, alpha=0.5, granularity="paragraph"): """ Hybrid retrieval: alpha * BM25 + (1-alpha) * Vector Args: query: Search query top_k: Number of results alpha: Weight for BM25 (0-1), (1-alpha) for vector search granularity: Filter by chunk level (document, section, paragraph) """ # Filter chunks by granularity filtered_indices = [i for i, chunk in enumerate(self.chunks) if chunk['level'] == granularity] if not filtered_indices: filtered_indices = list(range(len(self.chunks))) # BM25 scores tokenized_query = query.lower().split() bm25_scores = self.bm25_index.get_scores(tokenized_query) bm25_scores_filtered = bm25_scores[filtered_indices] # Normalize BM25 scores if bm25_scores_filtered.max() > 0: bm25_scores_filtered = bm25_scores_filtered / bm25_scores_filtered.max() # Vector scores query_embedding = self.embedding_model.encode([query]) faiss.normalize_L2(query_embedding) distances, indices = self.faiss_index.search(query_embedding, len(self.chunks)) vector_scores = distances[0] vector_scores_filtered = vector_scores[filtered_indices] # Hybrid scoring hybrid_scores = alpha * bm25_scores_filtered + (1 - alpha) * vector_scores_filtered # Get top-k top_k_local = np.argsort(hybrid_scores)[::-1][:top_k] top_k_global = [filtered_indices[i] for i in top_k_local] results = [] for idx in top_k_global: results.append({ "chunk": self.chunks[idx], "bm25_score": float(bm25_scores[idx]), "vector_score": float(vector_scores[idx]), "hybrid_score": float(alpha * bm25_scores[idx] + (1 - alpha) * vector_scores[idx]) }) return results # Example usage pipeline = HybridRAGPipeline() # Load corpus documents = { "ml_basics.txt": sample_doc, "dl_intro.txt": "Deep learning uses neural networks with multiple layers..." } pipeline.index_documents(documents) # Query with different granularities query = "what is supervised learning" print("=== Paragraph-level retrieval ===") results = pipeline.retrieve(query, top_k=5, alpha=0.5, granularity="paragraph") for i, result in enumerate(results, 1): print(f"\nRank {i}:") print(f"Content: {result['chunk']['content'][:150]}...") print(f"Level: {result['chunk']['level']}") print(f"Hybrid Score: {result['hybrid_score']:.4f}") ``` ## Retrieval Evaluation Framework Evaluate chunking strategies against golden dataset: ```python def evaluate_chunking_strategy(pipeline, questions, granularity="paragraph", k=10): """ Evaluate retrieval performance for specific chunk granularity """ metrics = { "recall": [], "precision": [], "mrr": [] # Mean Reciprocal Rank } for q in questions: query = q['query'] relevant_docs = set(q['relevant_docs']) # Retrieve with specific granularity results = pipeline.retrieve(query, top_k=k, granularity=granularity) retrieved_docs = set([r['chunk']['doc_id'] for r in results]) # Recall relevant_retrieved = len(relevant_docs.intersection(retrieved_docs)) recall = relevant_retrieved / len(relevant_docs) if relevant_docs else 0
عرض على GitHub
ملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub