daniel-simeone commited on
Commit
a71ea0a
·
1 Parent(s): 4bce094

initial app

Browse files
Files changed (9) hide show
  1. DEPLOYMENT.md +214 -0
  2. README.md +189 -6
  3. SETUP_PATH.md +70 -0
  4. app.py +295 -0
  5. example_usage.py +53 -0
  6. ingest_documents.py +87 -0
  7. ingestion.py +231 -0
  8. pdfs/.gitkeep +2 -0
  9. requirements.txt +12 -0
DEPLOYMENT.md ADDED
@@ -0,0 +1,214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Deploying to Hugging Face Spaces
2
+
3
+ This guide will walk you through deploying your RAG chatbot to Hugging Face Spaces.
4
+
5
+ ## Prerequisites
6
+
7
+ 1. **Hugging Face Account**: Sign up at https://huggingface.co/join
8
+ 2. **Access Token**: Get your token from https://huggingface.co/settings/tokens
9
+
10
+ ## Step-by-Step Deployment
11
+
12
+ ### Method 1: Using the Web Interface (Easiest)
13
+
14
+ 1. **Create a New Space**:
15
+ - Go to https://huggingface.co/new-space
16
+ - Fill in:
17
+ - **Space name**: Choose a name (e.g., `my-rag-chatbot`)
18
+ - **SDK**: Select **Gradio**
19
+ - **Hardware**: Choose based on your needs:
20
+ - **CPU basic**: Free, good for testing
21
+ - **CPU upgrade**: Better performance
22
+ - **GPU**: If you need faster model inference
23
+ - **Visibility**: Public or Private
24
+ - Click **Create Space**
25
+
26
+ 2. **Upload Your Files**:
27
+ - In your new Space, click the **Files and versions** tab
28
+ - Click **Add file** → **Upload files**
29
+ - Upload these files:
30
+ - `app.py`
31
+ - `ingestion.py`
32
+ - `requirements.txt`
33
+ - `README.md` (optional but recommended)
34
+ - `pdfs/` folder (if you want to include sample PDFs)
35
+ - `ingest_documents.py` (optional, for manual ingestion)
36
+
37
+ 3. **Important Notes**:
38
+ - **Vector Store**: The `vector_store/` folder is in `.gitignore` and won't be uploaded. You have two options:
39
+ - **Option A**: Run `ingest_documents.py` on the Space after deployment (via the Space's terminal)
40
+ - **Option B**: Upload the vector store files manually if they're not too large
41
+ - **PDFs**: If your PDFs are large (>50MB), consider hosting them elsewhere or using Hugging Face Datasets
42
+
43
+ 4. **Wait for Build**: Hugging Face will automatically:
44
+ - Install dependencies from `requirements.txt`
45
+ - Start your Gradio app
46
+ - Your Space will be live at: `https://huggingface.co/spaces/YOUR_USERNAME/YOUR_SPACE_NAME`
47
+
48
+ ### Method 2: Using Git (Recommended for Updates)
49
+
50
+ 1. **Install Git** (if not already installed):
51
+ - Windows: Download from https://git-scm.com/download/win
52
+ - Or use Git that comes with GitHub Desktop
53
+
54
+ 2. **Install Hugging Face CLI**:
55
+ ```bash
56
+ py -m pip install huggingface_hub
57
+ ```
58
+
59
+ 3. **Login to Hugging Face**:
60
+ ```bash
61
+ huggingface-cli login
62
+ ```
63
+ Enter your access token when prompted.
64
+
65
+ 4. **Create a New Space** (via web interface):
66
+ - Go to https://huggingface.co/new-space
67
+ - Create the space with Gradio SDK
68
+ - Note your space name (e.g., `YOUR_USERNAME/my-rag-chatbot`)
69
+
70
+ 5. **Initialize Git in Your Project**:
71
+ ```bash
72
+ cd C:\Users\DanielSimeone\Desktop\testing-hugging-face
73
+ git init
74
+ git add app.py ingestion.py requirements.txt README.md ingest_documents.py pdfs/
75
+ git commit -m "Initial commit for Hugging Face Space"
76
+ ```
77
+
78
+ 6. **Add Hugging Face Remote and Push**:
79
+ ```bash
80
+ git remote add origin https://huggingface.co/spaces/YOUR_USERNAME/YOUR_SPACE_NAME
81
+ git push -u origin main
82
+ ```
83
+
84
+ 7. **For Future Updates**:
85
+ ```bash
86
+ git add .
87
+ git commit -m "Update description"
88
+ git push
89
+ ```
90
+
91
+ ## Post-Deployment Setup
92
+
93
+ ### Setting Up the Vector Store on Hugging Face Spaces
94
+
95
+ Since the vector store isn't included in the repository, you need to create it on the Space:
96
+
97
+ 1. **Option A: Use the Space Terminal** (if available):
98
+ - Go to your Space → **Settings** → Enable **Embedded Gradio SDK**
99
+ - Or use the Space's built-in terminal/console
100
+ - Run: `python ingest_documents.py`
101
+
102
+ 2. **Option B: Upload Vector Store Files**:
103
+ - If your vector store files are small enough:
104
+ - Upload `vector_store/index.faiss`
105
+ - Upload `vector_store/documents.pkl`
106
+ - Upload `vector_store/embeddings.pkl`
107
+ - The app will automatically load them on startup
108
+
109
+ 3. **Option C: Pre-build in a Script**:
110
+ - Create a `setup.py` or modify the Space to run ingestion on first launch
111
+ - This is more complex but ensures the vector store is always ready
112
+
113
+ ## Required Files for Deployment
114
+
115
+ Your Space needs these files:
116
+
117
+ - ✅ `app.py` - Main Gradio application
118
+ - ✅ `ingestion.py` - Document ingestion module
119
+ - ✅ `requirements.txt` - Python dependencies
120
+ - ✅ `README.md` - Documentation (optional but recommended)
121
+ - ⚠️ `vector_store/` - Will be created on the Space
122
+ - ⚠️ `pdfs/` - Optional, include if you want sample PDFs
123
+
124
+ ## Configuration for Hugging Face Spaces
125
+
126
+ ### Update app.py for Spaces
127
+
128
+ The current `app.py` should work, but you might want to adjust:
129
+
130
+ 1. **Port**: Hugging Face Spaces uses port 7860 automatically
131
+ 2. **Server name**: Use `0.0.0.0` (already set)
132
+ 3. **Share**: Set to `False` (already set)
133
+
134
+ Your current launch code is fine:
135
+ ```python
136
+ app.launch(
137
+ share=False,
138
+ server_name="0.0.0.0",
139
+ server_port=7861, # Note: Spaces uses 7860, but this should auto-adjust
140
+ theme=MinimalistTheme()
141
+ )
142
+ ```
143
+
144
+ ### Optional: Add README Frontmatter
145
+
146
+ Add this to the top of your `README.md` for better Space display:
147
+
148
+ ```yaml
149
+ ---
150
+ title: RAG Chatbot
151
+ emoji: 🤖
152
+ colorFrom: blue
153
+ colorTo: purple
154
+ sdk: gradio
155
+ sdk_version: 6.3.0
156
+ app_file: app.py
157
+ pinned: false
158
+ ---
159
+ ```
160
+
161
+ ## Troubleshooting
162
+
163
+ ### Build Fails
164
+
165
+ - Check that all dependencies in `requirements.txt` are correct
166
+ - Ensure Python version compatibility (Spaces uses Python 3.10 by default)
167
+ - Check the build logs in your Space's **Logs** tab
168
+
169
+ ### Vector Store Not Loading
170
+
171
+ - Verify the vector store files are in the correct location
172
+ - Check file permissions
173
+ - Ensure the path in `app.py` is correct (should be `./vector_store`)
174
+
175
+ ### Model Loading Issues
176
+
177
+ - Large models may take time to download on first run
178
+ - Consider using smaller models for faster startup
179
+ - Check available disk space in your Space
180
+
181
+ ### Memory Issues
182
+
183
+ - If you get out-of-memory errors, consider:
184
+ - Using a smaller embedding model
185
+ - Reducing chunk size in `ingestion.py`
186
+ - Upgrading to a Space with more memory
187
+
188
+ ## Updating Your Space
189
+
190
+ After making changes locally:
191
+
192
+ ```bash
193
+ git add .
194
+ git commit -m "Description of changes"
195
+ git push
196
+ ```
197
+
198
+ Hugging Face will automatically rebuild your Space.
199
+
200
+ ## Sharing Your Space
201
+
202
+ Once deployed, your Space will be available at:
203
+ ```
204
+ https://huggingface.co/spaces/YOUR_USERNAME/YOUR_SPACE_NAME
205
+ ```
206
+
207
+ You can share this URL with others!
208
+
209
+ ## Next Steps
210
+
211
+ - Add more PDFs to the `pdfs/` folder
212
+ - Update URLs in `ingest_documents.py`
213
+ - Customize the theme further
214
+ - Add more features to the chatbot
README.md CHANGED
@@ -1,13 +1,196 @@
1
  ---
2
- title: Testing
3
- emoji: 🏃
4
- colorFrom: purple
5
- colorTo: red
6
  sdk: gradio
7
  sdk_version: 6.3.0
8
  app_file: app.py
9
  pinned: false
10
- short_description: testing
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: RAG Chatbot
3
+ emoji: 🤖
4
+ colorFrom: blue
5
+ colorTo: purple
6
  sdk: gradio
7
  sdk_version: 6.3.0
8
  app_file: app.py
9
  pinned: false
 
10
  ---
11
 
12
+ # Hugging Face RAG Chatbot
13
+
14
+ A Retrieval-Augmented Generation (RAG) chatbot that can ingest PDFs and URLs, then answer questions based on the ingested documents. Built for deployment on Hugging Face Spaces.
15
+
16
+ ## Features
17
+
18
+ - 📄 **PDF Ingestion**: Upload and process PDF documents
19
+ - 🌐 **URL Ingestion**: Extract and process content from web URLs
20
+ - 🔍 **Vector Search**: Semantic search using sentence transformers
21
+ - 💬 **Chatbot Interface**: Interactive Gradio interface for querying documents
22
+ - 🚀 **Hugging Face Ready**: Configured for easy deployment to Hugging Face Spaces
23
+
24
+ ## Setup
25
+
26
+ ### Local Development
27
+
28
+ 1. **Install dependencies:**
29
+ ```bash
30
+ pip install -r requirements.txt
31
+ ```
32
+
33
+ 2. **Run the application:**
34
+ ```bash
35
+ python app.py
36
+ ```
37
+
38
+ 3. **Access the interface:**
39
+ - Open your browser to `http://localhost:7860`
40
+
41
+ ### Usage
42
+
43
+ 1. **Ingest Documents (Run this first or periodically to update):**
44
+ - Add PDF files to the `pdfs/` folder
45
+ - Edit `ingest_documents.py` and add your URLs to the `URLS` list
46
+ - Run the ingestion script:
47
+ ```bash
48
+ py ingest_documents.py
49
+ ```
50
+ - Wait for processing to complete (this creates/updates the vector store)
51
+
52
+ 2. **Chat with Documents:**
53
+ - Run the chatbot app:
54
+ ```bash
55
+ py app.py
56
+ ```
57
+ - Open your browser to `http://localhost:7860`
58
+ - Toggle "Use RAG" to enable/disable document retrieval
59
+ - Ask questions about your ingested documents
60
+ - The chatbot will retrieve relevant context and generate answers
61
+
62
+ ## Deployment to Hugging Face Spaces
63
+
64
+ ### Option 1: Using Hugging Face CLI
65
+
66
+ 1. **Install Hugging Face CLI:**
67
+ ```bash
68
+ pip install huggingface_hub
69
+ ```
70
+
71
+ 2. **Login to Hugging Face:**
72
+ ```bash
73
+ huggingface-cli login
74
+ ```
75
+
76
+ 3. **Create a new Space:**
77
+ - Go to https://huggingface.co/new-space
78
+ - Choose a name and select "Gradio" as the SDK
79
+ - Create the space
80
+
81
+ 4. **Clone and push your code:**
82
+ ```bash
83
+ git clone https://huggingface.co/spaces/YOUR_USERNAME/YOUR_SPACE_NAME
84
+ cd YOUR_SPACE_NAME
85
+ # Copy your files here
86
+ git add .
87
+ git commit -m "Initial commit"
88
+ git push
89
+ ```
90
+
91
+ ### Option 2: Using Git
92
+
93
+ 1. **Initialize git repository:**
94
+ ```bash
95
+ git init
96
+ git add .
97
+ git commit -m "Initial commit"
98
+ ```
99
+
100
+ 2. **Add Hugging Face remote:**
101
+ ```bash
102
+ git remote add origin https://huggingface.co/spaces/YOUR_USERNAME/YOUR_SPACE_NAME
103
+ git push -u origin main
104
+ ```
105
+
106
+ ### Required Files for Hugging Face Spaces
107
+
108
+ Your Space needs these files:
109
+ - `app.py` - Main Gradio application
110
+ - `requirements.txt` - Python dependencies
111
+ - `README.md` - This file (optional but recommended)
112
+
113
+ ### Optional: Add app.py to README
114
+
115
+ For Hugging Face Spaces, you can also add a `app.py` reference in your README:
116
+
117
+ ```yaml
118
+ ---
119
+ title: RAG Chatbot
120
+ emoji: 🤖
121
+ colorFrom: blue
122
+ colorTo: purple
123
+ sdk: gradio
124
+ sdk_version: 4.0.0
125
+ app_file: app.py
126
+ pinned: false
127
+ ---
128
+ ```
129
+
130
+ ## Configuration
131
+
132
+ ### Changing the Chatbot Model
133
+
134
+ Edit `app.py` and change the `model_name` parameter in `RAGChatbot`:
135
+
136
+ ```python
137
+ chatbot = RAGChatbot(model_name="your-preferred-model")
138
+ ```
139
+
140
+ Popular options:
141
+ - `microsoft/DialoGPT-medium` (default)
142
+ - `gpt2`
143
+ - `facebook/blenderbot-400M-distill`
144
+ - `microsoft/DialoGPT-large`
145
+
146
+ ### Changing the Embedding Model
147
+
148
+ Edit the `embedding_model` parameter:
149
+
150
+ ```python
151
+ chatbot = RAGChatbot(embedding_model="sentence-transformers/all-mpnet-base-v2")
152
+ ```
153
+
154
+ ## Project Structure
155
+
156
+ ```
157
+ .
158
+ ├── app.py # Main Gradio chatbot application
159
+ ├── ingest_documents.py # Standalone script to ingest PDFs and URLs
160
+ ├── ingestion.py # Document ingestion and vector store module
161
+ ├── requirements.txt # Python dependencies
162
+ ├── README.md # This file
163
+ ├── pdfs/ # Folder for PDF files (add your PDFs here)
164
+ │ └── README.md
165
+ └── vector_store/ # Saved vector store (created after ingestion)
166
+ ├── index.faiss
167
+ ├── documents.pkl
168
+ └── embeddings.pkl
169
+ ```
170
+
171
+ ## Limitations
172
+
173
+ - Vector store is stored locally (not persistent on Hugging Face Spaces by default)
174
+ - Large documents may take time to process
175
+ - Some URLs may be blocked or require authentication
176
+ - GPU recommended for better performance with larger models
177
+
178
+ ## Troubleshooting
179
+
180
+ ### Out of Memory Errors
181
+ - Use smaller models
182
+ - Reduce chunk size in `ingestion.py`
183
+ - Process fewer documents at once
184
+
185
+ ### URL Fetching Issues
186
+ - Some websites block automated requests
187
+ - Try different URLs or use PDF uploads instead
188
+
189
+ ### Model Loading Issues
190
+ - Ensure you have sufficient disk space
191
+ - Check your internet connection for model downloads
192
+ - Some models require GPU - check model requirements
193
+
194
+ ## License
195
+
196
+ This project is open source and available under the MIT License.
SETUP_PATH.md ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Fixing Python PATH on Windows
2
+
3
+ Your Python 3.12 is installed but not in your PATH. Here's how to fix it:
4
+
5
+ ## Quick Fix (Temporary - Current Session Only)
6
+
7
+ For now, you can use these commands:
8
+ - `py -m pip` instead of `pip`
9
+ - `py` instead of `python`
10
+ - `C:\Users\DanielSimeone\AppData\Local\Programs\Python\Python312\python.exe` for direct access
11
+
12
+ ## Permanent Fix - Add Python to PATH
13
+
14
+ ### Method 1: Using Windows Settings (Recommended)
15
+
16
+ 1. **Open System Environment Variables:**
17
+ - Press `Win + X` and select "System"
18
+ - Click "Advanced system settings"
19
+ - Click "Environment Variables" button
20
+
21
+ 2. **Edit PATH:**
22
+ - Under "User variables" (or "System variables" if you want it for all users), find and select "Path"
23
+ - Click "Edit"
24
+ - Click "New" and add these two paths:
25
+ ```
26
+ C:\Users\DanielSimeone\AppData\Local\Programs\Python\Python312
27
+ C:\Users\DanielSimeone\AppData\Local\Programs\Python\Python312\Scripts
28
+ ```
29
+ - Click "OK" on all dialogs
30
+
31
+ 3. **Restart your terminal/PowerShell** for changes to take effect
32
+
33
+ ### Method 2: Using PowerShell (Run as Administrator)
34
+
35
+ ```powershell
36
+ [Environment]::SetEnvironmentVariable(
37
+ "Path",
38
+ [Environment]::GetEnvironmentVariable("Path", "User") + ";C:\Users\DanielSimeone\AppData\Local\Programs\Python\Python312;C:\Users\DanielSimeone\AppData\Local\Programs\Python\Python312\Scripts",
39
+ "User"
40
+ )
41
+ ```
42
+
43
+ Then restart your terminal.
44
+
45
+ ### Method 3: Reinstall Python with "Add to PATH" option
46
+
47
+ If you reinstall Python, make sure to check "Add Python to PATH" during installation.
48
+
49
+ ## Verify It Works
50
+
51
+ After adding to PATH and restarting your terminal, test:
52
+
53
+ ```bash
54
+ python --version
55
+ pip --version
56
+ ```
57
+
58
+ Both should work without errors.
59
+
60
+ ## For This Project
61
+
62
+ You can run the app using:
63
+ ```bash
64
+ py app.py
65
+ ```
66
+
67
+ Or after fixing PATH:
68
+ ```bash
69
+ python app.py
70
+ ```
app.py ADDED
@@ -0,0 +1,295 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Gradio app for Hugging Face chatbot with RAG capabilities.
3
+ """
4
+ import gradio as gr
5
+ from gradio.themes.base import Base
6
+ from gradio.themes.utils import colors, fonts, sizes
7
+ import os
8
+ from typing import List, Tuple
9
+ from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
10
+ from ingestion import DocumentIngestion
11
+ import torch
12
+
13
+
14
+ # Create a clean minimalist theme
15
+ class MinimalistTheme(Base):
16
+ """A clean, minimalist theme with subtle colors and simple styling."""
17
+ def __init__(self):
18
+ super().__init__(
19
+ primary_hue=colors.blue,
20
+ secondary_hue=colors.gray,
21
+ neutral_hue=colors.gray,
22
+ spacing_size=sizes.spacing_md,
23
+ radius_size=sizes.radius_sm,
24
+ text_size=sizes.text_md,
25
+ font=(
26
+ fonts.GoogleFont("Inter"),
27
+ "ui-sans-serif",
28
+ "system-ui",
29
+ "sans-serif",
30
+ ),
31
+ font_mono=(
32
+ fonts.GoogleFont("JetBrains Mono"),
33
+ "ui-monospace",
34
+ "monospace",
35
+ ),
36
+ )
37
+ super().set(
38
+ # Clean backgrounds
39
+ body_background_fill="#ffffff",
40
+ body_background_fill_dark="#0f0f0f",
41
+ block_background_fill="#ffffff",
42
+ block_background_fill_dark="#1a1a1a",
43
+
44
+ # Subtle borders
45
+ block_border_width="1px",
46
+ block_border_color="#e0e0e0",
47
+ block_border_color_dark="#2a2a2a",
48
+ block_shadow="none",
49
+
50
+ # Clean buttons
51
+ button_primary_background_fill="#2563eb",
52
+ button_primary_background_fill_hover="#1d4ed8",
53
+ button_primary_text_color="#ffffff",
54
+ button_primary_background_fill_dark="#3b82f6",
55
+ button_primary_background_fill_hover_dark="#2563eb",
56
+ button_secondary_background_fill="#f3f4f6",
57
+ button_secondary_background_fill_hover="#e5e7eb",
58
+ button_secondary_text_color="#111827",
59
+ button_secondary_background_fill_dark="#374151",
60
+ button_secondary_background_fill_hover_dark="#4b5563",
61
+ button_border_width="1px",
62
+
63
+ # Input fields
64
+ input_background_fill="#ffffff",
65
+ input_background_fill_dark="#1a1a1a",
66
+ input_border_width="1px",
67
+ input_border_color="#d1d5db",
68
+ input_border_color_dark="#374151",
69
+
70
+ # Text colors
71
+ body_text_color="#111827",
72
+ body_text_color_dark="#e5e7eb",
73
+ block_label_text_color="#374151",
74
+ block_label_text_color_dark="#9ca3af",
75
+ )
76
+
77
+
78
+ class RAGChatbot:
79
+ """Chatbot with RAG capabilities."""
80
+
81
+ def __init__(
82
+ self,
83
+ model_name: str = "microsoft/DialoGPT-medium",
84
+ embedding_model: str = "all-MiniLM-L6-v2",
85
+ vector_store_path: str = "./vector_store"
86
+ ):
87
+ """
88
+ Initialize the RAG chatbot.
89
+
90
+ Args:
91
+ model_name: Hugging Face model name for the chatbot
92
+ embedding_model: Model for document embeddings
93
+ vector_store_path: Path to saved vector store
94
+ """
95
+ self.model_name = model_name
96
+ self.device = "cuda" if torch.cuda.is_available() else "cpu"
97
+
98
+ # Load chatbot model
99
+ print(f"Loading chatbot model: {model_name}")
100
+ try:
101
+ self.tokenizer = AutoTokenizer.from_pretrained(model_name)
102
+ self.model = AutoModelForCausalLM.from_pretrained(model_name)
103
+ self.tokenizer.pad_token = self.tokenizer.eos_token
104
+ except Exception as e:
105
+ print(f"Warning: Could not load {model_name}. Using a simpler pipeline.")
106
+ self.model = None
107
+ self.tokenizer = None
108
+ self.chatbot_pipeline = pipeline(
109
+ "text-generation",
110
+ model="gpt2",
111
+ device=0 if self.device == "cuda" else -1
112
+ )
113
+
114
+ # Initialize document ingestion
115
+ self.ingestion = DocumentIngestion(embedding_model=embedding_model)
116
+
117
+ # Load vector store if it exists
118
+ if os.path.exists(vector_store_path) and os.path.exists(
119
+ os.path.join(vector_store_path, "index.faiss")
120
+ ):
121
+ try:
122
+ self.ingestion.load(vector_store_path)
123
+ print("Loaded existing vector store")
124
+ except Exception as e:
125
+ print(f"Could not load vector store: {e}")
126
+
127
+ self.chat_history = []
128
+
129
+ def generate_response(self, query: str, use_rag: bool = True, num_results: int = 3) -> str:
130
+ """
131
+ Generate a response to the user query.
132
+
133
+ Args:
134
+ query: User's question
135
+ use_rag: Whether to use RAG (retrieve relevant documents)
136
+ num_results: Number of document chunks to retrieve
137
+
138
+ Returns:
139
+ Generated response
140
+ """
141
+ # If RAG is enabled and we have a vector store, return relevant context
142
+ if use_rag and self.ingestion.index is not None:
143
+ try:
144
+ results = self.ingestion.search(query, k=num_results)
145
+ if results:
146
+ # Format the response with relevant context
147
+ response_parts = []
148
+ response_parts.append(f"Based on the documents, here's what I found regarding your question: '{query}'\n\n")
149
+
150
+ for i, result in enumerate(results, 1):
151
+ source = result['metadata']['source']
152
+ text = result['text']
153
+ # Clean up the text
154
+ text = text.strip()
155
+ if text:
156
+ response_parts.append(f"**Relevant information {i}** (from {source}):\n{text}\n")
157
+
158
+ response = "\n".join(response_parts)
159
+ return response
160
+ except Exception as e:
161
+ print(f"Error in RAG retrieval: {e}")
162
+ return f"I encountered an error while searching the documents: {str(e)}"
163
+
164
+ # If no RAG or no results, try to generate a response using the model
165
+ # But DialoGPT isn't great for this, so we'll keep it simple
166
+ if self.model and self.tokenizer:
167
+ # Simple generation without complex prompts
168
+ inputs = self.tokenizer.encode(query, return_tensors="pt")
169
+ inputs = inputs.to(self.device)
170
+
171
+ with torch.no_grad():
172
+ outputs = self.model.generate(
173
+ inputs,
174
+ max_new_tokens=100,
175
+ num_return_sequences=1,
176
+ temperature=0.7,
177
+ do_sample=True,
178
+ pad_token_id=self.tokenizer.eos_token_id,
179
+ eos_token_id=self.tokenizer.eos_token_id,
180
+ )
181
+
182
+ # Decode only new tokens
183
+ input_length = inputs.shape[1]
184
+ generated_tokens = outputs[0][input_length:]
185
+ response = self.tokenizer.decode(generated_tokens, skip_special_tokens=True)
186
+
187
+ # Clean up
188
+ response = response.replace("<|endoftext|>", "").strip()
189
+
190
+ if not response or len(response.strip()) < 3:
191
+ return "I understand your question, but I don't have relevant information in my knowledge base. Please enable RAG to search the documents."
192
+
193
+ return response
194
+ else:
195
+ return "I understand your question, but I don't have relevant information in my knowledge base. Please enable RAG to search the documents."
196
+
197
+ def chat(self, message: str, history, use_rag: bool):
198
+ """
199
+ Handle chat interaction.
200
+
201
+ Args:
202
+ message: User message
203
+ history: Chat history (list of ChatMessage or dicts with 'role' and 'content')
204
+ use_rag: Whether to use RAG
205
+
206
+ Returns:
207
+ Updated history
208
+ """
209
+ if not message or not message.strip():
210
+ return "", history or []
211
+
212
+ # Ensure history is a list
213
+ if history is None:
214
+ history = []
215
+
216
+ # Add user message as dictionary
217
+ history.append({"role": "user", "content": message})
218
+
219
+ # Generate response
220
+ try:
221
+ response = self.generate_response(message, use_rag=use_rag)
222
+ # Ensure response is not empty
223
+ if not response or not response.strip():
224
+ response = "I'm sorry, I couldn't generate a response. Please try again."
225
+ except Exception as e:
226
+ print(f"Error generating response: {e}")
227
+ import traceback
228
+ traceback.print_exc()
229
+ response = f"I encountered an error: {str(e)}"
230
+
231
+ # Add assistant response as dictionary
232
+ history.append({"role": "assistant", "content": response})
233
+
234
+ print(f"Debug - History length: {len(history)}")
235
+ print(f"Debug - Response: {response[:100] if response else 'None'}...")
236
+
237
+ return "", history
238
+
239
+
240
+ # Initialize chatbot
241
+ chatbot = RAGChatbot()
242
+
243
+
244
+ # Create Gradio interface
245
+ with gr.Blocks(title="Hugging Face RAG Chatbot") as app:
246
+ gr.Markdown("# 🤖 Hugging Face RAG Chatbot")
247
+ gr.Markdown("Chat with your documents! Run `ingest_documents.py` to update the knowledge base.")
248
+
249
+ chatbot_interface = gr.Chatbot(
250
+ label="Chat",
251
+ height=500,
252
+ value=[] # Initialize with empty list
253
+ )
254
+
255
+ with gr.Row():
256
+ msg = gr.Textbox(
257
+ label="Your Message",
258
+ placeholder="Ask a question about your documents...",
259
+ scale=4
260
+ )
261
+ use_rag = gr.Checkbox(
262
+ label="Use RAG",
263
+ value=True,
264
+ scale=1
265
+ )
266
+
267
+ with gr.Row():
268
+ submit_btn = gr.Button("Send", variant="primary")
269
+ clear_btn = gr.Button("Clear")
270
+
271
+ msg.submit(
272
+ chatbot.chat,
273
+ inputs=[msg, chatbot_interface, use_rag],
274
+ outputs=[msg, chatbot_interface]
275
+ )
276
+ submit_btn.click(
277
+ chatbot.chat,
278
+ inputs=[msg, chatbot_interface, use_rag],
279
+ outputs=[msg, chatbot_interface]
280
+ )
281
+ def clear_chat():
282
+ return [], ""
283
+
284
+ clear_btn.click(clear_chat, outputs=[chatbot_interface, msg])
285
+
286
+
287
+ if __name__ == "__main__":
288
+ # Get port from environment variable (Hugging Face Spaces sets this) or default to 7860
289
+ port = int(os.environ.get("PORT", 7860))
290
+ app.launch(
291
+ share=False,
292
+ server_name="0.0.0.0",
293
+ server_port=port,
294
+ theme=MinimalistTheme()
295
+ )
example_usage.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Example script showing how to use the document ingestion system programmatically.
3
+ """
4
+ from ingestion import DocumentIngestion
5
+
6
+
7
+ def main():
8
+ # Initialize the ingestion system
9
+ ingestion = DocumentIngestion(embedding_model="all-MiniLM-L6-v2")
10
+
11
+ # Example 1: Process PDFs
12
+ pdf_paths = [
13
+ # Add your PDF file paths here
14
+ # "path/to/document1.pdf",
15
+ # "path/to/document2.pdf",
16
+ ]
17
+
18
+ # Example 2: Process URLs
19
+ urls = [
20
+ # Add URLs here
21
+ # "https://en.wikipedia.org/wiki/Artificial_intelligence",
22
+ # "https://huggingface.co/docs/transformers",
23
+ ]
24
+
25
+ # Process documents
26
+ if pdf_paths or urls:
27
+ print("Processing documents...")
28
+ documents = ingestion.process_documents(pdf_paths=pdf_paths, urls=urls)
29
+ print(f"Processed {len(documents)} document chunks")
30
+
31
+ # Build vector store
32
+ ingestion.build_vector_store()
33
+
34
+ # Save vector store
35
+ ingestion.save("./vector_store")
36
+
37
+ # Example search
38
+ query = "What is artificial intelligence?"
39
+ results = ingestion.search(query, k=3)
40
+
41
+ print(f"\nSearch results for: '{query}'")
42
+ print("-" * 50)
43
+ for i, result in enumerate(results, 1):
44
+ print(f"\nResult {i}:")
45
+ print(f"Source: {result['metadata']['source']}")
46
+ print(f"Score: {result['score']:.4f}")
47
+ print(f"Text: {result['text'][:200]}...")
48
+ else:
49
+ print("Please add PDF paths or URLs to the script to test ingestion.")
50
+
51
+
52
+ if __name__ == "__main__":
53
+ main()
ingest_documents.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Standalone script to ingest PDFs and URLs into the vector store.
3
+ Run this script periodically to update your document knowledge base.
4
+ """
5
+ import os
6
+ from pathlib import Path
7
+ from ingestion import DocumentIngestion
8
+
9
+
10
+ # Configuration
11
+ PDF_FOLDER = "./pdfs" # Folder containing PDF files
12
+ URLS = [
13
+ # Add your URLs here, one per line
14
+ "https://www.ontario.ca/page/organic-crop-and-livestock-production-ontario"
15
+ ]
16
+
17
+
18
+ def main():
19
+ """Main ingestion function."""
20
+ print("=" * 60)
21
+ print("Document Ingestion Script")
22
+ print("=" * 60)
23
+
24
+ # Initialize ingestion system
25
+ print("\nInitializing document ingestion system...")
26
+ ingestion = DocumentIngestion(embedding_model="all-MiniLM-L6-v2")
27
+
28
+ # Collect PDF files
29
+ pdf_paths = []
30
+ if os.path.exists(PDF_FOLDER):
31
+ pdf_files = list(Path(PDF_FOLDER).glob("*.pdf"))
32
+ pdf_paths = [str(f) for f in pdf_files]
33
+ print(f"\nFound {len(pdf_paths)} PDF file(s) in {PDF_FOLDER}:")
34
+ for pdf in pdf_paths:
35
+ print(f" - {os.path.basename(pdf)}")
36
+ else:
37
+ print(f"\nPDF folder '{PDF_FOLDER}' not found. Creating it...")
38
+ os.makedirs(PDF_FOLDER, exist_ok=True)
39
+ print(f"Please add PDF files to {PDF_FOLDER} and run again.")
40
+
41
+ # Filter out empty URLs
42
+ urls = [url.strip() for url in URLS if url.strip()]
43
+
44
+ if urls:
45
+ print(f"\nFound {len(urls)} URL(s) to process:")
46
+ for url in urls:
47
+ print(f" - {url}")
48
+ else:
49
+ print("\nNo URLs configured. Add URLs to the URLS list in this script.")
50
+
51
+ if not pdf_paths and not urls:
52
+ print("\n[ERROR] No documents to process. Please add PDFs or URLs.")
53
+ return
54
+
55
+ # Process documents
56
+ print("\n" + "=" * 60)
57
+ print("Processing documents...")
58
+ print("=" * 60)
59
+
60
+ try:
61
+ documents = ingestion.process_documents(pdf_paths=pdf_paths, urls=urls)
62
+ print(f"\n[SUCCESS] Successfully processed {len(documents)} document chunks")
63
+
64
+ # Build vector store
65
+ print("\nBuilding vector store...")
66
+ ingestion.build_vector_store()
67
+
68
+ # Save vector store
69
+ print("\nSaving vector store...")
70
+ ingestion.save("./vector_store")
71
+
72
+ print("\n" + "=" * 60)
73
+ print("[SUCCESS] Ingestion complete!")
74
+ print("=" * 60)
75
+ print(f"\nTotal document chunks: {len(documents)}")
76
+ print(f"Vector store saved to: ./vector_store")
77
+ print("\nYou can now run 'py app.py' to start the chatbot.")
78
+
79
+ except Exception as e:
80
+ print(f"\n[ERROR] Error during ingestion: {str(e)}")
81
+ import traceback
82
+ traceback.print_exc()
83
+ return
84
+
85
+
86
+ if __name__ == "__main__":
87
+ main()
ingestion.py ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Document ingestion module for processing PDFs and URLs.
3
+ """
4
+ import os
5
+ from typing import List, Dict
6
+ from pathlib import Path
7
+ import requests
8
+ from bs4 import BeautifulSoup
9
+ from pypdf import PdfReader
10
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
11
+ from sentence_transformers import SentenceTransformer
12
+ import numpy as np
13
+ import faiss
14
+ import pickle
15
+
16
+
17
+ class DocumentIngestion:
18
+ """Handles ingestion of PDFs and URLs into a searchable vector store."""
19
+
20
+ def __init__(self, embedding_model: str = "all-MiniLM-L6-v2"):
21
+ """
22
+ Initialize the document ingestion system.
23
+
24
+ Args:
25
+ embedding_model: Hugging Face model name for embeddings
26
+ """
27
+ self.embedding_model = SentenceTransformer(embedding_model)
28
+ self.text_splitter = RecursiveCharacterTextSplitter(
29
+ chunk_size=1000,
30
+ chunk_overlap=200,
31
+ length_function=len,
32
+ )
33
+ self.documents = []
34
+ self.embeddings = None
35
+ self.index = None
36
+
37
+ def read_pdf(self, file_path: str) -> str:
38
+ """
39
+ Extract text from a PDF file.
40
+
41
+ Args:
42
+ file_path: Path to the PDF file
43
+
44
+ Returns:
45
+ Extracted text content
46
+ """
47
+ try:
48
+ reader = PdfReader(file_path)
49
+ text = ""
50
+ for page in reader.pages:
51
+ text += page.extract_text() + "\n"
52
+ return text
53
+ except Exception as e:
54
+ raise Exception(f"Error reading PDF {file_path}: {str(e)}")
55
+
56
+ def read_url(self, url: str) -> str:
57
+ """
58
+ Extract text from a URL.
59
+
60
+ Args:
61
+ url: URL to fetch and extract text from
62
+
63
+ Returns:
64
+ Extracted text content
65
+ """
66
+ try:
67
+ headers = {
68
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
69
+ }
70
+ response = requests.get(url, headers=headers, timeout=10)
71
+ response.raise_for_status()
72
+
73
+ soup = BeautifulSoup(response.content, 'html.parser')
74
+
75
+ # Remove script and style elements
76
+ for script in soup(["script", "style"]):
77
+ script.decompose()
78
+
79
+ # Get text
80
+ text = soup.get_text()
81
+
82
+ # Clean up whitespace
83
+ lines = (line.strip() for line in text.splitlines())
84
+ chunks = (phrase.strip() for line in lines for phrase in line.split(" "))
85
+ text = ' '.join(chunk for chunk in chunks if chunk)
86
+
87
+ return text
88
+ except Exception as e:
89
+ raise Exception(f"Error reading URL {url}: {str(e)}")
90
+
91
+ def process_documents(self, pdf_paths: List[str] = None, urls: List[str] = None) -> List[Dict]:
92
+ """
93
+ Process PDFs and URLs into chunks.
94
+
95
+ Args:
96
+ pdf_paths: List of PDF file paths
97
+ urls: List of URLs to process
98
+
99
+ Returns:
100
+ List of document chunks with metadata
101
+ """
102
+ all_texts = []
103
+ all_metadata = []
104
+
105
+ # Process PDFs
106
+ if pdf_paths:
107
+ for pdf_path in pdf_paths:
108
+ if not os.path.exists(pdf_path):
109
+ print(f"Warning: PDF file not found: {pdf_path}")
110
+ continue
111
+
112
+ text = self.read_pdf(pdf_path)
113
+ chunks = self.text_splitter.split_text(text)
114
+
115
+ for i, chunk in enumerate(chunks):
116
+ all_texts.append(chunk)
117
+ all_metadata.append({
118
+ 'source': pdf_path,
119
+ 'type': 'pdf',
120
+ 'chunk_index': i
121
+ })
122
+
123
+ # Process URLs
124
+ if urls:
125
+ for url in urls:
126
+ try:
127
+ text = self.read_url(url)
128
+ chunks = self.text_splitter.split_text(text)
129
+
130
+ for i, chunk in enumerate(chunks):
131
+ all_texts.append(chunk)
132
+ all_metadata.append({
133
+ 'source': url,
134
+ 'type': 'url',
135
+ 'chunk_index': i
136
+ })
137
+ except Exception as e:
138
+ print(f"Warning: Failed to process URL {url}: {str(e)}")
139
+ continue
140
+
141
+ # Create document objects
142
+ documents = []
143
+ for text, metadata in zip(all_texts, all_metadata):
144
+ documents.append({
145
+ 'text': text,
146
+ 'metadata': metadata
147
+ })
148
+
149
+ self.documents = documents
150
+ return documents
151
+
152
+ def build_vector_store(self):
153
+ """Build FAISS vector store from processed documents."""
154
+ if not self.documents:
155
+ raise ValueError("No documents processed. Call process_documents() first.")
156
+
157
+ # Extract texts
158
+ texts = [doc['text'] for doc in self.documents]
159
+
160
+ # Generate embeddings
161
+ print("Generating embeddings...")
162
+ self.embeddings = self.embedding_model.encode(texts, show_progress_bar=True)
163
+
164
+ # Build FAISS index
165
+ dimension = self.embeddings.shape[1]
166
+ self.index = faiss.IndexFlatL2(dimension)
167
+ self.index.add(self.embeddings.astype('float32'))
168
+
169
+ print(f"Vector store built with {len(self.documents)} documents")
170
+
171
+ def search(self, query: str, k: int = 5) -> List[Dict]:
172
+ """
173
+ Search for similar documents.
174
+
175
+ Args:
176
+ query: Search query
177
+ k: Number of results to return
178
+
179
+ Returns:
180
+ List of relevant document chunks with scores
181
+ """
182
+ if self.index is None:
183
+ raise ValueError("Vector store not built. Call build_vector_store() first.")
184
+
185
+ # Encode query
186
+ query_embedding = self.embedding_model.encode([query])
187
+
188
+ # Search
189
+ distances, indices = self.index.search(query_embedding.astype('float32'), k)
190
+
191
+ # Format results
192
+ results = []
193
+ for i, idx in enumerate(indices[0]):
194
+ if idx < len(self.documents):
195
+ results.append({
196
+ 'text': self.documents[idx]['text'],
197
+ 'metadata': self.documents[idx]['metadata'],
198
+ 'score': float(distances[0][i])
199
+ })
200
+
201
+ return results
202
+
203
+ def save(self, directory: str = "./vector_store"):
204
+ """Save the vector store to disk."""
205
+ os.makedirs(directory, exist_ok=True)
206
+
207
+ # Save index
208
+ faiss.write_index(self.index, os.path.join(directory, "index.faiss"))
209
+
210
+ # Save documents and embeddings
211
+ with open(os.path.join(directory, "documents.pkl"), "wb") as f:
212
+ pickle.dump(self.documents, f)
213
+
214
+ with open(os.path.join(directory, "embeddings.pkl"), "wb") as f:
215
+ pickle.dump(self.embeddings, f)
216
+
217
+ print(f"Vector store saved to {directory}")
218
+
219
+ def load(self, directory: str = "./vector_store"):
220
+ """Load the vector store from disk."""
221
+ # Load index
222
+ self.index = faiss.read_index(os.path.join(directory, "index.faiss"))
223
+
224
+ # Load documents and embeddings
225
+ with open(os.path.join(directory, "documents.pkl"), "rb") as f:
226
+ self.documents = pickle.load(f)
227
+
228
+ with open(os.path.join(directory, "embeddings.pkl"), "rb") as f:
229
+ self.embeddings = pickle.load(f)
230
+
231
+ print(f"Vector store loaded from {directory}")
pdfs/.gitkeep ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # This file ensures the pdfs folder is tracked in git
2
+ # Add your PDF files to this folder
requirements.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ gradio>=4.0.0
2
+ transformers>=4.35.0
3
+ torch>=2.0.0
4
+ sentence-transformers>=2.2.0
5
+ langchain>=0.1.0
6
+ langchain-community>=0.0.20
7
+ pypdf>=3.17.0
8
+ beautifulsoup4>=4.12.0
9
+ requests>=2.31.0
10
+ faiss-cpu>=1.7.4
11
+ numpy>=1.24.0
12
+ accelerate>=0.25.0