LiteLLM expose l'API OpenAI /v1/* pas l'API Ollama /api/*. open-webui doit etre branche via la section 'OpenAI API' (env OPENAI_API_BASE_URL + OPENAI_API_KEY), pas via 'Ollama'. L'override ajoute ces vars a open-webui, force-recreate pour propager. Confirme en prod: HTTP 200 sur :4000/v1/models depuis le conteneur open-webui. Les 4 modeles Qwen3.6 remontent dans le chat.
9.1 KiB
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
:3011sur le host (network_mode: host). - Health-check :
GET /api/versionsur Ollama (HEALTH_PATHcodé =/api/version). - Si cible DOWN → envoi 3 paquets magic sur
BROADCAST_IP:9avecTARGET_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
:3012sur le host. - Health-check :
GET /healthsur Jean (HEALTH_PATH=/healthpar défaut, paramétrable via env). - Si cible DOWN → même WoL (même
TARGET_MAC, mêmeBROADCAST_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_TIMEOUTet retry toutes lesCHECK_INTERVALs. - Route toutes les requêtes vers
http://${OLLAMA_HOST}:8080. - Voir
jean-proxy/app.py: variablesTARGET_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
:4000sur le host (network_mode: hostpour 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.envsi 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 auconfig.envde 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 avecHEALTH_PATHparamé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 servicesjean-proxyetlitellm. Ne redéfinit pas les services existants, doncdocker compose up -dne redémarre niopen-webui, nihermes-webui, niwol-proxy(zero impact).
Déploiement
Pré-requis
Déjà validé sur la machine de prod :
- Serveur Effi
.27accessible en SSH (ssh effi-server). - Docker + compose installés.
- Repo Effi à
/home/effi/effi. .envcontientOLLAMA_HOST,PC_GAMING_MAC,BROADCAST_IP.
Commandes (à lancer sur .27)
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 — réalisée
⚠️ LiteLLM expose l'API OpenAI (/v1/*), pas l'API Ollama (/api/*).
open-webui ne peut donc pas brancher LiteLLM sur sa section "Ollama" (≠ protocole).
L'override docker-compose.litellm.yml configure donc open-webui via la section OpenAI API :
OPENAI_API_BASE_URL=http://host.docker.internal:4000/v1OPENAI_API_KEY=sk-effi-litellm-masterENABLE_OPENAI_API=TrueOLLAMA_BASE_URL=http://host.docker.internal:3011(conservé pour rétrocompat — mais le PC gaming OOM si Jean tourne, donc ce backend ne remonte pas de modèles)
Appliqué via :
docker compose -f docker-compose.yml -f docker-compose.litellm.yml up -d --force-recreate open-webui
Dans l'UI open-webui : Admin → Settings → Connections → "OpenAI API". L'URL est pré-remplie (http://host.docker.internal:4000/v1) et la clé sk-effi-litellm-master. Les 4 modèles Qwen3.6 (qwen3.6-reason, qwen3.6-27b, qwen3.6-fast, qwen3.6) apparaissent dans le dropdown du chat.
Modèles exposés (profil Qwen3.6 — vramp oblige)
| Modèle dans open-webui | Raisonnement | Quand l'utiliser | Latence TTFT |
|---|---|---|---|
qwen3.6-fast / qwen3.6 |
OFF | Chat, mail, extraction simple | ~0.4-1 s |
qwen3.6-reason / qwen3.6-27b |
ON | Code, analyse, rapports, RAG, tâches complexes | 10-40 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 :
docker compose -f docker-compose.yml -f docker-compose.litellm.yml stop litellm jean-proxy
Comportement WoL —要点
- Les deux proxies (
wol-proxyetjean-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-proxya un retry supplémentaire pour couvrir ce cas.
Limitations / à surveiller
n_parallel=1cô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 passerchat_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
fallbackLiteLLM : 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.envviaLITELLM_MASTER_KEYet la référencer dansconfig.yamlvia${LITELLM_MASTER_KEY}(LiteLLM supporte l'expansion env vars).
Tests de non-régression
Après déploiement, vérifier que :
hermes-webuiest toujoursUpethealthy.open-webuitoujoursUpethealthy(jusqu'à la commutation optionnelle).wol-proxytoujoursUp(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.