MinhDS commited on
Commit
0828c2c
·
verified ·
1 Parent(s): 1704b48

Deploy ProfillyBot: Gradio ZeroGPU + CV RAG (Qwen2.5-3B)

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .dockerignore +48 -0
  2. .env.template +55 -0
  3. .gitattributes +8 -0
  4. .github/workflows/README.md +207 -0
  5. .github/workflows/code-quality.yml +38 -0
  6. .github/workflows/dependency-review.yml +22 -0
  7. .github/workflows/lint.yml +38 -0
  8. .github/workflows/pre-commit.yml +38 -0
  9. .github/workflows/security-scan.yml +55 -0
  10. .github/workflows/test-docker.yml +28 -0
  11. .github/workflows/test-python-reusable.yml +48 -0
  12. .github/workflows/test-python310.yml +14 -0
  13. .github/workflows/test-python311.yml +15 -0
  14. .github/workflows/test-python312.yml +14 -0
  15. .gitignore +207 -0
  16. .hfignore +19 -0
  17. .pre-commit-config.yaml +40 -0
  18. .streamlit/config.toml +16 -0
  19. .venv/.gitignore +1 -0
  20. .venv/CACHEDIR.TAG +1 -0
  21. .venv/Lib/site-packages/_virtualenv.pth +3 -0
  22. .venv/Lib/site-packages/_virtualenv.py +101 -0
  23. .venv/Scripts/activate +116 -0
  24. .venv/Scripts/activate.bat +71 -0
  25. .venv/Scripts/activate.csh +76 -0
  26. .venv/Scripts/activate.fish +124 -0
  27. .venv/Scripts/activate.nu +117 -0
  28. .venv/Scripts/activate.ps1 +82 -0
  29. .venv/Scripts/activate_this.py +59 -0
  30. .venv/Scripts/deactivate.bat +39 -0
  31. .venv/Scripts/pydoc.bat +22 -0
  32. .venv/Scripts/python.exe +3 -0
  33. .venv/Scripts/pythonw.exe +3 -0
  34. .venv/pyvenv.cfg +5 -0
  35. ARCHITECTURE.md +815 -0
  36. Dockerfile +68 -0
  37. Dockerfile.app +80 -0
  38. LICENSE +21 -0
  39. Makefile +146 -0
  40. README.md +417 -7
  41. SETUP_GUIDE.md +371 -0
  42. app.py +585 -0
  43. bm25_index/bm25_index.pkl +3 -0
  44. bm25_index/documents.pkl +3 -0
  45. bm25_index/metadata.json +5 -0
  46. bm25_index/tokenized_corpus.pkl +3 -0
  47. chroma_db/ae1acac2-515c-4deb-b9d0-675bd72bab8b/data_level0.bin +3 -0
  48. chroma_db/ae1acac2-515c-4deb-b9d0-675bd72bab8b/header.bin +3 -0
  49. chroma_db/ae1acac2-515c-4deb-b9d0-675bd72bab8b/length.bin +3 -0
  50. 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
+ ![Python 3.10](https://github.com/Tin-Hoang/slm-profile-rag/actions/workflows/test-python310.yml/badge.svg)
6
+ ![Python 3.11](https://github.com/Tin-Hoang/slm-profile-rag/actions/workflows/test-python311.yml/badge.svg)
7
+ ![Python 3.12](https://github.com/Tin-Hoang/slm-profile-rag/actions/workflows/test-python312.yml/badge.svg)
8
+ ![Docker](https://github.com/Tin-Hoang/slm-profile-rag/actions/workflows/test-docker.yml/badge.svg)
9
+ ![Lint](https://github.com/Tin-Hoang/slm-profile-rag/actions/workflows/lint.yml/badge.svg)
10
+ ![Code Quality](https://github.com/Tin-Hoang/slm-profile-rag/actions/workflows/code-quality.yml/badge.svg)
11
+ ![Security Scan](https://github.com/Tin-Hoang/slm-profile-rag/actions/workflows/security-scan.yml/badge.svg)
12
+ [![codecov](https://codecov.io/gh/Tin-Hoang/slm-profile-rag/graph/badge.svg?token=MW4RD9R66J)](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: pink
5
- colorTo: yellow
6
  sdk: gradio
7
- sdk_version: 6.20.0
8
- python_version: '3.12'
9
- app_file: app.py
10
  pinned: false
 
 
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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