File size: 7,124 Bytes
907b200
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# AILIXIR Hugging Face Spaces β€” Migration Flow

> **Base commit:** `fd94aa0` β€” pre-HF, Docker Compose with MySQL+Redis

---

## 1. Why Hugging Face Spaces?

| Need | Solution |
|------|----------|
| Free hosting for 5 microservices | 5 HF Spaces (2 accounts to keep more awake) |
| No MySQL/Redis infrastructure | SQLite for storage, `database` queue driver |
| Simple CI/CD | GitHub Actions auto-syncs code to each HF Space |
| Binary model files | Git LFS + Xet storage on HF |

---

## 2. Architecture Decisions

### SQLite over MySQL
- **Why:** HF Spaces provide ephemeral storage; SQLite has zero dependencies
- **Trade-off:** No concurrent write scaling; fine for single-user/small-team usage
- **DB Location:** `database/ailixir.sqlite` (mounted in the container)
- **Migrations:** Run automatically via `entrypoint.sh` on startup

### Two HF Accounts
- **`Ailixir-AI-Team`**: Backend API
- **`RottenShadow`**: Chemical RAG, Drug Repurposing
- **`shdwRow`** (secondary): ADMET, Generation
- **Why:** HF Spaces free tier puts unused spaces to sleep; spreading across 2 accounts keeps more services warm

### Queue: `sync` driver
- **Why:** No need for Redis/DB-backed queue worker; jobs run inline
- **Note:** For production scale, switch back to `database` and run a queue worker

### Binary Model Files: Git LFS + Xet
- **Why:** HF rejects large files in raw Git; Xet provides efficient binary storage
- **Tracked:** `*.ckpt`, `*.pt`, `*.pkl`

---

## 3. Per-Service Details

### Backend API (`Ailixir-AI-Team/ailixir-api`)
- **Tech:** Laravel 12 + PHP 8.3 CLI
- **Port:** 7860 (HF default)
- **Dockerfile:** Multi-stage (composer β†’ vite β†’ runtime)
- **Entrypoint:** Sources `RUN_MODE=hf` β†’ starts supervisord (laravel serve + queue worker)
- **Fixes applied:**
  - `config/view.php` β€” explicit compiled path (fixes 500 on `/`)
  - `bootstrap/app.php` β€” `trustProxies(at: '*')` (fixes mixed-content CSS)
  - `config/scribe.php` β€” `docs_url` moved to `/scribe-docs`
  - `/docs` route β€” serves rendered `API.md` (curl examples, responses, grouped)
  - `QUEUE_CONNECTION=sync` β€” no worker needed

### ADMET Prediction (`shdwRow/ailixir-admet`)
- **Tech:** FastAPI + PyTorch
- **Port:** 7860
- **Model:** `best_model.ckpt` (LFS-tracked)
- **Sync:** Xet-enabled checkout in workflow

### Chemical RAG (`RottenShadow/ailixir-chemical-rag`)
- **Tech:** FastAPI + ChromaDB
- **Port:** 7860

### Drug Repurposing (`RottenShadow/ailixir-drug-repurposing`)
- **Tech:** FastAPI + DeepPurpose
- **Port:** 7860
- **Special:** `Dockerfile.hf` with mock-mode fallback

### Generation (`shdwRow/ailixir-generation`)
- **Tech:** FastAPI + REINVENT4 + RDKit + DeepPurpose (optional)
- **Port:** 7860
- **Fixes applied:**
  - **REINVENT4** β€” git cloned and `install.py cpu` in `Dockerfile.hf`
  - **libXrender** β€” `libxrender1 libxext6` added (RDKit image rendering)
  - **DeepPurpose** β€” optional install; integrated `/reinvent_predict` route on same port 7860
  - **Graceful fallback** β€” if DeepPurpose unavailable, enrichment returns null affinity values
  - **Enrichment** β€” catches connection errors, sets `pred_pAff_mean` to `None`

---

## 4. Sync Workflow

**File:** `.github/workflows/sync-hf-all.yml`

### Trigger
- Push to `main` branch
- Manual dispatch via GitHub UI

### Jobs (5 total)

| Job | HF Space | Account | Token | Notes |
|-----|----------|---------|-------|-------|
| `backend` | `ailixir-api` | Ailixir-AI-Team | `HF_TOKEN_AILIXIR` | Excludes `ai_apps/`, `database/ailixir.sqlite`, `storage/` |
| `drug-repurposing` | `ailixir-drug-repurposing` | RottenShadow | `HF_TOKEN` | Renames `Dockerfile.hf` β†’ `Dockerfile` |
| `admet` | `ailixir-admet` | shdwRow | `HF_TOKEN_SHDWROW` | LFS + Xet for `.ckpt` |
| `chemical-rag` | `ailixir-chemical-rag` | RottenShadow | `HF_TOKEN` | |
| `generation` | `ailixir-generation` | shdwRow | `HF_TOKEN_SHDWROW` | LFS + Xet for `.ckpt`, `.pt`, `.pkl` |

### How each job works
1. Clone the HF Space repo (shallow, depth 1)
2. `rsync -av --delete` β€” sync files from main repo, delete files removed in source
3. Write `.env` with all required vars
4. Rename `Dockerfile.hf` β†’ `Dockerfile` if present
5. Setup LFS/Xet for model files (ADMET, Generation)
6. Commit and push to HF Space

### Key exclusions (backend job)
```
--exclude=ai_apps
--exclude=.git
--exclude=.env
--exclude=.dockerignore
--exclude=public/imgs
--exclude=storage
--exclude=Diagrams
--exclude=database/ailixir.sqlite
```

### Required GitHub Secrets
| Secret | Used by |
|--------|---------|
| `HF_TOKEN` | drug-repurposing, chemical-rag |
| `HF_TOKEN_AILIXIR` | backend |
| `HF_TOKEN_SHDWROW` | admet, generation |
| `GOOGLE_CLIENT_ID` | backend .env |
| `GOOGLE_CLIENT_SECRET` | backend .env |

---

## 5. Environment Variables (`.env` written by sync)

```
RUN_MODE=hf
APP_KEY=base64:...
AI_INTEGRATION_ROUTES_ENABLED=true
APP_URL=https://Ailixir-AI-Team-ailixir-api.hf.space
APP_DEBUG=true
QUEUE_CONNECTION=sync
ADMET_AI_URL=https://shdwRow-ailixir-admet.hf.space
CHEMICAL_AI_URL=https://RottenShadow-ailixir-chemical-rag.hf.space
DRUG_REPURPOSING_URL=https://RottenShadow-ailixir-drug-repurposing.hf.space
GENERATION_SERVICE_URL=https://shdwRow-ailixir-generation.hf.space
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
GOOGLE_REDIRECT_URI=https://Ailixir-AI-Team-ailixir-api.hf.space/api/user/auth/google
CLOUDINARY_CLOUD_NAME=dummy_cloud
CLOUDINARY_API_KEY=dummy_key
CLOUDINARY_API_SECRET=dummy_secret
```

---

## 6. Database Protection

The sync workflow **excludes** `database/ailixir.sqlite` from rsync and runs `git checkout HEAD -- database/ailixir.sqlite` before commit to prevent the live database from being pushed to git.

On HF Spaces, the database file persists across restarts (HF Spaces keep filesystem between restarts) but is **reset** when the Space rebuilds (code push).

---

## 7. Known Issues & Future Work

| Issue | Status | Resolution |
|-------|--------|------------|
| DeepPurpose on Python 3.10 | Unstable | Falls back to null affinity predictions; could switch to conda base image |
| Google OAuth secrets | Not set | Needs `GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET` added to GitHub secrets |
| Space sleep on free tier | Ongoing | Two-account setup mitigates; upgrade to HF Pro for always-on |
| Cloudinary credentials | Dummy values | Set real Cloudinary keys for image upload features |
| ADMET model files in Xet | Working | Large `.ckpt` files stored via HF Xet |
| Docs route `/docs` | Working | Serves rendered `API.md` with all examples and grouping |

---

## 8. Commit History (cleaned)

```
c49c94f Add HF Spaces sync workflow with two-account setup, DB protection
30f0a11 Generation HF: REINVENT4, DeepPurpose, libXrender, graceful fallback
a342297 AI microservices HF: ports 8000β†’7860, Dockerfiles, README_HF
af62962 Backend HF fixes: trustProxies, view cache path, docs route with API.md
fd80b74 Add HF Spaces environment config, deployment guide, and changelog
b4a5d77 Foundation: migrate MySQL→SQLite, add supervisor, port 7860, APP_KEY auto-gen
fd94aa0 Limit Docker Compose build parallelism in CI (original base)
```