Spaces:
Sleeping
Sleeping
Ryan
commited on
Commit
Β·
2132962
1
Parent(s):
7d34386
simplify changes
Browse files- .gitignore +3 -1
- CHANGES_SUMMARY.md +0 -143
- HF_SPACES_CHECKLIST.md +0 -140
- QUICK_START.md +0 -86
- hf_spaces_deploy/README.md +0 -102
- hf_spaces_deploy/app.py +0 -102
- hf_spaces_deploy/citation_validator.py +0 -338
- hf_spaces_deploy/config.py +0 -11
- hf_spaces_deploy/rag_chat.py +0 -181
- hf_spaces_deploy/requirements.txt +0 -10
- prepare_deployment.sh +0 -34
- upload_to_qdrant_cli.py +5 -1
.gitignore
CHANGED
|
@@ -35,7 +35,7 @@ env/
|
|
| 35 |
*.swo
|
| 36 |
*~
|
| 37 |
|
| 38 |
-
# Data files (don't commit
|
| 39 |
articles.json
|
| 40 |
article_chunks.jsonl
|
| 41 |
validation_results.json
|
|
@@ -46,3 +46,5 @@ Thumbs.db
|
|
| 46 |
|
| 47 |
# Gradio
|
| 48 |
flagged/
|
|
|
|
|
|
|
|
|
| 35 |
*.swo
|
| 36 |
*~
|
| 37 |
|
| 38 |
+
# Data files (large, don't commit)
|
| 39 |
articles.json
|
| 40 |
article_chunks.jsonl
|
| 41 |
validation_results.json
|
|
|
|
| 46 |
|
| 47 |
# Gradio
|
| 48 |
flagged/
|
| 49 |
+
|
| 50 |
+
TODO.txt
|
CHANGES_SUMMARY.md
DELETED
|
@@ -1,143 +0,0 @@
|
|
| 1 |
-
# π― Summary: Hugging Face Spaces Setup Complete
|
| 2 |
-
|
| 3 |
-
## β
What Was Done
|
| 4 |
-
|
| 5 |
-
Your project is now **fully configured** for deployment to Hugging Face Spaces!
|
| 6 |
-
|
| 7 |
-
### Files Created/Modified
|
| 8 |
-
|
| 9 |
-
1. **`app.py`** β¨ NEW
|
| 10 |
-
- Main Gradio interface optimized for HF Spaces
|
| 11 |
-
- Removed server configuration (HF handles this)
|
| 12 |
-
- Clean launch() call for HF environment
|
| 13 |
-
|
| 14 |
-
2. **`README.md`** βοΈ UPDATED
|
| 15 |
-
- Added HF Spaces YAML frontmatter
|
| 16 |
-
- Included deployment instructions
|
| 17 |
-
- Added configuration guide for Secrets
|
| 18 |
-
|
| 19 |
-
3. **`.gitignore`** β¨ NEW
|
| 20 |
-
- Excludes sensitive files (.env, data files)
|
| 21 |
-
- HF Spaces best practices
|
| 22 |
-
|
| 23 |
-
4. **`requirements.txt`** βοΈ UPDATED
|
| 24 |
-
- Added torch dependency (needed by sentence-transformers)
|
| 25 |
-
- All dependencies verified for HF Spaces
|
| 26 |
-
|
| 27 |
-
### Documentation Created
|
| 28 |
-
|
| 29 |
-
5. **`DEPLOYMENT.md`** β¨ NEW
|
| 30 |
-
- Complete step-by-step deployment guide
|
| 31 |
-
- Troubleshooting section
|
| 32 |
-
- Cost breakdown
|
| 33 |
-
|
| 34 |
-
6. **`HF_SPACES_CHECKLIST.md`** β¨ NEW
|
| 35 |
-
- Detailed checklist for deployment
|
| 36 |
-
- File exclusion list
|
| 37 |
-
- Common issues and solutions
|
| 38 |
-
|
| 39 |
-
7. **`QUICK_START.md`** β¨ NEW
|
| 40 |
-
- 5-minute quick start guide
|
| 41 |
-
- TL;DR version for fast deployment
|
| 42 |
-
- Quick reference table
|
| 43 |
-
|
| 44 |
-
8. **`CHANGES_SUMMARY.md`** β¨ NEW (this file)
|
| 45 |
-
- Overview of all changes made
|
| 46 |
-
|
| 47 |
-
### Helper Scripts
|
| 48 |
-
|
| 49 |
-
9. **`prepare_deployment.sh`** β¨ NEW
|
| 50 |
-
- Automated script to copy deployment files
|
| 51 |
-
- Already tested and working!
|
| 52 |
-
- Creates `hf_spaces_deploy/` directory
|
| 53 |
-
|
| 54 |
-
### Ready-to-Deploy Files
|
| 55 |
-
|
| 56 |
-
The `hf_spaces_deploy/` directory contains exactly what you need:
|
| 57 |
-
```
|
| 58 |
-
hf_spaces_deploy/
|
| 59 |
-
βββ app.py (3.3K)
|
| 60 |
-
βββ rag_chat.py (5.6K)
|
| 61 |
-
βββ citation_validator.py (13K)
|
| 62 |
-
βββ config.py (252B)
|
| 63 |
-
βββ requirements.txt (170B)
|
| 64 |
-
βββ README.md (2.9K)
|
| 65 |
-
```
|
| 66 |
-
|
| 67 |
-
## π Next Steps (Your Action Required)
|
| 68 |
-
|
| 69 |
-
### Quick Deploy (5 minutes):
|
| 70 |
-
|
| 71 |
-
1. **Create HF Space**: https://huggingface.co/spaces β "Create new Space"
|
| 72 |
-
- Choose Gradio SDK
|
| 73 |
-
- Use CPU basic (free)
|
| 74 |
-
|
| 75 |
-
2. **Add Secrets** in Space Settings:
|
| 76 |
-
- `QDRANT_URL`
|
| 77 |
-
- `QDRANT_API_KEY`
|
| 78 |
-
- `OPENAI_API_KEY`
|
| 79 |
-
|
| 80 |
-
3. **Upload files** from `hf_spaces_deploy/` folder
|
| 81 |
-
|
| 82 |
-
4. **Done!** Your app will be live in 2-5 minutes
|
| 83 |
-
|
| 84 |
-
### Detailed Instructions:
|
| 85 |
-
- See `QUICK_START.md` for step-by-step guide
|
| 86 |
-
- See `HF_SPACES_CHECKLIST.md` for complete checklist
|
| 87 |
-
- See `DEPLOYMENT.md` for troubleshooting
|
| 88 |
-
|
| 89 |
-
## π What Stays Local
|
| 90 |
-
|
| 91 |
-
These files are for local development only (NOT uploaded to HF Spaces):
|
| 92 |
-
- `.env` - Your secrets (use HF Secrets instead)
|
| 93 |
-
- `articles.json` - Source data (already in Qdrant)
|
| 94 |
-
- `article_chunks.jsonl` - Chunked data (already in Qdrant)
|
| 95 |
-
- `web_app.py` - Old version (replaced by app.py)
|
| 96 |
-
- `*_cli.py` - Setup scripts (not needed in deployment)
|
| 97 |
-
|
| 98 |
-
## β¨ Key Features of Your Deployment
|
| 99 |
-
|
| 100 |
-
- β
**Free hosting** on HF Spaces CPU tier
|
| 101 |
-
- β
**Secure** - API keys stored as Secrets
|
| 102 |
-
- β
**Fast** - Optimized for Gradio 4.0+
|
| 103 |
-
- β
**Professional** - Beautiful UI with Soft theme
|
| 104 |
-
- β
**Validated citations** - Every quote is verified
|
| 105 |
-
- β
**Easy updates** - Just git push to redeploy
|
| 106 |
-
|
| 107 |
-
## π Architecture
|
| 108 |
-
|
| 109 |
-
```
|
| 110 |
-
User Question
|
| 111 |
-
β
|
| 112 |
-
[Gradio UI (app.py)]
|
| 113 |
-
β
|
| 114 |
-
[RAG Logic (rag_chat.py)]
|
| 115 |
-
β
|
| 116 |
-
[Qdrant Vector DB] β Retrieve relevant chunks
|
| 117 |
-
β
|
| 118 |
-
[OpenAI GPT-4o-mini] β Generate answer with citations
|
| 119 |
-
β
|
| 120 |
-
[Citation Validator] β Verify quotes against sources
|
| 121 |
-
β
|
| 122 |
-
[Formatted Response] β Display to user
|
| 123 |
-
```
|
| 124 |
-
|
| 125 |
-
## π Notes
|
| 126 |
-
|
| 127 |
-
- Environment variables work automatically on HF Spaces (no .env needed)
|
| 128 |
-
- `load_dotenv()` gracefully handles missing .env file
|
| 129 |
-
- All code is production-ready and tested
|
| 130 |
-
- Deployment is reversible (just delete the Space)
|
| 131 |
-
|
| 132 |
-
## π€ Questions?
|
| 133 |
-
|
| 134 |
-
Refer to:
|
| 135 |
-
1. `QUICK_START.md` - Fast deployment
|
| 136 |
-
2. `HF_SPACES_CHECKLIST.md` - Detailed checklist
|
| 137 |
-
3. `DEPLOYMENT.md` - Complete guide
|
| 138 |
-
4. HF Spaces docs: https://huggingface.co/docs/hub/spaces
|
| 139 |
-
|
| 140 |
-
---
|
| 141 |
-
|
| 142 |
-
**Your project is 100% ready for Hugging Face Spaces! π**
|
| 143 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
HF_SPACES_CHECKLIST.md
DELETED
|
@@ -1,140 +0,0 @@
|
|
| 1 |
-
# Hugging Face Spaces Deployment Checklist
|
| 2 |
-
|
| 3 |
-
## β
Files Ready for Upload
|
| 4 |
-
|
| 5 |
-
These are the ONLY files you need to upload to Hugging Face Spaces:
|
| 6 |
-
|
| 7 |
-
- [ ] `app.py` - Main Gradio interface (β Created)
|
| 8 |
-
- [ ] `rag_chat.py` - RAG logic
|
| 9 |
-
- [ ] `citation_validator.py` - Citation validation
|
| 10 |
-
- [ ] `config.py` - Configuration constants
|
| 11 |
-
- [ ] `requirements.txt` - Python dependencies (β Updated)
|
| 12 |
-
- [ ] `README.md` - Documentation with HF metadata (β Updated)
|
| 13 |
-
|
| 14 |
-
## β Files to EXCLUDE (Do NOT upload)
|
| 15 |
-
|
| 16 |
-
- `.env` - Contains secrets (use HF Spaces Secrets instead)
|
| 17 |
-
- `articles.json` - Large data file (not needed, data is in Qdrant)
|
| 18 |
-
- `article_chunks.jsonl` - Large data file (not needed, data is in Qdrant)
|
| 19 |
-
- `validation_results.json` - Runtime output file
|
| 20 |
-
- `__pycache__/` - Python cache
|
| 21 |
-
- `web_app.py` - Old version (replaced by app.py)
|
| 22 |
-
- `extract_articles_cli.py` - Setup script (not needed for deployed app)
|
| 23 |
-
- `chunk_articles_cli.py` - Setup script (not needed for deployed app)
|
| 24 |
-
- `upload_to_qdrant_cli.py` - Setup script (not needed for deployed app)
|
| 25 |
-
|
| 26 |
-
## π§ Pre-Deployment Steps
|
| 27 |
-
|
| 28 |
-
### 1. Verify Data is in Qdrant
|
| 29 |
-
```bash
|
| 30 |
-
# Make sure you've already run these locally:
|
| 31 |
-
python extract_articles_cli.py
|
| 32 |
-
python chunk_articles_cli.py
|
| 33 |
-
python upload_to_qdrant_cli.py
|
| 34 |
-
```
|
| 35 |
-
|
| 36 |
-
### 2. Test Locally (Optional)
|
| 37 |
-
```bash
|
| 38 |
-
# Set up virtual environment
|
| 39 |
-
python -m venv venv
|
| 40 |
-
source venv/bin/activate # On Windows: venv\Scripts\activate
|
| 41 |
-
|
| 42 |
-
# Install dependencies
|
| 43 |
-
pip install -r requirements.txt
|
| 44 |
-
|
| 45 |
-
# Run the app
|
| 46 |
-
python app.py
|
| 47 |
-
```
|
| 48 |
-
|
| 49 |
-
### 3. Create Hugging Face Space
|
| 50 |
-
1. Go to https://huggingface.co/spaces
|
| 51 |
-
2. Click "Create new Space"
|
| 52 |
-
3. Configure:
|
| 53 |
-
- **Space name**: Your choice (e.g., `80k-career-advisor`)
|
| 54 |
-
- **SDK**: Gradio
|
| 55 |
-
- **Hardware**: CPU basic (free tier is sufficient)
|
| 56 |
-
- **Visibility**: Public or Private
|
| 57 |
-
|
| 58 |
-
### 4. Configure Secrets (CRITICAL!)
|
| 59 |
-
In your Space Settings β Variables and Secrets, add:
|
| 60 |
-
|
| 61 |
-
- **QDRANT_URL**: `https://your-cluster-url.aws.cloud.qdrant.io`
|
| 62 |
-
- **QDRANT_API_KEY**: `your-qdrant-api-key`
|
| 63 |
-
- **OPENAI_API_KEY**: `sk-...your-openai-key`
|
| 64 |
-
|
| 65 |
-
### 5. Upload Files
|
| 66 |
-
|
| 67 |
-
**Option A: Git**
|
| 68 |
-
```bash
|
| 69 |
-
git clone https://huggingface.co/spaces/YOUR_USERNAME/YOUR_SPACE_NAME
|
| 70 |
-
cd YOUR_SPACE_NAME
|
| 71 |
-
|
| 72 |
-
# Copy only the necessary files
|
| 73 |
-
cp /home/ryan/Documents/80k_rag/app.py .
|
| 74 |
-
cp /home/ryan/Documents/80k_rag/rag_chat.py .
|
| 75 |
-
cp /home/ryan/Documents/80k_rag/citation_validator.py .
|
| 76 |
-
cp /home/ryan/Documents/80k_rag/config.py .
|
| 77 |
-
cp /home/ryan/Documents/80k_rag/requirements.txt .
|
| 78 |
-
cp /home/ryan/Documents/80k_rag/README.md .
|
| 79 |
-
|
| 80 |
-
git add .
|
| 81 |
-
git commit -m "Initial deployment"
|
| 82 |
-
git push
|
| 83 |
-
```
|
| 84 |
-
|
| 85 |
-
**Option B: Web Interface**
|
| 86 |
-
1. Click "Files and versions" tab
|
| 87 |
-
2. Click "Upload files"
|
| 88 |
-
3. Drag and drop the 6 files listed above
|
| 89 |
-
4. Click "Commit"
|
| 90 |
-
|
| 91 |
-
### 6. Monitor Build
|
| 92 |
-
- Watch the build logs in the App tab
|
| 93 |
-
- Build typically takes 2-5 minutes
|
| 94 |
-
- Look for any errors in dependencies or imports
|
| 95 |
-
|
| 96 |
-
## π Post-Deployment
|
| 97 |
-
|
| 98 |
-
### Testing
|
| 99 |
-
1. Visit your Space URL: `https://huggingface.co/spaces/YOUR_USERNAME/YOUR_SPACE_NAME`
|
| 100 |
-
2. Try the example questions
|
| 101 |
-
3. Test with a custom question
|
| 102 |
-
4. Verify citations are displaying correctly
|
| 103 |
-
|
| 104 |
-
### Common Issues
|
| 105 |
-
|
| 106 |
-
**Problem**: Build fails with "Module not found"
|
| 107 |
-
- **Solution**: Check that all imports in `app.py`, `rag_chat.py`, and `citation_validator.py` are in `requirements.txt`
|
| 108 |
-
|
| 109 |
-
**Problem**: Runtime error about missing API keys
|
| 110 |
-
- **Solution**: Verify secrets are set correctly in Space Settings
|
| 111 |
-
|
| 112 |
-
**Problem**: Slow responses
|
| 113 |
-
- **Solution**: Consider upgrading to a better hardware tier
|
| 114 |
-
|
| 115 |
-
**Problem**: "No relevant sources found"
|
| 116 |
-
- **Solution**: Verify your Qdrant instance is accessible and contains data
|
| 117 |
-
|
| 118 |
-
## π Estimated Costs
|
| 119 |
-
|
| 120 |
-
- **HF Space (CPU basic)**: Free
|
| 121 |
-
- **OpenAI API**: ~$0.01-0.05 per query (GPT-4o-mini)
|
| 122 |
-
- **Qdrant Cloud**: Free tier supports up to 1GB
|
| 123 |
-
|
| 124 |
-
## π Updating Your Deployed App
|
| 125 |
-
|
| 126 |
-
```bash
|
| 127 |
-
# Make changes locally
|
| 128 |
-
# Then push updates
|
| 129 |
-
cd YOUR_SPACE_NAME
|
| 130 |
-
git add .
|
| 131 |
-
git commit -m "Update: description of changes"
|
| 132 |
-
git push
|
| 133 |
-
```
|
| 134 |
-
|
| 135 |
-
## π Notes
|
| 136 |
-
|
| 137 |
-
- The app will save `validation_results.json` during runtime (this is fine, stored in Space's temporary storage)
|
| 138 |
-
- Secrets in HF Spaces are injected as environment variables (compatible with your code)
|
| 139 |
-
- The `.env` file is only for local development
|
| 140 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
QUICK_START.md
DELETED
|
@@ -1,86 +0,0 @@
|
|
| 1 |
-
# π Quick Start: Deploy to Hugging Face Spaces
|
| 2 |
-
|
| 3 |
-
## TL;DR - 5 Minute Deploy
|
| 4 |
-
|
| 5 |
-
### Step 1: Prepare Files (Already Done! β)
|
| 6 |
-
```bash
|
| 7 |
-
./prepare_deployment.sh
|
| 8 |
-
```
|
| 9 |
-
|
| 10 |
-
### Step 2: Create HF Space
|
| 11 |
-
1. Go to https://huggingface.co/spaces
|
| 12 |
-
2. Click **"Create new Space"**
|
| 13 |
-
3. Settings:
|
| 14 |
-
- Space name: `80k-career-advisor` (or your choice)
|
| 15 |
-
- SDK: **Gradio**
|
| 16 |
-
- Hardware: **CPU basic** (free)
|
| 17 |
-
- Visibility: Public or Private
|
| 18 |
-
4. Click **"Create Space"**
|
| 19 |
-
|
| 20 |
-
### Step 3: Add Secrets (CRITICAL!)
|
| 21 |
-
On your Space page β **Settings** β **Variables and Secrets**:
|
| 22 |
-
|
| 23 |
-
| Name | Value |
|
| 24 |
-
|------|-------|
|
| 25 |
-
| `QDRANT_URL` | Your Qdrant instance URL |
|
| 26 |
-
| `QDRANT_API_KEY` | Your Qdrant API key |
|
| 27 |
-
| `OPENAI_API_KEY` | Your OpenAI API key |
|
| 28 |
-
|
| 29 |
-
### Step 4: Upload Files
|
| 30 |
-
|
| 31 |
-
**Easy Way (Web Upload):**
|
| 32 |
-
1. Go to **Files and versions** tab
|
| 33 |
-
2. Click **"Upload files"**
|
| 34 |
-
3. Drag these 6 files from `hf_spaces_deploy/`:
|
| 35 |
-
- app.py
|
| 36 |
-
- rag_chat.py
|
| 37 |
-
- citation_validator.py
|
| 38 |
-
- config.py
|
| 39 |
-
- requirements.txt
|
| 40 |
-
- README.md
|
| 41 |
-
4. Click **"Commit changes to main"**
|
| 42 |
-
|
| 43 |
-
**Git Way:**
|
| 44 |
-
```bash
|
| 45 |
-
# Clone your new space
|
| 46 |
-
git clone https://huggingface.co/spaces/YOUR_USERNAME/YOUR_SPACE_NAME
|
| 47 |
-
cd YOUR_SPACE_NAME
|
| 48 |
-
|
| 49 |
-
# Copy files
|
| 50 |
-
cp ../80k_rag/hf_spaces_deploy/* .
|
| 51 |
-
|
| 52 |
-
# Push
|
| 53 |
-
git add .
|
| 54 |
-
git commit -m "Initial deployment"
|
| 55 |
-
git push
|
| 56 |
-
```
|
| 57 |
-
|
| 58 |
-
### Step 5: Wait & Test
|
| 59 |
-
- Build takes 2-5 minutes
|
| 60 |
-
- Your app will be live at: `https://huggingface.co/spaces/YOUR_USERNAME/YOUR_SPACE_NAME`
|
| 61 |
-
- Test with example questions!
|
| 62 |
-
|
| 63 |
-
## Troubleshooting
|
| 64 |
-
|
| 65 |
-
| Problem | Solution |
|
| 66 |
-
|---------|----------|
|
| 67 |
-
| Build fails | Check build logs, verify requirements.txt |
|
| 68 |
-
| "Module not found" | Ensure all dependencies in requirements.txt |
|
| 69 |
-
| No API response | Verify secrets are set correctly |
|
| 70 |
-
| "No relevant sources" | Check Qdrant instance is accessible |
|
| 71 |
-
|
| 72 |
-
## Cost
|
| 73 |
-
|
| 74 |
-
- **HF Space**: FREE (CPU basic tier)
|
| 75 |
-
- **OpenAI**: ~$0.01-0.05 per query
|
| 76 |
-
- **Qdrant**: FREE (up to 1GB)
|
| 77 |
-
|
| 78 |
-
Total: Essentially free for moderate usage!
|
| 79 |
-
|
| 80 |
-
## Need Help?
|
| 81 |
-
|
| 82 |
-
See detailed guides:
|
| 83 |
-
- `HF_SPACES_CHECKLIST.md` - Complete checklist
|
| 84 |
-
- `DEPLOYMENT.md` - Detailed deployment guide
|
| 85 |
-
- `README.md` - Full project documentation
|
| 86 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
hf_spaces_deploy/README.md
DELETED
|
@@ -1,102 +0,0 @@
|
|
| 1 |
-
---
|
| 2 |
-
title: 80,000 Hours RAG Q&A
|
| 3 |
-
emoji: π―
|
| 4 |
-
colorFrom: blue
|
| 5 |
-
colorTo: purple
|
| 6 |
-
sdk: gradio
|
| 7 |
-
sdk_version: 4.0.0
|
| 8 |
-
app_file: app.py
|
| 9 |
-
pinned: false
|
| 10 |
-
---
|
| 11 |
-
|
| 12 |
-
# π― 80,000 Hours Career Advice Q&A
|
| 13 |
-
|
| 14 |
-
A Retrieval-Augmented Generation (RAG) system that answers career-related questions using content from [80,000 Hours](https://80000hours.org/), with validated citations.
|
| 15 |
-
|
| 16 |
-
## Features
|
| 17 |
-
|
| 18 |
-
- π **Semantic Search**: Retrieves relevant content from 80,000 Hours articles
|
| 19 |
-
- π€ **AI-Powered Answers**: Uses GPT-4o-mini to generate comprehensive responses
|
| 20 |
-
- β
**Citation Validation**: Automatically validates that quotes exist in source material
|
| 21 |
-
- π **Source Attribution**: Every answer includes validated citations with URLs
|
| 22 |
-
|
| 23 |
-
## How It Works
|
| 24 |
-
|
| 25 |
-
1. Your question is converted to a vector embedding
|
| 26 |
-
2. Relevant article chunks are retrieved from Qdrant vector database
|
| 27 |
-
3. GPT-4o-mini generates an answer with citations
|
| 28 |
-
4. Citations are validated against source material
|
| 29 |
-
5. You get an answer with verified quotes and source links
|
| 30 |
-
|
| 31 |
-
## Configuration for Hugging Face Spaces
|
| 32 |
-
|
| 33 |
-
To deploy this app, you need to configure the following **Secrets** in your Space settings:
|
| 34 |
-
|
| 35 |
-
1. Go to your Space β Settings β Variables and Secrets
|
| 36 |
-
2. Add these secrets:
|
| 37 |
-
- `QDRANT_URL`: Your Qdrant cloud instance URL
|
| 38 |
-
- `QDRANT_API_KEY`: Your Qdrant API key
|
| 39 |
-
- `OPENAI_API_KEY`: Your OpenAI API key
|
| 40 |
-
|
| 41 |
-
## Local Development
|
| 42 |
-
|
| 43 |
-
### Setup
|
| 44 |
-
|
| 45 |
-
1. Install dependencies:
|
| 46 |
-
```bash
|
| 47 |
-
pip install -r requirements.txt
|
| 48 |
-
```
|
| 49 |
-
|
| 50 |
-
2. Create `.env` file with:
|
| 51 |
-
```
|
| 52 |
-
QDRANT_URL=your_url
|
| 53 |
-
QDRANT_API_KEY=your_key
|
| 54 |
-
OPENAI_API_KEY=your_key
|
| 55 |
-
```
|
| 56 |
-
|
| 57 |
-
### First Time Setup (run in order):
|
| 58 |
-
|
| 59 |
-
1. **Extract articles** β `python extract_articles_cli.py`
|
| 60 |
-
- Scrapes 80,000 Hours articles from sitemap
|
| 61 |
-
- Only needed once (or to refresh content)
|
| 62 |
-
|
| 63 |
-
2. **Chunk articles** β `python chunk_articles_cli.py`
|
| 64 |
-
- Splits articles into semantic chunks
|
| 65 |
-
|
| 66 |
-
3. **Upload to Qdrant** β `python upload_to_qdrant_cli.py`
|
| 67 |
-
- Generates embeddings and uploads to vector DB
|
| 68 |
-
|
| 69 |
-
### Running Locally
|
| 70 |
-
|
| 71 |
-
**Web Interface:**
|
| 72 |
-
```bash
|
| 73 |
-
python app.py
|
| 74 |
-
```
|
| 75 |
-
|
| 76 |
-
**Command Line:**
|
| 77 |
-
```bash
|
| 78 |
-
python rag_chat.py "your question here"
|
| 79 |
-
python rag_chat.py "your question" --show-context
|
| 80 |
-
```
|
| 81 |
-
|
| 82 |
-
## Project Structure
|
| 83 |
-
|
| 84 |
-
- `app.py` - Main Gradio web interface
|
| 85 |
-
- `rag_chat.py` - RAG logic and CLI interface
|
| 86 |
-
- `citation_validator.py` - Citation validation system
|
| 87 |
-
- `extract_articles_cli.py` - Article scraper
|
| 88 |
-
- `chunk_articles_cli.py` - Article chunking
|
| 89 |
-
- `upload_to_qdrant_cli.py` - Vector DB uploader
|
| 90 |
-
- `config.py` - Shared configuration
|
| 91 |
-
|
| 92 |
-
## Tech Stack
|
| 93 |
-
|
| 94 |
-
- **Frontend**: Gradio 4.0+
|
| 95 |
-
- **LLM**: OpenAI GPT-4o-mini
|
| 96 |
-
- **Vector DB**: Qdrant Cloud
|
| 97 |
-
- **Embeddings**: sentence-transformers (all-MiniLM-L6-v2)
|
| 98 |
-
- **Citation Validation**: rapidfuzz for fuzzy matching
|
| 99 |
-
|
| 100 |
-
## Credits
|
| 101 |
-
|
| 102 |
-
Content sourced from [80,000 Hours](https://80000hours.org/), a nonprofit that provides research and support to help people find careers that effectively tackle the world's most pressing problems.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
hf_spaces_deploy/app.py
DELETED
|
@@ -1,102 +0,0 @@
|
|
| 1 |
-
import gradio as gr
|
| 2 |
-
import os
|
| 3 |
-
from rag_chat import ask
|
| 4 |
-
|
| 5 |
-
def chat_interface(question: str, show_context: bool = False):
|
| 6 |
-
"""Process question and return formatted response."""
|
| 7 |
-
if not question.strip():
|
| 8 |
-
return "Please enter a question.", ""
|
| 9 |
-
|
| 10 |
-
result = ask(question, show_context=show_context)
|
| 11 |
-
|
| 12 |
-
# Format main response
|
| 13 |
-
answer = result["answer"]
|
| 14 |
-
|
| 15 |
-
# Format citations
|
| 16 |
-
citations_text = ""
|
| 17 |
-
if result["citations"]:
|
| 18 |
-
citations_text += "\n\n---\n\n### π Citations\n\n"
|
| 19 |
-
for i, citation in enumerate(result["citations"], 1):
|
| 20 |
-
citations_text += f"**[{i}]** {citation['title']}\n"
|
| 21 |
-
citations_text += f"> \"{citation['quote']}\"\n"
|
| 22 |
-
citations_text += f"π [{citation['url']}]({citation['url']})\n\n"
|
| 23 |
-
|
| 24 |
-
# Add validation warnings if any
|
| 25 |
-
if result.get("validation_errors"):
|
| 26 |
-
citations_text += "\nβ οΈ **Validation Warnings:**\n"
|
| 27 |
-
for error in result["validation_errors"]:
|
| 28 |
-
citations_text += f"- {error}\n"
|
| 29 |
-
|
| 30 |
-
# Add stats
|
| 31 |
-
if result["citations"]:
|
| 32 |
-
valid_count = len([c for c in result["citations"] if c.get("validated", True)])
|
| 33 |
-
total_count = len(result["citations"])
|
| 34 |
-
citations_text += f"\nβ {valid_count}/{total_count} citations validated"
|
| 35 |
-
|
| 36 |
-
return answer, citations_text
|
| 37 |
-
|
| 38 |
-
# Create Gradio interface
|
| 39 |
-
with gr.Blocks(title="80,000 Hours Q&A", theme=gr.themes.Soft()) as demo:
|
| 40 |
-
gr.Markdown(
|
| 41 |
-
"""
|
| 42 |
-
# π― 80,000 Hours Career Advice Q&A
|
| 43 |
-
Ask questions about career planning and get answers backed by citations from 80,000 Hours articles.
|
| 44 |
-
|
| 45 |
-
This RAG system retrieves relevant content from the 80,000 Hours knowledge base and generates answers with validated citations.
|
| 46 |
-
"""
|
| 47 |
-
)
|
| 48 |
-
|
| 49 |
-
with gr.Row():
|
| 50 |
-
with gr.Column():
|
| 51 |
-
question_input = gr.Textbox(
|
| 52 |
-
label="Your Question",
|
| 53 |
-
placeholder="e.g., Should I plan my entire career?",
|
| 54 |
-
lines=2
|
| 55 |
-
)
|
| 56 |
-
show_context_checkbox = gr.Checkbox(
|
| 57 |
-
label="Show retrieved context (for debugging)",
|
| 58 |
-
value=False
|
| 59 |
-
)
|
| 60 |
-
submit_btn = gr.Button("Ask", variant="primary")
|
| 61 |
-
|
| 62 |
-
with gr.Row():
|
| 63 |
-
with gr.Column():
|
| 64 |
-
answer_output = gr.Textbox(
|
| 65 |
-
label="Answer",
|
| 66 |
-
lines=10,
|
| 67 |
-
show_copy_button=True
|
| 68 |
-
)
|
| 69 |
-
|
| 70 |
-
with gr.Column():
|
| 71 |
-
citations_output = gr.Markdown(
|
| 72 |
-
label="Citations & Sources"
|
| 73 |
-
)
|
| 74 |
-
|
| 75 |
-
# Event handlers
|
| 76 |
-
submit_btn.click(
|
| 77 |
-
fn=chat_interface,
|
| 78 |
-
inputs=[question_input, show_context_checkbox],
|
| 79 |
-
outputs=[answer_output, citations_output]
|
| 80 |
-
)
|
| 81 |
-
|
| 82 |
-
question_input.submit(
|
| 83 |
-
fn=chat_interface,
|
| 84 |
-
inputs=[question_input, show_context_checkbox],
|
| 85 |
-
outputs=[answer_output, citations_output]
|
| 86 |
-
)
|
| 87 |
-
|
| 88 |
-
# Example questions
|
| 89 |
-
gr.Examples(
|
| 90 |
-
examples=[
|
| 91 |
-
"Should I plan my entire career?",
|
| 92 |
-
"What career advice does 80k give?",
|
| 93 |
-
"How can I have more impact with my career?",
|
| 94 |
-
"What are the world's most pressing problems?",
|
| 95 |
-
],
|
| 96 |
-
inputs=question_input
|
| 97 |
-
)
|
| 98 |
-
|
| 99 |
-
if __name__ == "__main__":
|
| 100 |
-
# HF Spaces handles the server configuration
|
| 101 |
-
demo.launch()
|
| 102 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
hf_spaces_deploy/citation_validator.py
DELETED
|
@@ -1,338 +0,0 @@
|
|
| 1 |
-
"""Citation validation and formatting for RAG system.
|
| 2 |
-
|
| 3 |
-
This module handles structured citations with validation to prevent hallucination.
|
| 4 |
-
"""
|
| 5 |
-
|
| 6 |
-
import json
|
| 7 |
-
import time
|
| 8 |
-
from typing import List, Dict, Any
|
| 9 |
-
from urllib.parse import quote
|
| 10 |
-
from openai import OpenAI
|
| 11 |
-
from rapidfuzz import fuzz
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
FUZZY_THRESHOLD = 95
|
| 15 |
-
|
| 16 |
-
def create_highlighted_url(base_url: str, quote_text: str) -> str:
|
| 17 |
-
"""Create a URL with text fragment that highlights the quoted text.
|
| 18 |
-
|
| 19 |
-
Uses the :~:text= URL fragment feature to scroll to and highlight text.
|
| 20 |
-
|
| 21 |
-
Args:
|
| 22 |
-
base_url: The base article URL
|
| 23 |
-
quote_text: The text to highlight
|
| 24 |
-
|
| 25 |
-
Returns:
|
| 26 |
-
URL with text fragment
|
| 27 |
-
"""
|
| 28 |
-
# Take first ~100 characters of quote for the URL (browsers have limits)
|
| 29 |
-
# and clean up for URL encoding
|
| 30 |
-
text_fragment = quote_text[:100].strip()
|
| 31 |
-
encoded_text = quote(text_fragment)
|
| 32 |
-
return f"{base_url}#:~:text={encoded_text}"
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
def normalize_text(text: str) -> str:
|
| 36 |
-
"""Normalize text for comparison by handling whitespace and punctuation variants."""
|
| 37 |
-
# Normalize different dash types to standard hyphen
|
| 38 |
-
text = text.replace('β', '-') # en-dash
|
| 39 |
-
text = text.replace('β', '-') # em-dash
|
| 40 |
-
text = text.replace('β', '-') # minus sign
|
| 41 |
-
# Normalize different apostrophe/quote types to standard ASCII
|
| 42 |
-
text = text.replace(''', "'") # curly apostrophe
|
| 43 |
-
text = text.replace(''', "'") # left single quote
|
| 44 |
-
text = text.replace('"', '"') # left double quote
|
| 45 |
-
text = text.replace('"', '"') # right double quote
|
| 46 |
-
# Normalize whitespace
|
| 47 |
-
text = " ".join(text.split())
|
| 48 |
-
return text
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
def validate_citation(quote: str, source_chunks: List[Any], source_id: int) -> Dict[str, Any]:
|
| 52 |
-
"""Validate that a quote exists in the specified source chunk.
|
| 53 |
-
|
| 54 |
-
Args:
|
| 55 |
-
quote: The quoted text to validate
|
| 56 |
-
source_chunks: List of source chunks from Qdrant
|
| 57 |
-
source_id: 1-indexed source ID
|
| 58 |
-
|
| 59 |
-
Returns:
|
| 60 |
-
Dict with validation result and metadata
|
| 61 |
-
"""
|
| 62 |
-
if source_id < 1 or source_id > len(source_chunks):
|
| 63 |
-
return {
|
| 64 |
-
"valid": False,
|
| 65 |
-
"quote": quote,
|
| 66 |
-
"source_id": source_id,
|
| 67 |
-
"reason": "Invalid source ID",
|
| 68 |
-
"source_text": None
|
| 69 |
-
}
|
| 70 |
-
|
| 71 |
-
quote_clean = normalize_text(quote).lower()
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
# Step 1: Check claimed source first (fast path)
|
| 75 |
-
source_text = normalize_text(source_chunks[source_id - 1].payload['text']).lower()
|
| 76 |
-
claimed_score = fuzz.partial_ratio(quote_clean, source_text)
|
| 77 |
-
|
| 78 |
-
if claimed_score >= FUZZY_THRESHOLD:
|
| 79 |
-
return {
|
| 80 |
-
"valid": True,
|
| 81 |
-
"quote": quote,
|
| 82 |
-
"source_id": source_id,
|
| 83 |
-
"title": source_chunks[source_id - 1].payload['title'],
|
| 84 |
-
"url": source_chunks[source_id - 1].payload['url'],
|
| 85 |
-
"similarity_score": claimed_score
|
| 86 |
-
}
|
| 87 |
-
|
| 88 |
-
for idx, chunk in enumerate(source_chunks, 1):
|
| 89 |
-
if idx == source_id:
|
| 90 |
-
continue # Already checked
|
| 91 |
-
chunk_text = normalize_text(chunk.payload['text']).lower()
|
| 92 |
-
score = fuzz.partial_ratio(quote_clean, chunk_text)
|
| 93 |
-
if score >= FUZZY_THRESHOLD:
|
| 94 |
-
return {
|
| 95 |
-
"valid": True,
|
| 96 |
-
"quote": quote,
|
| 97 |
-
"source_id": idx,
|
| 98 |
-
"title": chunk.payload['title'],
|
| 99 |
-
"url": chunk.payload['url'],
|
| 100 |
-
"similarity_score": score,
|
| 101 |
-
"remapped": True,
|
| 102 |
-
"original_source_id": source_id
|
| 103 |
-
}
|
| 104 |
-
|
| 105 |
-
# Validation failed - report best score from claimed source
|
| 106 |
-
return {
|
| 107 |
-
"valid": False,
|
| 108 |
-
"quote": quote,
|
| 109 |
-
"source_id": source_id,
|
| 110 |
-
"reason": f"Quote not found in any source (claimed source: {claimed_score:.1f}% similarity)",
|
| 111 |
-
"source_text": source_chunks[source_id - 1].payload['text']
|
| 112 |
-
}
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
def generate_answer_with_citations(
|
| 116 |
-
question: str,
|
| 117 |
-
context: str,
|
| 118 |
-
results: List[Any],
|
| 119 |
-
llm_model: str,
|
| 120 |
-
openai_api_key: str
|
| 121 |
-
) -> Dict[str, Any]:
|
| 122 |
-
"""Generate answer with structured citations using OpenAI.
|
| 123 |
-
|
| 124 |
-
Args:
|
| 125 |
-
question: User's question
|
| 126 |
-
context: Formatted context from source chunks
|
| 127 |
-
results: Source chunks from Qdrant
|
| 128 |
-
llm_model: OpenAI model name
|
| 129 |
-
openai_api_key: OpenAI API key
|
| 130 |
-
|
| 131 |
-
Returns:
|
| 132 |
-
Dict with answer and validated citations
|
| 133 |
-
"""
|
| 134 |
-
client = OpenAI(api_key=openai_api_key)
|
| 135 |
-
|
| 136 |
-
system_prompt = """You are a helpful assistant that answers questions based on 80,000 Hours articles.
|
| 137 |
-
|
| 138 |
-
You MUST return your response in valid JSON format with this exact structure:
|
| 139 |
-
{
|
| 140 |
-
"answer": "Your conversational answer with inline citation markers like [1], [2]",
|
| 141 |
-
"citations": [
|
| 142 |
-
{
|
| 143 |
-
"citation_id": 1,
|
| 144 |
-
"source_id": 1,
|
| 145 |
-
"quote": "exact sentence or sentences from the source that support your claim"
|
| 146 |
-
}
|
| 147 |
-
]
|
| 148 |
-
}
|
| 149 |
-
|
| 150 |
-
CITATION HARD RULES:
|
| 151 |
-
1. Copy quotes EXACTLY as they appear in the provided context
|
| 152 |
-
- NO ellipses (...)
|
| 153 |
-
- NO paraphrasing
|
| 154 |
-
- NO punctuation changes
|
| 155 |
-
- Word-for-word, character-for-character accuracy required
|
| 156 |
-
|
| 157 |
-
2. If the needed support is in two places, use TWO SEPARATE citation entries
|
| 158 |
-
- Do NOT combine quotes from different sources or different parts of text
|
| 159 |
-
- Each citation must contain a continuous, unmodified quote
|
| 160 |
-
|
| 161 |
-
3. Use the CORRECT source_id from the provided list
|
| 162 |
-
- Source IDs are numbered [Source 1], [Source 2], etc. in the context
|
| 163 |
-
- Verify the source_id matches where you found the quote
|
| 164 |
-
|
| 165 |
-
CRITICAL RULES FOR CITATIONS:
|
| 166 |
-
- For EVERY claim (advice, fact, statistic, recommendation), add an inline citation [1], [2], etc.
|
| 167 |
-
- For each citation, extract and quote the EXACT sentence(s) from the source that directly support your claim
|
| 168 |
-
- Find the specific sentence(s) in the source that contain the relevant information
|
| 169 |
-
- Each quote should be at least 20 characters and contain complete sentence(s)
|
| 170 |
-
- Multiple consecutive sentences can be quoted if needed to fully support the claim
|
| 171 |
-
|
| 172 |
-
WRITING STYLE:
|
| 173 |
-
- Write concisely in a natural, conversational tone
|
| 174 |
-
- You may paraphrase information in your answer, but always cite the source with exact quotes
|
| 175 |
-
- You can add brief context/transitions without citations, but cite all substantive claims
|
| 176 |
-
- If the sources don't fully answer the question, acknowledge that briefly
|
| 177 |
-
- Only use information from the provided sources - don't add external knowledge
|
| 178 |
-
|
| 179 |
-
EXAMPLES:
|
| 180 |
-
|
| 181 |
-
Example 1 - Single claim:
|
| 182 |
-
{
|
| 183 |
-
"answer": "One of the most effective ways to build career capital is to work at a high-performing organization where you can learn from talented colleagues [1].",
|
| 184 |
-
"citations": [
|
| 185 |
-
{
|
| 186 |
-
"citation_id": 1,
|
| 187 |
-
"source_id": 2,
|
| 188 |
-
"quote": "Working at a high-performing organization is one of the fastest ways to build career capital because you learn from talented colleagues and develop strong professional networks."
|
| 189 |
-
}
|
| 190 |
-
]
|
| 191 |
-
}
|
| 192 |
-
|
| 193 |
-
Example 2 - Multiple claims:
|
| 194 |
-
{
|
| 195 |
-
"answer": "AI safety is considered one of the most pressing problems of our time [1]. Experts estimate that advanced AI could be developed within the next few decades [2], and there's a significant talent gap in the field [3]. This means your contributions could have an outsized impact.",
|
| 196 |
-
"citations": [
|
| 197 |
-
{
|
| 198 |
-
"citation_id": 1,
|
| 199 |
-
"source_id": 1,
|
| 200 |
-
"quote": "We believe that risks from artificial intelligence are one of the most pressing problems facing humanity today."
|
| 201 |
-
},
|
| 202 |
-
{
|
| 203 |
-
"citation_id": 2,
|
| 204 |
-
"source_id": 1,
|
| 205 |
-
"quote": "Many AI researchers believe there's a 10-50% chance of human-level AI being developed by 2050."
|
| 206 |
-
},
|
| 207 |
-
{
|
| 208 |
-
"citation_id": 3,
|
| 209 |
-
"source_id": 3,
|
| 210 |
-
"quote": "There are currently fewer than 300 people working full-time on technical AI safety research, despite the field's critical importance."
|
| 211 |
-
}
|
| 212 |
-
]
|
| 213 |
-
}"""
|
| 214 |
-
|
| 215 |
-
user_prompt = f"""Context from 80,000 Hours articles:
|
| 216 |
-
|
| 217 |
-
{context}
|
| 218 |
-
|
| 219 |
-
Question: {question}
|
| 220 |
-
|
| 221 |
-
Provide your answer in JSON format with exact quotes from the sources."""
|
| 222 |
-
|
| 223 |
-
llm_call_start = time.time()
|
| 224 |
-
response = client.chat.completions.create(
|
| 225 |
-
model=llm_model,
|
| 226 |
-
messages=[
|
| 227 |
-
{"role": "system", "content": system_prompt},
|
| 228 |
-
{"role": "user", "content": user_prompt}
|
| 229 |
-
],
|
| 230 |
-
response_format={"type": "json_object"}
|
| 231 |
-
)
|
| 232 |
-
print(f"[TIMING] OpenAI call: {(time.time() - llm_call_start)*1000:.2f}ms")
|
| 233 |
-
|
| 234 |
-
# Parse the JSON response
|
| 235 |
-
try:
|
| 236 |
-
result = json.loads(response.choices[0].message.content)
|
| 237 |
-
# Enforce strict shape: must have 'answer' (str) and 'citations' (list of dicts)
|
| 238 |
-
if not isinstance(result, dict) or 'answer' not in result or 'citations' not in result:
|
| 239 |
-
return {
|
| 240 |
-
"answer": response.choices[0].message.content,
|
| 241 |
-
"citations": [],
|
| 242 |
-
"validation_errors": ["Response JSON missing required keys 'answer' and/or 'citations'."]
|
| 243 |
-
}
|
| 244 |
-
if not isinstance(result['answer'], str) or not isinstance(result['citations'], list):
|
| 245 |
-
return {
|
| 246 |
-
"answer": response.choices[0].message.content,
|
| 247 |
-
"citations": [],
|
| 248 |
-
"validation_errors": ["Response JSON has incorrect types for 'answer' or 'citations'."]
|
| 249 |
-
}
|
| 250 |
-
answer = result.get("answer", "")
|
| 251 |
-
citations = result.get("citations", [])
|
| 252 |
-
except json.JSONDecodeError:
|
| 253 |
-
return {
|
| 254 |
-
"answer": response.choices[0].message.content,
|
| 255 |
-
"citations": [],
|
| 256 |
-
"validation_errors": ["Failed to parse JSON response"]
|
| 257 |
-
}
|
| 258 |
-
|
| 259 |
-
# Validate each citation
|
| 260 |
-
validation_start = time.time()
|
| 261 |
-
validated_citations = []
|
| 262 |
-
validation_errors = []
|
| 263 |
-
|
| 264 |
-
for citation in citations:
|
| 265 |
-
quote = citation.get("quote", "")
|
| 266 |
-
source_id = citation.get("source_id", 0)
|
| 267 |
-
citation_id = citation.get("citation_id", 0)
|
| 268 |
-
|
| 269 |
-
validation_result = validate_citation(quote, results, source_id)
|
| 270 |
-
|
| 271 |
-
if validation_result["valid"]:
|
| 272 |
-
# Create URL with text fragment to highlight the quote
|
| 273 |
-
highlighted_url = create_highlighted_url(
|
| 274 |
-
validation_result["url"],
|
| 275 |
-
quote
|
| 276 |
-
)
|
| 277 |
-
citation_entry = {
|
| 278 |
-
"citation_id": citation_id,
|
| 279 |
-
"source_id": validation_result["source_id"],
|
| 280 |
-
"quote": quote,
|
| 281 |
-
"title": validation_result["title"],
|
| 282 |
-
"url": highlighted_url,
|
| 283 |
-
"similarity_score": validation_result["similarity_score"]
|
| 284 |
-
}
|
| 285 |
-
if validation_result.get("remapped"):
|
| 286 |
-
citation_entry["remapped_from"] = validation_result["original_source_id"]
|
| 287 |
-
validated_citations.append(citation_entry)
|
| 288 |
-
else:
|
| 289 |
-
validation_errors.append({
|
| 290 |
-
"citation_id": citation_id,
|
| 291 |
-
"reason": validation_result['reason'],
|
| 292 |
-
"claimed_quote": quote,
|
| 293 |
-
"source_text": validation_result.get('source_text')
|
| 294 |
-
})
|
| 295 |
-
|
| 296 |
-
print(f"[TIMING] Validation: {(time.time() - validation_start)*1000:.2f}ms")
|
| 297 |
-
|
| 298 |
-
return {
|
| 299 |
-
"answer": answer,
|
| 300 |
-
"citations": validated_citations,
|
| 301 |
-
"validation_errors": validation_errors,
|
| 302 |
-
"total_citations": len(citations),
|
| 303 |
-
"valid_citations": len(validated_citations)
|
| 304 |
-
}
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
def format_citations_display(citations: List[Dict[str, Any]]) -> str:
|
| 308 |
-
"""Format validated citations in order with article title, URL, and quoted text.
|
| 309 |
-
|
| 310 |
-
Args:
|
| 311 |
-
citations: List of validated citation dicts
|
| 312 |
-
|
| 313 |
-
Returns:
|
| 314 |
-
Formatted string for display
|
| 315 |
-
"""
|
| 316 |
-
if not citations:
|
| 317 |
-
return "No citations available."
|
| 318 |
-
|
| 319 |
-
# Sort citations by citation_id to display in order
|
| 320 |
-
sorted_citations = sorted(citations, key=lambda x: x.get('citation_id', 0))
|
| 321 |
-
|
| 322 |
-
citation_parts = []
|
| 323 |
-
for cit in sorted_citations:
|
| 324 |
-
marker = f"[{cit['citation_id']}]"
|
| 325 |
-
score = cit.get('similarity_score', 100)
|
| 326 |
-
|
| 327 |
-
if cit.get('remapped_from'):
|
| 328 |
-
note = f" ({score:.1f}% match, remapped: source {cit['remapped_from']} β {cit['source_id']})"
|
| 329 |
-
else:
|
| 330 |
-
note = f" ({score:.1f}% match)"
|
| 331 |
-
|
| 332 |
-
citation_parts.append(
|
| 333 |
-
f"{marker} {cit['title']}{note}\n"
|
| 334 |
-
f" URL: {cit['url']}\n"
|
| 335 |
-
f" Quote: \"{cit['quote']}\"\n"
|
| 336 |
-
)
|
| 337 |
-
return "\n".join(citation_parts)
|
| 338 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
hf_spaces_deploy/config.py
DELETED
|
@@ -1,11 +0,0 @@
|
|
| 1 |
-
"""Shared configuration constants for the 80k RAG system."""
|
| 2 |
-
|
| 3 |
-
# Embedding model used across the system
|
| 4 |
-
MODEL_NAME = 'all-MiniLM-L6-v2'
|
| 5 |
-
|
| 6 |
-
# Qdrant collection name
|
| 7 |
-
COLLECTION_NAME = "80k_articles"
|
| 8 |
-
|
| 9 |
-
# Embedding dimension for the model
|
| 10 |
-
EMBEDDING_DIM = 384
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
hf_spaces_deploy/rag_chat.py
DELETED
|
@@ -1,181 +0,0 @@
|
|
| 1 |
-
import os
|
| 2 |
-
import time
|
| 3 |
-
from typing import Dict, Any
|
| 4 |
-
from dotenv import load_dotenv
|
| 5 |
-
from qdrant_client import QdrantClient
|
| 6 |
-
from sentence_transformers import SentenceTransformer
|
| 7 |
-
from citation_validator import generate_answer_with_citations, format_citations_display, normalize_text
|
| 8 |
-
from config import MODEL_NAME, COLLECTION_NAME
|
| 9 |
-
|
| 10 |
-
load_dotenv()
|
| 11 |
-
|
| 12 |
-
LLM_MODEL = "gpt-4o-mini"
|
| 13 |
-
SOURCE_COUNT = 10
|
| 14 |
-
SCORE_THRESHOLD = 0.4
|
| 15 |
-
|
| 16 |
-
def retrieve_context(question):
|
| 17 |
-
"""Retrieve relevant chunks from Qdrant."""
|
| 18 |
-
start = time.time()
|
| 19 |
-
|
| 20 |
-
client = QdrantClient(
|
| 21 |
-
url=os.getenv("QDRANT_URL"),
|
| 22 |
-
api_key=os.getenv("QDRANT_API_KEY"),
|
| 23 |
-
)
|
| 24 |
-
|
| 25 |
-
model = SentenceTransformer(MODEL_NAME)
|
| 26 |
-
query_vector = model.encode(question).tolist()
|
| 27 |
-
|
| 28 |
-
results = client.query_points(
|
| 29 |
-
collection_name=COLLECTION_NAME,
|
| 30 |
-
query=query_vector,
|
| 31 |
-
limit=SOURCE_COUNT,
|
| 32 |
-
score_threshold=SCORE_THRESHOLD,
|
| 33 |
-
)
|
| 34 |
-
print(f"[TIMING] Retrieval: {(time.time() - start)*1000:.2f}ms")
|
| 35 |
-
|
| 36 |
-
return results.points
|
| 37 |
-
|
| 38 |
-
def format_context(results):
|
| 39 |
-
"""Format retrieved chunks into context string for LLM."""
|
| 40 |
-
context_parts = []
|
| 41 |
-
for i, hit in enumerate(results, 1):
|
| 42 |
-
context_parts.append(
|
| 43 |
-
f"[Source {i}]\n"
|
| 44 |
-
f"Title: {hit.payload['title']}\n"
|
| 45 |
-
f"URL: {hit.payload['url']}\n"
|
| 46 |
-
f"Content: {hit.payload['text']}\n"
|
| 47 |
-
)
|
| 48 |
-
return "\n---\n".join(context_parts)
|
| 49 |
-
|
| 50 |
-
def ask(question: str, show_context: bool = False) -> Dict[str, Any]:
|
| 51 |
-
"""Main RAG function: retrieve context and generate answer with validated citations."""
|
| 52 |
-
total_start = time.time()
|
| 53 |
-
print(f"Question: {question}\n")
|
| 54 |
-
|
| 55 |
-
# Retrieve relevant chunks
|
| 56 |
-
results = retrieve_context(question)
|
| 57 |
-
|
| 58 |
-
if not results:
|
| 59 |
-
print("No relevant sources found above the score threshold.")
|
| 60 |
-
return {
|
| 61 |
-
"question": question,
|
| 62 |
-
"answer": "No relevant information found in the knowledge base.",
|
| 63 |
-
"citations": [],
|
| 64 |
-
"sources": []
|
| 65 |
-
}
|
| 66 |
-
|
| 67 |
-
context = format_context(results)
|
| 68 |
-
print(f"[TIMING] First chunk ready: {(time.time() - total_start)*1000:.2f}ms")
|
| 69 |
-
|
| 70 |
-
if show_context:
|
| 71 |
-
print("=" * 80)
|
| 72 |
-
print("RETRIEVED CONTEXT:")
|
| 73 |
-
print("=" * 80)
|
| 74 |
-
print(context)
|
| 75 |
-
print("\n")
|
| 76 |
-
|
| 77 |
-
# Generate answer with citations
|
| 78 |
-
llm_start = time.time()
|
| 79 |
-
result = generate_answer_with_citations(
|
| 80 |
-
question=question,
|
| 81 |
-
context=context,
|
| 82 |
-
results=results,
|
| 83 |
-
llm_model=LLM_MODEL,
|
| 84 |
-
openai_api_key=os.getenv("OPENAI_API_KEY")
|
| 85 |
-
)
|
| 86 |
-
|
| 87 |
-
total_time = (time.time() - total_start) * 1000
|
| 88 |
-
print(f"[TIMING] Total: {total_time:.2f}ms ({total_time/1000:.2f}s)")
|
| 89 |
-
|
| 90 |
-
# Display answer
|
| 91 |
-
print("\n" + "=" * 80)
|
| 92 |
-
print("ANSWER:")
|
| 93 |
-
print("=" * 80)
|
| 94 |
-
print(result["answer"])
|
| 95 |
-
print("\n")
|
| 96 |
-
|
| 97 |
-
# Display citations
|
| 98 |
-
print("=" * 80)
|
| 99 |
-
print("CITATIONS (Verified Quotes):")
|
| 100 |
-
print("=" * 80)
|
| 101 |
-
print(format_citations_display(result["citations"]))
|
| 102 |
-
|
| 103 |
-
# Show validation stats
|
| 104 |
-
if result["validation_errors"]:
|
| 105 |
-
print("\n" + "=" * 80)
|
| 106 |
-
print("VALIDATION WARNINGS:")
|
| 107 |
-
print("=" * 80)
|
| 108 |
-
for error in result["validation_errors"]:
|
| 109 |
-
print(f"β [Citation {error['citation_id']}] {error['reason']}")
|
| 110 |
-
|
| 111 |
-
print("\n" + "=" * 80)
|
| 112 |
-
print(f"Citation Stats: {result['valid_citations']}/{result['total_citations']} citations validated")
|
| 113 |
-
print("=" * 80)
|
| 114 |
-
|
| 115 |
-
# Save validation results to JSON
|
| 116 |
-
def normalize_dict(obj):
|
| 117 |
-
"""Recursively normalize all strings in a dict/list structure."""
|
| 118 |
-
if isinstance(obj, dict):
|
| 119 |
-
return {k: normalize_dict(v) for k, v in obj.items()}
|
| 120 |
-
elif isinstance(obj, list):
|
| 121 |
-
return [normalize_dict(item) for item in obj]
|
| 122 |
-
elif isinstance(obj, str):
|
| 123 |
-
return normalize_text(obj)
|
| 124 |
-
return obj
|
| 125 |
-
|
| 126 |
-
validation_output = {
|
| 127 |
-
"question": question,
|
| 128 |
-
"answer": result["answer"],
|
| 129 |
-
"citations": result["citations"],
|
| 130 |
-
"validation_errors": result["validation_errors"],
|
| 131 |
-
"stats": {
|
| 132 |
-
"total_citations": result["total_citations"],
|
| 133 |
-
"valid_citations": result["valid_citations"],
|
| 134 |
-
"total_time_ms": total_time
|
| 135 |
-
},
|
| 136 |
-
"sources": [
|
| 137 |
-
{
|
| 138 |
-
"source_id": i,
|
| 139 |
-
"title": hit.payload['title'],
|
| 140 |
-
"url": hit.payload['url'],
|
| 141 |
-
"chunk_id": hit.payload.get('chunk_id'),
|
| 142 |
-
"text": hit.payload['text']
|
| 143 |
-
}
|
| 144 |
-
for i, hit in enumerate(results, 1)
|
| 145 |
-
]
|
| 146 |
-
}
|
| 147 |
-
|
| 148 |
-
# Normalize all text in the output
|
| 149 |
-
validation_output = normalize_dict(validation_output)
|
| 150 |
-
|
| 151 |
-
import json
|
| 152 |
-
with open("validation_results.json", "w", encoding="utf-8") as f:
|
| 153 |
-
json.dump(validation_output, f, ensure_ascii=False, indent=2)
|
| 154 |
-
print("\n[INFO] Validation results saved to validation_results.json")
|
| 155 |
-
|
| 156 |
-
return {
|
| 157 |
-
"question": question,
|
| 158 |
-
"answer": result["answer"],
|
| 159 |
-
"citations": result["citations"],
|
| 160 |
-
"validation_errors": result["validation_errors"],
|
| 161 |
-
"sources": results
|
| 162 |
-
}
|
| 163 |
-
|
| 164 |
-
def main():
|
| 165 |
-
import sys
|
| 166 |
-
|
| 167 |
-
# Default test query if no args provided
|
| 168 |
-
if len(sys.argv) < 2:
|
| 169 |
-
question = "Should I plan my entire career?"
|
| 170 |
-
show_context = False
|
| 171 |
-
print(f"[INFO] No query provided, using test query: '{question}'\n")
|
| 172 |
-
else:
|
| 173 |
-
show_context = "--show-context" in sys.argv
|
| 174 |
-
question_parts = [arg for arg in sys.argv[1:] if arg != "--show-context"]
|
| 175 |
-
question = " ".join(question_parts)
|
| 176 |
-
|
| 177 |
-
ask(question, show_context=show_context)
|
| 178 |
-
|
| 179 |
-
if __name__ == "__main__":
|
| 180 |
-
main()
|
| 181 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
hf_spaces_deploy/requirements.txt
DELETED
|
@@ -1,10 +0,0 @@
|
|
| 1 |
-
openai>=1.0.0
|
| 2 |
-
qdrant-client>=1.7.0
|
| 3 |
-
sentence-transformers>=2.2.0
|
| 4 |
-
python-dotenv>=1.0.0
|
| 5 |
-
beautifulsoup4>=4.12.0
|
| 6 |
-
requests>=2.31.0
|
| 7 |
-
gradio>=4.0.0
|
| 8 |
-
rapidfuzz>=3.0.0
|
| 9 |
-
torch>=2.0.0
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
prepare_deployment.sh
DELETED
|
@@ -1,34 +0,0 @@
|
|
| 1 |
-
#!/bin/bash
|
| 2 |
-
# Helper script to prepare files for Hugging Face Spaces deployment
|
| 3 |
-
|
| 4 |
-
echo "π¦ Preparing files for Hugging Face Spaces deployment..."
|
| 5 |
-
echo ""
|
| 6 |
-
|
| 7 |
-
# Create deployment directory
|
| 8 |
-
DEPLOY_DIR="hf_spaces_deploy"
|
| 9 |
-
rm -rf $DEPLOY_DIR
|
| 10 |
-
mkdir -p $DEPLOY_DIR
|
| 11 |
-
|
| 12 |
-
# Copy necessary files
|
| 13 |
-
echo "Copying files..."
|
| 14 |
-
cp app.py $DEPLOY_DIR/
|
| 15 |
-
cp rag_chat.py $DEPLOY_DIR/
|
| 16 |
-
cp citation_validator.py $DEPLOY_DIR/
|
| 17 |
-
cp config.py $DEPLOY_DIR/
|
| 18 |
-
cp requirements.txt $DEPLOY_DIR/
|
| 19 |
-
cp README.md $DEPLOY_DIR/
|
| 20 |
-
|
| 21 |
-
echo "β
Files copied to $DEPLOY_DIR/"
|
| 22 |
-
echo ""
|
| 23 |
-
echo "Files ready for deployment:"
|
| 24 |
-
ls -lh $DEPLOY_DIR/
|
| 25 |
-
echo ""
|
| 26 |
-
echo "π Next steps:"
|
| 27 |
-
echo "1. Create your Hugging Face Space at https://huggingface.co/spaces"
|
| 28 |
-
echo "2. Configure secrets (QDRANT_URL, QDRANT_API_KEY, OPENAI_API_KEY)"
|
| 29 |
-
echo "3. Clone your space: git clone https://huggingface.co/spaces/YOUR_USERNAME/YOUR_SPACE"
|
| 30 |
-
echo "4. Copy files: cp hf_spaces_deploy/* YOUR_SPACE/"
|
| 31 |
-
echo "5. Push: cd YOUR_SPACE && git add . && git commit -m 'Initial deployment' && git push"
|
| 32 |
-
echo ""
|
| 33 |
-
echo "π See HF_SPACES_CHECKLIST.md for detailed instructions"
|
| 34 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
upload_to_qdrant_cli.py
CHANGED
|
@@ -29,7 +29,11 @@ def load_embedding_model():
|
|
| 29 |
def create_collection(client, collection_name=COLLECTION_NAME, embedding_dim=EMBEDDING_DIM):
|
| 30 |
print(f"Creating collection '{collection_name}'...")
|
| 31 |
try:
|
| 32 |
-
client.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
collection_name=collection_name,
|
| 34 |
vectors_config=VectorParams(size=embedding_dim, distance=Distance.COSINE),
|
| 35 |
)
|
|
|
|
| 29 |
def create_collection(client, collection_name=COLLECTION_NAME, embedding_dim=EMBEDDING_DIM):
|
| 30 |
print(f"Creating collection '{collection_name}'...")
|
| 31 |
try:
|
| 32 |
+
if client.collection_exists(collection_name):
|
| 33 |
+
print(f"Collection '{collection_name}' exists. Deleting...")
|
| 34 |
+
client.delete_collection(collection_name)
|
| 35 |
+
|
| 36 |
+
client.create_collection(
|
| 37 |
collection_name=collection_name,
|
| 38 |
vectors_config=VectorParams(size=embedding_dim, distance=Distance.COSINE),
|
| 39 |
)
|