Spaces:
Running on Zero
Running on Zero
Deploy ProfillyBot: Gradio ZeroGPU + CV RAG (Qwen2.5-3B)
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .dockerignore +48 -0
- .env.template +55 -0
- .gitattributes +8 -0
- .github/workflows/README.md +207 -0
- .github/workflows/code-quality.yml +38 -0
- .github/workflows/dependency-review.yml +22 -0
- .github/workflows/lint.yml +38 -0
- .github/workflows/pre-commit.yml +38 -0
- .github/workflows/security-scan.yml +55 -0
- .github/workflows/test-docker.yml +28 -0
- .github/workflows/test-python-reusable.yml +48 -0
- .github/workflows/test-python310.yml +14 -0
- .github/workflows/test-python311.yml +15 -0
- .github/workflows/test-python312.yml +14 -0
- .gitignore +207 -0
- .hfignore +19 -0
- .pre-commit-config.yaml +40 -0
- .streamlit/config.toml +16 -0
- .venv/.gitignore +1 -0
- .venv/CACHEDIR.TAG +1 -0
- .venv/Lib/site-packages/_virtualenv.pth +3 -0
- .venv/Lib/site-packages/_virtualenv.py +101 -0
- .venv/Scripts/activate +116 -0
- .venv/Scripts/activate.bat +71 -0
- .venv/Scripts/activate.csh +76 -0
- .venv/Scripts/activate.fish +124 -0
- .venv/Scripts/activate.nu +117 -0
- .venv/Scripts/activate.ps1 +82 -0
- .venv/Scripts/activate_this.py +59 -0
- .venv/Scripts/deactivate.bat +39 -0
- .venv/Scripts/pydoc.bat +22 -0
- .venv/Scripts/python.exe +3 -0
- .venv/Scripts/pythonw.exe +3 -0
- .venv/pyvenv.cfg +5 -0
- ARCHITECTURE.md +815 -0
- Dockerfile +68 -0
- Dockerfile.app +80 -0
- LICENSE +21 -0
- Makefile +146 -0
- README.md +417 -7
- SETUP_GUIDE.md +371 -0
- app.py +585 -0
- bm25_index/bm25_index.pkl +3 -0
- bm25_index/documents.pkl +3 -0
- bm25_index/metadata.json +5 -0
- bm25_index/tokenized_corpus.pkl +3 -0
- chroma_db/ae1acac2-515c-4deb-b9d0-675bd72bab8b/data_level0.bin +3 -0
- chroma_db/ae1acac2-515c-4deb-b9d0-675bd72bab8b/header.bin +3 -0
- chroma_db/ae1acac2-515c-4deb-b9d0-675bd72bab8b/length.bin +3 -0
- chroma_db/ae1acac2-515c-4deb-b9d0-675bd72bab8b/link_lists.bin +0 -0
.dockerignore
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*$py.class
|
| 5 |
+
*.so
|
| 6 |
+
.Python
|
| 7 |
+
build/
|
| 8 |
+
dist/
|
| 9 |
+
*.egg-info/
|
| 10 |
+
.venv/
|
| 11 |
+
venv/
|
| 12 |
+
env/
|
| 13 |
+
|
| 14 |
+
# UV
|
| 15 |
+
.uv/
|
| 16 |
+
uv.lock
|
| 17 |
+
|
| 18 |
+
# IDE
|
| 19 |
+
.vscode/
|
| 20 |
+
.idea/
|
| 21 |
+
*.swp
|
| 22 |
+
*.swo
|
| 23 |
+
|
| 24 |
+
# Git
|
| 25 |
+
.git/
|
| 26 |
+
.gitignore
|
| 27 |
+
|
| 28 |
+
# Logs
|
| 29 |
+
*.log
|
| 30 |
+
logs/
|
| 31 |
+
|
| 32 |
+
# Vector database and indexes (will be built in container)
|
| 33 |
+
chroma_db/
|
| 34 |
+
bm25_index/
|
| 35 |
+
|
| 36 |
+
# Testing
|
| 37 |
+
.pytest_cache/
|
| 38 |
+
.coverage
|
| 39 |
+
htmlcov/
|
| 40 |
+
|
| 41 |
+
# OS
|
| 42 |
+
.DS_Store
|
| 43 |
+
Thumbs.db
|
| 44 |
+
|
| 45 |
+
# Documentation
|
| 46 |
+
*.md
|
| 47 |
+
!README_HF.md
|
| 48 |
+
docs/
|
.env.template
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Environment Configuration
|
| 2 |
+
# Copy this file to .env and fill in your values
|
| 3 |
+
|
| 4 |
+
# Application Settings
|
| 5 |
+
APP_NAME="Profile Chatbot"
|
| 6 |
+
APP_PORT=7860
|
| 7 |
+
DEBUG=false
|
| 8 |
+
|
| 9 |
+
# =============================================================================
|
| 10 |
+
# Ollama Configuration (Local LLM)
|
| 11 |
+
# =============================================================================
|
| 12 |
+
OLLAMA_BASE_URL=http://localhost:11434
|
| 13 |
+
OLLAMA_HOST=http://localhost:11434
|
| 14 |
+
|
| 15 |
+
# Model to use with Ollama (pulled automatically on first run with Docker Compose)
|
| 16 |
+
# Options: llama3.2:3b (default), phi3:mini, gemma2:2b, llama3.2:1b (smaller)
|
| 17 |
+
OLLAMA_MODEL=llama3.2:3b
|
| 18 |
+
|
| 19 |
+
# =============================================================================
|
| 20 |
+
# Groq API Configuration (Cloud LLM - Optional)
|
| 21 |
+
# =============================================================================
|
| 22 |
+
# If set, Groq will be used as the default LLM provider instead of Ollama
|
| 23 |
+
# Get your API key at: https://console.groq.com/
|
| 24 |
+
GROQ_API_KEY=
|
| 25 |
+
|
| 26 |
+
# Optional: Other API Keys for alternative LLM providers
|
| 27 |
+
# OPENAI_API_KEY=your_openai_api_key_here
|
| 28 |
+
# ANTHROPIC_API_KEY=your_anthropic_api_key_here
|
| 29 |
+
|
| 30 |
+
# =============================================================================
|
| 31 |
+
# Storage Configuration
|
| 32 |
+
# =============================================================================
|
| 33 |
+
# Vector Database
|
| 34 |
+
CHROMA_PERSIST_DIR=./chroma_db
|
| 35 |
+
|
| 36 |
+
# Document Processing
|
| 37 |
+
DOCUMENTS_DIR=./data/documents
|
| 38 |
+
|
| 39 |
+
# =============================================================================
|
| 40 |
+
# Logging & Debugging
|
| 41 |
+
# =============================================================================
|
| 42 |
+
LOG_LEVEL=INFO
|
| 43 |
+
|
| 44 |
+
# LangSmith Tracing (get API key at https://smith.langchain.com)
|
| 45 |
+
# Set LANGCHAIN_TRACING_V2=true to enable tracing
|
| 46 |
+
LANGCHAIN_TRACING_V2=false
|
| 47 |
+
LANGCHAIN_ENDPOINT=https://api.smith.langchain.com
|
| 48 |
+
LANGCHAIN_API_KEY=
|
| 49 |
+
LANGCHAIN_PROJECT=profillybot
|
| 50 |
+
|
| 51 |
+
# =============================================================================
|
| 52 |
+
# Hugging Face Spaces (Optional - auto-set by HF Spaces)
|
| 53 |
+
# =============================================================================
|
| 54 |
+
# SPACE_ID=your_space_id
|
| 55 |
+
# SPACE_AUTHOR_NAME=your_username
|
.gitattributes
CHANGED
|
@@ -33,3 +33,11 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
.venv/Scripts/python.exe filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
.venv/Scripts/pythonw.exe filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
chroma_db/chroma.sqlite3 filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
data/documents/MY_CV_2_0.pdf filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
docs/arch_system_design.png filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
docs/banner_image.png filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
docs/MY_CV_2_0.pdf filter=lfs diff=lfs merge=lfs -text
|
| 43 |
+
docs/screenshot-example.png filter=lfs diff=lfs merge=lfs -text
|
.github/workflows/README.md
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# GitHub Actions Workflows
|
| 2 |
+
|
| 3 |
+
This directory contains automated CI/CD workflows for the ProfillyBot project.
|
| 4 |
+
|
| 5 |
+

|
| 6 |
+

|
| 7 |
+

|
| 8 |
+

|
| 9 |
+

|
| 10 |
+

|
| 11 |
+

|
| 12 |
+
[](https://codecov.io/gh/Tin-Hoang/slm-profile-rag)
|
| 13 |
+
|
| 14 |
+
## Workflows Overview
|
| 15 |
+
|
| 16 |
+
### 🧪 test-python3XX.yml - Unit Tests per Python Version
|
| 17 |
+
**Triggers:** Push/PR to main, master, develop branches; Manual dispatch
|
| 18 |
+
|
| 19 |
+
**Purpose:** Runs comprehensive unit tests for specific Python version (separate badges!)
|
| 20 |
+
|
| 21 |
+
**Workflows:**
|
| 22 |
+
- **test-python310.yml**: Tests on Python 3.10
|
| 23 |
+
- **test-python311.yml**: Tests on Python 3.11 (includes Codecov upload)
|
| 24 |
+
- **test-python312.yml**: Tests on Python 3.12
|
| 25 |
+
|
| 26 |
+
**Implementation:**
|
| 27 |
+
- Each workflow calls `test-python-reusable.yml` (DRY principle)
|
| 28 |
+
- Installs dependencies
|
| 29 |
+
- Runs ruff linting and formatting checks
|
| 30 |
+
- Executes pytest with coverage reporting
|
| 31 |
+
- Provides separate badge per Python version
|
| 32 |
+
|
| 33 |
+
### 🐳 test-docker.yml - Docker Build Test
|
| 34 |
+
**Triggers:** Push/PR to main, master, develop branches; Manual dispatch
|
| 35 |
+
|
| 36 |
+
**Purpose:** Validates Docker image build
|
| 37 |
+
|
| 38 |
+
**Jobs:**
|
| 39 |
+
- Builds Docker image to ensure Dockerfile is valid
|
| 40 |
+
- Uses caching for faster builds
|
| 41 |
+
|
| 42 |
+
### 🔍 lint.yml - Linting (Existing)
|
| 43 |
+
**Triggers:** Push/PR to main, develop branches
|
| 44 |
+
|
| 45 |
+
**Purpose:** Fast linting checks using ruff
|
| 46 |
+
|
| 47 |
+
**Jobs:**
|
| 48 |
+
- Runs ruff check on all Python files
|
| 49 |
+
- Runs ruff format check
|
| 50 |
+
- Uses UV for fast dependency installation
|
| 51 |
+
|
| 52 |
+
### 📊 code-quality.yml - Code Quality Analysis
|
| 53 |
+
**Triggers:** Push/PR to main, master, develop branches
|
| 54 |
+
|
| 55 |
+
**Purpose:** Advanced code quality checks
|
| 56 |
+
|
| 57 |
+
**Jobs:**
|
| 58 |
+
- Python syntax validation
|
| 59 |
+
- Code complexity analysis using radon
|
| 60 |
+
- Maintainability index calculation
|
| 61 |
+
|
| 62 |
+
### ✅ pre-commit.yml - Pre-commit Hooks
|
| 63 |
+
**Triggers:** Push/PR to main, master, develop branches
|
| 64 |
+
|
| 65 |
+
**Purpose:** Validates pre-commit hooks configuration
|
| 66 |
+
|
| 67 |
+
**Jobs:**
|
| 68 |
+
- Runs all pre-commit hooks defined in `.pre-commit-config.yaml`
|
| 69 |
+
- Ensures code formatting and quality standards
|
| 70 |
+
|
| 71 |
+
### 📦 dependency-review.yml - Dependency Security
|
| 72 |
+
**Triggers:** Pull requests only
|
| 73 |
+
|
| 74 |
+
**Purpose:** Reviews dependency changes for security issues
|
| 75 |
+
|
| 76 |
+
**Jobs:**
|
| 77 |
+
- Scans for vulnerable dependencies
|
| 78 |
+
- Checks for problematic licenses (GPL-3.0, AGPL-3.0)
|
| 79 |
+
- Fails on moderate or higher severity vulnerabilities
|
| 80 |
+
|
| 81 |
+
### 🔒 security-scan.yml - Security Scanning
|
| 82 |
+
**Triggers:** Push/PR to main, master, develop branches; Weekly schedule (Mondays)
|
| 83 |
+
|
| 84 |
+
**Purpose:** Comprehensive security vulnerability scanning
|
| 85 |
+
|
| 86 |
+
**Jobs:**
|
| 87 |
+
- **Safety**: Scans Python dependencies for known vulnerabilities
|
| 88 |
+
- **Bandit**: Static security analysis of Python code
|
| 89 |
+
- **pip-audit**: Audits Python packages for known vulnerabilities
|
| 90 |
+
- Uploads security reports as artifacts
|
| 91 |
+
|
| 92 |
+
## Local Development
|
| 93 |
+
|
| 94 |
+
### Running Tests Locally
|
| 95 |
+
|
| 96 |
+
```bash
|
| 97 |
+
# Install development dependencies
|
| 98 |
+
pip install -e ".[dev]"
|
| 99 |
+
|
| 100 |
+
# Run all tests
|
| 101 |
+
pytest tests/ -v
|
| 102 |
+
|
| 103 |
+
# Run tests with coverage
|
| 104 |
+
pytest tests/ -v --cov=src --cov-report=term-missing --cov-report=html
|
| 105 |
+
|
| 106 |
+
# Run specific test file
|
| 107 |
+
pytest tests/test_main_document_loader.py -v
|
| 108 |
+
```
|
| 109 |
+
|
| 110 |
+
### Running Linting Locally
|
| 111 |
+
|
| 112 |
+
```bash
|
| 113 |
+
# Install ruff
|
| 114 |
+
pip install ruff
|
| 115 |
+
|
| 116 |
+
# Check code
|
| 117 |
+
ruff check src tests app.py
|
| 118 |
+
|
| 119 |
+
# Format code
|
| 120 |
+
ruff format src tests app.py
|
| 121 |
+
```
|
| 122 |
+
|
| 123 |
+
### Running Pre-commit Hooks Locally
|
| 124 |
+
|
| 125 |
+
```bash
|
| 126 |
+
# Install pre-commit
|
| 127 |
+
pip install pre-commit
|
| 128 |
+
|
| 129 |
+
# Install hooks
|
| 130 |
+
pre-commit install
|
| 131 |
+
|
| 132 |
+
# Run on all files
|
| 133 |
+
pre-commit run --all-files
|
| 134 |
+
```
|
| 135 |
+
|
| 136 |
+
### Running Security Scans Locally
|
| 137 |
+
|
| 138 |
+
```bash
|
| 139 |
+
# Install security tools
|
| 140 |
+
pip install safety bandit pip-audit
|
| 141 |
+
|
| 142 |
+
# Run safety check
|
| 143 |
+
safety check
|
| 144 |
+
|
| 145 |
+
# Run bandit
|
| 146 |
+
bandit -r src/
|
| 147 |
+
|
| 148 |
+
# Run pip-audit
|
| 149 |
+
pip-audit
|
| 150 |
+
```
|
| 151 |
+
|
| 152 |
+
## Continuous Integration Matrix
|
| 153 |
+
|
| 154 |
+
| Workflow | Python Versions | OS | Caching | Coverage |
|
| 155 |
+
|----------|----------------|-----|---------|----------|
|
| 156 |
+
| test-python310.yml | 3.10 | Ubuntu | ✅ pip | ❌ |
|
| 157 |
+
| test-python311.yml | 3.11 | Ubuntu | ✅ pip | ✅ Codecov |
|
| 158 |
+
| test-python312.yml | 3.12 | Ubuntu | ✅ pip | ❌ |
|
| 159 |
+
| test-docker.yml | N/A | Ubuntu | ✅ Docker | ❌ |
|
| 160 |
+
| lint.yml | 3.11 | Ubuntu | ✅ UV | ❌ |
|
| 161 |
+
| code-quality.yml | 3.11 | Ubuntu | ✅ pip | ❌ |
|
| 162 |
+
| pre-commit.yml | 3.11 | Ubuntu | ✅ pre-commit | ❌ |
|
| 163 |
+
| security-scan.yml | 3.11 | Ubuntu | ✅ pip | ❌ |
|
| 164 |
+
|
| 165 |
+
## Troubleshooting
|
| 166 |
+
|
| 167 |
+
### Tests Failing Locally but Passing in CI (or vice versa)
|
| 168 |
+
|
| 169 |
+
1. Ensure you're using the correct Python version
|
| 170 |
+
2. Clear pytest cache: `pytest --cache-clear`
|
| 171 |
+
3. Reinstall dependencies: `pip install -e ".[dev]" --force-reinstall`
|
| 172 |
+
|
| 173 |
+
### Codecov Upload Issues
|
| 174 |
+
|
| 175 |
+
1. Ensure `CODECOV_TOKEN` is set in repository secrets
|
| 176 |
+
2. Check coverage.xml is generated: `pytest tests/ --cov=src --cov-report=xml`
|
| 177 |
+
|
| 178 |
+
### Docker Build Failures
|
| 179 |
+
|
| 180 |
+
1. Test locally: `docker build -t profillybot:test .`
|
| 181 |
+
2. Check Dockerfile syntax
|
| 182 |
+
3. Ensure all required files are present
|
| 183 |
+
|
| 184 |
+
## Contributing
|
| 185 |
+
|
| 186 |
+
When contributing to this project:
|
| 187 |
+
|
| 188 |
+
1. Ensure all workflows pass before submitting a PR
|
| 189 |
+
2. Add tests for new features
|
| 190 |
+
3. Run pre-commit hooks locally
|
| 191 |
+
4. Update this README if adding new workflows
|
| 192 |
+
|
| 193 |
+
## Maintenance
|
| 194 |
+
|
| 195 |
+
### Updating Dependencies
|
| 196 |
+
|
| 197 |
+
- GitHub Actions are set to use latest stable versions (v4, v5)
|
| 198 |
+
- Review and update action versions quarterly
|
| 199 |
+
- Monitor Dependabot alerts for security updates
|
| 200 |
+
|
| 201 |
+
### Adding New Workflows
|
| 202 |
+
|
| 203 |
+
1. Create a new `.yml` file in this directory
|
| 204 |
+
2. Follow the naming convention: `<purpose>.yml`
|
| 205 |
+
3. Add appropriate triggers and jobs
|
| 206 |
+
4. Update this README with workflow documentation
|
| 207 |
+
5. Add status badge to main README.md
|
.github/workflows/code-quality.yml
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Code Quality
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches: [ main, master, develop ]
|
| 6 |
+
pull_request:
|
| 7 |
+
branches: [ main, master, develop ]
|
| 8 |
+
|
| 9 |
+
jobs:
|
| 10 |
+
type-check:
|
| 11 |
+
name: Type Checking & Complexity Analysis
|
| 12 |
+
runs-on: ubuntu-latest
|
| 13 |
+
|
| 14 |
+
steps:
|
| 15 |
+
- name: Checkout code
|
| 16 |
+
uses: actions/checkout@v4
|
| 17 |
+
|
| 18 |
+
- name: Set up Python
|
| 19 |
+
uses: actions/setup-python@v5
|
| 20 |
+
with:
|
| 21 |
+
python-version: "3.11"
|
| 22 |
+
cache: 'pip'
|
| 23 |
+
|
| 24 |
+
- name: Install dependencies
|
| 25 |
+
run: |
|
| 26 |
+
python -m pip install --upgrade pip
|
| 27 |
+
pip install mypy radon
|
| 28 |
+
|
| 29 |
+
- name: Check for Python syntax errors
|
| 30 |
+
run: |
|
| 31 |
+
python -m py_compile app.py
|
| 32 |
+
find src tests -name "*.py" -exec python -m py_compile {} \;
|
| 33 |
+
|
| 34 |
+
- name: Run complexity analysis
|
| 35 |
+
continue-on-error: true
|
| 36 |
+
run: |
|
| 37 |
+
radon cc src tests app.py -a -s
|
| 38 |
+
radon mi src tests app.py -s
|
.github/workflows/dependency-review.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Dependency Review
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
pull_request:
|
| 5 |
+
branches: [ main, master, develop ]
|
| 6 |
+
|
| 7 |
+
permissions:
|
| 8 |
+
contents: read
|
| 9 |
+
|
| 10 |
+
jobs:
|
| 11 |
+
dependency-review:
|
| 12 |
+
name: Review Dependencies
|
| 13 |
+
runs-on: ubuntu-latest
|
| 14 |
+
steps:
|
| 15 |
+
- name: Checkout code
|
| 16 |
+
uses: actions/checkout@v4
|
| 17 |
+
|
| 18 |
+
- name: Dependency Review
|
| 19 |
+
uses: actions/dependency-review-action@v4
|
| 20 |
+
with:
|
| 21 |
+
fail-on-severity: moderate
|
| 22 |
+
deny-licenses: GPL-3.0, AGPL-3.0
|
.github/workflows/lint.yml
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Lint
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches: [ main, develop ]
|
| 6 |
+
pull_request:
|
| 7 |
+
branches: [ main, develop ]
|
| 8 |
+
|
| 9 |
+
jobs:
|
| 10 |
+
lint:
|
| 11 |
+
runs-on: ubuntu-latest
|
| 12 |
+
steps:
|
| 13 |
+
- uses: actions/checkout@v4
|
| 14 |
+
|
| 15 |
+
- name: Set up Python
|
| 16 |
+
uses: actions/setup-python@v5
|
| 17 |
+
with:
|
| 18 |
+
python-version: '3.11'
|
| 19 |
+
|
| 20 |
+
- name: Install UV
|
| 21 |
+
run: curl -LsSf https://astral.sh/uv/install.sh | sh
|
| 22 |
+
|
| 23 |
+
- name: Install dependencies
|
| 24 |
+
run: |
|
| 25 |
+
uv venv
|
| 26 |
+
source .venv/bin/activate
|
| 27 |
+
uv pip install -r requirements.txt
|
| 28 |
+
uv pip install "ruff>=0.14.6,<0.15.0" pytest pytest-cov
|
| 29 |
+
|
| 30 |
+
- name: Run Ruff Check
|
| 31 |
+
run: |
|
| 32 |
+
source .venv/bin/activate
|
| 33 |
+
ruff check .
|
| 34 |
+
|
| 35 |
+
- name: Run Ruff Format Check
|
| 36 |
+
run: |
|
| 37 |
+
source .venv/bin/activate
|
| 38 |
+
ruff format --check .
|
.github/workflows/pre-commit.yml
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Pre-commit Checks
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches: [ main, master, develop ]
|
| 6 |
+
pull_request:
|
| 7 |
+
branches: [ main, master, develop ]
|
| 8 |
+
|
| 9 |
+
jobs:
|
| 10 |
+
pre-commit:
|
| 11 |
+
name: Run Pre-commit Hooks
|
| 12 |
+
runs-on: ubuntu-latest
|
| 13 |
+
|
| 14 |
+
steps:
|
| 15 |
+
- name: Checkout code
|
| 16 |
+
uses: actions/checkout@v4
|
| 17 |
+
|
| 18 |
+
- name: Set up Python
|
| 19 |
+
uses: actions/setup-python@v5
|
| 20 |
+
with:
|
| 21 |
+
python-version: "3.10"
|
| 22 |
+
|
| 23 |
+
- name: Install pre-commit
|
| 24 |
+
run: |
|
| 25 |
+
python -m pip install --upgrade pip
|
| 26 |
+
pip install pre-commit
|
| 27 |
+
|
| 28 |
+
- name: Cache pre-commit hooks
|
| 29 |
+
uses: actions/cache@v4
|
| 30 |
+
with:
|
| 31 |
+
path: ~/.cache/pre-commit
|
| 32 |
+
key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
|
| 33 |
+
restore-keys: |
|
| 34 |
+
pre-commit-
|
| 35 |
+
|
| 36 |
+
- name: Run pre-commit on all files
|
| 37 |
+
run: |
|
| 38 |
+
pre-commit run --all-files --show-diff-on-failure
|
.github/workflows/security-scan.yml
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Security Scan
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches: [ main, master, develop ]
|
| 6 |
+
pull_request:
|
| 7 |
+
branches: [ main, master, develop ]
|
| 8 |
+
schedule:
|
| 9 |
+
# Run weekly on Monday at 00:00 UTC
|
| 10 |
+
- cron: '0 0 * * 1'
|
| 11 |
+
|
| 12 |
+
jobs:
|
| 13 |
+
security-scan:
|
| 14 |
+
name: Security Vulnerability Scan
|
| 15 |
+
runs-on: ubuntu-latest
|
| 16 |
+
|
| 17 |
+
steps:
|
| 18 |
+
- name: Checkout code
|
| 19 |
+
uses: actions/checkout@v4
|
| 20 |
+
|
| 21 |
+
- name: Set up Python
|
| 22 |
+
uses: actions/setup-python@v5
|
| 23 |
+
with:
|
| 24 |
+
python-version: "3.11"
|
| 25 |
+
cache: 'pip'
|
| 26 |
+
|
| 27 |
+
- name: Install dependencies
|
| 28 |
+
run: |
|
| 29 |
+
python -m pip install --upgrade pip
|
| 30 |
+
pip install safety bandit pip-audit
|
| 31 |
+
|
| 32 |
+
- name: Run Safety check
|
| 33 |
+
continue-on-error: true
|
| 34 |
+
run: |
|
| 35 |
+
pip install -e .
|
| 36 |
+
safety check --json || true
|
| 37 |
+
|
| 38 |
+
- name: Run Bandit security scan
|
| 39 |
+
continue-on-error: true
|
| 40 |
+
run: |
|
| 41 |
+
bandit -r src/ -f json -o bandit-report.json || true
|
| 42 |
+
bandit -r src/ -f txt || true
|
| 43 |
+
|
| 44 |
+
- name: Run pip-audit
|
| 45 |
+
continue-on-error: true
|
| 46 |
+
run: |
|
| 47 |
+
pip-audit --desc || true
|
| 48 |
+
|
| 49 |
+
- name: Upload Bandit report
|
| 50 |
+
if: always()
|
| 51 |
+
uses: actions/upload-artifact@v4
|
| 52 |
+
with:
|
| 53 |
+
name: bandit-security-report
|
| 54 |
+
path: bandit-report.json
|
| 55 |
+
if-no-files-found: ignore
|
.github/workflows/test-docker.yml
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Docker Build Test
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches: [ main, master, develop ]
|
| 6 |
+
pull_request:
|
| 7 |
+
branches: [ main, master, develop ]
|
| 8 |
+
workflow_dispatch:
|
| 9 |
+
|
| 10 |
+
jobs:
|
| 11 |
+
test-docker:
|
| 12 |
+
name: Test Docker Build
|
| 13 |
+
runs-on: ubuntu-latest
|
| 14 |
+
steps:
|
| 15 |
+
- name: Checkout code
|
| 16 |
+
uses: actions/checkout@v4
|
| 17 |
+
|
| 18 |
+
- name: Set up Docker Buildx
|
| 19 |
+
uses: docker/setup-buildx-action@v3
|
| 20 |
+
|
| 21 |
+
- name: Build Docker image
|
| 22 |
+
uses: docker/build-push-action@v5
|
| 23 |
+
with:
|
| 24 |
+
context: .
|
| 25 |
+
push: false
|
| 26 |
+
tags: profillybot:test
|
| 27 |
+
cache-from: type=gha
|
| 28 |
+
cache-to: type=gha,mode=max
|
.github/workflows/test-python-reusable.yml
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Test Python (Reusable)
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
workflow_call:
|
| 5 |
+
inputs:
|
| 6 |
+
python-version:
|
| 7 |
+
required: true
|
| 8 |
+
type: string
|
| 9 |
+
secrets:
|
| 10 |
+
CODECOV_TOKEN:
|
| 11 |
+
required: false
|
| 12 |
+
|
| 13 |
+
jobs:
|
| 14 |
+
test:
|
| 15 |
+
name: Test on Python ${{ inputs.python-version }}
|
| 16 |
+
runs-on: ubuntu-latest
|
| 17 |
+
steps:
|
| 18 |
+
- name: Checkout code
|
| 19 |
+
uses: actions/checkout@v4
|
| 20 |
+
|
| 21 |
+
- name: Set up Python ${{ inputs.python-version }}
|
| 22 |
+
uses: actions/setup-python@v5
|
| 23 |
+
with:
|
| 24 |
+
python-version: ${{ inputs.python-version }}
|
| 25 |
+
cache: 'pip'
|
| 26 |
+
|
| 27 |
+
- name: Install dependencies
|
| 28 |
+
run: |
|
| 29 |
+
python -m pip install --upgrade pip
|
| 30 |
+
pip install -e ".[dev]"
|
| 31 |
+
|
| 32 |
+
- name: Run linting with ruff
|
| 33 |
+
run: |
|
| 34 |
+
ruff check src tests
|
| 35 |
+
ruff format --check src tests
|
| 36 |
+
|
| 37 |
+
- name: Run tests with pytest
|
| 38 |
+
run: |
|
| 39 |
+
pytest tests/ -v --cov=src --cov-report=term-missing --cov-report=xml
|
| 40 |
+
|
| 41 |
+
- name: Upload coverage to Codecov
|
| 42 |
+
if: inputs.python-version == '3.11'
|
| 43 |
+
uses: codecov/codecov-action@v4
|
| 44 |
+
with:
|
| 45 |
+
file: ./coverage.xml
|
| 46 |
+
token: ${{ secrets.CODECOV_TOKEN }}
|
| 47 |
+
fail_ci_if_error: false
|
| 48 |
+
verbose: true
|
.github/workflows/test-python310.yml
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Tests Python 3.10
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches: [ main, master, develop ]
|
| 6 |
+
pull_request:
|
| 7 |
+
branches: [ main, master, develop ]
|
| 8 |
+
workflow_dispatch:
|
| 9 |
+
|
| 10 |
+
jobs:
|
| 11 |
+
test:
|
| 12 |
+
uses: ./.github/workflows/test-python-reusable.yml
|
| 13 |
+
with:
|
| 14 |
+
python-version: "3.10"
|
.github/workflows/test-python311.yml
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Tests Python 3.11
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches: [ main, master, develop ]
|
| 6 |
+
pull_request:
|
| 7 |
+
branches: [ main, master, develop ]
|
| 8 |
+
workflow_dispatch:
|
| 9 |
+
|
| 10 |
+
jobs:
|
| 11 |
+
test:
|
| 12 |
+
uses: ./.github/workflows/test-python-reusable.yml
|
| 13 |
+
with:
|
| 14 |
+
python-version: "3.11"
|
| 15 |
+
secrets: inherit
|
.github/workflows/test-python312.yml
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Tests Python 3.12
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches: [ main, master, develop ]
|
| 6 |
+
pull_request:
|
| 7 |
+
branches: [ main, master, develop ]
|
| 8 |
+
workflow_dispatch:
|
| 9 |
+
|
| 10 |
+
jobs:
|
| 11 |
+
test:
|
| 12 |
+
uses: ./.github/workflows/test-python-reusable.yml
|
| 13 |
+
with:
|
| 14 |
+
python-version: "3.12"
|
.gitignore
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Byte-compiled / optimized / DLL files
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[codz]
|
| 4 |
+
*$py.class
|
| 5 |
+
|
| 6 |
+
# C extensions
|
| 7 |
+
*.so
|
| 8 |
+
|
| 9 |
+
# Distribution / packaging
|
| 10 |
+
.Python
|
| 11 |
+
build/
|
| 12 |
+
develop-eggs/
|
| 13 |
+
dist/
|
| 14 |
+
downloads/
|
| 15 |
+
eggs/
|
| 16 |
+
.eggs/
|
| 17 |
+
lib/
|
| 18 |
+
lib64/
|
| 19 |
+
parts/
|
| 20 |
+
sdist/
|
| 21 |
+
var/
|
| 22 |
+
wheels/
|
| 23 |
+
share/python-wheels/
|
| 24 |
+
*.egg-info/
|
| 25 |
+
.installed.cfg
|
| 26 |
+
*.egg
|
| 27 |
+
MANIFEST
|
| 28 |
+
|
| 29 |
+
# PyInstaller
|
| 30 |
+
# Usually these files are written by a python script from a template
|
| 31 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
| 32 |
+
*.manifest
|
| 33 |
+
*.spec
|
| 34 |
+
|
| 35 |
+
# Installer logs
|
| 36 |
+
pip-log.txt
|
| 37 |
+
pip-delete-this-directory.txt
|
| 38 |
+
|
| 39 |
+
# Unit test / coverage reports
|
| 40 |
+
htmlcov/
|
| 41 |
+
.tox/
|
| 42 |
+
.nox/
|
| 43 |
+
.coverage
|
| 44 |
+
.coverage.*
|
| 45 |
+
.cache
|
| 46 |
+
nosetests.xml
|
| 47 |
+
coverage.xml
|
| 48 |
+
*.cover
|
| 49 |
+
*.py.cover
|
| 50 |
+
.hypothesis/
|
| 51 |
+
.pytest_cache/
|
| 52 |
+
cover/
|
| 53 |
+
|
| 54 |
+
# Translations
|
| 55 |
+
*.mo
|
| 56 |
+
*.pot
|
| 57 |
+
|
| 58 |
+
# Django stuff:
|
| 59 |
+
*.log
|
| 60 |
+
local_settings.py
|
| 61 |
+
db.sqlite3
|
| 62 |
+
db.sqlite3-journal
|
| 63 |
+
|
| 64 |
+
# Flask stuff:
|
| 65 |
+
instance/
|
| 66 |
+
.webassets-cache
|
| 67 |
+
|
| 68 |
+
# Scrapy stuff:
|
| 69 |
+
.scrapy
|
| 70 |
+
|
| 71 |
+
# Sphinx documentation
|
| 72 |
+
docs/_build/
|
| 73 |
+
|
| 74 |
+
# PyBuilder
|
| 75 |
+
.pybuilder/
|
| 76 |
+
target/
|
| 77 |
+
|
| 78 |
+
# Jupyter Notebook
|
| 79 |
+
.ipynb_checkpoints
|
| 80 |
+
|
| 81 |
+
# IPython
|
| 82 |
+
profile_default/
|
| 83 |
+
ipython_config.py
|
| 84 |
+
|
| 85 |
+
# pyenv
|
| 86 |
+
# For a library or package, you might want to ignore these files since the code is
|
| 87 |
+
# intended to run in multiple environments; otherwise, check them in:
|
| 88 |
+
# .python-version
|
| 89 |
+
|
| 90 |
+
# pipenv
|
| 91 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
| 92 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
| 93 |
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
| 94 |
+
# install all needed dependencies.
|
| 95 |
+
#Pipfile.lock
|
| 96 |
+
|
| 97 |
+
# UV
|
| 98 |
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
| 99 |
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
| 100 |
+
# commonly ignored for libraries.
|
| 101 |
+
#uv.lock
|
| 102 |
+
|
| 103 |
+
# poetry
|
| 104 |
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
| 105 |
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
| 106 |
+
# commonly ignored for libraries.
|
| 107 |
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
| 108 |
+
#poetry.lock
|
| 109 |
+
#poetry.toml
|
| 110 |
+
|
| 111 |
+
# pdm
|
| 112 |
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
| 113 |
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
| 114 |
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
| 115 |
+
#pdm.lock
|
| 116 |
+
#pdm.toml
|
| 117 |
+
.pdm-python
|
| 118 |
+
.pdm-build/
|
| 119 |
+
|
| 120 |
+
# pixi
|
| 121 |
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
| 122 |
+
#pixi.lock
|
| 123 |
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
| 124 |
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
| 125 |
+
.pixi
|
| 126 |
+
|
| 127 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
| 128 |
+
__pypackages__/
|
| 129 |
+
|
| 130 |
+
# Celery stuff
|
| 131 |
+
celerybeat-schedule
|
| 132 |
+
celerybeat.pid
|
| 133 |
+
|
| 134 |
+
# SageMath parsed files
|
| 135 |
+
*.sage.py
|
| 136 |
+
|
| 137 |
+
# Environments
|
| 138 |
+
.env
|
| 139 |
+
.envrc
|
| 140 |
+
.venv
|
| 141 |
+
env/
|
| 142 |
+
venv/
|
| 143 |
+
ENV/
|
| 144 |
+
env.bak/
|
| 145 |
+
venv.bak/
|
| 146 |
+
|
| 147 |
+
# Spyder project settings
|
| 148 |
+
.spyderproject
|
| 149 |
+
.spyproject
|
| 150 |
+
|
| 151 |
+
# Rope project settings
|
| 152 |
+
.ropeproject
|
| 153 |
+
|
| 154 |
+
# mkdocs documentation
|
| 155 |
+
/site
|
| 156 |
+
|
| 157 |
+
# mypy
|
| 158 |
+
.mypy_cache/
|
| 159 |
+
.dmypy.json
|
| 160 |
+
dmypy.json
|
| 161 |
+
|
| 162 |
+
# Pyre type checker
|
| 163 |
+
.pyre/
|
| 164 |
+
|
| 165 |
+
# pytype static type analyzer
|
| 166 |
+
.pytype/
|
| 167 |
+
|
| 168 |
+
# Cython debug symbols
|
| 169 |
+
cython_debug/
|
| 170 |
+
|
| 171 |
+
# PyCharm
|
| 172 |
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
| 173 |
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
| 174 |
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
| 175 |
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
| 176 |
+
#.idea/
|
| 177 |
+
|
| 178 |
+
# Abstra
|
| 179 |
+
# Abstra is an AI-powered process automation framework.
|
| 180 |
+
# Ignore directories containing user credentials, local state, and settings.
|
| 181 |
+
# Learn more at https://abstra.io/docs
|
| 182 |
+
.abstra/
|
| 183 |
+
|
| 184 |
+
# Visual Studio Code
|
| 185 |
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
| 186 |
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
| 187 |
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
| 188 |
+
# you could uncomment the following to ignore the entire vscode folder
|
| 189 |
+
# .vscode/
|
| 190 |
+
|
| 191 |
+
# Ruff stuff:
|
| 192 |
+
.ruff_cache/
|
| 193 |
+
|
| 194 |
+
# PyPI configuration file
|
| 195 |
+
.pypirc
|
| 196 |
+
|
| 197 |
+
# Cursor
|
| 198 |
+
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
| 199 |
+
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
| 200 |
+
# refer to https://docs.cursor.com/context/ignore-files
|
| 201 |
+
.cursorignore
|
| 202 |
+
.cursorindexingignore
|
| 203 |
+
|
| 204 |
+
# Marimo
|
| 205 |
+
marimo/_static/
|
| 206 |
+
marimo/_lsp/
|
| 207 |
+
__marimo__/
|
.hfignore
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.venv/
|
| 2 |
+
venv/
|
| 3 |
+
.env
|
| 4 |
+
.env.*
|
| 5 |
+
.git/
|
| 6 |
+
__pycache__/
|
| 7 |
+
**/__pycache__/
|
| 8 |
+
*.pyc
|
| 9 |
+
.pytest_cache/
|
| 10 |
+
.ruff_cache/
|
| 11 |
+
htmlcov/
|
| 12 |
+
.coverage
|
| 13 |
+
.cursor/
|
| 14 |
+
*.egg-info/
|
| 15 |
+
.mypy_cache/
|
| 16 |
+
_cv_preview.txt
|
| 17 |
+
.idea/
|
| 18 |
+
.vscode/
|
| 19 |
+
agent-transcripts/
|
.pre-commit-config.yaml
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# See https://pre-commit.com for more information
|
| 2 |
+
# See https://pre-commit.com/hooks.html for more hooks
|
| 3 |
+
repos:
|
| 4 |
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
| 5 |
+
rev: v4.5.0
|
| 6 |
+
hooks:
|
| 7 |
+
- id: trailing-whitespace
|
| 8 |
+
- id: end-of-file-fixer
|
| 9 |
+
- id: check-yaml
|
| 10 |
+
- id: check-toml
|
| 11 |
+
- id: check-added-large-files
|
| 12 |
+
args: ['--maxkb=6000']
|
| 13 |
+
- id: check-merge-conflict
|
| 14 |
+
- id: check-case-conflict
|
| 15 |
+
- id: check-docstring-first
|
| 16 |
+
- id: debug-statements
|
| 17 |
+
- id: mixed-line-ending
|
| 18 |
+
args: ['--fix=lf']
|
| 19 |
+
|
| 20 |
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
| 21 |
+
rev: v0.7.4
|
| 22 |
+
hooks:
|
| 23 |
+
# Run the linter
|
| 24 |
+
- id: ruff
|
| 25 |
+
args: ['--fix']
|
| 26 |
+
# Run the formatter
|
| 27 |
+
- id: ruff-format
|
| 28 |
+
|
| 29 |
+
# Uncomment to enable mypy type checking (requires adding mypy and type stubs to dev dependencies)
|
| 30 |
+
# - repo: https://github.com/pre-commit/mirrors-mypy
|
| 31 |
+
# rev: v1.11.2
|
| 32 |
+
# hooks:
|
| 33 |
+
# - id: mypy
|
| 34 |
+
# additional_dependencies: ['types-PyYAML']
|
| 35 |
+
# args: ['--ignore-missing-imports', '--no-strict-optional']
|
| 36 |
+
# exclude: '^tests/'
|
| 37 |
+
|
| 38 |
+
# Set this to allow pre-commit to work with uv or other package managers
|
| 39 |
+
default_language_version:
|
| 40 |
+
python: python3.10
|
.streamlit/config.toml
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[theme]
|
| 2 |
+
primaryColor = "#FF4B4B"
|
| 3 |
+
backgroundColor = "#FFFFFF"
|
| 4 |
+
secondaryBackgroundColor = "#F0F2F6"
|
| 5 |
+
textColor = "#262730"
|
| 6 |
+
font = "sans serif"
|
| 7 |
+
|
| 8 |
+
[server]
|
| 9 |
+
headless = true
|
| 10 |
+
# port = 8501 # Default Streamlit port (for local dev)
|
| 11 |
+
# Note: HuggingFace Spaces overrides this to 7860 via Dockerfile
|
| 12 |
+
enableCORS = false
|
| 13 |
+
enableXsrfProtection = true
|
| 14 |
+
|
| 15 |
+
[browser]
|
| 16 |
+
gatherUsageStats = false
|
.venv/.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
*
|
.venv/CACHEDIR.TAG
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
Signature: 8a477f597d28d172789f06886806bc55
|
.venv/Lib/site-packages/_virtualenv.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:69ac3d8f27e679c81b94ab30b3b56e9cd138219b1ba94a1fa3606d5a76a1433d
|
| 3 |
+
size 18
|
.venv/Lib/site-packages/_virtualenv.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Patches that are applied at runtime to the virtual environment."""
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
import sys
|
| 5 |
+
|
| 6 |
+
VIRTUALENV_PATCH_FILE = os.path.join(__file__)
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def patch_dist(dist):
|
| 10 |
+
"""
|
| 11 |
+
Distutils allows user to configure some arguments via a configuration file:
|
| 12 |
+
https://docs.python.org/3.11/install/index.html#distutils-configuration-files.
|
| 13 |
+
|
| 14 |
+
Some of this arguments though don't make sense in context of the virtual environment files, let's fix them up.
|
| 15 |
+
""" # noqa: D205
|
| 16 |
+
# we cannot allow some install config as that would get packages installed outside of the virtual environment
|
| 17 |
+
old_parse_config_files = dist.Distribution.parse_config_files
|
| 18 |
+
|
| 19 |
+
def parse_config_files(self, *args, **kwargs):
|
| 20 |
+
result = old_parse_config_files(self, *args, **kwargs)
|
| 21 |
+
install = self.get_option_dict("install")
|
| 22 |
+
|
| 23 |
+
if "prefix" in install: # the prefix governs where to install the libraries
|
| 24 |
+
install["prefix"] = VIRTUALENV_PATCH_FILE, os.path.abspath(sys.prefix)
|
| 25 |
+
for base in ("purelib", "platlib", "headers", "scripts", "data"):
|
| 26 |
+
key = f"install_{base}"
|
| 27 |
+
if key in install: # do not allow global configs to hijack venv paths
|
| 28 |
+
install.pop(key, None)
|
| 29 |
+
return result
|
| 30 |
+
|
| 31 |
+
dist.Distribution.parse_config_files = parse_config_files
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
# Import hook that patches some modules to ignore configuration values that break package installation in case
|
| 35 |
+
# of virtual environments.
|
| 36 |
+
_DISTUTILS_PATCH = "distutils.dist", "setuptools.dist"
|
| 37 |
+
# https://docs.python.org/3/library/importlib.html#setting-up-an-importer
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class _Finder:
|
| 41 |
+
"""A meta path finder that allows patching the imported distutils modules."""
|
| 42 |
+
|
| 43 |
+
fullname = None
|
| 44 |
+
|
| 45 |
+
# lock[0] is threading.Lock(), but initialized lazily to avoid importing threading very early at startup,
|
| 46 |
+
# because there are gevent-based applications that need to be first to import threading by themselves.
|
| 47 |
+
# See https://github.com/pypa/virtualenv/issues/1895 for details.
|
| 48 |
+
lock = [] # noqa: RUF012
|
| 49 |
+
|
| 50 |
+
def find_spec(self, fullname, path, target=None): # noqa: ARG002
|
| 51 |
+
if fullname in _DISTUTILS_PATCH and self.fullname is None:
|
| 52 |
+
# initialize lock[0] lazily
|
| 53 |
+
if len(self.lock) == 0:
|
| 54 |
+
import threading
|
| 55 |
+
|
| 56 |
+
lock = threading.Lock()
|
| 57 |
+
# there is possibility that two threads T1 and T2 are simultaneously running into find_spec,
|
| 58 |
+
# observing .lock as empty, and further going into hereby initialization. However due to the GIL,
|
| 59 |
+
# list.append() operation is atomic and this way only one of the threads will "win" to put the lock
|
| 60 |
+
# - that every thread will use - into .lock[0].
|
| 61 |
+
# https://docs.python.org/3/faq/library.html#what-kinds-of-global-value-mutation-are-thread-safe
|
| 62 |
+
self.lock.append(lock)
|
| 63 |
+
|
| 64 |
+
from functools import partial
|
| 65 |
+
from importlib.util import find_spec
|
| 66 |
+
|
| 67 |
+
with self.lock[0]:
|
| 68 |
+
self.fullname = fullname
|
| 69 |
+
try:
|
| 70 |
+
spec = find_spec(fullname, path)
|
| 71 |
+
if spec is not None:
|
| 72 |
+
# https://www.python.org/dev/peps/pep-0451/#how-loading-will-work
|
| 73 |
+
is_new_api = hasattr(spec.loader, "exec_module")
|
| 74 |
+
func_name = "exec_module" if is_new_api else "load_module"
|
| 75 |
+
old = getattr(spec.loader, func_name)
|
| 76 |
+
func = self.exec_module if is_new_api else self.load_module
|
| 77 |
+
if old is not func:
|
| 78 |
+
try: # noqa: SIM105
|
| 79 |
+
setattr(spec.loader, func_name, partial(func, old))
|
| 80 |
+
except AttributeError:
|
| 81 |
+
pass # C-Extension loaders are r/o such as zipimporter with <3.7
|
| 82 |
+
return spec
|
| 83 |
+
finally:
|
| 84 |
+
self.fullname = None
|
| 85 |
+
return None
|
| 86 |
+
|
| 87 |
+
@staticmethod
|
| 88 |
+
def exec_module(old, module):
|
| 89 |
+
old(module)
|
| 90 |
+
if module.__name__ in _DISTUTILS_PATCH:
|
| 91 |
+
patch_dist(module)
|
| 92 |
+
|
| 93 |
+
@staticmethod
|
| 94 |
+
def load_module(old, name):
|
| 95 |
+
module = old(name)
|
| 96 |
+
if module.__name__ in _DISTUTILS_PATCH:
|
| 97 |
+
patch_dist(module)
|
| 98 |
+
return module
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
sys.meta_path.insert(0, _Finder())
|
.venv/Scripts/activate
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2020-202x The virtualenv developers
|
| 2 |
+
#
|
| 3 |
+
# Permission is hereby granted, free of charge, to any person obtaining
|
| 4 |
+
# a copy of this software and associated documentation files (the
|
| 5 |
+
# "Software"), to deal in the Software without restriction, including
|
| 6 |
+
# without limitation the rights to use, copy, modify, merge, publish,
|
| 7 |
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
| 8 |
+
# permit persons to whom the Software is furnished to do so, subject to
|
| 9 |
+
# the following conditions:
|
| 10 |
+
#
|
| 11 |
+
# The above copyright notice and this permission notice shall be
|
| 12 |
+
# included in all copies or substantial portions of the Software.
|
| 13 |
+
#
|
| 14 |
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
| 15 |
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 16 |
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
| 17 |
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
| 18 |
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
| 19 |
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
| 20 |
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 21 |
+
|
| 22 |
+
# This file must be used with "source bin/activate" *from bash*
|
| 23 |
+
# you cannot run it directly
|
| 24 |
+
|
| 25 |
+
# Get script path (only used if environment is relocatable).
|
| 26 |
+
if [ -n "${BASH_VERSION:+x}" ] ; then
|
| 27 |
+
SCRIPT_PATH="${BASH_SOURCE[0]}"
|
| 28 |
+
if [ "$SCRIPT_PATH" = "$0" ]; then
|
| 29 |
+
# Only bash has a reasonably robust check for source'dness.
|
| 30 |
+
echo "You must source this script: \$ source $0" >&2
|
| 31 |
+
exit 33
|
| 32 |
+
fi
|
| 33 |
+
elif [ -n "${ZSH_VERSION:+x}" ] ; then
|
| 34 |
+
SCRIPT_PATH="${(%):-%x}"
|
| 35 |
+
elif [ -n "${KSH_VERSION:+x}" ] ; then
|
| 36 |
+
SCRIPT_PATH="${.sh.file}"
|
| 37 |
+
fi
|
| 38 |
+
|
| 39 |
+
deactivate () {
|
| 40 |
+
unset -f pydoc >/dev/null 2>&1 || true
|
| 41 |
+
|
| 42 |
+
# reset old environment variables
|
| 43 |
+
# ! [ -z ${VAR+_} ] returns true if VAR is declared at all
|
| 44 |
+
if ! [ -z "${_OLD_VIRTUAL_PATH:+_}" ] ; then
|
| 45 |
+
PATH="$_OLD_VIRTUAL_PATH"
|
| 46 |
+
export PATH
|
| 47 |
+
unset _OLD_VIRTUAL_PATH
|
| 48 |
+
fi
|
| 49 |
+
if ! [ -z "${_OLD_VIRTUAL_PYTHONHOME+_}" ] ; then
|
| 50 |
+
PYTHONHOME="$_OLD_VIRTUAL_PYTHONHOME"
|
| 51 |
+
export PYTHONHOME
|
| 52 |
+
unset _OLD_VIRTUAL_PYTHONHOME
|
| 53 |
+
fi
|
| 54 |
+
|
| 55 |
+
# The hash command must be called to get it to forget past
|
| 56 |
+
# commands. Without forgetting past commands the $PATH changes
|
| 57 |
+
# we made may not be respected
|
| 58 |
+
hash -r 2>/dev/null
|
| 59 |
+
|
| 60 |
+
if ! [ -z "${_OLD_VIRTUAL_PS1+_}" ] ; then
|
| 61 |
+
PS1="$_OLD_VIRTUAL_PS1"
|
| 62 |
+
export PS1
|
| 63 |
+
unset _OLD_VIRTUAL_PS1
|
| 64 |
+
fi
|
| 65 |
+
|
| 66 |
+
unset VIRTUAL_ENV
|
| 67 |
+
unset VIRTUAL_ENV_PROMPT
|
| 68 |
+
if [ ! "${1-}" = "nondestructive" ] ; then
|
| 69 |
+
# Self destruct!
|
| 70 |
+
unset -f deactivate
|
| 71 |
+
fi
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
# unset irrelevant variables
|
| 75 |
+
deactivate nondestructive
|
| 76 |
+
|
| 77 |
+
VIRTUAL_ENV='C:\Users\Admin\Downloads\AIDevMinh\slm-profile-rag\.venv'
|
| 78 |
+
if ([ "$OSTYPE" = "cygwin" ] || [ "$OSTYPE" = "msys" ]) && $(command -v cygpath &> /dev/null) ; then
|
| 79 |
+
VIRTUAL_ENV=$(cygpath -u "$VIRTUAL_ENV")
|
| 80 |
+
fi
|
| 81 |
+
export VIRTUAL_ENV
|
| 82 |
+
|
| 83 |
+
_OLD_VIRTUAL_PATH="$PATH"
|
| 84 |
+
PATH="$VIRTUAL_ENV/Scripts:$PATH"
|
| 85 |
+
export PATH
|
| 86 |
+
|
| 87 |
+
if [ "x" != x ] ; then
|
| 88 |
+
VIRTUAL_ENV_PROMPT="() "
|
| 89 |
+
else
|
| 90 |
+
VIRTUAL_ENV_PROMPT="($(basename "$VIRTUAL_ENV")) "
|
| 91 |
+
fi
|
| 92 |
+
export VIRTUAL_ENV_PROMPT
|
| 93 |
+
|
| 94 |
+
# unset PYTHONHOME if set
|
| 95 |
+
if ! [ -z "${PYTHONHOME+_}" ] ; then
|
| 96 |
+
_OLD_VIRTUAL_PYTHONHOME="$PYTHONHOME"
|
| 97 |
+
unset PYTHONHOME
|
| 98 |
+
fi
|
| 99 |
+
|
| 100 |
+
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT-}" ] ; then
|
| 101 |
+
_OLD_VIRTUAL_PS1="${PS1-}"
|
| 102 |
+
PS1="${VIRTUAL_ENV_PROMPT}${PS1-}"
|
| 103 |
+
export PS1
|
| 104 |
+
fi
|
| 105 |
+
|
| 106 |
+
# Make sure to unalias pydoc if it's already there
|
| 107 |
+
alias pydoc 2>/dev/null >/dev/null && unalias pydoc || true
|
| 108 |
+
|
| 109 |
+
pydoc () {
|
| 110 |
+
python -m pydoc "$@"
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
# The hash command must be called to get it to forget past
|
| 114 |
+
# commands. Without forgetting past commands the $PATH changes
|
| 115 |
+
# we made may not be respected
|
| 116 |
+
hash -r 2>/dev/null
|
.venv/Scripts/activate.bat
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@REM Copyright (c) 2020-202x The virtualenv developers
|
| 2 |
+
@REM
|
| 3 |
+
@REM Permission is hereby granted, free of charge, to any person obtaining
|
| 4 |
+
@REM a copy of this software and associated documentation files (the
|
| 5 |
+
@REM "Software"), to deal in the Software without restriction, including
|
| 6 |
+
@REM without limitation the rights to use, copy, modify, merge, publish,
|
| 7 |
+
@REM distribute, sublicense, and/or sell copies of the Software, and to
|
| 8 |
+
@REM permit persons to whom the Software is furnished to do so, subject to
|
| 9 |
+
@REM the following conditions:
|
| 10 |
+
@REM
|
| 11 |
+
@REM The above copyright notice and this permission notice shall be
|
| 12 |
+
@REM included in all copies or substantial portions of the Software.
|
| 13 |
+
@REM
|
| 14 |
+
@REM THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
| 15 |
+
@REM EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 16 |
+
@REM MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
| 17 |
+
@REM NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
| 18 |
+
@REM LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
| 19 |
+
@REM OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
| 20 |
+
@REM WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 21 |
+
|
| 22 |
+
@REM This file is UTF-8 encoded, so we need to update the current code page while executing it
|
| 23 |
+
@for /f "tokens=2 delims=:." %%a in ('"%SystemRoot%\System32\chcp.com"') do @set _OLD_CODEPAGE=%%a
|
| 24 |
+
@if defined _OLD_CODEPAGE (
|
| 25 |
+
@"%SystemRoot%\System32\chcp.com" 65001 > nul
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
@for %%i in ("C:\Users\Admin\Downloads\AIDevMinh\slm-profile-rag\.venv") do @set "VIRTUAL_ENV=%%~fi"
|
| 29 |
+
|
| 30 |
+
@set "VIRTUAL_ENV_PROMPT="
|
| 31 |
+
@if NOT DEFINED VIRTUAL_ENV_PROMPT (
|
| 32 |
+
@for %%d in ("%VIRTUAL_ENV%") do @set "VIRTUAL_ENV_PROMPT=%%~nxd"
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
@if defined _OLD_VIRTUAL_PROMPT (
|
| 36 |
+
@set "PROMPT=%_OLD_VIRTUAL_PROMPT%"
|
| 37 |
+
) else (
|
| 38 |
+
@if not defined PROMPT (
|
| 39 |
+
@set "PROMPT=$P$G"
|
| 40 |
+
)
|
| 41 |
+
@if not defined VIRTUAL_ENV_DISABLE_PROMPT (
|
| 42 |
+
@set "_OLD_VIRTUAL_PROMPT=%PROMPT%"
|
| 43 |
+
)
|
| 44 |
+
)
|
| 45 |
+
@if not defined VIRTUAL_ENV_DISABLE_PROMPT (
|
| 46 |
+
@set "PROMPT=(%VIRTUAL_ENV_PROMPT%) %PROMPT%"
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
@REM Don't use () to avoid problems with them in %PATH%
|
| 50 |
+
@if defined _OLD_VIRTUAL_PYTHONHOME @goto ENDIFVHOME
|
| 51 |
+
@set "_OLD_VIRTUAL_PYTHONHOME=%PYTHONHOME%"
|
| 52 |
+
:ENDIFVHOME
|
| 53 |
+
|
| 54 |
+
@set PYTHONHOME=
|
| 55 |
+
|
| 56 |
+
@REM if defined _OLD_VIRTUAL_PATH (
|
| 57 |
+
@if not defined _OLD_VIRTUAL_PATH @goto ENDIFVPATH1
|
| 58 |
+
@set "PATH=%_OLD_VIRTUAL_PATH%"
|
| 59 |
+
:ENDIFVPATH1
|
| 60 |
+
@REM ) else (
|
| 61 |
+
@if defined _OLD_VIRTUAL_PATH @goto ENDIFVPATH2
|
| 62 |
+
@set "_OLD_VIRTUAL_PATH=%PATH%"
|
| 63 |
+
:ENDIFVPATH2
|
| 64 |
+
|
| 65 |
+
@set "PATH=%VIRTUAL_ENV%\Scripts;%PATH%"
|
| 66 |
+
|
| 67 |
+
:END
|
| 68 |
+
@if defined _OLD_CODEPAGE (
|
| 69 |
+
@"%SystemRoot%\System32\chcp.com" %_OLD_CODEPAGE% > nul
|
| 70 |
+
@set _OLD_CODEPAGE=
|
| 71 |
+
)
|
.venv/Scripts/activate.csh
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2020-202x The virtualenv developers
|
| 2 |
+
#
|
| 3 |
+
# Permission is hereby granted, free of charge, to any person obtaining
|
| 4 |
+
# a copy of this software and associated documentation files (the
|
| 5 |
+
# "Software"), to deal in the Software without restriction, including
|
| 6 |
+
# without limitation the rights to use, copy, modify, merge, publish,
|
| 7 |
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
| 8 |
+
# permit persons to whom the Software is furnished to do so, subject to
|
| 9 |
+
# the following conditions:
|
| 10 |
+
#
|
| 11 |
+
# The above copyright notice and this permission notice shall be
|
| 12 |
+
# included in all copies or substantial portions of the Software.
|
| 13 |
+
#
|
| 14 |
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
| 15 |
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 16 |
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
| 17 |
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
| 18 |
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
| 19 |
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
| 20 |
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 21 |
+
|
| 22 |
+
# This file must be used with "source bin/activate.csh" *from csh*.
|
| 23 |
+
# You cannot run it directly.
|
| 24 |
+
# Created by Davide Di Blasi <davidedb@gmail.com>.
|
| 25 |
+
|
| 26 |
+
set newline='\
|
| 27 |
+
'
|
| 28 |
+
|
| 29 |
+
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH:q" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT:q" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate && unalias pydoc'
|
| 30 |
+
|
| 31 |
+
# Unset irrelevant variables.
|
| 32 |
+
deactivate nondestructive
|
| 33 |
+
|
| 34 |
+
setenv VIRTUAL_ENV 'C:\Users\Admin\Downloads\AIDevMinh\slm-profile-rag\.venv'
|
| 35 |
+
|
| 36 |
+
set _OLD_VIRTUAL_PATH="$PATH:q"
|
| 37 |
+
setenv PATH "$VIRTUAL_ENV:q/Scripts:$PATH:q"
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
if ('' != "") then
|
| 42 |
+
setenv VIRTUAL_ENV_PROMPT ''
|
| 43 |
+
else
|
| 44 |
+
setenv VIRTUAL_ENV_PROMPT "$VIRTUAL_ENV:t:q"
|
| 45 |
+
endif
|
| 46 |
+
|
| 47 |
+
if ( $?VIRTUAL_ENV_DISABLE_PROMPT ) then
|
| 48 |
+
if ( $VIRTUAL_ENV_DISABLE_PROMPT == "" ) then
|
| 49 |
+
set do_prompt = "1"
|
| 50 |
+
else
|
| 51 |
+
set do_prompt = "0"
|
| 52 |
+
endif
|
| 53 |
+
else
|
| 54 |
+
set do_prompt = "1"
|
| 55 |
+
endif
|
| 56 |
+
|
| 57 |
+
if ( $do_prompt == "1" ) then
|
| 58 |
+
# Could be in a non-interactive environment,
|
| 59 |
+
# in which case, $prompt is undefined and we wouldn't
|
| 60 |
+
# care about the prompt anyway.
|
| 61 |
+
if ( $?prompt ) then
|
| 62 |
+
set _OLD_VIRTUAL_PROMPT="$prompt:q"
|
| 63 |
+
if ( "$prompt:q" =~ *"$newline:q"* ) then
|
| 64 |
+
:
|
| 65 |
+
else
|
| 66 |
+
set prompt = '('"$VIRTUAL_ENV_PROMPT:q"') '"$prompt:q"
|
| 67 |
+
endif
|
| 68 |
+
endif
|
| 69 |
+
endif
|
| 70 |
+
|
| 71 |
+
unset env_name
|
| 72 |
+
unset do_prompt
|
| 73 |
+
|
| 74 |
+
alias pydoc python -m pydoc
|
| 75 |
+
|
| 76 |
+
rehash
|
.venv/Scripts/activate.fish
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2020-202x The virtualenv developers
|
| 2 |
+
#
|
| 3 |
+
# Permission is hereby granted, free of charge, to any person obtaining
|
| 4 |
+
# a copy of this software and associated documentation files (the
|
| 5 |
+
# "Software"), to deal in the Software without restriction, including
|
| 6 |
+
# without limitation the rights to use, copy, modify, merge, publish,
|
| 7 |
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
| 8 |
+
# permit persons to whom the Software is furnished to do so, subject to
|
| 9 |
+
# the following conditions:
|
| 10 |
+
#
|
| 11 |
+
# The above copyright notice and this permission notice shall be
|
| 12 |
+
# included in all copies or substantial portions of the Software.
|
| 13 |
+
#
|
| 14 |
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
| 15 |
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 16 |
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
| 17 |
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
| 18 |
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
| 19 |
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
| 20 |
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 21 |
+
|
| 22 |
+
# This file must be used using `source bin/activate.fish` *within a running fish ( http://fishshell.com ) session*.
|
| 23 |
+
# Do not run it directly.
|
| 24 |
+
|
| 25 |
+
function _bashify_path -d "Converts a fish path to something bash can recognize"
|
| 26 |
+
set fishy_path $argv
|
| 27 |
+
set bashy_path $fishy_path[1]
|
| 28 |
+
for path_part in $fishy_path[2..-1]
|
| 29 |
+
set bashy_path "$bashy_path:$path_part"
|
| 30 |
+
end
|
| 31 |
+
echo $bashy_path
|
| 32 |
+
end
|
| 33 |
+
|
| 34 |
+
function _fishify_path -d "Converts a bash path to something fish can recognize"
|
| 35 |
+
echo $argv | tr ':' '\n'
|
| 36 |
+
end
|
| 37 |
+
|
| 38 |
+
function deactivate -d 'Exit virtualenv mode and return to the normal environment.'
|
| 39 |
+
# reset old environment variables
|
| 40 |
+
if test -n "$_OLD_VIRTUAL_PATH"
|
| 41 |
+
# https://github.com/fish-shell/fish-shell/issues/436 altered PATH handling
|
| 42 |
+
if test (echo $FISH_VERSION | head -c 1) -lt 3
|
| 43 |
+
set -gx PATH (_fishify_path "$_OLD_VIRTUAL_PATH")
|
| 44 |
+
else
|
| 45 |
+
set -gx PATH $_OLD_VIRTUAL_PATH
|
| 46 |
+
end
|
| 47 |
+
set -e _OLD_VIRTUAL_PATH
|
| 48 |
+
end
|
| 49 |
+
|
| 50 |
+
if test -n "$_OLD_VIRTUAL_PYTHONHOME"
|
| 51 |
+
set -gx PYTHONHOME "$_OLD_VIRTUAL_PYTHONHOME"
|
| 52 |
+
set -e _OLD_VIRTUAL_PYTHONHOME
|
| 53 |
+
end
|
| 54 |
+
|
| 55 |
+
if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
|
| 56 |
+
and functions -q _old_fish_prompt
|
| 57 |
+
# Set an empty local `$fish_function_path` to allow the removal of `fish_prompt` using `functions -e`.
|
| 58 |
+
set -l fish_function_path
|
| 59 |
+
|
| 60 |
+
# Erase virtualenv's `fish_prompt` and restore the original.
|
| 61 |
+
functions -e fish_prompt
|
| 62 |
+
functions -c _old_fish_prompt fish_prompt
|
| 63 |
+
functions -e _old_fish_prompt
|
| 64 |
+
set -e _OLD_FISH_PROMPT_OVERRIDE
|
| 65 |
+
end
|
| 66 |
+
|
| 67 |
+
set -e VIRTUAL_ENV
|
| 68 |
+
set -e VIRTUAL_ENV_PROMPT
|
| 69 |
+
|
| 70 |
+
if test "$argv[1]" != 'nondestructive'
|
| 71 |
+
# Self-destruct!
|
| 72 |
+
functions -e pydoc
|
| 73 |
+
functions -e deactivate
|
| 74 |
+
functions -e _bashify_path
|
| 75 |
+
functions -e _fishify_path
|
| 76 |
+
end
|
| 77 |
+
end
|
| 78 |
+
|
| 79 |
+
# Unset irrelevant variables.
|
| 80 |
+
deactivate nondestructive
|
| 81 |
+
|
| 82 |
+
set -gx VIRTUAL_ENV 'C:\Users\Admin\Downloads\AIDevMinh\slm-profile-rag\.venv'
|
| 83 |
+
|
| 84 |
+
# https://github.com/fish-shell/fish-shell/issues/436 altered PATH handling
|
| 85 |
+
if test (echo $FISH_VERSION | head -c 1) -lt 3
|
| 86 |
+
set -gx _OLD_VIRTUAL_PATH (_bashify_path $PATH)
|
| 87 |
+
else
|
| 88 |
+
set -gx _OLD_VIRTUAL_PATH $PATH
|
| 89 |
+
end
|
| 90 |
+
set -gx PATH "$VIRTUAL_ENV"'/Scripts' $PATH
|
| 91 |
+
|
| 92 |
+
# Prompt override provided?
|
| 93 |
+
# If not, just use the environment name.
|
| 94 |
+
if test -n ''
|
| 95 |
+
set -gx VIRTUAL_ENV_PROMPT ''
|
| 96 |
+
else
|
| 97 |
+
set -gx VIRTUAL_ENV_PROMPT (basename "$VIRTUAL_ENV")
|
| 98 |
+
end
|
| 99 |
+
|
| 100 |
+
# Unset `$PYTHONHOME` if set.
|
| 101 |
+
if set -q PYTHONHOME
|
| 102 |
+
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
|
| 103 |
+
set -e PYTHONHOME
|
| 104 |
+
end
|
| 105 |
+
|
| 106 |
+
function pydoc
|
| 107 |
+
python -m pydoc $argv
|
| 108 |
+
end
|
| 109 |
+
|
| 110 |
+
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
|
| 111 |
+
# Copy the current `fish_prompt` function as `_old_fish_prompt`.
|
| 112 |
+
functions -c fish_prompt _old_fish_prompt
|
| 113 |
+
|
| 114 |
+
function fish_prompt
|
| 115 |
+
# Run the user's prompt first; it might depend on (pipe)status.
|
| 116 |
+
set -l prompt (_old_fish_prompt)
|
| 117 |
+
|
| 118 |
+
printf '(%s) ' $VIRTUAL_ENV_PROMPT
|
| 119 |
+
|
| 120 |
+
string join -- \n $prompt # handle multi-line prompts
|
| 121 |
+
end
|
| 122 |
+
|
| 123 |
+
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
|
| 124 |
+
end
|
.venv/Scripts/activate.nu
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2020-202x The virtualenv developers
|
| 2 |
+
#
|
| 3 |
+
# Permission is hereby granted, free of charge, to any person obtaining
|
| 4 |
+
# a copy of this software and associated documentation files (the
|
| 5 |
+
# "Software"), to deal in the Software without restriction, including
|
| 6 |
+
# without limitation the rights to use, copy, modify, merge, publish,
|
| 7 |
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
| 8 |
+
# permit persons to whom the Software is furnished to do so, subject to
|
| 9 |
+
# the following conditions:
|
| 10 |
+
#
|
| 11 |
+
# The above copyright notice and this permission notice shall be
|
| 12 |
+
# included in all copies or substantial portions of the Software.
|
| 13 |
+
#
|
| 14 |
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
| 15 |
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 16 |
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
| 17 |
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
| 18 |
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
| 19 |
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
| 20 |
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 21 |
+
|
| 22 |
+
# virtualenv activation module
|
| 23 |
+
# Activate with `overlay use activate.nu`
|
| 24 |
+
# Deactivate with `deactivate`, as usual
|
| 25 |
+
#
|
| 26 |
+
# To customize the overlay name, you can call `overlay use activate.nu as foo`,
|
| 27 |
+
# but then simply `deactivate` won't work because it is just an alias to hide
|
| 28 |
+
# the "activate" overlay. You'd need to call `overlay hide foo` manually.
|
| 29 |
+
|
| 30 |
+
export-env {
|
| 31 |
+
def is-string [x] {
|
| 32 |
+
($x | describe) == 'string'
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
def has-env [...names] {
|
| 36 |
+
$names | each {|n|
|
| 37 |
+
$n in $env
|
| 38 |
+
} | all {|i| $i == true}
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
# Emulates a `test -z`, but better as it handles e.g 'false'
|
| 42 |
+
def is-env-true [name: string] {
|
| 43 |
+
if (has-env $name) {
|
| 44 |
+
# Try to parse 'true', '0', '1', and fail if not convertible
|
| 45 |
+
let parsed = (do -i { $env | get $name | into bool })
|
| 46 |
+
if ($parsed | describe) == 'bool' {
|
| 47 |
+
$parsed
|
| 48 |
+
} else {
|
| 49 |
+
not ($env | get -i $name | is-empty)
|
| 50 |
+
}
|
| 51 |
+
} else {
|
| 52 |
+
false
|
| 53 |
+
}
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
let virtual_env = 'C:\Users\Admin\Downloads\AIDevMinh\slm-profile-rag\.venv'
|
| 57 |
+
let bin = 'Scripts'
|
| 58 |
+
|
| 59 |
+
let is_windows = ($nu.os-info.family) == 'windows'
|
| 60 |
+
let path_name = (if (has-env 'Path') {
|
| 61 |
+
'Path'
|
| 62 |
+
} else {
|
| 63 |
+
'PATH'
|
| 64 |
+
}
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
+
let venv_path = ([$virtual_env $bin] | path join)
|
| 68 |
+
let new_path = ($env | get $path_name | prepend $venv_path)
|
| 69 |
+
|
| 70 |
+
# If there is no default prompt, then use the env name instead
|
| 71 |
+
let virtual_env_prompt = (if ('' | is-empty) {
|
| 72 |
+
($virtual_env | path basename)
|
| 73 |
+
} else {
|
| 74 |
+
''
|
| 75 |
+
})
|
| 76 |
+
|
| 77 |
+
let new_env = {
|
| 78 |
+
$path_name : $new_path
|
| 79 |
+
VIRTUAL_ENV : $virtual_env
|
| 80 |
+
VIRTUAL_ENV_PROMPT : $virtual_env_prompt
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
let new_env = (if (is-env-true 'VIRTUAL_ENV_DISABLE_PROMPT') {
|
| 84 |
+
$new_env
|
| 85 |
+
} else {
|
| 86 |
+
# Creating the new prompt for the session
|
| 87 |
+
let virtual_prefix = $'(char lparen)($virtual_env_prompt)(char rparen) '
|
| 88 |
+
|
| 89 |
+
# Back up the old prompt builder
|
| 90 |
+
let old_prompt_command = (if (has-env 'PROMPT_COMMAND') {
|
| 91 |
+
$env.PROMPT_COMMAND
|
| 92 |
+
} else {
|
| 93 |
+
''
|
| 94 |
+
})
|
| 95 |
+
|
| 96 |
+
let new_prompt = (if (has-env 'PROMPT_COMMAND') {
|
| 97 |
+
if 'closure' in ($old_prompt_command | describe) {
|
| 98 |
+
{|| $'($virtual_prefix)(do $old_prompt_command)' }
|
| 99 |
+
} else {
|
| 100 |
+
{|| $'($virtual_prefix)($old_prompt_command)' }
|
| 101 |
+
}
|
| 102 |
+
} else {
|
| 103 |
+
{|| $'($virtual_prefix)' }
|
| 104 |
+
})
|
| 105 |
+
|
| 106 |
+
$new_env | merge {
|
| 107 |
+
PROMPT_COMMAND : $new_prompt
|
| 108 |
+
VIRTUAL_PREFIX : $virtual_prefix
|
| 109 |
+
}
|
| 110 |
+
})
|
| 111 |
+
|
| 112 |
+
# Environment variables that will be loaded as the virtual env
|
| 113 |
+
load-env $new_env
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
export alias pydoc = python -m pydoc
|
| 117 |
+
export alias deactivate = overlay hide activate
|
.venv/Scripts/activate.ps1
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2020-202x The virtualenv developers
|
| 2 |
+
#
|
| 3 |
+
# Permission is hereby granted, free of charge, to any person obtaining
|
| 4 |
+
# a copy of this software and associated documentation files (the
|
| 5 |
+
# "Software"), to deal in the Software without restriction, including
|
| 6 |
+
# without limitation the rights to use, copy, modify, merge, publish,
|
| 7 |
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
| 8 |
+
# permit persons to whom the Software is furnished to do so, subject to
|
| 9 |
+
# the following conditions:
|
| 10 |
+
#
|
| 11 |
+
# The above copyright notice and this permission notice shall be
|
| 12 |
+
# included in all copies or substantial portions of the Software.
|
| 13 |
+
#
|
| 14 |
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
| 15 |
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 16 |
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
| 17 |
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
| 18 |
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
| 19 |
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
| 20 |
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 21 |
+
|
| 22 |
+
$script:THIS_PATH = $myinvocation.mycommand.path
|
| 23 |
+
$script:BASE_DIR = Split-Path (Resolve-Path "$THIS_PATH/..") -Parent
|
| 24 |
+
|
| 25 |
+
function global:deactivate([switch] $NonDestructive) {
|
| 26 |
+
if (Test-Path variable:_OLD_VIRTUAL_PATH) {
|
| 27 |
+
$env:PATH = $variable:_OLD_VIRTUAL_PATH
|
| 28 |
+
Remove-Variable "_OLD_VIRTUAL_PATH" -Scope global
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
if (Test-Path function:_old_virtual_prompt) {
|
| 32 |
+
$function:prompt = $function:_old_virtual_prompt
|
| 33 |
+
Remove-Item function:\_old_virtual_prompt
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
if ($env:VIRTUAL_ENV) {
|
| 37 |
+
Remove-Item env:VIRTUAL_ENV -ErrorAction SilentlyContinue
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
if ($env:VIRTUAL_ENV_PROMPT) {
|
| 41 |
+
Remove-Item env:VIRTUAL_ENV_PROMPT -ErrorAction SilentlyContinue
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
if (!$NonDestructive) {
|
| 45 |
+
# Self destruct!
|
| 46 |
+
Remove-Item function:deactivate
|
| 47 |
+
Remove-Item function:pydoc
|
| 48 |
+
}
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
function global:pydoc {
|
| 52 |
+
python -m pydoc $args
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
# unset irrelevant variables
|
| 56 |
+
deactivate -nondestructive
|
| 57 |
+
|
| 58 |
+
$VIRTUAL_ENV = $BASE_DIR
|
| 59 |
+
$env:VIRTUAL_ENV = $VIRTUAL_ENV
|
| 60 |
+
|
| 61 |
+
if ("" -ne "") {
|
| 62 |
+
$env:VIRTUAL_ENV_PROMPT = ""
|
| 63 |
+
}
|
| 64 |
+
else {
|
| 65 |
+
$env:VIRTUAL_ENV_PROMPT = $( Split-Path $env:VIRTUAL_ENV -Leaf )
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
New-Variable -Scope global -Name _OLD_VIRTUAL_PATH -Value $env:PATH
|
| 69 |
+
|
| 70 |
+
$env:PATH = "$env:VIRTUAL_ENV/Scripts;" + $env:PATH
|
| 71 |
+
if (!$env:VIRTUAL_ENV_DISABLE_PROMPT) {
|
| 72 |
+
function global:_old_virtual_prompt {
|
| 73 |
+
""
|
| 74 |
+
}
|
| 75 |
+
$function:_old_virtual_prompt = $function:prompt
|
| 76 |
+
|
| 77 |
+
function global:prompt {
|
| 78 |
+
# Add the custom prefix to the existing prompt
|
| 79 |
+
$previous_prompt_value = & $function:_old_virtual_prompt
|
| 80 |
+
("(" + $env:VIRTUAL_ENV_PROMPT + ") " + $previous_prompt_value)
|
| 81 |
+
}
|
| 82 |
+
}
|
.venv/Scripts/activate_this.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2020-202x The virtualenv developers
|
| 2 |
+
#
|
| 3 |
+
# Permission is hereby granted, free of charge, to any person obtaining
|
| 4 |
+
# a copy of this software and associated documentation files (the
|
| 5 |
+
# "Software"), to deal in the Software without restriction, including
|
| 6 |
+
# without limitation the rights to use, copy, modify, merge, publish,
|
| 7 |
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
| 8 |
+
# permit persons to whom the Software is furnished to do so, subject to
|
| 9 |
+
# the following conditions:
|
| 10 |
+
#
|
| 11 |
+
# The above copyright notice and this permission notice shall be
|
| 12 |
+
# included in all copies or substantial portions of the Software.
|
| 13 |
+
#
|
| 14 |
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
| 15 |
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 16 |
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
| 17 |
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
| 18 |
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
| 19 |
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
| 20 |
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 21 |
+
|
| 22 |
+
"""
|
| 23 |
+
Activate virtualenv for current interpreter:
|
| 24 |
+
|
| 25 |
+
import runpy
|
| 26 |
+
runpy.run_path(this_file)
|
| 27 |
+
|
| 28 |
+
This can be used when you must use an existing Python interpreter, not the virtualenv bin/python.
|
| 29 |
+
""" # noqa: D415
|
| 30 |
+
|
| 31 |
+
from __future__ import annotations
|
| 32 |
+
|
| 33 |
+
import os
|
| 34 |
+
import site
|
| 35 |
+
import sys
|
| 36 |
+
|
| 37 |
+
try:
|
| 38 |
+
abs_file = os.path.abspath(__file__)
|
| 39 |
+
except NameError as exc:
|
| 40 |
+
msg = "You must use import runpy; runpy.run_path(this_file)"
|
| 41 |
+
raise AssertionError(msg) from exc
|
| 42 |
+
|
| 43 |
+
bin_dir = os.path.dirname(abs_file)
|
| 44 |
+
base = bin_dir[: -len("Scripts") - 1] # strip away the bin part from the __file__, plus the path separator
|
| 45 |
+
|
| 46 |
+
# prepend bin to PATH (this file is inside the bin directory)
|
| 47 |
+
os.environ["PATH"] = os.pathsep.join([bin_dir, *os.environ.get("PATH", "").split(os.pathsep)])
|
| 48 |
+
os.environ["VIRTUAL_ENV"] = base # virtual env is right above bin directory
|
| 49 |
+
os.environ["VIRTUAL_ENV_PROMPT"] = "" or os.path.basename(base) # noqa: SIM222
|
| 50 |
+
|
| 51 |
+
# add the virtual environments libraries to the host python import mechanism
|
| 52 |
+
prev_length = len(sys.path)
|
| 53 |
+
for lib in "..\\Lib\\site-packages".split(os.pathsep):
|
| 54 |
+
path = os.path.realpath(os.path.join(bin_dir, lib))
|
| 55 |
+
site.addsitedir(path)
|
| 56 |
+
sys.path[:] = sys.path[prev_length:] + sys.path[0:prev_length]
|
| 57 |
+
|
| 58 |
+
sys.real_prefix = sys.prefix
|
| 59 |
+
sys.prefix = base
|
.venv/Scripts/deactivate.bat
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@REM Copyright (c) 2020-202x The virtualenv developers
|
| 2 |
+
@REM
|
| 3 |
+
@REM Permission is hereby granted, free of charge, to any person obtaining
|
| 4 |
+
@REM a copy of this software and associated documentation files (the
|
| 5 |
+
@REM "Software"), to deal in the Software without restriction, including
|
| 6 |
+
@REM without limitation the rights to use, copy, modify, merge, publish,
|
| 7 |
+
@REM distribute, sublicense, and/or sell copies of the Software, and to
|
| 8 |
+
@REM permit persons to whom the Software is furnished to do so, subject to
|
| 9 |
+
@REM the following conditions:
|
| 10 |
+
@REM
|
| 11 |
+
@REM The above copyright notice and this permission notice shall be
|
| 12 |
+
@REM included in all copies or substantial portions of the Software.
|
| 13 |
+
@REM
|
| 14 |
+
@REM THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
| 15 |
+
@REM EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 16 |
+
@REM MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
| 17 |
+
@REM NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
| 18 |
+
@REM LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
| 19 |
+
@REM OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
| 20 |
+
@REM WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 21 |
+
|
| 22 |
+
@set VIRTUAL_ENV=
|
| 23 |
+
@set VIRTUAL_ENV_PROMPT=
|
| 24 |
+
|
| 25 |
+
@REM Don't use () to avoid problems with them in %PATH%
|
| 26 |
+
@if not defined _OLD_VIRTUAL_PROMPT @goto ENDIFVPROMPT
|
| 27 |
+
@set "PROMPT=%_OLD_VIRTUAL_PROMPT%"
|
| 28 |
+
@set _OLD_VIRTUAL_PROMPT=
|
| 29 |
+
:ENDIFVPROMPT
|
| 30 |
+
|
| 31 |
+
@if not defined _OLD_VIRTUAL_PYTHONHOME @goto ENDIFVHOME
|
| 32 |
+
@set "PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME%"
|
| 33 |
+
@set _OLD_VIRTUAL_PYTHONHOME=
|
| 34 |
+
:ENDIFVHOME
|
| 35 |
+
|
| 36 |
+
@if not defined _OLD_VIRTUAL_PATH @goto ENDIFVPATH
|
| 37 |
+
@set "PATH=%_OLD_VIRTUAL_PATH%"
|
| 38 |
+
@set _OLD_VIRTUAL_PATH=
|
| 39 |
+
:ENDIFVPATH
|
.venv/Scripts/pydoc.bat
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@REM Copyright (c) 2020-202x The virtualenv developers
|
| 2 |
+
@REM
|
| 3 |
+
@REM Permission is hereby granted, free of charge, to any person obtaining
|
| 4 |
+
@REM a copy of this software and associated documentation files (the
|
| 5 |
+
@REM "Software"), to deal in the Software without restriction, including
|
| 6 |
+
@REM without limitation the rights to use, copy, modify, merge, publish,
|
| 7 |
+
@REM distribute, sublicense, and/or sell copies of the Software, and to
|
| 8 |
+
@REM permit persons to whom the Software is furnished to do so, subject to
|
| 9 |
+
@REM the following conditions:
|
| 10 |
+
@REM
|
| 11 |
+
@REM The above copyright notice and this permission notice shall be
|
| 12 |
+
@REM included in all copies or substantial portions of the Software.
|
| 13 |
+
@REM
|
| 14 |
+
@REM THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
| 15 |
+
@REM EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 16 |
+
@REM MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
| 17 |
+
@REM NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
| 18 |
+
@REM LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
| 19 |
+
@REM OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
| 20 |
+
@REM WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 21 |
+
|
| 22 |
+
python.exe -m pydoc %*
|
.venv/Scripts/python.exe
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8b86e1ee66bac3fe3b998a2586791ef3151d5b7fee9af11f5b1491d2a4a93ff0
|
| 3 |
+
size 237568
|
.venv/Scripts/pythonw.exe
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:faaa4c9a1c0188a7bb7d648874b68c536db0248ca97b5d863854091864b94b3b
|
| 3 |
+
size 233984
|
.venv/pyvenv.cfg
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
home = C:\Users\Admin\AppData\Roaming\uv\python\cpython-3.13.2-windows-x86_64-none
|
| 2 |
+
implementation = CPython
|
| 3 |
+
uv = 0.6.9
|
| 4 |
+
version_info = 3.13.2
|
| 5 |
+
include-system-site-packages = false
|
ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,815 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🏗️ Architecture Overview
|
| 2 |
+
|
| 3 |
+
## System Architecture
|
| 4 |
+
|
| 5 |
+
<p align="center">
|
| 6 |
+
<img src="docs/arch_system_design.png" alt="System Architecture Diagram" width="900" />
|
| 7 |
+
</p>
|
| 8 |
+
<p align="center"><em>High-level system architecture diagram illustrating the complete RAG pipeline flow</em></p>
|
| 9 |
+
|
| 10 |
+
### Detailed Component View
|
| 11 |
+
|
| 12 |
+
```
|
| 13 |
+
┌─────────────────────────────────────────────────────────────┐
|
| 14 |
+
│ User Interface │
|
| 15 |
+
│ (Streamlit Web App) │
|
| 16 |
+
│ app.py │
|
| 17 |
+
└───────────────────────┬─────────────────────────────────────┘
|
| 18 |
+
│
|
| 19 |
+
▼
|
| 20 |
+
┌─────────────────────────────────────────────────────────────┐
|
| 21 |
+
│ RAG Pipeline │
|
| 22 |
+
│ (rag_pipeline.py) │
|
| 23 |
+
│ │
|
| 24 |
+
│ ┌────────────────┐ ┌──────────────┐ │
|
| 25 |
+
│ │ System Prompt │ │ QA Chain │ │
|
| 26 |
+
│ │ Template │─────▶│ (LangChain) │ │
|
| 27 |
+
│ └────────────────┘ └──────┬───────┘ │
|
| 28 |
+
└─────────────────────────────────┼──────────────────────────┘
|
| 29 |
+
│
|
| 30 |
+
┌─────────────┴─────────────┐
|
| 31 |
+
▼ ▼
|
| 32 |
+
┌───────────────────────┐ ┌──────────────────────┐
|
| 33 |
+
│ Vector Store │ │ LLM Handler │
|
| 34 |
+
│ (vectorstore.py) │ │ (llm_handler.py) │
|
| 35 |
+
│ │ │ │
|
| 36 |
+
│ ┌─────────────────┐ │ │ ┌───────────────┐ │
|
| 37 |
+
│ │ ChromaDB │ │ │ │ Ollama │ │
|
| 38 |
+
│ │ (Embeddings) │ │ │ │ (llama3.2) │ │
|
| 39 |
+
│ └─────────────────┘ │ │ └───────────────┘ │
|
| 40 |
+
│ │ │ │
|
| 41 |
+
│ ┌─────────────────┐ │ └──────────────────────┘
|
| 42 |
+
│ │ HuggingFace │ │
|
| 43 |
+
│ │ Embeddings │ │
|
| 44 |
+
│ │ (all-MiniLM) │ │
|
| 45 |
+
│ └─────────────────┘ │
|
| 46 |
+
└───────────────────────┘
|
| 47 |
+
▲
|
| 48 |
+
│
|
| 49 |
+
┌───────────┴────────────┐
|
| 50 |
+
│ Document Processor │
|
| 51 |
+
│ (document_processor.py)│
|
| 52 |
+
│ │
|
| 53 |
+
│ ┌──────────────────┐ │
|
| 54 |
+
│ │ PDF Loader │ │
|
| 55 |
+
│ │ DOCX Loader │ │
|
| 56 |
+
│ │ HTML Loader │ │
|
| 57 |
+
│ │ Text Loader │ │
|
| 58 |
+
│ └──────────────────┘ │
|
| 59 |
+
│ │
|
| 60 |
+
│ ┌──────────────────┐ │
|
| 61 |
+
│ │ Text Splitter │ │
|
| 62 |
+
│ │ (Chunking) │ │
|
| 63 |
+
│ └──────────────────┘ │
|
| 64 |
+
└────────────────────────┘
|
| 65 |
+
▲
|
| 66 |
+
│
|
| 67 |
+
┌───────────┴────────────┐
|
| 68 |
+
│ Source Documents │
|
| 69 |
+
│ data/documents/ │
|
| 70 |
+
│ │
|
| 71 |
+
│ • Resume.pdf │
|
| 72 |
+
│ • LinkedIn.html │
|
| 73 |
+
│ • Projects.docx │
|
| 74 |
+
│ • ... │
|
| 75 |
+
└────────────────────────┘
|
| 76 |
+
|
| 77 |
+
┌──────────────────────────────────────────────────────────────┐
|
| 78 |
+
│ Configuration Layer │
|
| 79 |
+
├──────────────────────────────────────────────────────────────┤
|
| 80 |
+
│ │
|
| 81 |
+
│ config.yaml env.template (.env) │
|
| 82 |
+
│ ├─ profile ├─ OLLAMA_BASE_URL │
|
| 83 |
+
│ ├─ llm ├─ CHROMA_PERSIST_DIR │
|
| 84 |
+
│ ├─ embeddings ├─ DOCUMENTS_DIR │
|
| 85 |
+
│ ├─ vectorstore ├─ LOG_LEVEL │
|
| 86 |
+
│ ├─ document_proc └─ API_KEYS (optional) │
|
| 87 |
+
│ ├─ rag │
|
| 88 |
+
│ ├─ ui │
|
| 89 |
+
│ └─ logging │
|
| 90 |
+
│ │
|
| 91 |
+
│ (config_loader.py) │
|
| 92 |
+
└──────────────────────────────────────────────────────────────┘
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
+
## Data Flow
|
| 96 |
+
|
| 97 |
+
### 1️⃣ Indexing Phase (One-time Setup)
|
| 98 |
+
|
| 99 |
+
```
|
| 100 |
+
Documents → Load → Chunk → Build Indexes
|
| 101 |
+
↓ ↓ ↓ ↓
|
| 102 |
+
resume.pdf PyPDF Split ┌─────────────────────────────┐
|
| 103 |
+
linkedin.html BS4 into │ BM25 Index (keyword) │
|
| 104 |
+
projects.docx docx chunks │ ./bm25_index/ │
|
| 105 |
+
├─────────────────────────────┤
|
| 106 |
+
│ Vector Index (semantic) │
|
| 107 |
+
│ ./chroma_db/ (ChromaDB) │
|
| 108 |
+
└─────────────────────────────┘
|
| 109 |
+
```
|
| 110 |
+
|
| 111 |
+
**Command:** `python -m src.build_vectorstore`
|
| 112 |
+
|
| 113 |
+
**Strategy Options:**
|
| 114 |
+
- `--strategy bm25_vector` - Build both indexes (default, recommended)
|
| 115 |
+
- `--strategy vector` - Vector index only
|
| 116 |
+
- `--strategy bm25` - BM25 index only
|
| 117 |
+
|
| 118 |
+
### 2️⃣ Query Phase (Runtime) - WITH HYBRID SEARCH
|
| 119 |
+
|
| 120 |
+
```
|
| 121 |
+
User Question
|
| 122 |
+
↓
|
| 123 |
+
┌─────────────────────────────────────┐
|
| 124 |
+
│ Extract Chat History (if enabled) │
|
| 125 |
+
│ • Format previous Q&A pairs │
|
| 126 |
+
│ • Truncate by turns/tokens │
|
| 127 |
+
│ • Include in context │
|
| 128 |
+
└────────────┬────────────────────────┘
|
| 129 |
+
↓
|
| 130 |
+
┌─────────────────────────────────────┐
|
| 131 |
+
│ Load Main Document (if enabled) │
|
| 132 |
+
│ • Check cache validity │
|
| 133 |
+
│ • Auto-detect format │
|
| 134 |
+
│ • Count tokens │
|
| 135 |
+
│ • Summarize if needed │
|
| 136 |
+
└────────────┬────────────────────────┘
|
| 137 |
+
↓
|
| 138 |
+
[Main Doc Content]
|
| 139 |
+
│
|
| 140 |
+
├─────────────────────────────────────┐
|
| 141 |
+
↓ ↓
|
| 142 |
+
┌─────────────────────────────────────┐ Main Doc (Priority)
|
| 143 |
+
│ Hybrid Retrieval Strategy │ ↓
|
| 144 |
+
│ │ [Always Available]
|
| 145 |
+
│ ┌─────────────┐ ┌─────────────┐ │ [10k tokens max]
|
| 146 |
+
│ │ BM25 │ │ Vector │ │
|
| 147 |
+
│ │ (keyword) │ │ (semantic) │ │
|
| 148 |
+
│ └──────┬──────┘ └──────┬──────┘ │
|
| 149 |
+
│ │ │ │
|
| 150 |
+
│ └───────┬────────┘ │
|
| 151 |
+
│ ↓ │
|
| 152 |
+
│ Reciprocal Rank Fusion (RRF) │
|
| 153 |
+
│ (70% vector, 30% BM25) │
|
| 154 |
+
└────────────────┬────────────────────┘
|
| 155 |
+
↓
|
| 156 |
+
[Retrieved Context]
|
| 157 |
+
│
|
| 158 |
+
└─────────┬───────────────────┘
|
| 159 |
+
↓
|
| 160 |
+
Construct Prompt:
|
| 161 |
+
System Prompt → Chat History → Main Doc → Retrieved Context → Question
|
| 162 |
+
↓
|
| 163 |
+
Send to LLM (Ollama/Groq)
|
| 164 |
+
↓
|
| 165 |
+
Generate Answer (streaming)
|
| 166 |
+
↓
|
| 167 |
+
Response Enhancer
|
| 168 |
+
• Detect negative phrases
|
| 169 |
+
• Rewrite positively
|
| 170 |
+
• Fix markdown formatting
|
| 171 |
+
• Add forward-looking closings
|
| 172 |
+
↓
|
| 173 |
+
Display in Streamlit UI (with source citations)
|
| 174 |
+
```
|
| 175 |
+
|
| 176 |
+
## Component Details
|
| 177 |
+
|
| 178 |
+
### 📑 Main Document Integration
|
| 179 |
+
|
| 180 |
+
The Main Document feature ensures critical profile information is always available in the LLM context, regardless of VectorDB retrieval quality.
|
| 181 |
+
|
| 182 |
+
```python
|
| 183 |
+
MainDocumentLoader
|
| 184 |
+
├── Format Auto-Detection
|
| 185 |
+
│ ├── Markdown (.md) → LangChain TextLoader
|
| 186 |
+
│ ├── Plain Text (.txt) → LangChain TextLoader
|
| 187 |
+
│ ├── PDF (.pdf) → Existing PDF loader
|
| 188 |
+
│ ├── Word (.docx) → Existing DOCX loader
|
| 189 |
+
│ └── HTML (.html) → Existing HTML loader
|
| 190 |
+
│
|
| 191 |
+
├── Token Management
|
| 192 |
+
│ ├── Counting: tiktoken (cl100k_base encoding)
|
| 193 |
+
│ ├── Max Limit: 10,000 tokens (configurable)
|
| 194 |
+
│ ├── Truncation: Smart token-based trimming
|
| 195 |
+
│ └── Summarization: LLM-based if exceeds limit
|
| 196 |
+
│
|
| 197 |
+
├── Caching Strategy
|
| 198 |
+
│ ├── File hash-based invalidation (MD5)
|
| 199 |
+
│ ├── Configurable check interval (60s default)
|
| 200 |
+
│ └── Automatic reload on file changes
|
| 201 |
+
│
|
| 202 |
+
└── Integration Point
|
| 203 |
+
└── Positioned BEFORE VectorDB context (high priority)
|
| 204 |
+
```
|
| 205 |
+
|
| 206 |
+
**Architecture Flow:**
|
| 207 |
+
```
|
| 208 |
+
Main Document (Priority Context)
|
| 209 |
+
↓
|
| 210 |
+
[Essential Info Always Available]
|
| 211 |
+
↓
|
| 212 |
+
VectorDB Retrieval (Additional Context)
|
| 213 |
+
↓
|
| 214 |
+
[Supplementary Information]
|
| 215 |
+
↓
|
| 216 |
+
Combined Context → LLM → Response
|
| 217 |
+
```
|
| 218 |
+
|
| 219 |
+
**Benefits:**
|
| 220 |
+
- ✅ Critical information never missed by retrieval
|
| 221 |
+
- ✅ Auto-format detection (no manual config)
|
| 222 |
+
- ✅ Intelligent token management with LLM summarization
|
| 223 |
+
- ✅ Efficient caching for performance
|
| 224 |
+
- ✅ Graceful degradation if unavailable
|
| 225 |
+
|
| 226 |
+
**Note:** The system also supports chat history for conversational context. Previous Q&A pairs are included in the prompt (after system prompt, before main document) with configurable truncation by turns or tokens. Responses are post-processed to improve tone, remove negative language, and fix markdown formatting.
|
| 227 |
+
|
| 228 |
+
### 📄 Document Processing Pipeline
|
| 229 |
+
|
| 230 |
+
```python
|
| 231 |
+
DocumentProcessor
|
| 232 |
+
├── Supported Formats
|
| 233 |
+
│ ├── PDF → pypdf
|
| 234 |
+
│ ├── Word → python-docx
|
| 235 |
+
│ ├── HTML → BeautifulSoup4
|
| 236 |
+
│ └── Text/MD → LangChain TextLoader
|
| 237 |
+
│
|
| 238 |
+
├── Chunking Strategy
|
| 239 |
+
│ ├── Size: 1000 chars (configurable)
|
| 240 |
+
│ ├── Overlap: 200 chars (configurable)
|
| 241 |
+
│ └── Separators: ["\n\n", "\n", ". ", " ", ""]
|
| 242 |
+
│
|
| 243 |
+
└── Output: List[Document]
|
| 244 |
+
└── Each with content + metadata
|
| 245 |
+
```
|
| 246 |
+
|
| 247 |
+
### 🔍 Retrieval Strategy System
|
| 248 |
+
|
| 249 |
+
The retrieval system uses a pluggable strategy pattern for extensibility.
|
| 250 |
+
|
| 251 |
+
```python
|
| 252 |
+
RetrieverFactory
|
| 253 |
+
├── Registered Strategies
|
| 254 |
+
│ ├── "vector" → VectorStrategy (semantic search)
|
| 255 |
+
│ ├── "bm25" → BM25Strategy (keyword search)
|
| 256 |
+
│ ├── "bm25_vector" → BM25VectorStrategy (hybrid)
|
| 257 |
+
│ └── (future: "page_index", "graph_vector")
|
| 258 |
+
│
|
| 259 |
+
└── Strategy Interface (BaseRetrieverStrategy)
|
| 260 |
+
├── build_index(documents) → Build/update index
|
| 261 |
+
├── load_index() → Load from disk
|
| 262 |
+
├── retrieve(query, k) → Get relevant docs
|
| 263 |
+
└── as_retriever() → LangChain compatible
|
| 264 |
+
```
|
| 265 |
+
|
| 266 |
+
#### Hybrid Search (BM25 + Vector)
|
| 267 |
+
|
| 268 |
+
```
|
| 269 |
+
┌─────────────────────────────────────────────────────────────────┐
|
| 270 |
+
│ BM25VectorStrategy │
|
| 271 |
+
│ │
|
| 272 |
+
│ ┌─────────────────────┐ ┌─────────────────────────┐ │
|
| 273 |
+
│ │ BM25Retriever │ │ VectorRetriever │ │
|
| 274 |
+
│ │ (keyword match) │ │ (semantic match) │ │
|
| 275 |
+
│ │ │ │ │ │
|
| 276 |
+
│ │ • Exact terms │ │ • Meaning/context │ │
|
| 277 |
+
│ │ • Abbreviations │ │ • Synonyms │ │
|
| 278 |
+
│ │ • Proper nouns │ │ • Related concepts │ │
|
| 279 |
+
│ └──────────┬──────────┘ └───────────┬─────────────┘ │
|
| 280 |
+
│ │ (k=10) │ (k=10) │
|
| 281 |
+
│ └───────���──┬───────────────────┘ │
|
| 282 |
+
│ ↓ │
|
| 283 |
+
│ Reciprocal Rank Fusion (RRF) │
|
| 284 |
+
│ weights: {vector: 0.7, bm25: 0.3} │
|
| 285 |
+
│ ↓ │
|
| 286 |
+
│ Top K results (k=4) │
|
| 287 |
+
└─────────────────────────────────────────────────────────────────┘
|
| 288 |
+
```
|
| 289 |
+
|
| 290 |
+
**RRF Formula:** `score(d) = Σ (weight × 1/(k + rank(d)))`
|
| 291 |
+
|
| 292 |
+
### 🧠 Vector Store Architecture
|
| 293 |
+
|
| 294 |
+
```python
|
| 295 |
+
VectorStoreManager
|
| 296 |
+
├── Embedding Model
|
| 297 |
+
│ └── sentence-transformers/all-MiniLM-L6-v2
|
| 298 |
+
│ ├── Dimension: 384
|
| 299 |
+
│ ├── Speed: ~2000 sentences/sec (CPU)
|
| 300 |
+
│ └── Quality: Good for semantic search
|
| 301 |
+
│
|
| 302 |
+
├── ChromaDB
|
| 303 |
+
│ ├── Type: Persistent (SQLite)
|
| 304 |
+
│ ├── Location: ./chroma_db/
|
| 305 |
+
│ ├── Collection: profile_documents
|
| 306 |
+
│ └── Indexing: HNSW (approximate NN)
|
| 307 |
+
│
|
| 308 |
+
└── Retrieval
|
| 309 |
+
├── Search Type: Similarity (or MMR)
|
| 310 |
+
├── Top K: 10 (before fusion)
|
| 311 |
+
└── Distance: Cosine similarity
|
| 312 |
+
```
|
| 313 |
+
|
| 314 |
+
### 📝 BM25 Store Architecture
|
| 315 |
+
|
| 316 |
+
```python
|
| 317 |
+
BM25Store
|
| 318 |
+
├── Algorithm: BM25Okapi (rank-bm25)
|
| 319 |
+
│
|
| 320 |
+
├── Tokenization
|
| 321 |
+
│ ├── "simple": Whitespace + punctuation split
|
| 322 |
+
│ └── "nltk": NLTK word_tokenize (optional)
|
| 323 |
+
│
|
| 324 |
+
├── Persistence
|
| 325 |
+
│ ├── Location: ./bm25_index/
|
| 326 |
+
│ ├── Format: Pickle (index + documents)
|
| 327 |
+
│ └── Metadata: JSON (hash, stats)
|
| 328 |
+
│
|
| 329 |
+
└── Retrieval
|
| 330 |
+
├── Top K: 10 (before fusion)
|
| 331 |
+
└── Scoring: BM25 term frequency
|
| 332 |
+
```
|
| 333 |
+
|
| 334 |
+
### 🤖 LLM Integration
|
| 335 |
+
|
| 336 |
+
```python
|
| 337 |
+
LLMHandler
|
| 338 |
+
├── Provider Support
|
| 339 |
+
│ ├── Ollama (Local)
|
| 340 |
+
│ │ ├── Base URL: http://localhost:11434
|
| 341 |
+
│ │ ├── Protocol: HTTP/REST API
|
| 342 |
+
│ │ └── Auto-detected if no API key
|
| 343 |
+
│ │
|
| 344 |
+
│ └── Groq (Cloud)
|
| 345 |
+
│ ├── Base URL: https://api.groq.com/openai/v1
|
| 346 |
+
│ ├── Protocol: OpenAI-compatible API
|
| 347 |
+
│ └── Requires GROQ_API_KEY env var
|
| 348 |
+
│
|
| 349 |
+
├── Model Options (Ollama)
|
| 350 |
+
│ ├── llama3.2:3b (Recommended)
|
| 351 |
+
│ ├── phi3:mini
|
| 352 |
+
│ ├── gemma2:2b
|
| 353 |
+
│ └── llama3.1:8b (with GPU)
|
| 354 |
+
│
|
| 355 |
+
├── Model Options (Groq)
|
| 356 |
+
│ ├── openai/gpt-oss-120b
|
| 357 |
+
│ ├── llama-3.3-70b-versatile
|
| 358 |
+
│ ├── llama-3.1-8b-instant
|
| 359 |
+
│ ├── mixtral-8x7b-32768
|
| 360 |
+
│ └── gemma2-9b-it
|
| 361 |
+
│
|
| 362 |
+
└── Parameters
|
| 363 |
+
├── Temperature: 0.7
|
| 364 |
+
├── Max Tokens: 800 (increased for better formatting)
|
| 365 |
+
├── Top P: 0.9
|
| 366 |
+
└── Context Window: Model-dependent (8192-128k tokens)
|
| 367 |
+
```
|
| 368 |
+
|
| 369 |
+
### 🔗 RAG Chain
|
| 370 |
+
|
| 371 |
+
```python
|
| 372 |
+
RAGPipeline
|
| 373 |
+
├── Retrieval Strategy
|
| 374 |
+
│ └── RetrieverFactory.create(strategy_name)
|
| 375 |
+
│ ├── "vector" → Vector-only retriever
|
| 376 |
+
│ ├── "bm25" → BM25-only retriever
|
| 377 |
+
│ └── "bm25_vector" → Fusion retriever (default)
|
| 378 |
+
│
|
| 379 |
+
├── Prompt Template Structure
|
| 380 |
+
│ ├── System Prompt (from config)
|
| 381 |
+
│ ├── Chat History (formatted previous Q&A pairs)
|
| 382 |
+
│ ├── Main Document (priority context, always available)
|
| 383 |
+
│ ├── Retrieved Context (from strategy)
|
| 384 |
+
│ └── User Question
|
| 385 |
+
│
|
| 386 |
+
├── LLM
|
| 387 |
+
│ └── Ollama or Groq (auto-detected or configured)
|
| 388 |
+
│
|
| 389 |
+
└── Output
|
| 390 |
+
├── Answer (post-processed for tone/formatting, streaming support)
|
| 391 |
+
└── Source Documents (citations)
|
| 392 |
+
```
|
| 393 |
+
|
| 394 |
+
## Configuration Hierarchy
|
| 395 |
+
|
| 396 |
+
```
|
| 397 |
+
1. Environment Variables (.env)
|
| 398 |
+
├── Override config.yaml values
|
| 399 |
+
├── Secrets (API keys)
|
| 400 |
+
└── Runtime settings (ports, URLs)
|
| 401 |
+
↓
|
| 402 |
+
2. config.yaml
|
| 403 |
+
├── Application defaults
|
| 404 |
+
├── Model selection
|
| 405 |
+
├── Retrieval strategy (vector, bm25, bm25_vector)
|
| 406 |
+
└── RAG parameters
|
| 407 |
+
↓
|
| 408 |
+
3. Code Defaults
|
| 409 |
+
└── Fallback values if config missing
|
| 410 |
+
```
|
| 411 |
+
|
| 412 |
+
### Retrieval Configuration
|
| 413 |
+
|
| 414 |
+
```yaml
|
| 415 |
+
retrieval:
|
| 416 |
+
strategy: "bm25_vector" # Which strategy to use
|
| 417 |
+
final_k: 4 # Documents returned to LLM
|
| 418 |
+
|
| 419 |
+
vector:
|
| 420 |
+
search_type: "similarity" # or "mmr"
|
| 421 |
+
k: 10 # Docs before fusion
|
| 422 |
+
|
| 423 |
+
bm25:
|
| 424 |
+
k: 10 # Docs before fusion
|
| 425 |
+
persist_path: "./bm25_index"
|
| 426 |
+
tokenizer: "simple"
|
| 427 |
+
|
| 428 |
+
fusion:
|
| 429 |
+
algorithm: "rrf" # Reciprocal Rank Fusion
|
| 430 |
+
rrf_k: 60 # RRF constant
|
| 431 |
+
weights:
|
| 432 |
+
vector: 0.7
|
| 433 |
+
bm25: 0.3
|
| 434 |
+
```
|
| 435 |
+
|
| 436 |
+
### Chat History Configuration
|
| 437 |
+
|
| 438 |
+
```yaml
|
| 439 |
+
chat:
|
| 440 |
+
enable_history: true # Enable conversation context
|
| 441 |
+
max_history_turns: 10 # Max Q&A pairs to include
|
| 442 |
+
max_history_tokens: 2000 # Token limit for history
|
| 443 |
+
```
|
| 444 |
+
|
| 445 |
+
### RAG Configuration
|
| 446 |
+
|
| 447 |
+
```yaml
|
| 448 |
+
rag:
|
| 449 |
+
enhance_responses: true # Enable post-processing enhancement
|
| 450 |
+
include_sources: true # Show source citations
|
| 451 |
+
source_max_length: 150 # Max length of source preview
|
| 452 |
+
```
|
| 453 |
+
|
| 454 |
+
## Deployment Architecture
|
| 455 |
+
|
| 456 |
+
### Local Development
|
| 457 |
+
|
| 458 |
+
```
|
| 459 |
+
┌──────────────────┐
|
| 460 |
+
│ Developer │
|
| 461 |
+
│ Machine │
|
| 462 |
+
│ │
|
| 463 |
+
│ ┌────────────┐ │
|
| 464 |
+
│ │ Ollama │ │ ← Port 11434 (optional)
|
| 465 |
+
│ │ Server │ │
|
| 466 |
+
│ └────────────┘ │
|
| 467 |
+
│ │
|
| 468 |
+
│ ┌────────────┐ │
|
| 469 |
+
│ │ Groq API │ │ ← https://api.groq.com (optional)
|
| 470 |
+
│ │ (Cloud) │ │
|
| 471 |
+
│ └────────────┘ │
|
| 472 |
+
│ │
|
| 473 |
+
│ ┌────────────┐ │
|
| 474 |
+
│ │ Streamlit │ │ ← Port 8501
|
| 475 |
+
│ │ App │ │
|
| 476 |
+
│ └────────────┘ │
|
| 477 |
+
│ │
|
| 478 |
+
│ ┌────────────┐ │
|
| 479 |
+
│ │ ChromaDB │ │ ← ./chroma_db/
|
| 480 |
+
│ │ (local) │ │
|
| 481 |
+
│ └────────────┘ │
|
| 482 |
+
│ │
|
| 483 |
+
│ ┌────────────┐ │
|
| 484 |
+
│ │ BM25 Index │ │ ← ./bm25_index/
|
| 485 |
+
│ │ (local) │ │
|
| 486 |
+
│ └────────────┘ │
|
| 487 |
+
└──────────────────┘
|
| 488 |
+
```
|
| 489 |
+
|
| 490 |
+
### Hugging Face Spaces
|
| 491 |
+
|
| 492 |
+
```
|
| 493 |
+
┌───────────────────────────────┐
|
| 494 |
+
│ HF Spaces Container │
|
| 495 |
+
│ │
|
| 496 |
+
│ ┌─────────────────────────┐ │
|
| 497 |
+
│ │ Dockerfile │ │
|
| 498 |
+
│ │ ├─ Install Ollama │ │
|
| 499 |
+
│ │ ├─ Pull Model │ │
|
| 500 |
+
│ │ ├─ Build Indexes │ │
|
| 501 |
+
│ │ └─ Start Services │ │
|
| 502 |
+
│ └─────────────────────────┘ │
|
| 503 |
+
│ │
|
| 504 |
+
│ ┌──────────┐ ┌──────────┐ │
|
| 505 |
+
│ │ Ollama │ │Streamlit │ │
|
| 506 |
+
│ │ Server │ │ App │ │
|
| 507 |
+
│ └──────────┘ └──────────┘ │
|
| 508 |
+
│ │
|
| 509 |
+
│ ┌─────────────────────────┐ │
|
| 510 |
+
│ │ Persistent Storage │ │
|
| 511 |
+
│ │ ├─ chroma_db/ │ │
|
| 512 |
+
│ │ └─ bm25_index/ │ │
|
| 513 |
+
│ └─────────────────────────┘ │
|
| 514 |
+
└───────────────────────────────┘
|
| 515 |
+
↑
|
| 516 |
+
│ HTTPS
|
| 517 |
+
│
|
| 518 |
+
┌────────┴──────────┐
|
| 519 |
+
│ Public Users │
|
| 520 |
+
│ (Recruiters) │
|
| 521 |
+
└───────────────────┘
|
| 522 |
+
```
|
| 523 |
+
|
| 524 |
+
## Build & Packaging
|
| 525 |
+
|
| 526 |
+
### UV + Hatchling + Versioningit
|
| 527 |
+
|
| 528 |
+
```
|
| 529 |
+
pyproject.toml
|
| 530 |
+
├── [build-system]
|
| 531 |
+
│ ├── requires: ["hatchling", "versioningit"]
|
| 532 |
+
│ └── build-backend: "hatchling.build"
|
| 533 |
+
│
|
| 534 |
+
├── [project]
|
| 535 |
+
│ ├── name: "profillybot"
|
| 536 |
+
│ ├── version: <from git tags via versioningit>
|
| 537 |
+
│ └── dependencies: [...]
|
| 538 |
+
│
|
| 539 |
+
├── [tool.versioningit]
|
| 540 |
+
│ ├── Read git tags (v0.1.0, v0.2.0, etc.)
|
| 541 |
+
│ ├── Generate version string
|
| 542 |
+
│ └── Write to src/_version.py
|
| 543 |
+
│
|
| 544 |
+
└── [tool.ruff]
|
| 545 |
+
├── Linting rules
|
| 546 |
+
└── Formatting config
|
| 547 |
+
```
|
| 548 |
+
|
| 549 |
+
### Version from Git Tags
|
| 550 |
+
|
| 551 |
+
```bash
|
| 552 |
+
# Tag release
|
| 553 |
+
git tag v0.1.0
|
| 554 |
+
git push origin v0.1.0
|
| 555 |
+
|
| 556 |
+
# Version automatically set
|
| 557 |
+
python -c "from src import __version__; print(__version__)"
|
| 558 |
+
# Output: 0.1.0
|
| 559 |
+
|
| 560 |
+
# Development version (after tag)
|
| 561 |
+
# Output: 0.1.0+5.g1a2b3c4 (5 commits after v0.1.0)
|
| 562 |
+
```
|
| 563 |
+
|
| 564 |
+
## Code Quality Pipeline
|
| 565 |
+
|
| 566 |
+
```
|
| 567 |
+
Developer Writes Code
|
| 568 |
+
↓
|
| 569 |
+
┌───────────────────┐
|
| 570 |
+
│ Pre-commit │
|
| 571 |
+
│ (Optional) │
|
| 572 |
+
└─────────┬─────────┘
|
| 573 |
+
↓
|
| 574 |
+
┌───────────────────┐
|
| 575 |
+
│ Ruff Check │ ← Linting
|
| 576 |
+
│ Ruff Format │ ← Formatting
|
| 577 |
+
└─────────┬─────────┘
|
| 578 |
+
↓
|
| 579 |
+
┌───────────────────┐
|
| 580 |
+
│ Git Commit │
|
| 581 |
+
└─────────┬─────────┘
|
| 582 |
+
↓
|
| 583 |
+
┌───────────────────┐
|
| 584 |
+
│ Push to GitHub │
|
| 585 |
+
└─────────┬─────────┘
|
| 586 |
+
↓
|
| 587 |
+
┌───────────────────┐
|
| 588 |
+
│ GitHub Actions │
|
| 589 |
+
│ • Lint Check │
|
| 590 |
+
│ • Format Check │
|
| 591 |
+
│ • Tests (future) │
|
| 592 |
+
└───────────────────┘
|
| 593 |
+
```
|
| 594 |
+
|
| 595 |
+
## Module Dependencies
|
| 596 |
+
|
| 597 |
+
```
|
| 598 |
+
app.py
|
| 599 |
+
├─ src.config_loader
|
| 600 |
+
├─ src.rag_pipeline
|
| 601 |
+
├─ src.llm_handler (provider detection)
|
| 602 |
+
├─ src.response_enhancer (post-stream enhancement)
|
| 603 |
+
├─ src.main_document_loader (token counting for history)
|
| 604 |
+
└─ streamlit (UI, chat history management)
|
| 605 |
+
|
| 606 |
+
src.rag_pipeline
|
| 607 |
+
├─ src.config_loader
|
| 608 |
+
├─ src.llm_handler
|
| 609 |
+
├─ src.retrieval (RetrieverFactory, strategies)
|
| 610 |
+
├─ src.main_document_loader
|
| 611 |
+
├─ src.response_enhancer
|
| 612 |
+
└─ langchain (LCEL chains)
|
| 613 |
+
|
| 614 |
+
src.retrieval
|
| 615 |
+
├─ src.retrieval.base (BaseRetrieverStrategy)
|
| 616 |
+
├─ src.retrieval.factory (RetrieverFactory)
|
| 617 |
+
├─ src.retrieval.fusion (RRF, FusionRetriever)
|
| 618 |
+
├─ src.retrieval.stores.bm25_store
|
| 619 |
+
└─ src.retrieval.strategies.*
|
| 620 |
+
|
| 621 |
+
src.retrieval.strategies.vector
|
| 622 |
+
├─ src.vectorstore
|
| 623 |
+
└─ src.retrieval.base
|
| 624 |
+
|
| 625 |
+
src.retrieval.strategies.bm25
|
| 626 |
+
├─ src.retrieval.stores.bm25_store
|
| 627 |
+
└─ rank_bm25
|
| 628 |
+
|
| 629 |
+
src.retrieval.strategies.bm25_vector
|
| 630 |
+
├─ src.retrieval.strategies.vector
|
| 631 |
+
├─ src.retrieval.strategies.bm25
|
| 632 |
+
└─ src.retrieval.fusion
|
| 633 |
+
|
| 634 |
+
src.main_document_loader
|
| 635 |
+
├─ src.config_loader
|
| 636 |
+
├─ src.document_processor
|
| 637 |
+
├─ src.llm_handler
|
| 638 |
+
├─ tiktoken
|
| 639 |
+
└─ pathlib, hashlib, time
|
| 640 |
+
|
| 641 |
+
src.response_enhancer
|
| 642 |
+
├─ src.config_loader
|
| 643 |
+
└─ re (regex pattern matching)
|
| 644 |
+
|
| 645 |
+
src.vectorstore
|
| 646 |
+
├─ src.config_loader
|
| 647 |
+
├─ chromadb
|
| 648 |
+
└─ langchain_huggingface
|
| 649 |
+
|
| 650 |
+
src.llm_handler
|
| 651 |
+
├─ src.config_loader
|
| 652 |
+
├─ langchain_community.llms (Ollama)
|
| 653 |
+
└─ langchain_groq (Groq support)
|
| 654 |
+
|
| 655 |
+
src.document_processor
|
| 656 |
+
├─ src.config_loader
|
| 657 |
+
├─ pypdf
|
| 658 |
+
├─ python-docx
|
| 659 |
+
├─ beautifulsoup4
|
| 660 |
+
└─ langchain
|
| 661 |
+
|
| 662 |
+
src.config_loader
|
| 663 |
+
├─ pyyaml
|
| 664 |
+
└─ python-dotenv
|
| 665 |
+
|
| 666 |
+
src.build_vectorstore
|
| 667 |
+
├─ src.document_processor
|
| 668 |
+
├─ src.retrieval (RetrieverFactory)
|
| 669 |
+
└─ src.vectorstore
|
| 670 |
+
```
|
| 671 |
+
|
| 672 |
+
## Performance Characteristics
|
| 673 |
+
|
| 674 |
+
### Indexing (One-time)
|
| 675 |
+
|
| 676 |
+
| Documents | Chunks | Embedding Time | ChromaDB Insert | Total |
|
| 677 |
+
|-----------|--------|----------------|-----------------|-------|
|
| 678 |
+
| 5 PDFs | ~100 | ~5 seconds | ~1 second | ~6s |
|
| 679 |
+
| 20 PDFs | ~400 | ~20 seconds | ~2 seconds | ~22s |
|
| 680 |
+
| 50 PDFs | ~1000 | ~50 seconds | ~5 seconds | ~55s |
|
| 681 |
+
|
| 682 |
+
### Query (Runtime)
|
| 683 |
+
|
| 684 |
+
| Step | Time (CPU) | Time (GPU) | Notes |
|
| 685 |
+
|------|------------|------------|-------|
|
| 686 |
+
| Chat history extraction | <1ms | <1ms | Token counting overhead |
|
| 687 |
+
| Main doc loading | <1ms | <1ms | Cached after first load |
|
| 688 |
+
| Embed query | 50ms | 10ms | Sentence transformers |
|
| 689 |
+
| Vector search | 10-50ms | 10-50ms | ChromaDB HNSW |
|
| 690 |
+
| BM25 search | 5-20ms | 5-20ms | In-memory index |
|
| 691 |
+
| Fusion (RRF) | <1ms | <1ms | Rank combination |
|
| 692 |
+
| LLM inference (Ollama) | 2-5s | 0.5-1s | Local model |
|
| 693 |
+
| LLM inference (Groq) | 0.5-2s | N/A | Cloud API |
|
| 694 |
+
| Response enhancement | <10ms | <10ms | Post-processing |
|
| 695 |
+
| **Total (Ollama)** | **2-5s** | **0.5-1s** | |
|
| 696 |
+
| **Total (Groq)** | **0.6-2s** | **N/A** | Faster cloud option |
|
| 697 |
+
|
| 698 |
+
### Memory Usage
|
| 699 |
+
|
| 700 |
+
| Component | RAM | Disk |
|
| 701 |
+
|-----------|-----|------|
|
| 702 |
+
| Streamlit | ~200MB | - |
|
| 703 |
+
| Ollama (llama3.2:3b) | ~2GB | ~2GB |
|
| 704 |
+
| ChromaDB | ~100MB | ~50MB per 1k docs |
|
| 705 |
+
| BM25 Index | ~50MB | ~10MB per 1k docs |
|
| 706 |
+
| Embeddings | ~500MB | ~500MB |
|
| 707 |
+
| Chat History | ~1-10MB | - |
|
| 708 |
+
| Response Enhancer | <1MB | - |
|
| 709 |
+
| **Total** | **~3GB** | **~3GB** |
|
| 710 |
+
|
| 711 |
+
## Security Architecture
|
| 712 |
+
|
| 713 |
+
```
|
| 714 |
+
User Input
|
| 715 |
+
↓
|
| 716 |
+
┌─────────────────────┐
|
| 717 |
+
│ Input Validation │ ← Length limits
|
| 718 |
+
│ (Streamlit) │ ← Character filtering
|
| 719 |
+
└─────────┬───────────┘
|
| 720 |
+
↓
|
| 721 |
+
┌─────────────────────┐
|
| 722 |
+
│ RAG Pipeline │ ← Context isolation
|
| 723 |
+
└─────────┬───────────┘
|
| 724 |
+
↓
|
| 725 |
+
┌─────────────────────┐
|
| 726 |
+
│ LLM (Local) │ ← No external API calls
|
| 727 |
+
│ │ ← Data stays local
|
| 728 |
+
└─────────────────────┘
|
| 729 |
+
|
| 730 |
+
Secrets Management:
|
| 731 |
+
├─ .env (local)
|
| 732 |
+
├─ .gitignore (.env excluded)
|
| 733 |
+
└─ HF Spaces Secrets (cloud)
|
| 734 |
+
```
|
| 735 |
+
|
| 736 |
+
## Extensibility Points
|
| 737 |
+
|
| 738 |
+
### 🔌 Plugin Architecture
|
| 739 |
+
|
| 740 |
+
```python
|
| 741 |
+
# Easy to extend:
|
| 742 |
+
|
| 743 |
+
# 1. New document types
|
| 744 |
+
DocumentProcessor.load_custom_format()
|
| 745 |
+
|
| 746 |
+
# 2. New LLM providers
|
| 747 |
+
LLMHandler.get_openai_llm()
|
| 748 |
+
LLMHandler.get_anthropic_llm()
|
| 749 |
+
# Already supports: Ollama (local), Groq (cloud)
|
| 750 |
+
|
| 751 |
+
# 3. New retrieval strategies (extensible system!)
|
| 752 |
+
@RetrieverFactory.register("page_index")
|
| 753 |
+
class PageIndexStrategy(BaseRetrieverStrategy):
|
| 754 |
+
"""Vision-based document retrieval (ColPali)"""
|
| 755 |
+
...
|
| 756 |
+
|
| 757 |
+
@RetrieverFactory.register("graph_vector")
|
| 758 |
+
class GraphVectorStrategy(BaseRetrieverStrategy):
|
| 759 |
+
"""Knowledge graph + vector hybrid"""
|
| 760 |
+
...
|
| 761 |
+
|
| 762 |
+
# 4. New UI features
|
| 763 |
+
app.py → add_authentication()
|
| 764 |
+
app.py → add_analytics()
|
| 765 |
+
# Already supports: Chat history, provider switching, streaming responses
|
| 766 |
+
|
| 767 |
+
# 5. New embedding models
|
| 768 |
+
VectorStoreManager(embedding_model="...")
|
| 769 |
+
|
| 770 |
+
# 6. New fusion algorithms
|
| 771 |
+
# Add to src/retrieval/fusion.py
|
| 772 |
+
def custom_fusion(results_list, weights):
|
| 773 |
+
...
|
| 774 |
+
|
| 775 |
+
# 7. New response enhancement patterns
|
| 776 |
+
# Edit src/response_enhancer.py
|
| 777 |
+
ResponseEnhancer.negative_patterns.append((pattern, rewrite_func))
|
| 778 |
+
|
| 779 |
+
# 8. Custom chat history strategies
|
| 780 |
+
# Modify truncate_chat_history() in app.py
|
| 781 |
+
# Add custom truncation logic (e.g., importance-based)
|
| 782 |
+
```
|
| 783 |
+
|
| 784 |
+
### Adding a New Retrieval Strategy
|
| 785 |
+
|
| 786 |
+
1. Create `src/retrieval/strategies/my_strategy.py`
|
| 787 |
+
2. Implement `BaseRetrieverStrategy` interface
|
| 788 |
+
3. Register with `@RetrieverFactory.register("my_strategy")`
|
| 789 |
+
4. Add config section in `config.yaml`
|
| 790 |
+
5. Import in `src/retrieval/strategies/__init__.py`
|
| 791 |
+
|
| 792 |
+
```python
|
| 793 |
+
from src.retrieval import RetrieverFactory
|
| 794 |
+
from src.retrieval.base import BaseRetrieverStrategy
|
| 795 |
+
|
| 796 |
+
@RetrieverFactory.register("my_strategy")
|
| 797 |
+
class MyStrategy(BaseRetrieverStrategy):
|
| 798 |
+
@property
|
| 799 |
+
def name(self) -> str:
|
| 800 |
+
return "my_strategy"
|
| 801 |
+
|
| 802 |
+
def build_index(self, documents): ...
|
| 803 |
+
def load_index(self) -> bool: ...
|
| 804 |
+
def retrieve(self, query, k=4): ...
|
| 805 |
+
def as_retriever(self, **kwargs): ...
|
| 806 |
+
```
|
| 807 |
+
|
| 808 |
+
---
|
| 809 |
+
|
| 810 |
+
**This architecture prioritizes:**
|
| 811 |
+
- ✅ Simplicity (easy to understand)
|
| 812 |
+
- ✅ Modularity (easy to extend)
|
| 813 |
+
- ✅ Performance (optimized for small-medium datasets)
|
| 814 |
+
- ✅ Privacy (local processing)
|
| 815 |
+
- ✅ Deployability (cloud-ready)
|
Dockerfile
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Dockerfile for HuggingFace Spaces with Ollama support
|
| 2 |
+
FROM python:3.11-slim
|
| 3 |
+
|
| 4 |
+
# Set working directory
|
| 5 |
+
WORKDIR /app
|
| 6 |
+
|
| 7 |
+
# Install system dependencies
|
| 8 |
+
RUN apt-get update && apt-get install -y \
|
| 9 |
+
curl \
|
| 10 |
+
build-essential \
|
| 11 |
+
zstd \
|
| 12 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 13 |
+
|
| 14 |
+
# Install Ollama
|
| 15 |
+
RUN curl -fsSL https://ollama.ai/install.sh | sh
|
| 16 |
+
|
| 17 |
+
# Copy requirements
|
| 18 |
+
COPY requirements.txt .
|
| 19 |
+
|
| 20 |
+
# Install CPU-only PyTorch first (saves ~2GB by avoiding CUDA dependencies)
|
| 21 |
+
RUN pip install --no-cache-dir torch --index-url https://download.pytorch.org/whl/cpu
|
| 22 |
+
|
| 23 |
+
# Install Python dependencies
|
| 24 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 25 |
+
|
| 26 |
+
# Copy application files
|
| 27 |
+
COPY . .
|
| 28 |
+
|
| 29 |
+
# Create necessary directories
|
| 30 |
+
RUN mkdir -p data/documents chroma_db bm25_index
|
| 31 |
+
|
| 32 |
+
# Expose Streamlit port
|
| 33 |
+
EXPOSE 7860
|
| 34 |
+
|
| 35 |
+
# Expose Ollama port
|
| 36 |
+
EXPOSE 11434
|
| 37 |
+
|
| 38 |
+
# Start script that runs both Ollama and Streamlit
|
| 39 |
+
COPY <<'EOF' /app/start.sh
|
| 40 |
+
#!/bin/bash
|
| 41 |
+
set -e
|
| 42 |
+
|
| 43 |
+
# Start Ollama in the background
|
| 44 |
+
ollama serve &
|
| 45 |
+
OLLAMA_PID=$!
|
| 46 |
+
|
| 47 |
+
# Wait for Ollama to be ready
|
| 48 |
+
echo "Waiting for Ollama to start..."
|
| 49 |
+
sleep 5
|
| 50 |
+
|
| 51 |
+
# Pull the model (use small model for HF Spaces)
|
| 52 |
+
MODEL=${OLLAMA_MODEL:-llama3.2:3b}
|
| 53 |
+
echo "Pulling model: $MODEL"
|
| 54 |
+
ollama pull $MODEL || echo "Model pull failed, will retry on app start"
|
| 55 |
+
|
| 56 |
+
# Build retrieval indexes if documents exist and indexes don't
|
| 57 |
+
if [ -d "data/documents" ] && [ ! -f "chroma_db/chroma.sqlite3" ]; then
|
| 58 |
+
echo "Building retrieval indexes (BM25 + Vector)..."
|
| 59 |
+
python -m src.build_vectorstore || echo "Index build failed, will build on first use"
|
| 60 |
+
fi
|
| 61 |
+
|
| 62 |
+
# Start Streamlit
|
| 63 |
+
streamlit run app.py --server.port=7860 --server.address=0.0.0.0
|
| 64 |
+
EOF
|
| 65 |
+
|
| 66 |
+
RUN chmod +x /app/start.sh
|
| 67 |
+
|
| 68 |
+
CMD ["/app/start.sh"]
|
Dockerfile.app
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Dockerfile for Streamlit RAG Application (for Docker Compose)
|
| 2 |
+
# Use with docker-compose.yml - Ollama runs as a separate service
|
| 3 |
+
FROM python:3.11-slim
|
| 4 |
+
|
| 5 |
+
# Set working directory
|
| 6 |
+
WORKDIR /app
|
| 7 |
+
|
| 8 |
+
# Install system dependencies
|
| 9 |
+
RUN apt-get update && apt-get install -y \
|
| 10 |
+
curl \
|
| 11 |
+
build-essential \
|
| 12 |
+
zstd \
|
| 13 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 14 |
+
|
| 15 |
+
# Copy requirements
|
| 16 |
+
COPY requirements.txt .
|
| 17 |
+
|
| 18 |
+
# Install CPU-only PyTorch first (saves ~2GB by avoiding CUDA dependencies)
|
| 19 |
+
RUN pip install --no-cache-dir torch --index-url https://download.pytorch.org/whl/cpu
|
| 20 |
+
|
| 21 |
+
# Install Python dependencies
|
| 22 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 23 |
+
|
| 24 |
+
# Copy application files
|
| 25 |
+
COPY src/ ./src/
|
| 26 |
+
COPY app.py .
|
| 27 |
+
COPY config.yaml .
|
| 28 |
+
COPY .streamlit/ ./.streamlit/
|
| 29 |
+
|
| 30 |
+
# Create necessary directories
|
| 31 |
+
RUN mkdir -p data/documents chroma_db bm25_index
|
| 32 |
+
|
| 33 |
+
# Create startup script
|
| 34 |
+
COPY <<'EOF' /app/start.sh
|
| 35 |
+
#!/bin/bash
|
| 36 |
+
set -e
|
| 37 |
+
|
| 38 |
+
echo "Starting ProfillyBot Application..."
|
| 39 |
+
|
| 40 |
+
# Wait for Ollama to be available (if using Ollama)
|
| 41 |
+
if [ -n "$OLLAMA_HOST" ]; then
|
| 42 |
+
echo "Waiting for Ollama at $OLLAMA_HOST..."
|
| 43 |
+
for i in {1..30}; do
|
| 44 |
+
if curl -sf "$OLLAMA_HOST/api/tags" > /dev/null 2>&1; then
|
| 45 |
+
echo "Ollama is ready!"
|
| 46 |
+
break
|
| 47 |
+
fi
|
| 48 |
+
echo "Waiting for Ollama... ($i/30)"
|
| 49 |
+
sleep 2
|
| 50 |
+
done
|
| 51 |
+
fi
|
| 52 |
+
|
| 53 |
+
# Build retrieval indexes if documents exist and indexes don't
|
| 54 |
+
if [ -d "data/documents" ] && [ "$(ls -A data/documents 2>/dev/null)" ]; then
|
| 55 |
+
if [ ! -f "chroma_db/chroma.sqlite3" ]; then
|
| 56 |
+
echo "Building retrieval indexes (BM25 + Vector)..."
|
| 57 |
+
python -m src.build_vectorstore || echo "Index build failed, will build on first use"
|
| 58 |
+
else
|
| 59 |
+
echo "Indexes already exist, skipping build"
|
| 60 |
+
fi
|
| 61 |
+
fi
|
| 62 |
+
|
| 63 |
+
# Start Streamlit
|
| 64 |
+
echo "Starting Streamlit on port ${STREAMLIT_SERVER_PORT:-7860}..."
|
| 65 |
+
exec streamlit run app.py \
|
| 66 |
+
--server.port=${STREAMLIT_SERVER_PORT:-7860} \
|
| 67 |
+
--server.address=${STREAMLIT_SERVER_ADDRESS:-0.0.0.0} \
|
| 68 |
+
--server.headless=true
|
| 69 |
+
EOF
|
| 70 |
+
|
| 71 |
+
RUN chmod +x /app/start.sh
|
| 72 |
+
|
| 73 |
+
# Expose Streamlit port
|
| 74 |
+
EXPOSE 7860
|
| 75 |
+
|
| 76 |
+
# Health check
|
| 77 |
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
|
| 78 |
+
CMD curl -f http://localhost:7860/_stcore/health || exit 1
|
| 79 |
+
|
| 80 |
+
CMD ["/app/start.sh"]
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2025 Tin-Hoang
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
Makefile
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Makefile for ProfillyBot
|
| 2 |
+
# Run 'make help' to see available commands
|
| 3 |
+
|
| 4 |
+
.PHONY: help install install-dev test test-cov lint format clean \
|
| 5 |
+
docker-up docker-down docker-build docker-logs docker-shell \
|
| 6 |
+
docker-rebuild docker-ps ollama-pull index-build run
|
| 7 |
+
|
| 8 |
+
# Default target
|
| 9 |
+
.DEFAULT_GOAL := help
|
| 10 |
+
|
| 11 |
+
# Variables
|
| 12 |
+
PYTHON := python
|
| 13 |
+
DOCKER_COMPOSE := docker compose
|
| 14 |
+
APP_PORT := 7860
|
| 15 |
+
OLLAMA_MODEL ?= llama3.2:3b
|
| 16 |
+
|
| 17 |
+
# =============================================================================
|
| 18 |
+
# Help
|
| 19 |
+
# =============================================================================
|
| 20 |
+
help: ## Show this help message
|
| 21 |
+
@echo "Usage: make [target]"
|
| 22 |
+
@echo ""
|
| 23 |
+
@echo "Available targets:"
|
| 24 |
+
@awk 'BEGIN {FS = ":.*##"; printf ""} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-20s\033[0m %s\n", $$1, $$2 }' $(MAKEFILE_LIST)
|
| 25 |
+
|
| 26 |
+
# =============================================================================
|
| 27 |
+
# Development Setup
|
| 28 |
+
# =============================================================================
|
| 29 |
+
install: ## Install dependencies
|
| 30 |
+
pip install -r requirements.txt
|
| 31 |
+
|
| 32 |
+
install-dev: ## Install development dependencies
|
| 33 |
+
pip install -e ".[dev]"
|
| 34 |
+
|
| 35 |
+
install-uv: ## Install dependencies using uv (faster)
|
| 36 |
+
uv pip install -r requirements.txt
|
| 37 |
+
|
| 38 |
+
install-uv-dev: ## Install dev dependencies using uv
|
| 39 |
+
uv pip install -e ".[dev]"
|
| 40 |
+
|
| 41 |
+
# =============================================================================
|
| 42 |
+
# Testing & Quality
|
| 43 |
+
# =============================================================================
|
| 44 |
+
test: ## Run tests
|
| 45 |
+
pytest tests/ -v
|
| 46 |
+
|
| 47 |
+
test-cov: ## Run tests with coverage report
|
| 48 |
+
pytest tests/ -v --cov=src --cov-report=term-missing --cov-report=html
|
| 49 |
+
|
| 50 |
+
lint: ## Run linter (ruff)
|
| 51 |
+
ruff check src/ tests/ app.py
|
| 52 |
+
|
| 53 |
+
lint-fix: ## Run linter and fix issues
|
| 54 |
+
ruff check src/ tests/ app.py --fix
|
| 55 |
+
|
| 56 |
+
format: ## Format code (ruff)
|
| 57 |
+
ruff format src/ tests/ app.py
|
| 58 |
+
|
| 59 |
+
format-check: ## Check code formatting
|
| 60 |
+
ruff format src/ tests/ app.py --check
|
| 61 |
+
|
| 62 |
+
check: lint format-check ## Run all checks (lint + format)
|
| 63 |
+
|
| 64 |
+
# =============================================================================
|
| 65 |
+
# Local Development
|
| 66 |
+
# =============================================================================
|
| 67 |
+
run: ## Run the Streamlit app locally
|
| 68 |
+
streamlit run app.py --server.port=$(APP_PORT)
|
| 69 |
+
|
| 70 |
+
index-build: ## Build vector and BM25 indexes
|
| 71 |
+
$(PYTHON) -m src.build_vectorstore
|
| 72 |
+
|
| 73 |
+
index-clean: ## Remove existing indexes
|
| 74 |
+
rm -rf chroma_db/ bm25_index/
|
| 75 |
+
|
| 76 |
+
index-rebuild: index-clean index-build ## Rebuild indexes from scratch
|
| 77 |
+
|
| 78 |
+
# =============================================================================
|
| 79 |
+
# Docker Compose
|
| 80 |
+
# =============================================================================
|
| 81 |
+
docker-up: ## Start all services
|
| 82 |
+
$(DOCKER_COMPOSE) up -d
|
| 83 |
+
|
| 84 |
+
docker-down: ## Stop all services
|
| 85 |
+
$(DOCKER_COMPOSE) down
|
| 86 |
+
|
| 87 |
+
docker-build: ## Build Docker images
|
| 88 |
+
$(DOCKER_COMPOSE) build
|
| 89 |
+
|
| 90 |
+
docker-rebuild: ## Rebuild and restart services
|
| 91 |
+
$(DOCKER_COMPOSE) up -d --build
|
| 92 |
+
|
| 93 |
+
docker-logs: ## Show logs (follow mode)
|
| 94 |
+
$(DOCKER_COMPOSE) logs -f
|
| 95 |
+
|
| 96 |
+
docker-logs-app: ## Show app logs only
|
| 97 |
+
$(DOCKER_COMPOSE) logs -f app
|
| 98 |
+
|
| 99 |
+
docker-ps: ## Show running containers
|
| 100 |
+
$(DOCKER_COMPOSE) ps
|
| 101 |
+
|
| 102 |
+
docker-shell: ## Open shell in app container
|
| 103 |
+
$(DOCKER_COMPOSE) exec app /bin/bash
|
| 104 |
+
|
| 105 |
+
docker-clean: ## Stop services and remove volumes
|
| 106 |
+
$(DOCKER_COMPOSE) down -v
|
| 107 |
+
|
| 108 |
+
docker-restart: docker-down docker-up ## Restart all services
|
| 109 |
+
|
| 110 |
+
# =============================================================================
|
| 111 |
+
# Ollama Commands
|
| 112 |
+
# =============================================================================
|
| 113 |
+
ollama-pull: ## Pull the configured Ollama model
|
| 114 |
+
$(DOCKER_COMPOSE) exec ollama ollama pull $(OLLAMA_MODEL)
|
| 115 |
+
|
| 116 |
+
ollama-list: ## List available Ollama models
|
| 117 |
+
$(DOCKER_COMPOSE) exec ollama ollama list
|
| 118 |
+
|
| 119 |
+
ollama-shell: ## Open shell in Ollama container
|
| 120 |
+
$(DOCKER_COMPOSE) exec ollama /bin/bash
|
| 121 |
+
|
| 122 |
+
# =============================================================================
|
| 123 |
+
# Setup & Utilities
|
| 124 |
+
# =============================================================================
|
| 125 |
+
setup: ## Initial project setup (copy env, install deps)
|
| 126 |
+
@if [ ! -f .env ]; then cp .env.template .env && echo "Created .env from template"; fi
|
| 127 |
+
@echo "Installing dependencies..."
|
| 128 |
+
@make install-dev
|
| 129 |
+
@echo "Setup complete! Edit .env if needed, then run 'make run' or 'make docker-up'"
|
| 130 |
+
|
| 131 |
+
clean: ## Clean up build artifacts and caches
|
| 132 |
+
rm -rf __pycache__ .pytest_cache .ruff_cache htmlcov .coverage
|
| 133 |
+
find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
|
| 134 |
+
find . -type f -name "*.pyc" -delete 2>/dev/null || true
|
| 135 |
+
|
| 136 |
+
clean-all: clean index-clean ## Clean everything including indexes
|
| 137 |
+
rm -rf *.egg-info build dist
|
| 138 |
+
|
| 139 |
+
# =============================================================================
|
| 140 |
+
# Pre-commit
|
| 141 |
+
# =============================================================================
|
| 142 |
+
pre-commit-install: ## Install pre-commit hooks
|
| 143 |
+
pre-commit install
|
| 144 |
+
|
| 145 |
+
pre-commit-run: ## Run pre-commit on all files
|
| 146 |
+
pre-commit run --all-files
|
README.md
CHANGED
|
@@ -1,13 +1,423 @@
|
|
| 1 |
---
|
| 2 |
title: ProfillyBot
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version:
|
| 8 |
-
python_version:
|
| 9 |
-
app_file:
|
| 10 |
pinned: false
|
|
|
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
title: ProfillyBot
|
| 3 |
+
emoji: 🤖
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: green
|
| 6 |
sdk: gradio
|
| 7 |
+
sdk_version: 5.49.1
|
| 8 |
+
python_version: "3.11"
|
| 9 |
+
app_file: gradio_app.py
|
| 10 |
pinned: false
|
| 11 |
+
license: mit
|
| 12 |
+
short_description: Cute RAG chatbot for professional profile Q&A on ZeroGPU
|
| 13 |
---
|
| 14 |
|
| 15 |
+
# 🤖 ProfillyBot
|
| 16 |
+
|
| 17 |
+
<p align="center">
|
| 18 |
+
<img src="docs/banner_image.png" alt="ProfillyBot Banner" width="100%" />
|
| 19 |
+
</p>
|
| 20 |
+
|
| 21 |
+
<p align="center">
|
| 22 |
+
<img src="https://github.com/Tin-Hoang/slm-profile-rag/actions/workflows/test-python310.yml/badge.svg" alt="Python 3.10"/>
|
| 23 |
+
<img src="https://github.com/Tin-Hoang/slm-profile-rag/actions/workflows/test-python311.yml/badge.svg" alt="Python 3.11"/>
|
| 24 |
+
<img src="https://github.com/Tin-Hoang/slm-profile-rag/actions/workflows/test-python312.yml/badge.svg" alt="Python 3.12"/>
|
| 25 |
+
<img src="https://github.com/Tin-Hoang/slm-profile-rag/actions/workflows/test-docker.yml/badge.svg" alt="Docker"/>
|
| 26 |
+
<img src="https://github.com/Tin-Hoang/slm-profile-rag/actions/workflows/lint.yml/badge.svg" alt="Lint"/>
|
| 27 |
+
<img src="https://github.com/Tin-Hoang/slm-profile-rag/actions/workflows/code-quality.yml/badge.svg" alt="Code Quality"/>
|
| 28 |
+
<img src="https://github.com/Tin-Hoang/slm-profile-rag/actions/workflows/security-scan.yml/badge.svg" alt="Security Scan"/>
|
| 29 |
+
<a href="https://codecov.io/gh/Tin-Hoang/slm-profile-rag">
|
| 30 |
+
<img src="https://codecov.io/gh/Tin-Hoang/slm-profile-rag/graph/badge.svg?token=MW4RD9R66J" alt="Codecov"/>
|
| 31 |
+
</a>
|
| 32 |
+
</p>
|
| 33 |
+
|
| 34 |
+
**ProfillyBot** is a RAG (Retrieval Augmented Generation) chatbot that answers questions about your professional profile using your resume, project reports, and other documents.
|
| 35 |
+
|
| 36 |
+
## ✨ Features
|
| 37 |
+
|
| 38 |
+
- 📄 **Multi-format Support**: Process PDF, Word, HTML, and text documents
|
| 39 |
+
- 🔍 **Hybrid Search**: Combines BM25 (keyword) + Vector (semantic) search with Reciprocal Rank Fusion
|
| 40 |
+
- 🧠 **Extensible Retrieval**: Pluggable strategy system - easily add new retrieval methods (PageIndex, GraphRAG, etc.)
|
| 41 |
+
- 📌 **Main Document Support**: Guaranteed context - critical information always available, auto-format detection
|
| 42 |
+
- 🦙 **Ollama Integration**: Run small language models locally
|
| 43 |
+
- 🎨 **Clean UI**: Streamlit-based interface
|
| 44 |
+
- ⚙️ **Highly Configurable**: YAML-based settings for easy customization
|
| 45 |
+
- 🚀 **HuggingFace Spaces Ready**: Deploy with one click
|
| 46 |
+
- ✨ **Smart Response Enhancement**: Automatically removes negative language and adds professional, recruiter-friendly tone
|
| 47 |
+
|
| 48 |
+
## 🏗️ Architecture
|
| 49 |
+
|
| 50 |
+
<p align="center">
|
| 51 |
+
<img src="docs/arch_system_design.png" alt="System Architecture Diagram" width="800" />
|
| 52 |
+
</p>
|
| 53 |
+
<p align="center"><em>Overall system design showing the RAG pipeline and component interactions</em></p>
|
| 54 |
+
|
| 55 |
+
Detailed architecture can be found in [ARCHITECTURE.md](ARCHITECTURE.md).
|
| 56 |
+
|
| 57 |
+
## 🛠️ Tech Stack
|
| 58 |
+
|
| 59 |
+
- **Python 3.10+** with UV package manager
|
| 60 |
+
- **LangChain** for RAG pipeline
|
| 61 |
+
- **ChromaDB** for vector storage
|
| 62 |
+
- **rank-bm25** for lexical search
|
| 63 |
+
- **Ollama** for local LLM serving
|
| 64 |
+
- **Streamlit** for web interface
|
| 65 |
+
- **sentence-transformers** for embeddings
|
| 66 |
+
- **tiktoken** for token counting
|
| 67 |
+
- **Ruff** for linting/formatting
|
| 68 |
+
|
| 69 |
+
## 📦 Installation
|
| 70 |
+
|
| 71 |
+
### Prerequisites
|
| 72 |
+
|
| 73 |
+
1. **Python 3.10+**
|
| 74 |
+
2. **UV Package Manager**: Install via:
|
| 75 |
+
```bash
|
| 76 |
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
| 77 |
+
```
|
| 78 |
+
3. **Ollama**: Install from [ollama.ai](https://ollama.ai)
|
| 79 |
+
|
| 80 |
+
### Setup
|
| 81 |
+
|
| 82 |
+
1. **Clone the repository**:
|
| 83 |
+
```bash
|
| 84 |
+
git clone <your-repo-url>
|
| 85 |
+
cd profillybot
|
| 86 |
+
```
|
| 87 |
+
|
| 88 |
+
2. **Install dependencies with UV**:
|
| 89 |
+
```bash
|
| 90 |
+
uv venv
|
| 91 |
+
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
| 92 |
+
uv pip install -r requirements.txt
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
+
3. **Set up environment variables**:
|
| 96 |
+
```bash
|
| 97 |
+
cp .env.template .env
|
| 98 |
+
# Edit .env with your settings
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
4. **Configure the chatbot**:
|
| 102 |
+
- Edit `config.yaml` to set your name, title, and preferences
|
| 103 |
+
- Adjust model settings, chunking parameters, and system prompt
|
| 104 |
+
|
| 105 |
+
5. **Add your documents**:
|
| 106 |
+
```bash
|
| 107 |
+
# Place your PDFs, Word docs, HTML files in:
|
| 108 |
+
mkdir -p data/documents
|
| 109 |
+
# Copy your resume, project reports, LinkedIn profile, etc.
|
| 110 |
+
```
|
| 111 |
+
|
| 112 |
+
6. **Create your Main Profile Document** (📌 **Recommended**):
|
| 113 |
+
|
| 114 |
+
The Main Document feature ensures critical information is **always** included in responses, never missed by vector search.
|
| 115 |
+
|
| 116 |
+
```bash
|
| 117 |
+
# Create main profile document (any format: .md, .txt, .pdf, .docx, .html)
|
| 118 |
+
nano data/documents/main_profile.md
|
| 119 |
+
```
|
| 120 |
+
|
| 121 |
+
**Include essential information:**
|
| 122 |
+
- Full name, title, contact info
|
| 123 |
+
- Current role and key responsibilities
|
| 124 |
+
- Core skills and expertise
|
| 125 |
+
- Major projects with metrics
|
| 126 |
+
- Education and certifications
|
| 127 |
+
|
| 128 |
+
**Example structure:**
|
| 129 |
+
```markdown
|
| 130 |
+
# Your Name
|
| 131 |
+
**Title**: Your Professional Title
|
| 132 |
+
**Email**: your.email@example.com
|
| 133 |
+
|
| 134 |
+
## Professional Summary
|
| 135 |
+
Brief summary of your experience...
|
| 136 |
+
|
| 137 |
+
## Core Skills
|
| 138 |
+
- Skill 1, Skill 2, Skill 3
|
| 139 |
+
|
| 140 |
+
## Current Role
|
| 141 |
+
### Company - Role (Dates)
|
| 142 |
+
- Achievement 1 with metrics
|
| 143 |
+
- Achievement 2 with metrics
|
| 144 |
+
|
| 145 |
+
## Education
|
| 146 |
+
- Degree, University, Year
|
| 147 |
+
```
|
| 148 |
+
|
| 149 |
+
**Why use this?**
|
| 150 |
+
- ✅ **Guaranteed Context**: Critical info never missed by vector similarity search
|
| 151 |
+
- ✅ **Priority Positioning**: Appears BEFORE retrieved chunks (higher LLM attention)
|
| 152 |
+
- ✅ **Auto-Format Detection**: Supports MD, TXT, PDF, DOCX, HTML automatically
|
| 153 |
+
- ✅ **Smart Token Management**: LLM-based summarization if content exceeds 10k tokens
|
| 154 |
+
|
| 155 |
+
The feature is enabled by default in `config.yaml`. See [docs/MAIN_DOCUMENT_GUIDE.md](docs/MAIN_DOCUMENT_GUIDE.md) for advanced configuration.
|
| 156 |
+
|
| 157 |
+
7. **Pull Ollama model**:
|
| 158 |
+
```bash
|
| 159 |
+
ollama pull llama3.2:3b
|
| 160 |
+
# Or other small models: phi3:mini, gemma2:2b
|
| 161 |
+
```
|
| 162 |
+
|
| 163 |
+
## 🚀 Usage
|
| 164 |
+
|
| 165 |
+
### Local Development
|
| 166 |
+
|
| 167 |
+
1. **Build the retrieval index** (default: hybrid BM25 + Vector):
|
| 168 |
+
```bash
|
| 169 |
+
# Build with default strategy from config (bm25_vector)
|
| 170 |
+
python -m src.build_vectorstore
|
| 171 |
+
|
| 172 |
+
# Or specify a strategy explicitly
|
| 173 |
+
python -m src.build_vectorstore --strategy bm25_vector # Hybrid (recommended)
|
| 174 |
+
python -m src.build_vectorstore --strategy vector # Vector-only
|
| 175 |
+
python -m src.build_vectorstore --strategy bm25 # BM25-only
|
| 176 |
+
```
|
| 177 |
+
|
| 178 |
+
2. **Run the Streamlit app**:
|
| 179 |
+
```bash
|
| 180 |
+
streamlit run app.py
|
| 181 |
+
```
|
| 182 |
+
|
| 183 |
+
3. **Open browser**: Navigate to `http://localhost:8501`
|
| 184 |
+
|
| 185 |
+
**Screenshot Example:**
|
| 186 |
+
|
| 187 |
+
<p align="center">
|
| 188 |
+
<img src="docs/screenshot-example.png" alt="ProfillyBot UI Example" width="800" />
|
| 189 |
+
</p>
|
| 190 |
+
<p align="center"><em>Streamlit chatbot interface in action</em></p>
|
| 191 |
+
|
| 192 |
+
### 🐳 Docker Compose
|
| 193 |
+
|
| 194 |
+
Run the complete stack with Docker Compose (includes Ollama):
|
| 195 |
+
|
| 196 |
+
```bash
|
| 197 |
+
# Copy environment template
|
| 198 |
+
cp .env.template .env
|
| 199 |
+
|
| 200 |
+
# Start all services (app + Ollama)
|
| 201 |
+
docker compose up -d
|
| 202 |
+
|
| 203 |
+
# View logs
|
| 204 |
+
docker compose logs -f
|
| 205 |
+
|
| 206 |
+
# Stop services
|
| 207 |
+
docker compose down
|
| 208 |
+
```
|
| 209 |
+
|
| 210 |
+
Access the app at `http://localhost:7860`
|
| 211 |
+
|
| 212 |
+
### 📋 Makefile Commands
|
| 213 |
+
|
| 214 |
+
Use the Makefile for common development tasks:
|
| 215 |
+
|
| 216 |
+
```bash
|
| 217 |
+
make help # Show all available commands
|
| 218 |
+
```
|
| 219 |
+
|
| 220 |
+
| Category | Command | Description |
|
| 221 |
+
|----------|---------|-------------|
|
| 222 |
+
| **Setup** | `make setup` | Initial project setup (copy env, install deps) |
|
| 223 |
+
| | `make install` | Install dependencies |
|
| 224 |
+
| | `make install-dev` | Install with dev dependencies |
|
| 225 |
+
| **Development** | `make run` | Run Streamlit app locally |
|
| 226 |
+
| | `make index-build` | Build vector and BM25 indexes |
|
| 227 |
+
| | `make index-rebuild` | Rebuild indexes from scratch |
|
| 228 |
+
| **Quality** | `make test` | Run tests |
|
| 229 |
+
| | `make test-cov` | Run tests with coverage |
|
| 230 |
+
| | `make lint` | Run linter (ruff) |
|
| 231 |
+
| | `make format` | Format code |
|
| 232 |
+
| | `make check` | Run all checks |
|
| 233 |
+
| **Docker** | `make docker-up` | Start all services |
|
| 234 |
+
| | `make docker-down` | Stop all services |
|
| 235 |
+
| | `make docker-rebuild` | Rebuild and restart |
|
| 236 |
+
| | `make docker-logs` | Show logs (follow mode) |
|
| 237 |
+
| | `make docker-clean` | Stop and remove volumes |
|
| 238 |
+
| **Ollama** | `make ollama-pull` | Pull configured model |
|
| 239 |
+
| | `make ollama-list` | List available models |
|
| 240 |
+
| **Cleanup** | `make clean` | Remove build artifacts |
|
| 241 |
+
| | `make clean-all` | Clean everything + indexes |
|
| 242 |
+
|
| 243 |
+
## 🌐 Deployment to Hugging Face Spaces (ZeroGPU)
|
| 244 |
+
|
| 245 |
+
ProfillyBot on Spaces uses **Gradio + ZeroGPU** with `Qwen/Qwen2.5-3B-Instruct`
|
| 246 |
+
(`gradio_app.py`). Streamlit (`app.py`) remains available for local/Ollama/Groq.
|
| 247 |
+
|
| 248 |
+
### Live Space
|
| 249 |
+
|
| 250 |
+
- [huggingface.co/spaces/MinhDS/ProfillyBot](https://huggingface.co/spaces/MinhDS/ProfillyBot)
|
| 251 |
+
|
| 252 |
+
### Deploy / update
|
| 253 |
+
|
| 254 |
+
1. Create a Gradio Space with **ZeroGPU** hardware (Pro account required to *create* ZeroGPU Spaces).
|
| 255 |
+
2. Ensure this repo has:
|
| 256 |
+
- `data/documents/MY_CV_2_0.pdf` (or your profile docs)
|
| 257 |
+
- Pre-built `chroma_db/` + `bm25_index/` (recommended) — or let the app build on first start
|
| 258 |
+
3. Push to the Space:
|
| 259 |
+
```bash
|
| 260 |
+
git remote add hf https://huggingface.co/spaces/MinhDS/ProfillyBot
|
| 261 |
+
git push hf main
|
| 262 |
+
```
|
| 263 |
+
4. Confirm Space Settings: SDK **Gradio**, hardware **ZeroGPU**, app file `gradio_app.py`.
|
| 264 |
+
|
| 265 |
+
### Important Notes for HF Spaces
|
| 266 |
+
|
| 267 |
+
- **ZeroGPU is Gradio-only** — do not use the Docker/Ollama `Dockerfile` on ZeroGPU
|
| 268 |
+
- **SLM**: `Qwen/Qwen2.5-3B-Instruct` via the `transformers` provider
|
| 269 |
+
- **Embeddings**: `all-MiniLM-L6-v2` on CPU (does not consume GPU quota)
|
| 270 |
+
- **Vector DB**: Pre-build indexes locally when possible for faster cold start
|
| 271 |
+
- **Secrets**: optional `HF_TOKEN` only if you switch to a gated model
|
| 272 |
+
|
| 273 |
+
## ⚙️ Configuration
|
| 274 |
+
|
| 275 |
+
### `config.yaml` - Main Settings
|
| 276 |
+
|
| 277 |
+
```yaml
|
| 278 |
+
profile:
|
| 279 |
+
name: "Your Name" # ← Change this!
|
| 280 |
+
title: "Your Title"
|
| 281 |
+
|
| 282 |
+
llm:
|
| 283 |
+
model: "llama3.2:3b" # Choose your model
|
| 284 |
+
temperature: 0.7
|
| 285 |
+
|
| 286 |
+
# Retrieval Strategy Configuration
|
| 287 |
+
retrieval:
|
| 288 |
+
strategy: "bm25_vector" # Options: vector, bm25, bm25_vector
|
| 289 |
+
final_k: 4 # Number of documents to return
|
| 290 |
+
|
| 291 |
+
vector:
|
| 292 |
+
search_type: "similarity"
|
| 293 |
+
k: 10 # Docs to retrieve before fusion
|
| 294 |
+
|
| 295 |
+
bm25:
|
| 296 |
+
k: 10 # Docs to retrieve before fusion
|
| 297 |
+
tokenizer: "simple"
|
| 298 |
+
|
| 299 |
+
fusion:
|
| 300 |
+
algorithm: "rrf" # Reciprocal Rank Fusion
|
| 301 |
+
weights:
|
| 302 |
+
vector: 0.7 # 70% weight on semantic search
|
| 303 |
+
bm25: 0.3 # 30% weight on keyword search
|
| 304 |
+
|
| 305 |
+
document_processing:
|
| 306 |
+
chunk_size: 1000
|
| 307 |
+
chunk_overlap: 200
|
| 308 |
+
```
|
| 309 |
+
|
| 310 |
+
### `.env` - Environment Variables
|
| 311 |
+
|
| 312 |
+
```bash
|
| 313 |
+
OLLAMA_BASE_URL=http://localhost:11434
|
| 314 |
+
CHROMA_PERSIST_DIR=./chroma_db
|
| 315 |
+
```
|
| 316 |
+
|
| 317 |
+
## 📚 Project Structure
|
| 318 |
+
|
| 319 |
+
```
|
| 320 |
+
profillybot/
|
| 321 |
+
├── app.py # Streamlit app entry point
|
| 322 |
+
├── pyproject.toml # UV/pip dependencies & config
|
| 323 |
+
├── config.yaml # RAG & LLM settings
|
| 324 |
+
├── .env.template # Environment variables template
|
| 325 |
+
├── Makefile # Development commands
|
| 326 |
+
├── docker-compose.yml # Docker Compose configuration
|
| 327 |
+
├── Dockerfile # Docker image (standalone with Ollama)
|
| 328 |
+
├── Dockerfile.app # Docker image (for Compose)
|
| 329 |
+
├── README.md
|
| 330 |
+
├── data/
|
| 331 |
+
│ └── documents/ # Your profile documents
|
| 332 |
+
├── src/
|
| 333 |
+
│ ├── __init__.py
|
| 334 |
+
│ ├── document_processor.py # Load & chunk documents
|
| 335 |
+
│ ├── vectorstore.py # ChromaDB operations
|
| 336 |
+
│ ├── llm_handler.py # Ollama/LLM interface
|
| 337 |
+
│ ├── rag_pipeline.py # RAG chain logic
|
| 338 |
+
│ ├── main_document_loader.py # Main document management
|
| 339 |
+
│ ├── response_enhancer.py # Response post-processing
|
| 340 |
+
│ ├── config_loader.py # Load config.yaml & .env
|
| 341 |
+
│ ├── build_vectorstore.py # CLI to build indexes
|
| 342 |
+
│ └── retrieval/ # Extensible retrieval system
|
| 343 |
+
│ ├── __init__.py
|
| 344 |
+
│ ├── base.py # BaseRetrieverStrategy ABC
|
| 345 |
+
│ ├── factory.py # RetrieverFactory
|
| 346 |
+
│ ├── fusion.py # RRF and fusion algorithms
|
| 347 |
+
│ ├── stores/
|
| 348 |
+
│ │ └── bm25_store.py # BM25 index storage
|
| 349 |
+
│ └── strategies/
|
| 350 |
+
│ ├── vector.py # Vector-only strategy
|
| 351 |
+
│ ├── bm25.py # BM25-only strategy
|
| 352 |
+
│ └── bm25_vector.py # Hybrid BM25+Vector strategy
|
| 353 |
+
├── chroma_db/ # Vector database (auto-generated)
|
| 354 |
+
├── bm25_index/ # BM25 index (auto-generated)
|
| 355 |
+
└── tests/ # Unit tests
|
| 356 |
+
```
|
| 357 |
+
|
| 358 |
+
## 🎯 Recommended Models for HF Spaces
|
| 359 |
+
|
| 360 |
+
| Model | Size | Speed | Quality | HF Spaces Tier |
|
| 361 |
+
|-------|------|-------|---------|----------------|
|
| 362 |
+
| `llama3.2:3b` | 3B | Fast | Good | Free ✅ |
|
| 363 |
+
| `phi3:mini` | 3.8B | Fast | Good | Free ✅ |
|
| 364 |
+
| `gemma2:2b` | 2B | Very Fast | Decent | Free ✅ |
|
| 365 |
+
| `llama3.1:8b` | 8B | Medium | Great | Upgraded GPU |
|
| 366 |
+
|
| 367 |
+
## 🔍 LLM Tracing with LangSmith
|
| 368 |
+
|
| 369 |
+
Monitor prompts, responses, and latency using [LangSmith](https://smith.langchain.com).
|
| 370 |
+
|
| 371 |
+
### Setup
|
| 372 |
+
|
| 373 |
+
1. **Get API Key**: Sign up at [smith.langchain.com](https://smith.langchain.com) (free tier: 5,000 traces/month)
|
| 374 |
+
|
| 375 |
+
2. **Configure `.env`**:
|
| 376 |
+
```bash
|
| 377 |
+
LANGCHAIN_TRACING_V2=true
|
| 378 |
+
LANGCHAIN_API_KEY=your_api_key_here
|
| 379 |
+
LANGCHAIN_PROJECT=profillybot
|
| 380 |
+
```
|
| 381 |
+
|
| 382 |
+
3. **Restart the app**:
|
| 383 |
+
```bash
|
| 384 |
+
streamlit run app.py
|
| 385 |
+
```
|
| 386 |
+
|
| 387 |
+
### What You Can See
|
| 388 |
+
|
| 389 |
+
- Full prompts sent to LLM (system prompt + context + question)
|
| 390 |
+
- Complete LLM responses
|
| 391 |
+
- Latency breakdown per step
|
| 392 |
+
- Token usage (when available)
|
| 393 |
+
|
| 394 |
+
## 🔧 Troubleshooting
|
| 395 |
+
|
| 396 |
+
### Ollama Connection Issues
|
| 397 |
+
```bash
|
| 398 |
+
# Check Ollama is running
|
| 399 |
+
ollama list
|
| 400 |
+
|
| 401 |
+
# Start Ollama service
|
| 402 |
+
ollama serve
|
| 403 |
+
```
|
| 404 |
+
|
| 405 |
+
### ChromaDB / Index Errors
|
| 406 |
+
```bash
|
| 407 |
+
# Rebuild all indexes from scratch
|
| 408 |
+
rm -rf chroma_db/ bm25_index/
|
| 409 |
+
python -m src.build_vectorstore --force-rebuild
|
| 410 |
+
```
|
| 411 |
+
|
| 412 |
+
### HuggingFace Spaces Issues
|
| 413 |
+
- Check logs in the Space's "Logs" tab
|
| 414 |
+
- Ensure `requirements.txt` is generated: `uv pip compile pyproject.toml -o requirements.txt`
|
| 415 |
+
- Verify GPU/CPU settings match your model size
|
| 416 |
+
|
| 417 |
+
## 📄 License
|
| 418 |
+
|
| 419 |
+
MIT License - see LICENSE file
|
| 420 |
+
|
| 421 |
+
---
|
| 422 |
+
|
| 423 |
+
**Note**: Remember to update `config.yaml` with your personal information before deploying!
|
SETUP_GUIDE.md
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🚀 Setup Guide - ProfillyBot
|
| 2 |
+
|
| 3 |
+
This guide will walk you through setting up ProfillyBot, your personal profile chatbot, from scratch.
|
| 4 |
+
|
| 5 |
+
## 📋 Prerequisites
|
| 6 |
+
|
| 7 |
+
### 1. Install Python 3.10+
|
| 8 |
+
|
| 9 |
+
```bash
|
| 10 |
+
# Check your Python version
|
| 11 |
+
python --version # Should be 3.10 or higher
|
| 12 |
+
```
|
| 13 |
+
|
| 14 |
+
### 2. Install UV Package Manager
|
| 15 |
+
|
| 16 |
+
```bash
|
| 17 |
+
# Linux/macOS
|
| 18 |
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
| 19 |
+
|
| 20 |
+
# Windows (PowerShell)
|
| 21 |
+
irm https://astral.sh/uv/install.ps1 | iex
|
| 22 |
+
```
|
| 23 |
+
|
| 24 |
+
### 3. Install Ollama
|
| 25 |
+
|
| 26 |
+
Download and install from [ollama.ai](https://ollama.ai)
|
| 27 |
+
|
| 28 |
+
```bash
|
| 29 |
+
# Verify installation
|
| 30 |
+
ollama --version
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
## 🛠️ Local Setup
|
| 34 |
+
|
| 35 |
+
### Step 1: Clone and Install
|
| 36 |
+
|
| 37 |
+
```bash
|
| 38 |
+
# Clone the repository
|
| 39 |
+
git clone https://github.com/Tin-Hoang/slm-profile-rag.git
|
| 40 |
+
cd slm-profile-rag # or rename the folder to profillybot if you prefer
|
| 41 |
+
|
| 42 |
+
# Create virtual environment with UV
|
| 43 |
+
uv venv
|
| 44 |
+
|
| 45 |
+
# Activate virtual environment
|
| 46 |
+
# Linux/macOS:
|
| 47 |
+
source .venv/bin/activate
|
| 48 |
+
# Windows:
|
| 49 |
+
# .venv\Scripts\activate
|
| 50 |
+
|
| 51 |
+
# Install dependencies
|
| 52 |
+
uv pip install -r requirements.txt
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
### Step 2: Configuration
|
| 56 |
+
|
| 57 |
+
#### 2.1 Environment Variables
|
| 58 |
+
|
| 59 |
+
```bash
|
| 60 |
+
# Copy environment template
|
| 61 |
+
cp env.template .env
|
| 62 |
+
|
| 63 |
+
# Edit .env file (optional, defaults work for local development)
|
| 64 |
+
# Update OLLAMA_BASE_URL if needed
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
#### 2.2 Configure Your Profile
|
| 68 |
+
|
| 69 |
+
Edit `config.yaml`:
|
| 70 |
+
|
| 71 |
+
```yaml
|
| 72 |
+
profile:
|
| 73 |
+
name: "Your Full Name" # ⚠️ CHANGE THIS
|
| 74 |
+
title: "Your Professional Title" # ⚠️ CHANGE THIS
|
| 75 |
+
greeting: "Hi! I'm ProfillyBot, trained on {name}'s professional background..."
|
| 76 |
+
|
| 77 |
+
llm:
|
| 78 |
+
model: "llama3.2:3b" # Choose based on your hardware
|
| 79 |
+
temperature: 0.7
|
| 80 |
+
|
| 81 |
+
# Adjust other settings as needed
|
| 82 |
+
```
|
| 83 |
+
|
| 84 |
+
### Step 3: Pull LLM Model
|
| 85 |
+
|
| 86 |
+
```bash
|
| 87 |
+
# Pull the model specified in config.yaml
|
| 88 |
+
ollama pull llama3.2:3b
|
| 89 |
+
|
| 90 |
+
# Or try other models:
|
| 91 |
+
# ollama pull phi3:mini # Microsoft Phi-3 (3.8B)
|
| 92 |
+
# ollama pull gemma2:2b # Google Gemma 2 (2B)
|
| 93 |
+
# ollama pull llama3.1:8b # Larger, better quality (if you have GPU)
|
| 94 |
+
```
|
| 95 |
+
|
| 96 |
+
### Step 4: Add Your Documents
|
| 97 |
+
|
| 98 |
+
```bash
|
| 99 |
+
# Navigate to documents directory
|
| 100 |
+
cd data/documents
|
| 101 |
+
|
| 102 |
+
# Copy your profile documents here
|
| 103 |
+
# Supported formats: PDF, DOCX, HTML, TXT, MD
|
| 104 |
+
```
|
| 105 |
+
|
| 106 |
+
**Recommended Documents:**
|
| 107 |
+
- ✅ Resume/CV (PDF or DOCX)
|
| 108 |
+
- ✅ LinkedIn profile (export as PDF)
|
| 109 |
+
- ✅ Project reports and case studies
|
| 110 |
+
- ✅ Portfolio descriptions
|
| 111 |
+
- ✅ Publications, certifications
|
| 112 |
+
- ✅ Cover letters, personal statements
|
| 113 |
+
|
| 114 |
+
**Tips:**
|
| 115 |
+
- Use descriptive filenames
|
| 116 |
+
- Ensure documents are well-structured with headings
|
| 117 |
+
- Include detailed information (the more context, the better!)
|
| 118 |
+
- Remove the sample `SAMPLE_README.md` file
|
| 119 |
+
|
| 120 |
+
### Step 5: Build Vector Store
|
| 121 |
+
|
| 122 |
+
```bash
|
| 123 |
+
# Make sure you're in the project root
|
| 124 |
+
cd ../.. # if you're still in data/documents
|
| 125 |
+
|
| 126 |
+
# Build the vector database
|
| 127 |
+
python -m src.build_vectorstore
|
| 128 |
+
|
| 129 |
+
# Output should show:
|
| 130 |
+
# - Processing documents from: ./data/documents
|
| 131 |
+
# - Successfully processed X document chunks
|
| 132 |
+
# - ✅ Vector store built successfully!
|
| 133 |
+
```
|
| 134 |
+
|
| 135 |
+
**Troubleshooting:**
|
| 136 |
+
- If no documents found: Check `data/documents/` directory
|
| 137 |
+
- If import errors: Ensure virtual environment is activated
|
| 138 |
+
- If memory issues: Reduce chunk_size in `config.yaml`
|
| 139 |
+
|
| 140 |
+
### Step 6: Start Ollama (if not running)
|
| 141 |
+
|
| 142 |
+
```bash
|
| 143 |
+
# Start Ollama server
|
| 144 |
+
ollama serve
|
| 145 |
+
```
|
| 146 |
+
|
| 147 |
+
Keep this terminal running, or run it as a background service.
|
| 148 |
+
|
| 149 |
+
### Step 7: Run the Application
|
| 150 |
+
|
| 151 |
+
```bash
|
| 152 |
+
# In a new terminal (with virtual environment activated)
|
| 153 |
+
streamlit run app.py
|
| 154 |
+
|
| 155 |
+
# The app will open at: http://localhost:8501
|
| 156 |
+
```
|
| 157 |
+
|
| 158 |
+
## 🎨 Customization
|
| 159 |
+
|
| 160 |
+
### Change UI Theme
|
| 161 |
+
|
| 162 |
+
Edit `.streamlit/config.toml`:
|
| 163 |
+
|
| 164 |
+
```toml
|
| 165 |
+
[theme]
|
| 166 |
+
primaryColor = "#FF4B4B" # Change to your color
|
| 167 |
+
backgroundColor = "#FFFFFF"
|
| 168 |
+
```
|
| 169 |
+
|
| 170 |
+
### Adjust RAG Settings
|
| 171 |
+
|
| 172 |
+
Edit `config.yaml`:
|
| 173 |
+
|
| 174 |
+
```yaml
|
| 175 |
+
document_processing:
|
| 176 |
+
chunk_size: 1000 # Increase for more context per chunk
|
| 177 |
+
chunk_overlap: 200 # Increase for better continuity
|
| 178 |
+
|
| 179 |
+
vectorstore:
|
| 180 |
+
search_kwargs:
|
| 181 |
+
k: 4 # Number of chunks to retrieve (increase for more context)
|
| 182 |
+
|
| 183 |
+
llm:
|
| 184 |
+
temperature: 0.7 # Lower (0.3) for factual, higher (0.9) for creative
|
| 185 |
+
max_tokens: 512 # Maximum response length
|
| 186 |
+
```
|
| 187 |
+
|
| 188 |
+
### Add Example Questions
|
| 189 |
+
|
| 190 |
+
Edit `config.yaml`:
|
| 191 |
+
|
| 192 |
+
```yaml
|
| 193 |
+
ui:
|
| 194 |
+
example_questions:
|
| 195 |
+
- "What is {name}'s background?"
|
| 196 |
+
- "What programming languages does {name} know?"
|
| 197 |
+
- "Tell me about {name}'s biggest project"
|
| 198 |
+
# Add your own questions
|
| 199 |
+
```
|
| 200 |
+
|
| 201 |
+
### Customize System Prompt
|
| 202 |
+
|
| 203 |
+
Edit `config.yaml`:
|
| 204 |
+
|
| 205 |
+
```yaml
|
| 206 |
+
llm:
|
| 207 |
+
system_prompt: |
|
| 208 |
+
You are an AI assistant representing {name}.
|
| 209 |
+
[Customize your instructions here]
|
| 210 |
+
```
|
| 211 |
+
|
| 212 |
+
## 🌐 Deployment to Hugging Face Spaces
|
| 213 |
+
|
| 214 |
+
### Option 1: Direct Upload
|
| 215 |
+
|
| 216 |
+
1. Go to [Hugging Face Spaces](https://huggingface.co/spaces)
|
| 217 |
+
2. Click "Create new Space"
|
| 218 |
+
3. Choose "Streamlit" SDK
|
| 219 |
+
4. Upload all project files
|
| 220 |
+
5. Add your documents to `data/documents/`
|
| 221 |
+
6. Space will automatically build and deploy
|
| 222 |
+
|
| 223 |
+
### Option 2: Git Integration
|
| 224 |
+
|
| 225 |
+
```bash
|
| 226 |
+
# Initialize git (if not already)
|
| 227 |
+
git init
|
| 228 |
+
git add .
|
| 229 |
+
git commit -m "Initial commit"
|
| 230 |
+
|
| 231 |
+
# Add Hugging Face as remote
|
| 232 |
+
git remote add hf https://huggingface.co/spaces/YOUR_USERNAME/SPACE_NAME
|
| 233 |
+
|
| 234 |
+
# Push to deploy
|
| 235 |
+
git push hf main
|
| 236 |
+
```
|
| 237 |
+
|
| 238 |
+
### Important for HF Spaces
|
| 239 |
+
|
| 240 |
+
1. **Pre-build Vector Store**: Build locally and include `chroma_db/` in git
|
| 241 |
+
|
| 242 |
+
```bash
|
| 243 |
+
# Build locally
|
| 244 |
+
python -m src.build_vectorstore
|
| 245 |
+
|
| 246 |
+
# Remove chroma_db from .gitignore temporarily
|
| 247 |
+
# Commit and push
|
| 248 |
+
git add chroma_db/
|
| 249 |
+
git commit -m "Add pre-built vector store"
|
| 250 |
+
git push hf main
|
| 251 |
+
```
|
| 252 |
+
|
| 253 |
+
2. **Model Size**: Use small models (3B-7B params) for free tier
|
| 254 |
+
- Recommended: `llama3.2:3b`, `phi3:mini`, `gemma2:2b`
|
| 255 |
+
|
| 256 |
+
3. **Persistent Storage**: Enable in Space settings for ChromaDB
|
| 257 |
+
|
| 258 |
+
4. **Secrets**: Add API keys in Space Settings → Repository secrets
|
| 259 |
+
|
| 260 |
+
## 🧪 Testing
|
| 261 |
+
|
| 262 |
+
```bash
|
| 263 |
+
# Install dev dependencies
|
| 264 |
+
uv pip install pytest pytest-cov ruff
|
| 265 |
+
|
| 266 |
+
# Run tests
|
| 267 |
+
pytest
|
| 268 |
+
|
| 269 |
+
# Run linter
|
| 270 |
+
ruff check .
|
| 271 |
+
|
| 272 |
+
# Format code
|
| 273 |
+
ruff format .
|
| 274 |
+
```
|
| 275 |
+
|
| 276 |
+
## 🔧 Common Issues
|
| 277 |
+
|
| 278 |
+
### Issue: "Ollama connection failed"
|
| 279 |
+
|
| 280 |
+
**Solution:**
|
| 281 |
+
```bash
|
| 282 |
+
# Make sure Ollama is running
|
| 283 |
+
ollama serve
|
| 284 |
+
|
| 285 |
+
# Check if model is pulled
|
| 286 |
+
ollama list
|
| 287 |
+
|
| 288 |
+
# Pull model if missing
|
| 289 |
+
ollama pull llama3.2:3b
|
| 290 |
+
```
|
| 291 |
+
|
| 292 |
+
### Issue: "Vector store not found"
|
| 293 |
+
|
| 294 |
+
**Solution:**
|
| 295 |
+
```bash
|
| 296 |
+
# Rebuild vector store
|
| 297 |
+
python -m src.build_vectorstore --force-rebuild
|
| 298 |
+
```
|
| 299 |
+
|
| 300 |
+
### Issue: "No documents found"
|
| 301 |
+
|
| 302 |
+
**Solution:**
|
| 303 |
+
- Check `data/documents/` has files
|
| 304 |
+
- Verify file extensions are supported (.pdf, .docx, .html, .txt, .md)
|
| 305 |
+
- Check file permissions
|
| 306 |
+
|
| 307 |
+
### Issue: "Out of memory"
|
| 308 |
+
|
| 309 |
+
**Solution:**
|
| 310 |
+
```yaml
|
| 311 |
+
# Reduce chunk size in config.yaml
|
| 312 |
+
document_processing:
|
| 313 |
+
chunk_size: 500 # Reduced from 1000
|
| 314 |
+
|
| 315 |
+
# Or use a smaller model
|
| 316 |
+
llm:
|
| 317 |
+
model: "gemma2:2b"
|
| 318 |
+
```
|
| 319 |
+
|
| 320 |
+
### Issue: "Slow response times"
|
| 321 |
+
|
| 322 |
+
**Solution:**
|
| 323 |
+
- Use smaller model: `gemma2:2b` or `phi3:mini`
|
| 324 |
+
- Reduce retrieval chunks: `k: 2` instead of `k: 4`
|
| 325 |
+
- Lower temperature: `temperature: 0.5`
|
| 326 |
+
- Enable GPU for Ollama (if available)
|
| 327 |
+
|
| 328 |
+
## 📊 Performance Optimization
|
| 329 |
+
|
| 330 |
+
### For Local Development
|
| 331 |
+
|
| 332 |
+
```yaml
|
| 333 |
+
# config.yaml
|
| 334 |
+
embeddings:
|
| 335 |
+
device: "cuda" # If you have GPU
|
| 336 |
+
|
| 337 |
+
llm:
|
| 338 |
+
model: "llama3.1:8b" # Better quality with GPU
|
| 339 |
+
```
|
| 340 |
+
|
| 341 |
+
### For Production/HF Spaces
|
| 342 |
+
|
| 343 |
+
```yaml
|
| 344 |
+
embeddings:
|
| 345 |
+
device: "cpu"
|
| 346 |
+
model_name: "sentence-transformers/all-MiniLM-L6-v2" # Fast, lightweight
|
| 347 |
+
|
| 348 |
+
llm:
|
| 349 |
+
model: "llama3.2:3b" # Best balance for CPU
|
| 350 |
+
temperature: 0.6
|
| 351 |
+
max_tokens: 384 # Faster responses
|
| 352 |
+
```
|
| 353 |
+
|
| 354 |
+
## 🎯 Next Steps
|
| 355 |
+
|
| 356 |
+
1. ✅ Customize your profile information
|
| 357 |
+
2. ✅ Add comprehensive documents
|
| 358 |
+
3. ✅ Test with various questions
|
| 359 |
+
4. ✅ Deploy to Hugging Face Spaces
|
| 360 |
+
5. ✅ Share the link with recruiters!
|
| 361 |
+
|
| 362 |
+
## 📞 Support
|
| 363 |
+
|
| 364 |
+
- Check [README.md](README.md) for general information
|
| 365 |
+
- Review [config.yaml](config.yaml) for all options
|
| 366 |
+
- See [tests/](tests/) for examples
|
| 367 |
+
- Open an issue for bugs
|
| 368 |
+
|
| 369 |
+
---
|
| 370 |
+
|
| 371 |
+
**Happy chatting! 🚀**
|
app.py
ADDED
|
@@ -0,0 +1,585 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Streamlit app for ProfillyBot."""
|
| 2 |
+
|
| 3 |
+
import logging
|
| 4 |
+
import os
|
| 5 |
+
import sys
|
| 6 |
+
from datetime import datetime
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
|
| 9 |
+
import streamlit as st
|
| 10 |
+
|
| 11 |
+
# Add src to path
|
| 12 |
+
sys.path.insert(0, str(Path(__file__).parent))
|
| 13 |
+
|
| 14 |
+
from src.config_loader import get_config
|
| 15 |
+
from src.llm_handler import (
|
| 16 |
+
detect_default_provider,
|
| 17 |
+
get_available_groq_models,
|
| 18 |
+
get_available_ollama_models,
|
| 19 |
+
get_default_groq_model,
|
| 20 |
+
get_default_ollama_model,
|
| 21 |
+
get_llm_handler,
|
| 22 |
+
)
|
| 23 |
+
from src.rag_pipeline import RAGPipeline
|
| 24 |
+
|
| 25 |
+
# Configure logging
|
| 26 |
+
logging.basicConfig(
|
| 27 |
+
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
| 28 |
+
)
|
| 29 |
+
logger = logging.getLogger(__name__)
|
| 30 |
+
|
| 31 |
+
# Log LangSmith tracing status
|
| 32 |
+
if os.getenv("LANGCHAIN_TRACING_V2", "").lower() == "true":
|
| 33 |
+
logger.info(
|
| 34 |
+
f"LangSmith tracing enabled for project: {os.getenv('LANGCHAIN_PROJECT', 'profillybot')}"
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def init_session_state():
|
| 39 |
+
"""Initialize Streamlit session state."""
|
| 40 |
+
if "messages" not in st.session_state:
|
| 41 |
+
st.session_state.messages = []
|
| 42 |
+
|
| 43 |
+
if "rag_pipeline" not in st.session_state:
|
| 44 |
+
st.session_state.rag_pipeline = None
|
| 45 |
+
|
| 46 |
+
if "config" not in st.session_state:
|
| 47 |
+
st.session_state.config = None
|
| 48 |
+
|
| 49 |
+
if "pending_question" not in st.session_state:
|
| 50 |
+
st.session_state.pending_question = None
|
| 51 |
+
|
| 52 |
+
# LLM Provider settings
|
| 53 |
+
if "llm_provider" not in st.session_state:
|
| 54 |
+
st.session_state.llm_provider = detect_default_provider()
|
| 55 |
+
|
| 56 |
+
if "groq_api_key" not in st.session_state:
|
| 57 |
+
st.session_state.groq_api_key = ""
|
| 58 |
+
|
| 59 |
+
if "groq_model" not in st.session_state:
|
| 60 |
+
st.session_state.groq_model = get_default_groq_model()
|
| 61 |
+
|
| 62 |
+
if "ollama_model" not in st.session_state:
|
| 63 |
+
st.session_state.ollama_model = get_default_ollama_model()
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def load_config():
|
| 67 |
+
"""Load configuration."""
|
| 68 |
+
if st.session_state.config is None:
|
| 69 |
+
try:
|
| 70 |
+
st.session_state.config = get_config()
|
| 71 |
+
logger.info("Configuration loaded successfully")
|
| 72 |
+
except Exception as e:
|
| 73 |
+
st.error(f"Error loading configuration: {e}")
|
| 74 |
+
st.stop()
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def load_rag_pipeline(force_reload: bool = False):
|
| 78 |
+
"""Load RAG pipeline with current provider settings.
|
| 79 |
+
|
| 80 |
+
Args:
|
| 81 |
+
force_reload: Force reload even if pipeline exists
|
| 82 |
+
"""
|
| 83 |
+
if st.session_state.rag_pipeline is None or force_reload:
|
| 84 |
+
with st.spinner("Loading RAG pipeline... This may take a moment."):
|
| 85 |
+
try:
|
| 86 |
+
# Get provider settings from session state
|
| 87 |
+
provider = st.session_state.llm_provider
|
| 88 |
+
api_key = st.session_state.groq_api_key or None
|
| 89 |
+
model = None
|
| 90 |
+
|
| 91 |
+
if provider == "groq":
|
| 92 |
+
model = st.session_state.groq_model
|
| 93 |
+
elif provider == "ollama":
|
| 94 |
+
model = st.session_state.ollama_model
|
| 95 |
+
|
| 96 |
+
# Create LLM handler with overrides
|
| 97 |
+
llm_handler = get_llm_handler(
|
| 98 |
+
provider_override=provider,
|
| 99 |
+
api_key_override=api_key,
|
| 100 |
+
model_override=model,
|
| 101 |
+
)
|
| 102 |
+
|
| 103 |
+
st.session_state.rag_pipeline = RAGPipeline(llm_handler=llm_handler)
|
| 104 |
+
logger.info(f"RAG pipeline loaded with provider: {provider}")
|
| 105 |
+
|
| 106 |
+
except FileNotFoundError:
|
| 107 |
+
st.error(
|
| 108 |
+
"❌ Vector store not found! Please build it first:\n\n"
|
| 109 |
+
"```bash\n"
|
| 110 |
+
"python -m src.build_vectorstore\n"
|
| 111 |
+
"```"
|
| 112 |
+
)
|
| 113 |
+
st.info(
|
| 114 |
+
"Make sure you have:\n"
|
| 115 |
+
"1. Added your documents to `data/documents/`\n"
|
| 116 |
+
"2. Run the build command above\n"
|
| 117 |
+
"3. Started Ollama and pulled the model"
|
| 118 |
+
)
|
| 119 |
+
st.stop()
|
| 120 |
+
|
| 121 |
+
except Exception as e:
|
| 122 |
+
error_str = str(e).lower()
|
| 123 |
+
|
| 124 |
+
# Check if it's a Groq API error - try fallback to Ollama
|
| 125 |
+
if st.session_state.llm_provider == "groq" and (
|
| 126 |
+
"401" in error_str
|
| 127 |
+
or "invalid" in error_str
|
| 128 |
+
or "api_key" in error_str
|
| 129 |
+
or "429" in error_str
|
| 130 |
+
or "rate" in error_str
|
| 131 |
+
):
|
| 132 |
+
logger.warning(f"Groq API error: {e}. Falling back to Ollama.")
|
| 133 |
+
|
| 134 |
+
# Show warning to user
|
| 135 |
+
st.warning(
|
| 136 |
+
"⚠️ **Groq API Error** - Falling back to Ollama\n\n"
|
| 137 |
+
"Possible causes:\n"
|
| 138 |
+
"- Invalid API key\n"
|
| 139 |
+
"- Rate limit exceeded (free tier cap)\n"
|
| 140 |
+
"- API key expired\n\n"
|
| 141 |
+
"**Get a new key at:** https://console.groq.com\n\n"
|
| 142 |
+
"Using local Ollama instead..."
|
| 143 |
+
)
|
| 144 |
+
|
| 145 |
+
# Fallback to Ollama
|
| 146 |
+
try:
|
| 147 |
+
st.session_state.llm_provider = "ollama"
|
| 148 |
+
ollama_model = st.session_state.ollama_model or get_default_ollama_model()
|
| 149 |
+
|
| 150 |
+
llm_handler = get_llm_handler(
|
| 151 |
+
provider_override="ollama",
|
| 152 |
+
model_override=ollama_model,
|
| 153 |
+
)
|
| 154 |
+
|
| 155 |
+
st.session_state.rag_pipeline = RAGPipeline(llm_handler=llm_handler)
|
| 156 |
+
logger.info("Fallback to Ollama successful")
|
| 157 |
+
st.success(f"✅ Now using Ollama with model: {ollama_model}")
|
| 158 |
+
return
|
| 159 |
+
|
| 160 |
+
except Exception as fallback_error:
|
| 161 |
+
st.error(f"Fallback to Ollama also failed: {fallback_error}")
|
| 162 |
+
st.info(
|
| 163 |
+
"Make sure Ollama is running:\n"
|
| 164 |
+
"- Start Ollama: `ollama serve`\n"
|
| 165 |
+
"- Pull a model: `ollama pull llama3.2:3b`"
|
| 166 |
+
)
|
| 167 |
+
logger.error(f"Ollama fallback error: {fallback_error}")
|
| 168 |
+
st.stop()
|
| 169 |
+
|
| 170 |
+
# Not a Groq error or other error
|
| 171 |
+
st.error(f"Error loading RAG pipeline: {e}")
|
| 172 |
+
if st.session_state.llm_provider == "groq":
|
| 173 |
+
st.info(
|
| 174 |
+
"Groq issues:\n"
|
| 175 |
+
"- Check your API key is valid\n"
|
| 176 |
+
"- Get a free key at https://console.groq.com"
|
| 177 |
+
)
|
| 178 |
+
else:
|
| 179 |
+
st.info(
|
| 180 |
+
"Ollama issues:\n"
|
| 181 |
+
"- Ollama not running: `ollama serve`\n"
|
| 182 |
+
"- Model not pulled: `ollama pull llama3.2:3b`"
|
| 183 |
+
)
|
| 184 |
+
logger.error(f"RAG pipeline error: {e}", exc_info=True)
|
| 185 |
+
st.stop()
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
def configure_page():
|
| 189 |
+
"""Configure Streamlit page settings."""
|
| 190 |
+
config = st.session_state.config
|
| 191 |
+
|
| 192 |
+
page_title = config.get("ui.page_title", "💬 ProfillyBot")
|
| 193 |
+
page_icon = config.get("ui.page_icon", "🤖")
|
| 194 |
+
layout = config.get("ui.layout", "centered")
|
| 195 |
+
|
| 196 |
+
st.set_page_config(page_title=page_title, page_icon=page_icon, layout=layout)
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
def display_header():
|
| 200 |
+
"""Display app header."""
|
| 201 |
+
config = st.session_state.config
|
| 202 |
+
|
| 203 |
+
profile_name = config.get("profile.name", "Candidate")
|
| 204 |
+
profile_title = config.get("profile.title", "Professional")
|
| 205 |
+
greeting = config.format_template(config.get("profile.greeting", ""))
|
| 206 |
+
|
| 207 |
+
st.title(f"💬 ProfillyBot — Chat with {profile_name}'s Profile")
|
| 208 |
+
st.markdown(f"**{profile_title}**")
|
| 209 |
+
|
| 210 |
+
if greeting:
|
| 211 |
+
st.info(greeting)
|
| 212 |
+
|
| 213 |
+
st.divider()
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
def display_sidebar():
|
| 217 |
+
"""Display sidebar with information and controls."""
|
| 218 |
+
config = st.session_state.config
|
| 219 |
+
|
| 220 |
+
with st.sidebar:
|
| 221 |
+
st.header("ℹ️ About")
|
| 222 |
+
|
| 223 |
+
profile_name = config.get("profile.name", "the candidate")
|
| 224 |
+
st.markdown(
|
| 225 |
+
f"This chatbot can answer questions about **{profile_name}'s** "
|
| 226 |
+
"professional background, skills, experience, and projects."
|
| 227 |
+
)
|
| 228 |
+
|
| 229 |
+
st.divider()
|
| 230 |
+
|
| 231 |
+
# Example questions
|
| 232 |
+
st.subheader("💡 Example Questions")
|
| 233 |
+
example_questions = config.get("ui.example_questions", [])
|
| 234 |
+
|
| 235 |
+
for question in example_questions:
|
| 236 |
+
formatted_q = config.format_template(question)
|
| 237 |
+
if st.button(formatted_q, key=f"example_{hash(formatted_q)}"):
|
| 238 |
+
st.session_state.pending_question = formatted_q
|
| 239 |
+
st.rerun()
|
| 240 |
+
|
| 241 |
+
st.divider()
|
| 242 |
+
|
| 243 |
+
# LLM Provider Selection
|
| 244 |
+
st.subheader("🤖 LLM Provider")
|
| 245 |
+
|
| 246 |
+
# Provider selector
|
| 247 |
+
providers = ["ollama", "groq"]
|
| 248 |
+
current_provider_idx = (
|
| 249 |
+
providers.index(st.session_state.llm_provider)
|
| 250 |
+
if st.session_state.llm_provider in providers
|
| 251 |
+
else 0
|
| 252 |
+
)
|
| 253 |
+
|
| 254 |
+
new_provider = st.radio(
|
| 255 |
+
"Select Provider",
|
| 256 |
+
providers,
|
| 257 |
+
index=current_provider_idx,
|
| 258 |
+
format_func=lambda x: "🦙 Ollama (Local)" if x == "ollama" else "⚡ Groq (Cloud)",
|
| 259 |
+
key="provider_radio",
|
| 260 |
+
help="Ollama runs locally. Groq requires an API key but is faster.",
|
| 261 |
+
)
|
| 262 |
+
|
| 263 |
+
# Groq-specific settings
|
| 264 |
+
if new_provider == "groq":
|
| 265 |
+
st.markdown("---")
|
| 266 |
+
groq_key = st.text_input(
|
| 267 |
+
"Groq API Key",
|
| 268 |
+
value=st.session_state.groq_api_key,
|
| 269 |
+
type="password",
|
| 270 |
+
placeholder="Enter your Groq API key",
|
| 271 |
+
help="Get a free key at https://console.groq.com",
|
| 272 |
+
key="groq_key_input",
|
| 273 |
+
)
|
| 274 |
+
|
| 275 |
+
groq_models = get_available_groq_models()
|
| 276 |
+
current_model_idx = (
|
| 277 |
+
groq_models.index(st.session_state.groq_model)
|
| 278 |
+
if st.session_state.groq_model in groq_models
|
| 279 |
+
else 0
|
| 280 |
+
)
|
| 281 |
+
|
| 282 |
+
new_model = st.selectbox(
|
| 283 |
+
"Groq Model",
|
| 284 |
+
groq_models,
|
| 285 |
+
index=current_model_idx,
|
| 286 |
+
key="groq_model_select",
|
| 287 |
+
)
|
| 288 |
+
|
| 289 |
+
# Check if settings changed
|
| 290 |
+
settings_changed = (
|
| 291 |
+
new_provider != st.session_state.llm_provider
|
| 292 |
+
or groq_key != st.session_state.groq_api_key
|
| 293 |
+
or new_model != st.session_state.groq_model
|
| 294 |
+
)
|
| 295 |
+
|
| 296 |
+
if settings_changed and st.button("🔄 Apply Changes", type="primary"):
|
| 297 |
+
st.session_state.llm_provider = new_provider
|
| 298 |
+
st.session_state.groq_api_key = groq_key
|
| 299 |
+
st.session_state.groq_model = new_model
|
| 300 |
+
st.session_state.rag_pipeline = None # Force reload
|
| 301 |
+
st.rerun()
|
| 302 |
+
else:
|
| 303 |
+
# Ollama selected - show model selector
|
| 304 |
+
st.markdown("---")
|
| 305 |
+
ollama_models = get_available_ollama_models()
|
| 306 |
+
current_ollama_idx = (
|
| 307 |
+
ollama_models.index(st.session_state.ollama_model)
|
| 308 |
+
if st.session_state.ollama_model in ollama_models
|
| 309 |
+
else 0
|
| 310 |
+
)
|
| 311 |
+
|
| 312 |
+
new_ollama_model = st.selectbox(
|
| 313 |
+
"Ollama Model",
|
| 314 |
+
ollama_models,
|
| 315 |
+
index=current_ollama_idx,
|
| 316 |
+
key="ollama_model_select",
|
| 317 |
+
help="Make sure the model is pulled: ollama pull <model>",
|
| 318 |
+
)
|
| 319 |
+
|
| 320 |
+
settings_changed = (
|
| 321 |
+
new_provider != st.session_state.llm_provider
|
| 322 |
+
or new_ollama_model != st.session_state.ollama_model
|
| 323 |
+
)
|
| 324 |
+
|
| 325 |
+
if settings_changed and st.button("🔄 Apply Changes", type="primary"):
|
| 326 |
+
st.session_state.llm_provider = new_provider
|
| 327 |
+
st.session_state.ollama_model = new_ollama_model
|
| 328 |
+
st.session_state.rag_pipeline = None
|
| 329 |
+
st.rerun()
|
| 330 |
+
|
| 331 |
+
# Show current active settings
|
| 332 |
+
st.markdown("---")
|
| 333 |
+
st.caption(f"**Active:** {st.session_state.llm_provider.upper()}")
|
| 334 |
+
if st.session_state.llm_provider == "groq":
|
| 335 |
+
st.caption(f"**Model:** {st.session_state.groq_model}")
|
| 336 |
+
else:
|
| 337 |
+
st.caption(f"**Model:** {st.session_state.ollama_model}")
|
| 338 |
+
|
| 339 |
+
st.divider()
|
| 340 |
+
|
| 341 |
+
# Clear chat button
|
| 342 |
+
if st.button("🗑️ Clear Chat History"):
|
| 343 |
+
st.session_state.messages = []
|
| 344 |
+
st.rerun()
|
| 345 |
+
|
| 346 |
+
# Settings
|
| 347 |
+
with st.expander("⚙️ Advanced Settings"):
|
| 348 |
+
st.markdown("**Vector Store**")
|
| 349 |
+
st.markdown(f"- Collection: {config.get('vectorstore.collection_name', 'N/A')}")
|
| 350 |
+
st.markdown(f"- Top K: {config.get('vectorstore.search_kwargs.k', 'N/A')}")
|
| 351 |
+
|
| 352 |
+
st.markdown("**Document Processing**")
|
| 353 |
+
st.markdown(f"- Chunk Size: {config.get('document_processing.chunk_size', 'N/A')}")
|
| 354 |
+
st.markdown(f"- Overlap: {config.get('document_processing.chunk_overlap', 'N/A')}")
|
| 355 |
+
|
| 356 |
+
|
| 357 |
+
def extract_chat_history(messages: list, exclude_last: bool = True) -> list[dict]:
|
| 358 |
+
"""Extract and format chat history for LLM.
|
| 359 |
+
|
| 360 |
+
Args:
|
| 361 |
+
messages: List of Streamlit message dicts
|
| 362 |
+
exclude_last: Whether to exclude the last message (usually the current one being processed)
|
| 363 |
+
|
| 364 |
+
Returns:
|
| 365 |
+
List of formatted message dicts with 'role' and 'content' keys
|
| 366 |
+
"""
|
| 367 |
+
if not messages:
|
| 368 |
+
return []
|
| 369 |
+
|
| 370 |
+
# Exclude the last message if requested (current user message being processed)
|
| 371 |
+
history_messages = messages[:-1] if exclude_last else messages
|
| 372 |
+
|
| 373 |
+
# Filter to only user and assistant messages, format for LLM
|
| 374 |
+
formatted_history = []
|
| 375 |
+
for msg in history_messages:
|
| 376 |
+
role = msg.get("role")
|
| 377 |
+
content = msg.get("content", "")
|
| 378 |
+
|
| 379 |
+
if role in ("user", "assistant") and content:
|
| 380 |
+
formatted_history.append({"role": role, "content": content})
|
| 381 |
+
|
| 382 |
+
return formatted_history
|
| 383 |
+
|
| 384 |
+
|
| 385 |
+
def truncate_chat_history(
|
| 386 |
+
history: list[dict], max_turns: int | None = None, max_tokens: int | None = None
|
| 387 |
+
) -> list[dict]:
|
| 388 |
+
"""Truncate chat history based on turn count or token limit.
|
| 389 |
+
|
| 390 |
+
Args:
|
| 391 |
+
history: List of message dicts
|
| 392 |
+
max_turns: Maximum number of Q&A pairs to keep (None = no limit)
|
| 393 |
+
max_tokens: Maximum tokens allowed (None = no limit)
|
| 394 |
+
|
| 395 |
+
Returns:
|
| 396 |
+
Truncated history list
|
| 397 |
+
"""
|
| 398 |
+
from src.main_document_loader import get_main_document_loader
|
| 399 |
+
|
| 400 |
+
if not history:
|
| 401 |
+
return []
|
| 402 |
+
|
| 403 |
+
# Apply turn-based truncation first (keep most recent)
|
| 404 |
+
if max_turns is not None and max_turns > 0:
|
| 405 |
+
# Each turn = 1 user + 1 assistant message
|
| 406 |
+
max_messages = max_turns * 2
|
| 407 |
+
history = history[-max_messages:] if len(history) > max_messages else history
|
| 408 |
+
|
| 409 |
+
# Apply token-based truncation if needed
|
| 410 |
+
if max_tokens is not None and max_tokens > 0:
|
| 411 |
+
loader = get_main_document_loader()
|
| 412 |
+
current_tokens = 0
|
| 413 |
+
truncated_history = []
|
| 414 |
+
|
| 415 |
+
# Go through history in reverse, adding messages until we hit token limit
|
| 416 |
+
for msg in reversed(history):
|
| 417 |
+
content = msg.get("content", "")
|
| 418 |
+
msg_tokens = loader.count_tokens(content)
|
| 419 |
+
|
| 420 |
+
if current_tokens + msg_tokens <= max_tokens:
|
| 421 |
+
truncated_history.insert(0, msg)
|
| 422 |
+
current_tokens += msg_tokens
|
| 423 |
+
else:
|
| 424 |
+
break
|
| 425 |
+
|
| 426 |
+
return truncated_history
|
| 427 |
+
|
| 428 |
+
return history
|
| 429 |
+
|
| 430 |
+
|
| 431 |
+
def display_chat_messages():
|
| 432 |
+
"""Display chat messages."""
|
| 433 |
+
config = st.session_state.config
|
| 434 |
+
show_timestamps = config.get("ui.show_timestamps", True)
|
| 435 |
+
|
| 436 |
+
for message in st.session_state.messages:
|
| 437 |
+
with st.chat_message(message["role"]):
|
| 438 |
+
st.markdown(message["content"])
|
| 439 |
+
|
| 440 |
+
# Display timestamp if enabled
|
| 441 |
+
if show_timestamps and "timestamp" in message:
|
| 442 |
+
st.caption(f"_{message['timestamp']}_")
|
| 443 |
+
|
| 444 |
+
# Display sources if available
|
| 445 |
+
if "sources" in message and message["sources"]:
|
| 446 |
+
with st.expander("📚 Sources"):
|
| 447 |
+
st.markdown(message["sources"])
|
| 448 |
+
|
| 449 |
+
|
| 450 |
+
def process_user_input(prompt: str):
|
| 451 |
+
"""Process user input and generate response.
|
| 452 |
+
|
| 453 |
+
Args:
|
| 454 |
+
prompt: User's question
|
| 455 |
+
"""
|
| 456 |
+
config = st.session_state.config
|
| 457 |
+
rag_pipeline = st.session_state.rag_pipeline
|
| 458 |
+
|
| 459 |
+
# Add user message
|
| 460 |
+
timestamp = datetime.now().strftime("%H:%M:%S")
|
| 461 |
+
st.session_state.messages.append({"role": "user", "content": prompt, "timestamp": timestamp})
|
| 462 |
+
|
| 463 |
+
# Display user message
|
| 464 |
+
with st.chat_message("user"):
|
| 465 |
+
st.markdown(prompt)
|
| 466 |
+
if config.get("ui.show_timestamps", True):
|
| 467 |
+
st.caption(f"_{timestamp}_")
|
| 468 |
+
|
| 469 |
+
# Extract and prepare chat history
|
| 470 |
+
chat_history = []
|
| 471 |
+
if config.get("chat.enable_history", True):
|
| 472 |
+
# Extract history excluding the current message we just added
|
| 473 |
+
raw_history = extract_chat_history(st.session_state.messages, exclude_last=True)
|
| 474 |
+
|
| 475 |
+
# Truncate based on configuration
|
| 476 |
+
max_turns = config.get("chat.max_history_turns", 10)
|
| 477 |
+
max_tokens = config.get("chat.max_history_tokens", 2000)
|
| 478 |
+
|
| 479 |
+
chat_history = truncate_chat_history(
|
| 480 |
+
raw_history,
|
| 481 |
+
max_turns=max_turns if max_turns > 0 else None,
|
| 482 |
+
max_tokens=max_tokens if max_tokens > 0 else None,
|
| 483 |
+
)
|
| 484 |
+
|
| 485 |
+
if chat_history:
|
| 486 |
+
logger.debug(f"Including {len(chat_history)} messages from chat history")
|
| 487 |
+
|
| 488 |
+
# Generate streaming response
|
| 489 |
+
with st.chat_message("assistant"):
|
| 490 |
+
try:
|
| 491 |
+
# Create placeholder for streaming content
|
| 492 |
+
response_placeholder = st.empty()
|
| 493 |
+
|
| 494 |
+
# Stream the response token by token with spinner
|
| 495 |
+
streamed_content = ""
|
| 496 |
+
with st.spinner("Thinking (this may take a few minutes on a free-tier HF space)..."):
|
| 497 |
+
for chunk in rag_pipeline.stream_answer(prompt, chat_history=chat_history):
|
| 498 |
+
streamed_content += chunk
|
| 499 |
+
response_placeholder.markdown(streamed_content + "▌")
|
| 500 |
+
|
| 501 |
+
# Remove cursor and show final streamed content
|
| 502 |
+
response_placeholder.markdown(streamed_content)
|
| 503 |
+
|
| 504 |
+
# Enhance the response for better tone (post-stream enhancement)
|
| 505 |
+
answer = streamed_content
|
| 506 |
+
if config.get("rag.enhance_responses", True):
|
| 507 |
+
from src.response_enhancer import get_response_enhancer
|
| 508 |
+
|
| 509 |
+
enhancer = get_response_enhancer()
|
| 510 |
+
enhanced_answer = enhancer.enhance_with_context(streamed_content, prompt)
|
| 511 |
+
if enhanced_answer != streamed_content:
|
| 512 |
+
answer = enhanced_answer
|
| 513 |
+
# Update display with enhanced version
|
| 514 |
+
response_placeholder.markdown(answer)
|
| 515 |
+
logger.debug("Response enhanced for better tone")
|
| 516 |
+
|
| 517 |
+
# Get source documents (after streaming completes)
|
| 518 |
+
sources = rag_pipeline.get_source_documents(prompt, chat_history=chat_history)
|
| 519 |
+
sources_text = ""
|
| 520 |
+
if config.get("rag.include_sources", True) and sources:
|
| 521 |
+
sources_text = rag_pipeline.format_sources(sources)
|
| 522 |
+
with st.expander("📚 Sources"):
|
| 523 |
+
st.markdown(sources_text)
|
| 524 |
+
|
| 525 |
+
# Add to message history
|
| 526 |
+
timestamp = datetime.now().strftime("%H:%M:%S")
|
| 527 |
+
st.session_state.messages.append(
|
| 528 |
+
{
|
| 529 |
+
"role": "assistant",
|
| 530 |
+
"content": answer,
|
| 531 |
+
"timestamp": timestamp,
|
| 532 |
+
"sources": sources_text,
|
| 533 |
+
}
|
| 534 |
+
)
|
| 535 |
+
|
| 536 |
+
except Exception as e:
|
| 537 |
+
error_msg = f"I apologize, but I encountered an error: {e}"
|
| 538 |
+
st.error(error_msg)
|
| 539 |
+
logger.error(f"Error generating response: {e}", exc_info=True)
|
| 540 |
+
|
| 541 |
+
st.session_state.messages.append(
|
| 542 |
+
{"role": "assistant", "content": error_msg, "timestamp": timestamp}
|
| 543 |
+
)
|
| 544 |
+
|
| 545 |
+
|
| 546 |
+
def main():
|
| 547 |
+
"""Main application entry point."""
|
| 548 |
+
# Initialize session state
|
| 549 |
+
init_session_state()
|
| 550 |
+
|
| 551 |
+
# Load configuration
|
| 552 |
+
load_config()
|
| 553 |
+
|
| 554 |
+
# Configure page
|
| 555 |
+
configure_page()
|
| 556 |
+
|
| 557 |
+
# Load RAG pipeline
|
| 558 |
+
load_rag_pipeline()
|
| 559 |
+
|
| 560 |
+
# Display UI
|
| 561 |
+
display_header()
|
| 562 |
+
display_sidebar()
|
| 563 |
+
|
| 564 |
+
# Display chat messages
|
| 565 |
+
display_chat_messages()
|
| 566 |
+
|
| 567 |
+
# Process pending question from example button click
|
| 568 |
+
if st.session_state.pending_question:
|
| 569 |
+
pending = st.session_state.pending_question
|
| 570 |
+
st.session_state.pending_question = None
|
| 571 |
+
process_user_input(pending)
|
| 572 |
+
st.rerun()
|
| 573 |
+
|
| 574 |
+
# Chat input
|
| 575 |
+
if prompt := st.chat_input("Ask me anything about the profile..."):
|
| 576 |
+
process_user_input(prompt)
|
| 577 |
+
|
| 578 |
+
# Limit chat history
|
| 579 |
+
max_history = st.session_state.config.get("ui.max_chat_history", 50)
|
| 580 |
+
if len(st.session_state.messages) > max_history:
|
| 581 |
+
st.session_state.messages = st.session_state.messages[-max_history:]
|
| 582 |
+
|
| 583 |
+
|
| 584 |
+
if __name__ == "__main__":
|
| 585 |
+
main()
|
bm25_index/bm25_index.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2ae5802b1436443f1b185dc4c4c1b25407932fa7a3a01429f52e766794176495
|
| 3 |
+
size 25337
|
bm25_index/documents.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:fc6e6df6c9c7d9403681d373ad2944e7cf2d774268aba63884a0c5a8cac33a34
|
| 3 |
+
size 14995
|
bm25_index/metadata.json
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"doc_hash": "cd60d29f92f5390d",
|
| 3 |
+
"num_documents": 10,
|
| 4 |
+
"tokenizer": "simple"
|
| 5 |
+
}
|
bm25_index/tokenized_corpus.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3c4219624d37be72d6ff179b599692006d53bc43f3d4dd03a5b7861ff0e4f567
|
| 3 |
+
size 16329
|
chroma_db/ae1acac2-515c-4deb-b9d0-675bd72bab8b/data_level0.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8d3b07c5f2f6b3b7e52a35ab5f91bef157094ca463032216b041d6f20e31699c
|
| 3 |
+
size 167600
|
chroma_db/ae1acac2-515c-4deb-b9d0-675bd72bab8b/header.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a0e81c3b22454233bc12d0762f06dcca48261a75231cf87c79b75e69a6c00150
|
| 3 |
+
size 100
|
chroma_db/ae1acac2-515c-4deb-b9d0-675bd72bab8b/length.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6ec18261d55fddaecb8c3d38325422e9e8c189edcc7508d0bedb6375ccc174b4
|
| 3 |
+
size 400
|
chroma_db/ae1acac2-515c-4deb-b9d0-675bd72bab8b/link_lists.bin
ADDED
|
File without changes
|