Sharada25/dhammaai
DhammaAI - Vipassana Meditation Guide
A Production-Ready RAG-based AI Chatbot with Progressive Web App (PWA) Capabilities
An intelligent meditation guide powered by advanced RAG techniques, deployed on Hugging Face Spaces with complete mobile support.
  
Table of Contents
- Project Overview
- Architecture & Tech Stack
- Detailed System Design
- Document Ingestion Pipeline
- RAG Implementation
- LLM Integration
- Database Design
- PWA Implementation
- Deployment Journey
- Cost Analysis
- Interview Q&A
- Setup Instructions
Project Overview
DhammaAI is an AI-powered meditation guide that provides authentic answers about Vipassana meditation based on S.N. Goenka's teachings. The project combines cutting-edge AI technologies with traditional web development to create a production-ready, mobile-first application.
Key Features
- Advanced RAG System: Hybrid retrieval using FAISS (vector search) + BM25 (keyword search) + CrossEncoder (reranking)
- Multilingual Support: English, Hindi, and Marathi with accurate language detection
- Voice I/O: Speech-to-text input and text-to-speech output
- Progressive Web App: Installable on mobile devices (Android & iOS)
- Chat History: Persistent conversation storage using MongoDB Atlas
- Feedback System: User feedback collection for continuous improvement
- Plug-and-Play LLM: Easy switching between OpenAI and HuggingFace models
- 100% Free Hosting: Deployed on Hugging Face Spaces (16GB RAM)
Live Demo
URL: https://huggingface.co/spaces/Sharada25/dhammaai
Architecture & Tech Stack
Backend Technologies
AI/ML Stack
Frontend Technologies
DevOps & Hosting
Detailed System Design
High-Level Architecture
┌─────────────────────────────────────────────────────────────┐
│ User Interface │
│ (HTML + TailwindCSS + JavaScript + Service Worker + PWA) │
└────────────────────┬────────────────────────────────────────┘
│ HTTP/HTTPS
▼
┌─────────────────────────────────────────────────────────────┐
│ Flask Application │
│ • /chat endpoint (streaming responses) │
│ • /feedback endpoint │
│ • /analytics endpoint │
│ • /upload endpoint (document ingestion) │
└────────────────────┬────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ VipassanaRAGAgent │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 1. Query Expansion (Multi-Query) │ │
│ │ 2. Hybrid Retrieval (FAISS + BM25) │ │
│ │ 3. CrossEncoder Reranking │ │
│ │ 4. Context Building │ │
│ │ 5. LLM Response Generation │ │
│ └─────────────────────────────────────────────────────┘ │
└────────────────────┬────────────────────────────────────────┘
│
┌────────────┼────────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ FAISS │ │ BM25 │ │ MongoDB │
│ Vector Store │ │ Index Store │ │ Atlas │
│ (Semantic) │ │ (Keyword) │ │ (Chat Hist) │
└──────────────┘ └──────────────┘ └──────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ LLM Provider (Plug-and-Play) │
│ • HuggingFace Inference API (Qwen2.5-7B-Instruct) │
│ • OpenAI API (GPT-3.5-turbo / GPT-4) - Optional │
└─────────────────────────────────────────────────────────────┘Request Flow
- User Query → Frontend captures text/voice input
- Frontend → Sends POST request to
/chatendpoint - Flask App → Receives query, calls
VipassanaRAGAgent.answer() - RAG Agent:
- Expands query into multiple variations
- Searches FAISS vector store (semantic)
- Searches BM25 index (keyword)
- Merges and deduplicates results
- Reranks with CrossEncoder for precision
- Selects top-k chunks
- Context Building → Formats retrieved chunks with sources
- LLM Call → Sends context + query to HuggingFace/OpenAI
- Response Stream → Streams generated text back to frontend
- MongoDB → Saves conversation to database
- Frontend → Renders markdown response with sources
Document Ingestion Pipeline
Overview
The document ingestion system processes PDF files and builds searchable indexes for RAG.
Process Flow
PDF Files → PDF Extraction → Text Cleaning → Chunking → Embedding → IndexingStep-by-Step Process
1. PDF Extraction
- Library Used: PyPDF2
- Process: Extracts text from each page of PDFs
- Input: PDF files from
data/knowledge_base/Vipassana Books/ - Output: Raw text strings
2. Text Cleaning
def _clean_text(self, text: str) -> str:
"""
Cleans and normalizes text from PDFs
"""
# Unicode normalization (NFKC)
cleaned = unicodedata.normalize('NFKC', text)
# Remove unwanted patterns
cleaned = re.sub(r"(/c\d+)+", " ", cleaned) # Remove /c123 patterns
cleaned = re.sub(r"\[\s*\d+\s*\]", " ", cleaned) # Remove [123]
cleaned = re.sub(r"-\s*\n\s*", "", cleaned) # Join hyphenated words
cleaned = re.sub(r"\n+", " ", cleaned) # Replace newlines with spaces
cleaned = re.sub(r"\s+", " ", cleaned).strip() # Normalize whitespace
return cleanedWhy this cleaning?
- PDFs often contain metadata, page numbers, and formatting artifacts
- CrossEncoder and LLM work better with clean, normalized text
- Removes noise that reduces retrieval accuracy
3. Chunking Strategy
CHUNK_SIZE = 1000 # characters
OVERLAP = 200 # characters (overlap between chunks)Chunking Logic:
- Fixed-size chunks of 1000 characters
- 200-character overlap to maintain context continuity
- Ensures semantic coherence across chunk boundaries
Why chunking?
- LLMs have context window limits (can't process entire books)
- Smaller chunks improve retrieval precision
- Overlap prevents information loss at boundaries
4. Embedding Generation
# Model: sentence-transformers/all-MiniLM-L6-v2
model = SentenceTransformer('all-MiniLM-L6-v2')
# Generate embeddings
chunk_embedding = model.encode(chunk_text, convert_to_numpy=True)
# Output: 384-dimensional vectorModel Choice: all-MiniLM-L6-v2
- Size: 22 MB (fast loading)
- Dimensions: 384 (good balance)
- Performance: 95% accuracy on semantic similarity tasks
- Speed: Very fast on CPU
- Multilingual: Supports English, Hindi, Marathi
5. Vector Indexing (FAISS)
import faiss
# Create FAISS index (Inner Product similarity)
dimension = 384
index = faiss.IndexFlatIP(dimension)
# Normalize vectors (for cosine similarity)
normalized_embeddings = normalize(embeddings)
# Add to index
index.add(normalized_embeddings.astype('float32'))
# Save index
faiss.write_index(index, 'data/vector_store/index.faiss')FAISS Configuration:
- Index Type: IndexFlatIP (Inner Product)
- Why IP?: After normalization, IP = cosine similarity
- Advantage: Exact search (no approximation)
- Speed: Fast for datasets up to 100K vectors
6. BM25 Indexing
from rank_bm25 import BM25Okapi
# Tokenize all chunks
tokenized_corpus = [chunk.lower().split() for chunk in all_chunks]
# Build BM25 index
bm25 = BM25Okapi(tokenized_corpus)
# Save index
with open('data/vector_store/bm25.pkl', 'wb') as f:
pickle.dump(bm25, f)BM25 (Best Matching 25):
- Type: Keyword-based search algorithm
- Use Case: Complements vector search (catches exact keyword matches)
- Advantage: Fast, works well for specific terms (e.g., "anapana", "sila")
7. Metadata Storage
// data/vector_store/meta.json
[
{
"chunk": "Vipassana means to see things as they really are...",
"source": "Art-of-Living-in-English.pdf",
"page": 5,
"chunk_id": 0
},
...
]Metadata Purpose:
- Links chunks back to source documents
- Enables source citation in responses
- Helps users verify information authenticity
RAG Implementation
Why RAG?
Problem: LLMs hallucinate and lack domain-specific knowledge.
Solution: Retrieval-Augmented Generation (RAG)
- Retrieves relevant information from knowledge base
- Augments LLM prompt with factual context
- LLM generates accurate, grounded responses
Our RAG Pipeline
1. Multi-Query Expansion
def _expand_query(self, query: str) -> List[str]:
"""
Expands user query into multiple variations for better recall
"""
query_list = [query]
# Domain-specific term mapping
vipassana_terms_map = {
'meditation': ['practice', 'technique', 'sadhana'],
'vipassana': ['insight meditation', 'mindfulness'],
'anapana': ['breathing', 'breath', 'respiration'],
'goenka': ['s.n. goenka', 'teacher', 'acharya'],
'dhamma': ['dharma', 'teaching', 'truth'],
'suffering': ['dukkha', 'pain', 'misery'],
'anicca': ['impermanence', 'change'],
}
# Generate query variations
for key, terms in vipassana_terms_map.items():
if key in query.lower():
for term in terms:
query_list.append(query.lower().replace(key, term))
return query_list[:5] # Return top 5 variationsWhy Query Expansion?
- Users may phrase questions differently than documents
- Increases recall (finds more relevant documents)
- Captures synonyms and domain-specific terminology
Example:
- Input: "What is anapana?"
- Expanded: ["What is anapana?", "What is breathing?", "What is breath?", "What is respiration?"]
2. Hybrid Retrieval
FAISS Search (Semantic):
# Generate query embedding
query_embedding = model.encode(query, convert_to_numpy=True)
query_embedding = normalize(query_embedding.reshape(1, -1))
# Search FAISS index
distances, indices = index.search(query_embedding.astype('float32'), top_k=30)
# distances: similarity scores (higher = more similar)
# indices: positions of retrieved chunks in metadataBM25 Search (Keyword):
# Tokenize query
tokenized_query = query.lower().split()
# Get BM25 scores
bm25_scores = bm25.get_scores(tokenized_query)
# Get top-k indices
top_indices = np.argsort(bm25_scores)[::-1][:15]Why Hybrid?
- FAISS: Great for semantic similarity ("What is suffering?" → "dukkha")
- BM25: Great for exact matches ("anapana" → "anapana")
- Combined: Best of both worlds
3. CrossEncoder Reranking
from sentence_transformers import CrossEncoder
# Load reranker model
reranker = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
# Create query-document pairs
pairs = [[query, chunk] for chunk in retrieved_chunks]
# Get relevance scores
relevance_scores = reranker.predict(pairs)
# Sort by relevance
sorted_chunks = sorted(zip(chunks, scores), key=lambda x: x[1], reverse=True)
# Return top-k most relevant
return sorted_chunks[:5]Why Reranking?
- FAISS/BM25: Fast but less accurate (retrieve 30-50 candidates)
- CrossEncoder: Slower but highly accurate (rerank to top-5)
- Result: Best precision with acceptable speed
CrossEncoder vs Bi-Encoder:
- Bi-Encoder (FAISS): Encodes query and docs separately → fast
- Cross-Encoder: Encodes query+doc together → accurate
4. Context Building
def build_context(retrieved_items: List[Dict]) -> str:
"""
Formats retrieved chunks into a single context string
"""
context_parts = []
sources = []
for item in retrieved_items:
chunk = item['chunk']
source = item['source']
# Add source attribution to each chunk
context_parts.append(f"[Source: {source}]\n{chunk}")
if source not in sources:
sources.append(source)
# Join with separators
context = "\n\n---\n\n".join(context_parts)
return context, sourcesContext Format:
[Source: Art-of-Living-in-English.pdf]
Vipassana means to see things as they really are...
---
[Source: SN_Goenka-The_Discourse_Summaries.pdf]
The technique of Vipassana is based on observation of bodily sensations...
---
[Source: research paper.pdf]
Studies show that Vipassana meditation leads to reduced stress...Why This Format?
- Clear source attribution for each piece of information
- Easy for LLM to understand and cite
- User can verify information
LLM Integration
Plug-and-Play Architecture
Problem: Different environments have different constraints:
- Local: Can use OpenAI API
- HF Spaces: OpenAI blocked, need HuggingFace models
Solution: Abstraction layer that supports both providers
Configuration
# Environment variable controls which LLM to use
LLM_PROVIDER = os.getenv("LLM_PROVIDER", "huggingface") # or "openai"
# OpenAI configuration
OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-3.5-turbo")
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
# HuggingFace configuration
HF_MODEL = os.getenv("HF_MODEL", "Qwen/Qwen2.5-7B-Instruct")
HF_API_TOKEN = os.getenv("HF_API_TOKEN") # Optional for higher rate limitsLLM Provider Implementation
OpenAI Implementation
if self.llm_provider == "openai":
# Initialize client
self.openai_client = OpenAI(
api_key=api_key,
timeout=60.0,
max_retries=3
)
# Generate response
response = self.openai_client.chat.completions.create(
model=OPENAI_MODEL,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
],
temperature=0.05, # Low for consistency
max_tokens=1200,
top_p=0.9
)
return response.choices[0].message.contentOpenAI Advantages:
- Highest quality responses
- Best multilingual support
- Fast response times
- Excellent instruction following
OpenAI Disadvantages:
- Costs ~$0.002 per chat
- Blocked on HF Spaces (network restrictions)
HuggingFace Implementation
elif self.llm_provider == "huggingface":
# Initialize client
self.hf_client = InferenceClient(
model=HF_MODEL,
token=token,
timeout=60.0
)
# Generate response
response = self.hf_client.chat_completion(
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt}
],
max_tokens=1200,
temperature=0.1,
top_p=0.9
)
return response.choices[0].message.contentHuggingFace Advantages:
- 100% FREE (no API costs)
- Works on HF Spaces (no network blocking)
- Excellent multilingual quality (Qwen2.5-7B)
- No rate limits with HF token
- Superior Hindi/Marathi support vs other free models
HuggingFace Disadvantages:
- Slightly slower than GPT-3.5
- May hit rate limits without API token
Qwen2.5-7B-Instruct Model
Why Qwen2.5? (Best Free Multilingual Model)
- Superior Multilingual: Best Hindi/Marathi support among free 7B models
- Massive Training: 18 trillion tokens (vs Mistral's 8 trillion)
- Open Source: Apache 2.0 license (FREE forever)
- Performance: Outperforms Mistral-7B on multilingual tasks
- Size: 7 billion parameters (efficient)
- Speed: Fast inference on HF Spaces
- Indic Language Excellence: Specifically trained for Hindi, Marathi, and other Indic languages
- Instruction-Tuned: Better instruction following than Mistral
Model Specifications:
Model: Qwen/Qwen2.5-7B-Instruct
Parameters: 7 billion
Context Window: 32768 tokens (~24000 words) - 4x larger than Mistral!
Training Data: 18 trillion tokens (multilingual focus)
Performance: 75% on MMLU, 90% on GSM8K (better than Mistral)
License: Apache 2.0 (FREE forever)
Multilingual Quality: ⭐⭐⭐⭐ (vs Mistral's ⭐⭐)Is it Free Forever?
- ✅ YES - Model is open source (Apache 2.0)
- ✅ YES - HF Inference API is free for public models
- ✅ YES - Can self-host if needed
- ⚠️ Note: Free tier has rate limits (50 requests/hour without token, unlimited with token)
System Prompt Engineering
system_prompt = """
You are the Vipassana Guide AI, a compassionate, precise, and conversational meditation teacher.
Your knowledge is based **STRICTLY** on the provided CONTEXT (pure Goenka Vipassana knowledge base).
CRITICAL GUIDELINES:
1. **Beautiful Markdown Formatting**:
- Use ## headings with emojis (e.g., '## 🧘 Understanding Anapana')
- Use **bold** for key terms
- Use *italic* for Pali/Sanskrit terms
- Use bullet points and numbered lists
- Use > blockquotes for important quotes
- Use relevant emojis (🙏 ☸️ 🧘 💎 ✨)
2. **Multilingual Support** (CRITICAL):
- Detect user's language automatically
- Respond ONLY in that language
- Marathi ≠ Hindi (completely different!)
- Marathi markers: 'kay', 'aahe', 'kasa'
- Hindi markers: 'kya', 'hai', 'kaise'
3. **Context-Only Responses**:
- Use ONLY information from CONTEXT
- If insufficient info: "I don't have enough information..."
4. **Conversational & Precise**:
- Direct, practical, actionable
- No unnecessary flowery language
5. **Source Citation**:
- Include [Source: Discourse 3] at end of sections
6. **Authentic Terminology**:
- Maintain exact Pali/Sanskrit terms from context
"""Why This Prompt Design?
- Strict Context Grounding: Prevents hallucinations
- Multilingual Detection: Automatic language switching
- Beautiful Formatting: Enhanced readability
- Source Attribution: Builds trust
- Professional Tone: Suitable for users seeking guidance
Database Design
MongoDB Atlas Configuration
Why MongoDB?
- Free Tier: 512 MB storage (sufficient for chat history)
- Cloud-Hosted: No server maintenance
- NoSQL: Flexible schema for conversation data
- Atlas Features: Automatic backups, monitoring, SSL/TLS
Connection:
MONGODB_URI = "mongodb+srv://username:password@cluster0.xxxxx.mongodb.net/"
MONGODB_DATABASE = "vipassana_chat"
client = MongoClient(
MONGODB_URI,
serverSelectionTimeoutMS=5000,
connectTimeoutMS=10000,
socketTimeoutMS=10000
)
db = client[MONGODB_DATABASE]Collections Schema
1. Chat History Collection
Collection: chat_history
{
"_id": ObjectId("..."),
"session_id": "uuid-v4-string", // Frontend generates
"timestamp": ISODate("2024-11-10T10:30:00Z"),
"user_query": "What is Vipassana?",
"assistant_response": "## 🧘 Understanding Vipassana\n\nVipassana means...",
"sources": [
"Art-of-Living-in-English.pdf",
"SN_Goenka-The_Discourse_Summaries.pdf"
],
"language": "en", // Auto-detected
"feedback": null // Updated when user provides feedback
}Indexes:
db.chat_history.createIndex({ "session_id": 1, "timestamp": 1 })
db.chat_history.createIndex({ "timestamp": -1 })2. Feedback Collection
Collection: feedback
{
"_id": ObjectId("..."),
"chat_id": ObjectId("..."), // Links to chat_history
"timestamp": ISODate("2024-11-10T10:32:00Z"),
"feedback_type": "positive", // "positive" | "negative"
"comment": "Very helpful answer!", // Optional
"session_id": "uuid-v4-string"
}Indexes:
db.feedback.createIndex({ "timestamp": -1 })
db.feedback.createIndex({ "feedback_type": 1 })Database Operations
Save Chat
def save_chat(session_id, query, response, sources, language="en"):
"""Saves a chat exchange to MongoDB"""
chat_doc = {
"session_id": session_id,
"timestamp": datetime.utcnow(),
"user_query": query,
"assistant_response": response,
"sources": sources,
"language": language,
"feedback": None
}
result = db.chat_history.insert_one(chat_doc)
return result.inserted_idSave Feedback
def save_feedback(chat_id, feedback_type, comment=None):
"""Saves user feedback for a chat"""
feedback_doc = {
"chat_id": ObjectId(chat_id),
"timestamp": datetime.utcnow(),
"feedback_type": feedback_type,
"comment": comment
}
db.feedback.insert_one(feedback_doc)
# Update chat_history with feedback
db.chat_history.update_one(
{"_id": ObjectId(chat_id)},
{"$set": {"feedback": feedback_type}}
)Get Chat History
def get_chat_history(session_id, limit=50):
"""Retrieves chat history for a session"""
cursor = db.chat_history.find(
{"session_id": session_id}
).sort("timestamp", -1).limit(limit)
return list(cursor)Analytics
Metrics Tracked:
- Total chats
- Chats per day/week/month
- Positive vs negative feedback ratio
- Most common questions
- Average response length
- Source distribution (which PDFs used most)
- Language distribution
Analytics Endpoint (/analytics):
@app.route("/analytics")
def analytics_dashboard():
"""Renders analytics dashboard"""
stats = {
"total_chats": db.chat_history.count_documents({}),
"positive_feedback": db.feedback.count_documents({"feedback_type": "positive"}),
"negative_feedback": db.feedback.count_documents({"feedback_type": "negative"}),
"chats_today": db.chat_history.count_documents({
"timestamp": {"$gte": datetime.utcnow().replace(hour=0, minute=0)}
})
}
return render_template("analytics.html", stats=stats)PWA Implementation
What is a PWA?
Progressive Web App = Web app that behaves like a native mobile app
Key Features:
- Installable: Add to home screen (Android/iOS)
- Offline Support: Service worker caches assets
- App-Like: Fullscreen, no browser UI
- Push Notifications: (Optional, not implemented)
- Responsive: Works on all screen sizes
PWA Components
1. Web App Manifest (static/manifest.json)
{
"name": "DhammaAI - Vipassana Guide",
"short_name": "DhammaAI",
"description": "AI meditation guide based on S.N. Goenka's teachings",
"start_url": "/",
"display": "standalone",
"background_color": "#0b7095",
"theme_color": "#0b7095",
"orientation": "portrait",
"icons": [
{
"src": "/static/images/icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/static/images/icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
]
}Icon Generation:
- Source: Dhamma wheel GIF
- Sizes: 72x72, 96x96, 128x128, 144x144, 152x152, 192x192, 384x384, 512x512
- Background: Vipassana blue (#0b7095)
- Padding: 15% (for visual balance)
2. Service Worker (static/sw.js)
const CACHE_NAME = 'vipassana-ai-v1';
const urlsToCache = [
'/',
'/static/manifest.json',
'/static/images/icon-192x192.png',
'/static/images/icon-512x512.png',
'https://cdn.tailwindcss.com',
'https://cdn.jsdelivr.net/npm/marked/marked.min.js'
];
// Install event: Cache resources
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => cache.addAll(urlsToCache))
.then(() => self.skipWaiting())
);
});
// Fetch event: Serve from cache, fallback to network
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then((response) => response || fetch(event.request))
);
});
// Activate event: Clean up old caches
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (cacheName !== CACHE_NAME) {
return caches.delete(cacheName);
}
})
);
})
);
});Service Worker Lifecycle:
- Install: Downloads and caches static assets
- Activate: Cleans up old cache versions
- Fetch: Intercepts network requests, serves from cache
3. Service Worker Registration (templates/index.html)
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/static/sw.js')
.then(registration => {
console.log('Service Worker registered:', registration.scope);
})
.catch(error => {
console.log('Service Worker registration failed:', error);
});
});
}4. Mobile Responsiveness
Viewport Configuration:
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">Mobile-Specific CSS:
/* Prevent zoom on input focus (iOS) */
html {
font-size: 16px;
}
/* Prevent horizontal scroll */
body {
overflow-x: hidden;
position: fixed;
width: 100%;
height: 100%;
}
/* Mobile optimizations */
@media (max-width: 768px) {
#chat-container {
padding: 1rem !important;
}
#user-input {
font-size: 16px !important; /* Prevents iOS zoom */
}
.chat-bubble {
max-width: 85% !important;
}
}5. Voice Input/Output
Speech Recognition (Input):
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
const recognition = new SpeechRecognition();
recognition.lang = 'en-US';
recognition.interimResults = false;
recognition.maxAlternatives = 1;
recognition.onresult = (event) => {
const transcript = event.results[0][0].transcript;
document.getElementById('user-input').value = transcript;
sendMessage();
};
recognition.start();Speech Synthesis (Output):
const utterance = new SpeechSynthesisUtterance(text);
utterance.lang = 'en-US';
utterance.rate = 0.9;
utterance.pitch = 1.0;
window.speechSynthesis.speak(utterance);Requirements:
- HTTPS: Required for Web Speech API
- Browser Support: Chrome, Edge, Safari
- Permissions: User must grant microphone access
Installation Instructions
Android (Chrome):
- Visit https://huggingface.co/spaces/Sharada25/dhammaai
- Tap menu (⋮) → "Add to Home screen"
- Tap "Install"
- App icon appears on home screen
iOS (Safari):
- Visit https://huggingface.co/spaces/Sharada25/dhammaai
- Tap Share button (□↑)
- Tap "Add to Home Screen"
- Tap "Add"
- App icon appears on home screen
Deployment Journey
Why Not Next.js?
Common Misconception: "PWA = Next.js"
Reality:
- We used Flask (Python backend) + Vanilla JavaScript frontend
- Next.js is overkill for this project
- Flask is simpler, faster to deploy, and perfect for ML/AI apps
PWA ≠ Next.js:
- PWA = Web standards (Service Worker + Manifest)
- Works with any web framework (React, Vue, Angular, Flask, Django, etc.)
Deployment Platform Evaluation
Option 1: Render.com (Attempted)
Why We Tried:
- Free tier available
- Easy deployment from GitHub
- Supports Docker
Why It Failed:
Build failed: Out of memory (used over 512Mi)Problem:
- Memory Limit: 512 MB on free tier
- Our App Needs:
- FAISS index: ~50 MB
- Sentence Transformers model: ~80 MB
- BM25 index: ~20 MB
- CrossEncoder model: ~100 MB
- Python dependencies: ~200 MB
- Total: ~450 MB (with overhead → 600+ MB)
Verdict: ❌ Insufficient RAM
Option 2: Railway.app (Considered)
Why We Considered:
- 512 MB RAM on free tier
- $5/month for more resources
- Good for small apps
Why We Skipped:
- Same memory issue as Render.com
- Would need paid plan ($5-10/month)
- User wanted 100% free
Verdict: ❌ Cost + Memory issues
Option 3: Hugging Face Spaces (Selected ✅)
Why We Chose It:
- 16 GB RAM on free tier (32x more than Render/Railway!)
- Built for ML apps (models, embeddings, LLMs)
- Docker support (full control over environment)
- Git LFS (handles large model files)
- Free forever (community-supported)
- HF Inference API (use HF models without network blocking)
Free Tier Specifications:
RAM: 16 GB (shared CPU)
Storage: Unlimited via Git LFS
Bandwidth: Unlimited
Persistent Storage: Yes
Custom Domain: Yes
HTTPS: Automatic
Uptime: Sleep after 48h inactivity (wakes in ~30s)
Cost: $0 foreverPerfect For:
- RAG applications
- ML model serving
- AI chatbots
- Document processing
- Embedding generation
Verdict: ✅ Perfect fit!
Deployment Process (HF Spaces)
Step 1: Create Dockerfile
FROM python:3.11-slim
WORKDIR /app
# Install system dependencies + SSL certs for MongoDB
RUN apt-get update && apt-get install -y \
build-essential \
curl \
ca-certificates \
libssl-dev \
&& rm -rf /var/lib/apt/lists/*
# Install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application files
COPY . .
# Expose port 7860 (HF Spaces standard)
EXPOSE 7860
ENV PORT=7860
# Start application
CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:7860", "--workers", "1", "--timeout", "300", "--access-logfile", "-", "--error-logfile", "-"]Key Points:
python:3.11-slim: Smaller image sizeca-certificates+libssl-dev: Required for MongoDB SSL- Port
7860: HF Spaces standard workers=1: Single worker (sufficient for free tier)timeout=300: 5 minutes (for slow model loading)
Step 2: Create README.md (HF Spaces Config)
---
title: DhammaAI - Vipassana Guide
emoji: 🧘
colorFrom: blue
colorTo: purple
sdk: docker
pinned: false
license: mit
---This configures:
- Space title and emoji
- UI theme colors
- SDK type (Docker)
- License
Step 3: Setup Git LFS
Why Git LFS?
- PDF files: 10-50 MB each
- FAISS indexes: 30-100 MB
- Model files: 50-200 MB
- Total: 500+ MB of binary files
Git LFS Setup:
# Install Git LFS
git lfs install
# Configure LFS for binary files
echo "*.pdf filter=lfs diff=lfs merge=lfs -text" >> .gitattributes
echo "*.faiss filter=lfs diff=lfs merge=lfs -text" >> .gitattributes
echo "*.png filter=lfs diff=lfs merge=lfs -text" >> .gitattributes
echo "*.gif filter=lfs diff=lfs merge=lfs -text" >> .gitattributes
# Migrate existing files to LFS
git lfs migrate import --include="*.pdf,*.faiss,*.png,*.gif" --everything
# Commit and push
git add .gitattributes
git commit -m "Add Git LFS for binary files"
git pushStep 4: Push to HF Spaces
# Add HF Spaces as Git remote
git remote add hf https://huggingface.co/spaces/Sharada25/dhammaai
# Push code (with LFS files)
git push hf master:main
# LFS files automatically uploaded
# Uploading LFS objects: 100% (24/24), 29 MB | 5.0 MB/s, done.Step 5: Configure Secrets
HF Spaces Settings → Secrets:
LLM_PROVIDER=huggingface
HF_API_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxx
OPENAI_API_KEY=sk-proj-xxxxxxxx (optional)
MONGODB_URI=mongodb+srv://username:password@cluster0.xxxxx.mongodb.net/
MONGODB_DATABASE=vipassana_chat
FLASK_SECRET_KEY=random-secret-key-hereWhy Secrets?
- Not stored in Git (security)
- Injected as environment variables at runtime
- Can be updated without redeploying
Step 6: Build & Deploy
Automatic Build Process:
- HF Spaces pulls code from Git
- Downloads LFS files (PDFs, indexes, models)
- Builds Docker image
- Installs dependencies (~3-5 minutes)
- Starts Gunicorn server
- App goes live at https://huggingface.co/spaces/Sharada25/dhammaai
Build Logs:
Building Docker image...
[+] Building 245.3s
=> [1/5] FROM python:3.11-slim
=> [2/5] RUN apt-get update && apt-get install...
=> [3/5] COPY requirements.txt .
=> [4/5] RUN pip install --no-cache-dir -r requirements.txt
=> [5/5] COPY . .
Successfully built image
Starting container...
[LLM] Initializing LLM provider: huggingface
[LLM] HuggingFace Inference client initialized successfully
[LLM] Using model: Qwen/Qwen2.5-7B-Instruct
Initializing embedding model: sentence-transformers/all-MiniLM-L6-v2
Initializing reranking model: cross-encoder/ms-marco-MiniLM-L-6-v2
Index loaded successfully with 1247 chunks.
✓ MongoDB Atlas database manager initialized
VipassanaRAGAgent initialized successfully with Reranking enabled.
Running on http://0.0.0.0:7860Deployment Challenges & Solutions
Challenge 1: OpenAI API Network Blocking
Problem:
Error: Connection error to OpenAI APIRoot Cause: HF Spaces blocks outbound connections to external APIs (anti-abuse)
Solution: Switch to HuggingFace Inference API (internal, not blocked)
Implementation: Plug-and-play LLM architecture (covered above)
Challenge 2: MongoDB SSL Handshake Failure
Problem:
Error: SSL handshake failed: tlsv1 alert internal errorRoot Cause: Missing SSL/TLS certificates in Docker image
Solution: Install ca-certificates and libssl-dev
RUN apt-get install -y ca-certificates libssl-devChallenge 3: Git Secret Exposure
Problem: Accidentally pushed HF token to Git
Error:
Push rejected: file contains Hugging Face secret (token: hf_xxxxx)Solution:
- Remove files with secrets from Git history:
git filter-branch --force --index-filter \
"git rm --cached --ignore-unmatch HF_QUICK_FIX.md deploy_to_huggingface.ps1" \
--prune-empty --tag-name-filter cat -- --all- Push clean history
- Use environment variables/secrets instead
Challenge 4: Large File Git Push
Problem: PDF and FAISS files rejected (>100 MB)
Error:
remote: error: File is 150 MB; this exceeds GitHub's file size limit of 100 MBSolution: Git LFS (Large File Storage)
- Stores large files on separate server
- Git only stores pointers
- Transparent to users
Cost Analysis
Complete Cost Breakdown
Total Monthly Cost: $0
Only Potential Cost: OpenAI API (if you switch LLM_PROVIDER to "openai")
- Cost: ~$0.002 per chat with GPT-3.5-turbo
- 100 chats/day = $0.20/day = $6/month
- But you can use HuggingFace models for FREE forever!
Is It Really Free Forever?
Hugging Face Spaces Free Tier:
- ✅ Yes, FREE forever (community edition)
- ✅ No credit card required
- ✅ No expiration date
- ⚠️ Sleeps after 48h inactivity (wakes in 30s)
- ⚠️ Shared CPU (not dedicated)
- ⚠️ May have rate limits during high traffic
To Keep Awake 24/7 (Optional):
- Upgrade to "CPU basic - persistent" → Still $0 with card on file
- Or use free uptime monitor (e.g., UptimeRobot) to ping every hour
MongoDB Atlas Free Tier:
- ✅ Yes, FREE forever (M0 cluster)
- ✅ 512 MB storage (~10K chats)
- ✅ No credit card required
- ⚠️ Limited to 100 concurrent connections
Mistral-7B Model:
- ✅ Yes, FREE forever (Apache 2.0 license)
- ✅ Open source, can self-host
- ✅ No usage limits
- ✅ No API costs via HF Inference API
FAISS, Sentence Transformers, etc.:
- ✅ Yes, FREE forever (open source)
- ✅ MIT/Apache licenses
- ✅ No dependencies on paid services
Interview Q&A
Technical Questions
Q: Explain your RAG pipeline.
A: Our RAG pipeline uses a 3-stage approach:
- Hybrid Retrieval: FAISS (semantic) + BM25 (keyword) search for high recall
- CrossEncoder Reranking: Reranks top-50 candidates to top-5 for high precision
- LLM Generation: Mistral-7B generates contextual answer with citations
This gives us the best of both worlds: fast retrieval (FAISS) with high accuracy (CrossEncoder).
Q: Why use both FAISS and BM25?
A:
- FAISS: Catches semantic matches ("suffering" → "dukkha")
- BM25: Catches exact keyword matches ("anapana" → "anapana")
- Together: Covers both semantic understanding and precise terminology
Example: Query "breathing technique" → FAISS finds "anapana" (semantic), BM25 finds exact "breathing" mentions.
Q: How does CrossEncoder reranking work?
A:
- Bi-Encoder (FAISS): Encodes query and docs separately → fast but less accurate
- Cross-Encoder: Encodes query+doc together → accurate but slow
We use Bi-Encoder for retrieval (top-50 fast), then Cross-Encoder for reranking (top-5 accurate).
Q: Why Mistral-7B over other models?
A:
- Performance: Comparable to GPT-3.5 on most benchmarks
- Size: 7B params = fast inference on CPU
- License: Apache 2.0 = free forever, no restrictions
- Multilingual: Excellent support for Hindi, Marathi
- Instruction-Tuned: Follows system prompts well
Q: How do you handle multilingual input?
A:
- Detection: System prompt instructs LLM to auto-detect language
- Markers: Marathi vs Hindi differentiation using unique words
- Response: LLM generates response in detected language
- Models: Both Sentence Transformers and Mistral support 80+ languages
Q: Explain your database schema.
A: Two main collections:
- chat_history: Stores all conversations with metadata (session, timestamp, sources, feedback)
- feedback: Stores user feedback linked to specific chats
Indexes on session_id, timestamp, and feedback_type for fast queries.
Q: How does your PWA work offline?
A:
- Service Worker: Caches HTML, CSS, JS, icons
- Offline: Static assets served from cache
- API Calls: Still need internet for chat (LLM requires server)
- Partial Offline: UI loads instantly, shows "connecting..." for chat
Q: Why Flask instead of FastAPI?
A:
- Simplicity: Flask is simpler, well-documented
- Ecosystem: More tutorials, easier debugging
- SSE Streaming: Flask supports SSE easily (for streaming responses)
- Template Rendering: Flask's Jinja2 for server-side rendering
- FastAPI would be overkill for this project
Q: How do you prevent hallucinations?
A:
- Strict Context: System prompt enforces "ONLY use provided CONTEXT"
- Low Temperature: 0.05-0.1 for deterministic output
- Source Citations: Forces LLM to reference sources
- Short Responses: max_tokens=1200 prevents rambling
- RAG: Grounds responses in actual documents
Q: Explain your deployment architecture.
A:
- Container: Docker image on HF Spaces (16GB RAM)
- Server: Gunicorn WSGI server (1 worker, 5min timeout)
- Models: Loaded on startup (FAISS, Sentence Transformers, CrossEncoder)
- LLM: HF Inference API (serverless)
- Database: MongoDB Atlas (cloud)
- CDN: TailwindCSS, Marked.js from public CDNs
Behavioral Questions
Q: What was the biggest challenge?
A: Deploying to Hugging Face Spaces. Initially tried Render.com and Railway.app but hit 512MB RAM limits. Had to:
- Research alternatives (found HF Spaces with 16GB)
- Learn Docker and Git LFS
- Solve network blocking issues (OpenAI → HuggingFace)
- Debug SSL/TLS certificate issues with MongoDB
- Clean Git history after exposing secrets
Took 3 days of debugging but resulted in a better solution (free + more powerful).
Q: How did you optimize for performance?
A:
- FAISS: Fast vector search (exact search for <100K vectors)
- BM25: Tokenized index for instant keyword search
- Caching: Service worker caches static assets
- Single Worker: Avoids memory duplication
- Lazy Loading: Models loaded once on startup, reused
- Streaming: SSE streams responses token-by-token (feels faster)
Q: What would you improve?
A:
- Caching: Redis cache for frequent queries
- Async: FastAPI with async for concurrent requests
- CDN: Self-host TailwindCSS (remove external dependency)
- Monitoring: Add logging, error tracking (Sentry)
- Testing: Unit tests, integration tests
- CI/CD: Automated testing before deployment
Q: How do you handle errors?
A:
- Try-Catch: All LLM calls wrapped in try-except
- Specific Errors: Different handling for APIConnectionError, TimeoutError, etc.
- User-Friendly Messages: "Service slow, please retry" instead of stack traces
- Logging: All errors logged with timestamps
- Fallbacks: If LLM fails, show cached response or error message
Setup Instructions
Local Development
Prerequisites
- Python 3.11+
- MongoDB Atlas account (free)
- HuggingFace account (free, optional)
- OpenAI API key (optional)
Installation
# Clone repository
git clone https://github.com/YOUR_USERNAME/vri_assistant.git
cd vri_assistant
# Create virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install dependencies
pip install -r requirements.txt
# Install Git LFS (for large files)
git lfs install
git lfs pull # Download PDF/model filesConfiguration
Create .env file:
# LLM Configuration (choose one)
LLM_PROVIDER=huggingface # or "openai"
# HuggingFace (free)
HF_API_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxx # Optional
# OpenAI (if using openai provider)
# OPENAI_API_KEY=sk-proj-xxxxxxxxxxxxx
# MongoDB Atlas
MONGODB_URI=mongodb+srv://username:password@cluster0.xxxxx.mongodb.net/
MONGODB_DATABASE=vipassana_chat
# Flask
FLASK_SECRET_KEY=your-secret-key-hereRun Application
# Development server
python app.py
# Production server (Gunicorn)
gunicorn app:app --bind 0.0.0.0:5000 --workers 1 --timeout 300Visit: http://localhost:5000
Production Deployment (HF Spaces)
1. Create HF Space
- Go to https://huggingface.co/spaces
- Click "Create new Space"
- Name:
dhammaai - SDK: Docker
- Hardware: CPU basic (free)
- Visibility: Public
2. Add Secrets
Settings → Variables and secrets → Add secret:
LLM_PROVIDER=huggingface
HF_API_TOKEN=your_token
MONGODB_URI=your_mongodb_uri
MONGODB_DATABASE=vipassana_chat
FLASK_SECRET_KEY=random_string3. Deploy
# Add HF Spaces as remote
git remote add hf https://huggingface.co/spaces/YOUR_USERNAME/dhammaai
# Push code
git push hf master:main
# Wait for build (3-5 minutes)4. Monitor
- Check build logs in "Logs" tab
- Wait for "Running" status
- Visit Space URL
- Test with a question
Project Statistics
- Lines of Code: ~2,500 (Python + JavaScript + HTML/CSS)
- PDF Documents: 10 (Vipassana teachings)
- Total Document Pages: ~1,200 pages
- Text Chunks: 1,247 indexed chunks
- Vector Dimensions: 384 (FAISS index)
- Model Files: 4 (embedding, reranking, BM25, FAISS)
- Total Storage: ~500 MB (code + models + data)
- Dependencies: 20+ Python packages
- Supported Languages: English, Hindi, Marathi
- API Endpoints: 6 (chat, feedback, analytics, upload, etc.)
- Database Collections: 2 (chat_history, feedback)
Technology Decision Matrix
Conclusion
DhammaAI demonstrates:
- ✅ Production-ready RAG architecture
- ✅ Advanced ML techniques (hybrid retrieval, reranking)
- ✅ Full-stack development (Flask + JavaScript + MongoDB)
- ✅ Cloud deployment (Docker + HF Spaces)
- ✅ PWA implementation (installable mobile app)
- ✅ Cost optimization (100% free hosting)
- ✅ Scalable architecture (supports 1000+ users)
Key Takeaways for Interviews:
- RAG is essential for domain-specific AI apps
- Hybrid approaches work best (FAISS + BM25 + CrossEncoder)
- Open source can replace paid APIs (HuggingFace vs OpenAI)
- Resource constraints drive innovation (HF Spaces discovery)
- PWAs are powerful (web app = mobile app, no app stores)
Links & Resources
- Live Demo: https://huggingface.co/spaces/Sharada25/dhammaai
- GitHub: https://github.com/YOURUSERNAME/vriassistant
- MongoDB Atlas: https://www.mongodb.com/cloud/atlas
- Hugging Face: https://huggingface.co
- FAISS: https://github.com/facebookresearch/faiss
- Sentence Transformers: https://www.sbert.net
Built with ❤️ for Vipassana practitioners worldwide
May all beings be happy. May all beings be peaceful. May all beings be liberated.
🙏 Bhavatu Sabba Mangalam 🙏
