ProfillyBot / ARCHITECTURE.md
MinhDS's picture
Deploy ProfillyBot: Gradio ZeroGPU + CV RAG (Qwen2.5-3B)
0828c2c verified
|
Raw
History Blame Contribute Delete
31.5 kB
# πŸ—οΈ Architecture Overview
## System Architecture
<p align="center">
<img src="docs/arch_system_design.png" alt="System Architecture Diagram" width="900" />
</p>
<p align="center"><em>High-level system architecture diagram illustrating the complete RAG pipeline flow</em></p>
### 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: <from git tags via versioningit>
β”‚ └── 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)