# πŸ—οΈ Architecture Overview ## System Architecture

System Architecture Diagram

High-level system architecture diagram illustrating the complete RAG pipeline flow

### Detailed Component View ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ User Interface β”‚ β”‚ (Streamlit Web App) β”‚ β”‚ app.py β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ RAG Pipeline β”‚ β”‚ (rag_pipeline.py) β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ System Prompt β”‚ β”‚ QA Chain β”‚ β”‚ β”‚ β”‚ Template │─────▢│ (LangChain) β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β–Ό β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Vector Store β”‚ β”‚ LLM Handler β”‚ β”‚ (vectorstore.py) β”‚ β”‚ (llm_handler.py) β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ ChromaDB β”‚ β”‚ β”‚ β”‚ Ollama β”‚ β”‚ β”‚ β”‚ (Embeddings) β”‚ β”‚ β”‚ β”‚ (llama3.2) β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ HuggingFace β”‚ β”‚ β”‚ β”‚ Embeddings β”‚ β”‚ β”‚ β”‚ (all-MiniLM) β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β–² β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Document Processor β”‚ β”‚ (document_processor.py)β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ PDF Loader β”‚ β”‚ β”‚ β”‚ DOCX Loader β”‚ β”‚ β”‚ β”‚ HTML Loader β”‚ β”‚ β”‚ β”‚ Text Loader β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Text Splitter β”‚ β”‚ β”‚ β”‚ (Chunking) β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β–² β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Source Documents β”‚ β”‚ data/documents/ β”‚ β”‚ β”‚ β”‚ β€’ Resume.pdf β”‚ β”‚ β€’ LinkedIn.html β”‚ β”‚ β€’ Projects.docx β”‚ β”‚ β€’ ... β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Configuration Layer β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ β”‚ β”‚ config.yaml env.template (.env) β”‚ β”‚ β”œβ”€ profile β”œβ”€ OLLAMA_BASE_URL β”‚ β”‚ β”œβ”€ llm β”œβ”€ CHROMA_PERSIST_DIR β”‚ β”‚ β”œβ”€ embeddings β”œβ”€ DOCUMENTS_DIR β”‚ β”‚ β”œβ”€ vectorstore β”œβ”€ LOG_LEVEL β”‚ β”‚ β”œβ”€ document_proc └─ API_KEYS (optional) β”‚ β”‚ β”œβ”€ rag β”‚ β”‚ β”œβ”€ ui β”‚ β”‚ └─ logging β”‚ β”‚ β”‚ β”‚ (config_loader.py) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` ## Data Flow ### 1️⃣ Indexing Phase (One-time Setup) ``` Documents β†’ Load β†’ Chunk β†’ Build Indexes ↓ ↓ ↓ ↓ resume.pdf PyPDF Split β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” linkedin.html BS4 into β”‚ BM25 Index (keyword) β”‚ projects.docx docx chunks β”‚ ./bm25_index/ β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ Vector Index (semantic) β”‚ β”‚ ./chroma_db/ (ChromaDB) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` **Command:** `python -m src.build_vectorstore` **Strategy Options:** - `--strategy bm25_vector` - Build both indexes (default, recommended) - `--strategy vector` - Vector index only - `--strategy bm25` - BM25 index only ### 2️⃣ Query Phase (Runtime) - WITH HYBRID SEARCH ``` User Question ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Extract Chat History (if enabled) β”‚ β”‚ β€’ Format previous Q&A pairs β”‚ β”‚ β€’ Truncate by turns/tokens β”‚ β”‚ β€’ Include in context β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Load Main Document (if enabled) β”‚ β”‚ β€’ Check cache validity β”‚ β”‚ β€’ Auto-detect format β”‚ β”‚ β€’ Count tokens β”‚ β”‚ β€’ Summarize if needed β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ [Main Doc Content] β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” ↓ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” Main Doc (Priority) β”‚ Hybrid Retrieval Strategy β”‚ ↓ β”‚ β”‚ [Always Available] β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ [10k tokens max] β”‚ β”‚ BM25 β”‚ β”‚ Vector β”‚ β”‚ β”‚ β”‚ (keyword) β”‚ β”‚ (semantic) β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ ↓ β”‚ β”‚ Reciprocal Rank Fusion (RRF) β”‚ β”‚ (70% vector, 30% BM25) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ [Retrieved Context] β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ Construct Prompt: System Prompt β†’ Chat History β†’ Main Doc β†’ Retrieved Context β†’ Question ↓ Send to LLM (Ollama/Groq) ↓ Generate Answer (streaming) ↓ Response Enhancer β€’ Detect negative phrases β€’ Rewrite positively β€’ Fix markdown formatting β€’ Add forward-looking closings ↓ Display in Streamlit UI (with source citations) ``` ## Component Details ### πŸ“‘ Main Document Integration The Main Document feature ensures critical profile information is always available in the LLM context, regardless of VectorDB retrieval quality. ```python MainDocumentLoader β”œβ”€β”€ Format Auto-Detection β”‚ β”œβ”€β”€ Markdown (.md) β†’ LangChain TextLoader β”‚ β”œβ”€β”€ Plain Text (.txt) β†’ LangChain TextLoader β”‚ β”œβ”€β”€ PDF (.pdf) β†’ Existing PDF loader β”‚ β”œβ”€β”€ Word (.docx) β†’ Existing DOCX loader β”‚ └── HTML (.html) β†’ Existing HTML loader β”‚ β”œβ”€β”€ Token Management β”‚ β”œβ”€β”€ Counting: tiktoken (cl100k_base encoding) β”‚ β”œβ”€β”€ Max Limit: 10,000 tokens (configurable) β”‚ β”œβ”€β”€ Truncation: Smart token-based trimming β”‚ └── Summarization: LLM-based if exceeds limit β”‚ β”œβ”€β”€ Caching Strategy β”‚ β”œβ”€β”€ File hash-based invalidation (MD5) β”‚ β”œβ”€β”€ Configurable check interval (60s default) β”‚ └── Automatic reload on file changes β”‚ └── Integration Point └── Positioned BEFORE VectorDB context (high priority) ``` **Architecture Flow:** ``` Main Document (Priority Context) ↓ [Essential Info Always Available] ↓ VectorDB Retrieval (Additional Context) ↓ [Supplementary Information] ↓ Combined Context β†’ LLM β†’ Response ``` **Benefits:** - βœ… Critical information never missed by retrieval - βœ… Auto-format detection (no manual config) - βœ… Intelligent token management with LLM summarization - βœ… Efficient caching for performance - βœ… Graceful degradation if unavailable **Note:** The system also supports chat history for conversational context. Previous Q&A pairs are included in the prompt (after system prompt, before main document) with configurable truncation by turns or tokens. Responses are post-processed to improve tone, remove negative language, and fix markdown formatting. ### πŸ“„ Document Processing Pipeline ```python DocumentProcessor β”œβ”€β”€ Supported Formats β”‚ β”œβ”€β”€ PDF β†’ pypdf β”‚ β”œβ”€β”€ Word β†’ python-docx β”‚ β”œβ”€β”€ HTML β†’ BeautifulSoup4 β”‚ └── Text/MD β†’ LangChain TextLoader β”‚ β”œβ”€β”€ Chunking Strategy β”‚ β”œβ”€β”€ Size: 1000 chars (configurable) β”‚ β”œβ”€β”€ Overlap: 200 chars (configurable) β”‚ └── Separators: ["\n\n", "\n", ". ", " ", ""] β”‚ └── Output: List[Document] └── Each with content + metadata ``` ### πŸ” Retrieval Strategy System The retrieval system uses a pluggable strategy pattern for extensibility. ```python RetrieverFactory β”œβ”€β”€ Registered Strategies β”‚ β”œβ”€β”€ "vector" β†’ VectorStrategy (semantic search) β”‚ β”œβ”€β”€ "bm25" β†’ BM25Strategy (keyword search) β”‚ β”œβ”€β”€ "bm25_vector" β†’ BM25VectorStrategy (hybrid) β”‚ └── (future: "page_index", "graph_vector") β”‚ └── Strategy Interface (BaseRetrieverStrategy) β”œβ”€β”€ build_index(documents) β†’ Build/update index β”œβ”€β”€ load_index() β†’ Load from disk β”œβ”€β”€ retrieve(query, k) β†’ Get relevant docs └── as_retriever() β†’ LangChain compatible ``` #### Hybrid Search (BM25 + Vector) ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ BM25VectorStrategy β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ BM25Retriever β”‚ β”‚ VectorRetriever β”‚ β”‚ β”‚ β”‚ (keyword match) β”‚ β”‚ (semantic match) β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β€’ Exact terms β”‚ β”‚ β€’ Meaning/context β”‚ β”‚ β”‚ β”‚ β€’ Abbreviations β”‚ β”‚ β€’ Synonyms β”‚ β”‚ β”‚ β”‚ β€’ Proper nouns β”‚ β”‚ β€’ Related concepts β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ (k=10) β”‚ (k=10) β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ ↓ β”‚ β”‚ Reciprocal Rank Fusion (RRF) β”‚ β”‚ weights: {vector: 0.7, bm25: 0.3} β”‚ β”‚ ↓ β”‚ β”‚ Top K results (k=4) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` **RRF Formula:** `score(d) = Ξ£ (weight Γ— 1/(k + rank(d)))` ### 🧠 Vector Store Architecture ```python VectorStoreManager β”œβ”€β”€ Embedding Model β”‚ └── sentence-transformers/all-MiniLM-L6-v2 β”‚ β”œβ”€β”€ Dimension: 384 β”‚ β”œβ”€β”€ Speed: ~2000 sentences/sec (CPU) β”‚ └── Quality: Good for semantic search β”‚ β”œβ”€β”€ ChromaDB β”‚ β”œβ”€β”€ Type: Persistent (SQLite) β”‚ β”œβ”€β”€ Location: ./chroma_db/ β”‚ β”œβ”€β”€ Collection: profile_documents β”‚ └── Indexing: HNSW (approximate NN) β”‚ └── Retrieval β”œβ”€β”€ Search Type: Similarity (or MMR) β”œβ”€β”€ Top K: 10 (before fusion) └── Distance: Cosine similarity ``` ### πŸ“ BM25 Store Architecture ```python BM25Store β”œβ”€β”€ Algorithm: BM25Okapi (rank-bm25) β”‚ β”œβ”€β”€ Tokenization β”‚ β”œβ”€β”€ "simple": Whitespace + punctuation split β”‚ └── "nltk": NLTK word_tokenize (optional) β”‚ β”œβ”€β”€ Persistence β”‚ β”œβ”€β”€ Location: ./bm25_index/ β”‚ β”œβ”€β”€ Format: Pickle (index + documents) β”‚ └── Metadata: JSON (hash, stats) β”‚ └── Retrieval β”œβ”€β”€ Top K: 10 (before fusion) └── Scoring: BM25 term frequency ``` ### πŸ€– LLM Integration ```python LLMHandler β”œβ”€β”€ Provider Support β”‚ β”œβ”€β”€ Ollama (Local) β”‚ β”‚ β”œβ”€β”€ Base URL: http://localhost:11434 β”‚ β”‚ β”œβ”€β”€ Protocol: HTTP/REST API β”‚ β”‚ └── Auto-detected if no API key β”‚ β”‚ β”‚ └── Groq (Cloud) β”‚ β”œβ”€β”€ Base URL: https://api.groq.com/openai/v1 β”‚ β”œβ”€β”€ Protocol: OpenAI-compatible API β”‚ └── Requires GROQ_API_KEY env var β”‚ β”œβ”€β”€ Model Options (Ollama) β”‚ β”œβ”€β”€ llama3.2:3b (Recommended) β”‚ β”œβ”€β”€ phi3:mini β”‚ β”œβ”€β”€ gemma2:2b β”‚ └── llama3.1:8b (with GPU) β”‚ β”œβ”€β”€ Model Options (Groq) β”‚ β”œβ”€β”€ openai/gpt-oss-120b β”‚ β”œβ”€β”€ llama-3.3-70b-versatile β”‚ β”œβ”€β”€ llama-3.1-8b-instant β”‚ β”œβ”€β”€ mixtral-8x7b-32768 β”‚ └── gemma2-9b-it β”‚ └── Parameters β”œβ”€β”€ Temperature: 0.7 β”œβ”€β”€ Max Tokens: 800 (increased for better formatting) β”œβ”€β”€ Top P: 0.9 └── Context Window: Model-dependent (8192-128k tokens) ``` ### πŸ”— RAG Chain ```python RAGPipeline β”œβ”€β”€ Retrieval Strategy β”‚ └── RetrieverFactory.create(strategy_name) β”‚ β”œβ”€β”€ "vector" β†’ Vector-only retriever β”‚ β”œβ”€β”€ "bm25" β†’ BM25-only retriever β”‚ └── "bm25_vector" β†’ Fusion retriever (default) β”‚ β”œβ”€β”€ Prompt Template Structure β”‚ β”œβ”€β”€ System Prompt (from config) β”‚ β”œβ”€β”€ Chat History (formatted previous Q&A pairs) β”‚ β”œβ”€β”€ Main Document (priority context, always available) β”‚ β”œβ”€β”€ Retrieved Context (from strategy) β”‚ └── User Question β”‚ β”œβ”€β”€ LLM β”‚ └── Ollama or Groq (auto-detected or configured) β”‚ └── Output β”œβ”€β”€ Answer (post-processed for tone/formatting, streaming support) └── Source Documents (citations) ``` ## Configuration Hierarchy ``` 1. Environment Variables (.env) β”œβ”€β”€ Override config.yaml values β”œβ”€β”€ Secrets (API keys) └── Runtime settings (ports, URLs) ↓ 2. config.yaml β”œβ”€β”€ Application defaults β”œβ”€β”€ Model selection β”œβ”€β”€ Retrieval strategy (vector, bm25, bm25_vector) └── RAG parameters ↓ 3. Code Defaults └── Fallback values if config missing ``` ### Retrieval Configuration ```yaml retrieval: strategy: "bm25_vector" # Which strategy to use final_k: 4 # Documents returned to LLM vector: search_type: "similarity" # or "mmr" k: 10 # Docs before fusion bm25: k: 10 # Docs before fusion persist_path: "./bm25_index" tokenizer: "simple" fusion: algorithm: "rrf" # Reciprocal Rank Fusion rrf_k: 60 # RRF constant weights: vector: 0.7 bm25: 0.3 ``` ### Chat History Configuration ```yaml chat: enable_history: true # Enable conversation context max_history_turns: 10 # Max Q&A pairs to include max_history_tokens: 2000 # Token limit for history ``` ### RAG Configuration ```yaml rag: enhance_responses: true # Enable post-processing enhancement include_sources: true # Show source citations source_max_length: 150 # Max length of source preview ``` ## Deployment Architecture ### Local Development ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Developer β”‚ β”‚ Machine β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Ollama β”‚ β”‚ ← Port 11434 (optional) β”‚ β”‚ Server β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Groq API β”‚ β”‚ ← https://api.groq.com (optional) β”‚ β”‚ (Cloud) β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Streamlit β”‚ β”‚ ← Port 8501 β”‚ β”‚ App β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ ChromaDB β”‚ β”‚ ← ./chroma_db/ β”‚ β”‚ (local) β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ BM25 Index β”‚ β”‚ ← ./bm25_index/ β”‚ β”‚ (local) β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` ### Hugging Face Spaces ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ HF Spaces Container β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Dockerfile β”‚ β”‚ β”‚ β”‚ β”œβ”€ Install Ollama β”‚ β”‚ β”‚ β”‚ β”œβ”€ Pull Model β”‚ β”‚ β”‚ β”‚ β”œβ”€ Build Indexes β”‚ β”‚ β”‚ β”‚ └─ Start Services β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Ollama β”‚ β”‚Streamlit β”‚ β”‚ β”‚ β”‚ Server β”‚ β”‚ App β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Persistent Storage β”‚ β”‚ β”‚ β”‚ β”œβ”€ chroma_db/ β”‚ β”‚ β”‚ β”‚ └─ bm25_index/ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↑ β”‚ HTTPS β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Public Users β”‚ β”‚ (Recruiters) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` ## Build & Packaging ### UV + Hatchling + Versioningit ``` pyproject.toml β”œβ”€β”€ [build-system] β”‚ β”œβ”€β”€ requires: ["hatchling", "versioningit"] β”‚ └── build-backend: "hatchling.build" β”‚ β”œβ”€β”€ [project] β”‚ β”œβ”€β”€ name: "profillybot" β”‚ β”œβ”€β”€ version: β”‚ └── dependencies: [...] β”‚ β”œβ”€β”€ [tool.versioningit] β”‚ β”œβ”€β”€ Read git tags (v0.1.0, v0.2.0, etc.) β”‚ β”œβ”€β”€ Generate version string β”‚ └── Write to src/_version.py β”‚ └── [tool.ruff] β”œβ”€β”€ Linting rules └── Formatting config ``` ### Version from Git Tags ```bash # Tag release git tag v0.1.0 git push origin v0.1.0 # Version automatically set python -c "from src import __version__; print(__version__)" # Output: 0.1.0 # Development version (after tag) # Output: 0.1.0+5.g1a2b3c4 (5 commits after v0.1.0) ``` ## Code Quality Pipeline ``` Developer Writes Code ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Pre-commit β”‚ β”‚ (Optional) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Ruff Check β”‚ ← Linting β”‚ Ruff Format β”‚ ← Formatting β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Git Commit β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Push to GitHub β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ GitHub Actions β”‚ β”‚ β€’ Lint Check β”‚ β”‚ β€’ Format Check β”‚ β”‚ β€’ Tests (future) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` ## Module Dependencies ``` app.py β”œβ”€ src.config_loader β”œβ”€ src.rag_pipeline β”œβ”€ src.llm_handler (provider detection) β”œβ”€ src.response_enhancer (post-stream enhancement) β”œβ”€ src.main_document_loader (token counting for history) └─ streamlit (UI, chat history management) src.rag_pipeline β”œβ”€ src.config_loader β”œβ”€ src.llm_handler β”œβ”€ src.retrieval (RetrieverFactory, strategies) β”œβ”€ src.main_document_loader β”œβ”€ src.response_enhancer └─ langchain (LCEL chains) src.retrieval β”œβ”€ src.retrieval.base (BaseRetrieverStrategy) β”œβ”€ src.retrieval.factory (RetrieverFactory) β”œβ”€ src.retrieval.fusion (RRF, FusionRetriever) β”œβ”€ src.retrieval.stores.bm25_store └─ src.retrieval.strategies.* src.retrieval.strategies.vector β”œβ”€ src.vectorstore └─ src.retrieval.base src.retrieval.strategies.bm25 β”œβ”€ src.retrieval.stores.bm25_store └─ rank_bm25 src.retrieval.strategies.bm25_vector β”œβ”€ src.retrieval.strategies.vector β”œβ”€ src.retrieval.strategies.bm25 └─ src.retrieval.fusion src.main_document_loader β”œβ”€ src.config_loader β”œβ”€ src.document_processor β”œβ”€ src.llm_handler β”œβ”€ tiktoken └─ pathlib, hashlib, time src.response_enhancer β”œβ”€ src.config_loader └─ re (regex pattern matching) src.vectorstore β”œβ”€ src.config_loader β”œβ”€ chromadb └─ langchain_huggingface src.llm_handler β”œβ”€ src.config_loader β”œβ”€ langchain_community.llms (Ollama) └─ langchain_groq (Groq support) src.document_processor β”œβ”€ src.config_loader β”œβ”€ pypdf β”œβ”€ python-docx β”œβ”€ beautifulsoup4 └─ langchain src.config_loader β”œβ”€ pyyaml └─ python-dotenv src.build_vectorstore β”œβ”€ src.document_processor β”œβ”€ src.retrieval (RetrieverFactory) └─ src.vectorstore ``` ## Performance Characteristics ### Indexing (One-time) | Documents | Chunks | Embedding Time | ChromaDB Insert | Total | |-----------|--------|----------------|-----------------|-------| | 5 PDFs | ~100 | ~5 seconds | ~1 second | ~6s | | 20 PDFs | ~400 | ~20 seconds | ~2 seconds | ~22s | | 50 PDFs | ~1000 | ~50 seconds | ~5 seconds | ~55s | ### Query (Runtime) | Step | Time (CPU) | Time (GPU) | Notes | |------|------------|------------|-------| | Chat history extraction | <1ms | <1ms | Token counting overhead | | Main doc loading | <1ms | <1ms | Cached after first load | | Embed query | 50ms | 10ms | Sentence transformers | | Vector search | 10-50ms | 10-50ms | ChromaDB HNSW | | BM25 search | 5-20ms | 5-20ms | In-memory index | | Fusion (RRF) | <1ms | <1ms | Rank combination | | LLM inference (Ollama) | 2-5s | 0.5-1s | Local model | | LLM inference (Groq) | 0.5-2s | N/A | Cloud API | | Response enhancement | <10ms | <10ms | Post-processing | | **Total (Ollama)** | **2-5s** | **0.5-1s** | | | **Total (Groq)** | **0.6-2s** | **N/A** | Faster cloud option | ### Memory Usage | Component | RAM | Disk | |-----------|-----|------| | Streamlit | ~200MB | - | | Ollama (llama3.2:3b) | ~2GB | ~2GB | | ChromaDB | ~100MB | ~50MB per 1k docs | | BM25 Index | ~50MB | ~10MB per 1k docs | | Embeddings | ~500MB | ~500MB | | Chat History | ~1-10MB | - | | Response Enhancer | <1MB | - | | **Total** | **~3GB** | **~3GB** | ## Security Architecture ``` User Input ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Input Validation β”‚ ← Length limits β”‚ (Streamlit) β”‚ ← Character filtering β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ RAG Pipeline β”‚ ← Context isolation β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ LLM (Local) β”‚ ← No external API calls β”‚ β”‚ ← Data stays local β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ Secrets Management: β”œβ”€ .env (local) β”œβ”€ .gitignore (.env excluded) └─ HF Spaces Secrets (cloud) ``` ## Extensibility Points ### πŸ”Œ Plugin Architecture ```python # Easy to extend: # 1. New document types DocumentProcessor.load_custom_format() # 2. New LLM providers LLMHandler.get_openai_llm() LLMHandler.get_anthropic_llm() # Already supports: Ollama (local), Groq (cloud) # 3. New retrieval strategies (extensible system!) @RetrieverFactory.register("page_index") class PageIndexStrategy(BaseRetrieverStrategy): """Vision-based document retrieval (ColPali)""" ... @RetrieverFactory.register("graph_vector") class GraphVectorStrategy(BaseRetrieverStrategy): """Knowledge graph + vector hybrid""" ... # 4. New UI features app.py β†’ add_authentication() app.py β†’ add_analytics() # Already supports: Chat history, provider switching, streaming responses # 5. New embedding models VectorStoreManager(embedding_model="...") # 6. New fusion algorithms # Add to src/retrieval/fusion.py def custom_fusion(results_list, weights): ... # 7. New response enhancement patterns # Edit src/response_enhancer.py ResponseEnhancer.negative_patterns.append((pattern, rewrite_func)) # 8. Custom chat history strategies # Modify truncate_chat_history() in app.py # Add custom truncation logic (e.g., importance-based) ``` ### Adding a New Retrieval Strategy 1. Create `src/retrieval/strategies/my_strategy.py` 2. Implement `BaseRetrieverStrategy` interface 3. Register with `@RetrieverFactory.register("my_strategy")` 4. Add config section in `config.yaml` 5. Import in `src/retrieval/strategies/__init__.py` ```python from src.retrieval import RetrieverFactory from src.retrieval.base import BaseRetrieverStrategy @RetrieverFactory.register("my_strategy") class MyStrategy(BaseRetrieverStrategy): @property def name(self) -> str: return "my_strategy" def build_index(self, documents): ... def load_index(self) -> bool: ... def retrieve(self, query, k=4): ... def as_retriever(self, **kwargs): ... ``` --- **This architecture prioritizes:** - βœ… Simplicity (easy to understand) - βœ… Modularity (easy to extend) - βœ… Performance (optimized for small-medium datasets) - βœ… Privacy (local processing) - βœ… Deployability (cloud-ready)