Architecture (voir docs/ARCHITECTURE-LITELLM.md):
open-webui -> LiteLLM:4000
|- qwen3.6-27b -> jean-proxy:3012 -> Jean (.11:8080)
|- llama3.1-8b -> wol-proxy:3011 -> Ollama (.11:11434)
Nouveaux fichiers:
- wol-proxy/ (versionne l'app.py de prod deja en place)
- jean-proxy/ (clone du wol-proxy avec HEALTH_PATH=/health + retry post-WoL
pour services qui mettent du temps a demarrer apres WoL, ex: Jean qui
charge 14 Go en VRAM)
- litellm/config.yaml (routing par nom de modele, drop_params=true)
- docker-compose.litellm.yml (override ADDITIF: ajoute uniquement jean-proxy
et litellm, ne redemarre PAS open-webui/wol-proxy/hermes)
Teste en prod sur .27: Jean + Ollama repondent via LiteLLM:4000 avec
path-through de chat_template_kwargs.enable_thinking (raisonnement Qwen3.6
desactivable par requete). Zero impact hermes-webui/wol-proxy.
Basculer open-webui reste une etape manuelle (step 3 de la doc).
161 lines
8.6 KiB
Markdown
161 lines
8.6 KiB
Markdown
# Architecture LLM multi-backends — LiteLLM + WOL
|
||
|
||
## État avant
|
||
|
||
```
|
||
open-webui (:3000) ─► OLLAMA_BASE_URL=host.docker.internal:3011
|
||
│
|
||
wol-proxy (:3011, host network)
|
||
│
|
||
▼ (WOL paquet + forward TCP)
|
||
PC gaming .11 (s'éteint la nuit)
|
||
└─ Ollama :11434
|
||
```
|
||
|
||
Problème : backend unique (Ollama), pas moyen d'utiliser Jean (Qwen3.6-27B + MTP sur ROCm) sans changer la config à la main.
|
||
|
||
## État après — LiteLLM + double proxy WOL
|
||
|
||
```
|
||
open-webui (:3000) ─► OLLAMA_BASE_URL=host.docker.internal:4000 ← optional: à basculer manuellement
|
||
│
|
||
LiteLLM proxy (:4000, host network) ← NOUVEAU
|
||
│ routing par nom de modèle
|
||
├── "qwen3.6-27b" / "qwen3.6" ─► jean-proxy (:3012) ─► Jean (.11:8080)
|
||
└── "llama3.1-8b" / "llama3.1" ─► wol-proxy (:3011) ─► Ollama (.11:11434)
|
||
│
|
||
└── WOL paquet (MAC PC gaming) si cible DOWN
|
||
```
|
||
|
||
## Composants
|
||
|
||
### wol-proxy (`/wol-proxy/`) — EXISTANT, inchangé
|
||
|
||
Reverse-proxy Flask générique vers Ollama + déclenchement WoL.
|
||
- Écoute `:3011` sur le host (`network_mode: host`).
|
||
- Health-check : `GET /api/version` sur Ollama (`HEALTH_PATH` codé = `/api/version`).
|
||
- Si cible DOWN → envoi 3 paquets magic sur `BROADCAST_IP:9` avec `TARGET_MAC`, attente jusqu'à `WAKE_TIMEOUT` (60 s).
|
||
- Route ensuite toutes les requêtes vers `http://${OLLAMA_HOST}:11434`.
|
||
|
||
### jean-proxy (`/jean-proxy/`) — NOUVEAU, clone du wol-proxy
|
||
|
||
Même mécanique, adapté pour Jean (llama.cpp).
|
||
- Écoute `:3012` sur le host.
|
||
- Health-check : `GET /health` sur Jean (`HEALTH_PATH=/health` par défaut, *paramétrable via env*).
|
||
- Si cible DOWN → même WoL (même `TARGET_MAC`, même `BROADCAST_IP`).
|
||
- Bonus : **retry post-WoL** — Jean met ~30 s à charger le modèle 14 Go après le boot du PC. Si la connexion est refusée (Jean n'a pas encore ouvert son port), jean-proxy attend jusqu'à `WAKE_TIMEOUT` et retry toutes les `CHECK_INTERVAL` s.
|
||
- Route toutes les requêtes vers `http://${OLLAMA_HOST}:8080`.
|
||
- Voir `jean-proxy/app.py` : variables `TARGET_PORT=8080`, `HEALTH_PATH=/health`, `LISTEN_PORT=3012`.
|
||
|
||
### litellm (`/litellm/`) — NOUVEAU
|
||
|
||
Proxy OpenAI-compatible unique pour open-webui.
|
||
- Image officielle : `ghcr.io/berriai/litellm:main-stable`.
|
||
- Écoute `:4000` sur le host (`network_mode: host` pour joindre les proxies :301x).
|
||
- Routing par nom de modèle via `litellm/config.yaml` :
|
||
|
||
| Model name (visible open-webui) | Route vers | Backend réel |
|
||
|---|---|---|
|
||
| `qwen3.6-27b`, `qwen3.6` | `http://localhost:3012/v1` (jean-proxy) | Jean (`llama.cpp` OpenAI API) |
|
||
| `llama3.1-8b`, `llama3.1` | `http://localhost:3011` (wol-proxy) | Ollama (via `/api/chat` façon Ollama) |
|
||
|
||
- Master key : `sk-effi-litellm-master` (à mettre dans `.env` si on veut la surcharger — voir plus bas).
|
||
- `drop_params: true` : supprime les params non reconnus par le backend (évite les erreurs sur champs OpenAI exotiques).
|
||
- Pass-through du `chat_template_kwargs.enable_thinking` : LiteLLM le transmet tel quel à Jean — c'est ainsi qu'on peut désactiver le raisonnement Qwen3.6 par requête sans toucher au `config.env` de Jean.
|
||
|
||
## Fichiers
|
||
|
||
- `wol-proxy/app.py` — code existant (préalablement non versionné, désormais commit).
|
||
- `wol-proxy/Dockerfile` — idem.
|
||
- `jean-proxy/app.py` — clone du wol-proxy avec `HEALTH_PATH` paramétrable + retry post-WoL pour services lents à démarrer.
|
||
- `jean-proxy/Dockerfile` — idem que wol-proxy.
|
||
- `litellm/config.yaml` — routing des modèles, master key.
|
||
- `docker-compose.litellm.yml` — **override** : ajoute uniquement les services `jean-proxy` et `litellm`. Ne redéfinit **pas** les services existants, donc `docker compose up -d` ne redémarre ni `open-webui`, ni `hermes-webui`, ni `wol-proxy` (zero impact).
|
||
|
||
## Déploiement
|
||
|
||
### Pré-requis
|
||
|
||
Déjà validé sur la machine de prod :
|
||
- Serveur Effi `.27` accessible en SSH (`ssh effi-server`).
|
||
- Docker + compose installés.
|
||
- Repo Effi à `/home/effi/effi`.
|
||
- `.env` contient `OLLAMA_HOST`, `PC_GAMING_MAC`, `BROADCAST_IP`.
|
||
|
||
### Commandes (à lancer sur .27)
|
||
|
||
```bash
|
||
cd /home/effi/effi
|
||
# Démarre uniquement litellm + jean-proxy, n' touche pas au reste
|
||
docker compose -f docker-compose.yml -f docker-compose.litellm.yml up -d --build jean-proxy litellm
|
||
|
||
# Test 1 — Jean route
|
||
curl -s -H "Authorization: Bearer sk-effi-litellm-master" \
|
||
http://localhost:4000/v1/models
|
||
|
||
# Test 2 — chat Qwen3.6 via Jean (reasoning off)
|
||
curl -s -X POST http://localhost:4000/v1/chat/completions \
|
||
-H "Authorization: Bearer sk-effi-litellm-master" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"model":"qwen3.6-27b","messages":[{"role":"user","content":"Bonjour"}],
|
||
"chat_template_kwargs":{"enable_thinking":false},"max_tokens":50}'
|
||
|
||
# Test 3 — chat llama3.1 via Ollama (provoque WoL si .11 éteint)
|
||
curl -s -X POST http://localhost:4000/v1/chat/completions \
|
||
-H "Authorization: Bearer sk-effi-litellm-master" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"model":"llama3.1","messages":[{"role":"user","content":"Bonjour"}],"max_tokens":30}'
|
||
```
|
||
|
||
### Commutation de open-webui vers LiteLLM (étape utilisateur)
|
||
|
||
Une fois les tests validés :
|
||
|
||
1. Éditer `/home/effi/effi/docker-compose.yml` (seulement cette ligne) :
|
||
```yaml
|
||
OLLAMA_BASE_URL=http://host.docker.internal:4000 # LiteLLM au lieu de :3011
|
||
```
|
||
2. Ajouter la master key LiteLLM dans `/home/effi/effi/.env` :
|
||
```
|
||
LITELLM_MASTER_KEY=sk-effi-litellm-master
|
||
```
|
||
3. Redémarrer open-webui uniquement :
|
||
```bash
|
||
docker compose -f docker-compose.yml -f docker-compose.litellm.yml up -d open-webui
|
||
```
|
||
4. Dans open-webui : Admin → Settings → Connections → l'URL est déjà `http://host.docker.internal:4000`. Vérifier que les deux modèles apparaissent dans le dropdown.
|
||
|
||
ℹ️ Cette étape est volontairement laissée manuelle — c'est l'unique impact sur le service en prod (1 redémarrage open-webui). Hermes, wol-proxy, qdrant etc. ne sont pas touchés.
|
||
|
||
### En cas de rollback
|
||
|
||
Revenir à `OLLAMA_BASE_URL=http://host.docker.internal:3011` et redémarrer open-webui. LiteLLM et jean-proxy peuvent rester UP (en override, ils ne gênent rien) ou être arrêtés :
|
||
```bash
|
||
docker compose -f docker-compose.yml -f docker-compose.litellm.yml stop litellm jean-proxy
|
||
```
|
||
|
||
## Comportement WoL —要点
|
||
|
||
- Les deux proxies (`wol-proxy` et `jean-proxy`) partagent **la même adresse MAC cible** (`PC_GAMING_MAC`).
|
||
- Quel que soit le backend demandé, si le PC gaming est DOWN, un seul WoL est envoyé (par le proxy concerné).
|
||
- Pendant la phase de wake-up (`WAKE_TIMEOUT=60s`), le proxy bloque la requête HTTP jusqu'à ce que la cible réponde — l'utilisateur voit une latence plus longue mais pas d'erreur.
|
||
- Après WoL réussi :
|
||
- Ollama répond en ~10-20 s (lancement container + warmup).
|
||
- Jean répond en ~30-60 s (charger le modèle 14 Go en VRAM). `jean-proxy` a un retry supplémentaire pour couvrir ce cas.
|
||
|
||
## Limitations / à surveiller
|
||
|
||
- **`n_parallel=1` côté Jean** : une seule requête à la fois. Si open-webui + un script Effi appellent Jean en parallèle, la 2e attend. Pour la prod multi-tenant, mettre en place une queue ou ouvrir un 2e slot (mais VRAM saturée).
|
||
- **`drop_params: true`** : LiteLLM supprime tout paramètre non standard OpenAI. Si on veut passer `chat_template_kwargs` à Jean, vérifier que LiteLLM le transmet — testé OK, mais à revalider si on ajoute d'autres flags exotiques.
|
||
- **Pas de failover** dans la config actuelle : si jean-proxy retourne 502 (cible injoignable après WoL), LiteLLM renvoie l'erreur au client. On pourrait ajouter un `fallback` LiteLLM : qwen3.6 → llama3.1 si Jean down.
|
||
- **Master key en clair dans `config.yaml`** : acceptable en interne, mais pour un déploiement plus sclériser, déplacer la clé dans `.env` via `LITELLM_MASTER_KEY` et la référencer dans `config.yaml` via `${LITELLM_MASTER_KEY}` (LiteLLM supporte l'expansion env vars).
|
||
|
||
## Tests de non-régression
|
||
|
||
Après déploiement, vérifier que :
|
||
- `hermes-webui` est toujours `Up` et `healthy`.
|
||
- `open-webui` toujours `Up` et `healthy` (jusqu'à la commutation optionnelle).
|
||
- `wol-proxy` toujours `Up` (inchangé).
|
||
- `curl http://192.168.1.27:8787/` (Hermes UI) répond toujours.
|
||
- `curl http://192.168.1.27:3000/` (open-webui) répond toujours.
|
||
- `curl http://localhost:3011/api/version` (ollama via wol-proxy direct) répond toujours. |