From e9cd1edbffd6dea98f7a569aa413d05eb05866d0 Mon Sep 17 00:00:00 2001 From: Folzo Date: Mon, 20 Jul 2026 11:22:47 +0200 Subject: [PATCH] docs: add jean-router architecture and session journal - Add SESSION-2026-07-20-JEAN-ROUTER.md (complete session journal) - Add ARCHITECTURE-JEAN-ROUTER.md (new architecture documentation) - Remove ARCHITECTURE-LITELLM.md (replaced by jean-router) - Update README.md with jean-router, Jean backend, and VRAM management - Document dynamic preset discovery and VRAM switching logic --- README.md | 93 ++++-- .../SESSION-2026-07-20-JEAN-ROUTER.md | 256 ++++++++++++++ docs/ARCHITECTURE-JEAN-ROUTER.md | 313 ++++++++++++++++++ docs/ARCHITECTURE-LITELLM.md | 166 ---------- 4 files changed, 639 insertions(+), 189 deletions(-) create mode 100644 docs/05-journal/SESSION-2026-07-20-JEAN-ROUTER.md create mode 100644 docs/ARCHITECTURE-JEAN-ROUTER.md delete mode 100644 docs/ARCHITECTURE-LITELLM.md diff --git a/README.md b/README.md index 2cf96a8..bd54b08 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,8 @@ Effi est un assistant IA personnel **100% auto-hébergé**, avec mémoire persis | Machine | IP | Rôle | Services | |---------|-----|------|----------| -| **effi-server** | `192.168.1.27` | Serveur Docker principal | OpenWebUI, Qdrant, n8n, Ollama proxy | -| **folzo-Corp** | `192.168.1.11` | LLM + STT + TTS + Gen | Ollama, ComfyUI, effi-stt, openedai-speech, kreuzberg | +| **effi-server** | `192.168.1.27` | Serveur Docker principal | OpenWebUI, Qdrant, n8n, jean-router | +| **folzo-Corp** | `192.168.1.11` | LLM + STT + TTS + Gen | Jean, Ollama, ComfyUI, effi-stt, openedai-speech, kreuzberg | | **NAS Synology** | `192.168.1.25` | Monitoring | Beszel Hub | ### Schéma Global @@ -28,26 +28,30 @@ Effi est un assistant IA personnel **100% auto-hébergé**, avec mémoire persis │ EFFI SERVER (192.168.1.27) │ │ FOLZO-CORP (192.168.1.11) │ │ │ │ │ │ ┌──────────────┐ ┌──────────┐ │ │ ┌──────────────┐ ┌──────────────┐ │ -│ │ Open WebUI │ │ Qdrant │ │ │ │ Ollama │ │ ComfyUI │ │ -│ │ Port 3000 │ │ Port 6333│ │ │ │ Port 11434 │ │ Port 8188 │ │ -│ └──────────────┘ └──────────┘ │ │ │ qwen3:8b │ │ Image/Audio │ │ -│ │ │ └──────────────┘ └──────────────┘ │ -│ ┌──────────────┐ ┌──────────┐ │ │ │ -│ │ n8n │ │ wol-proxy│ │ │ ┌──────────────┐ ┌──────────────┐ │ -│ │ Port 5678 │ │ Port 3011│ │ │ │ effi-stt │ │ openedai │ │ -│ └──────────────┘ └──────────┘ │ │ │ Port 8200 │ │ Port 8100 │ │ -│ │ │ │ Whisper STT │ │ TTS │ │ -│ ┌──────────────┐ ┌──────────┐ │ │ └──────────────┘ └──────────────┘ │ -│ │ Beszel Agent │ │ Watchtower│ │ │ │ +│ │ Open WebUI │ │ Qdrant │ │ │ │ Jean │ │ Ollama │ │ +│ │ Port 3000 │ │ Port 6333│ │ │ │ Port 8080 │ │ Port 11434 │ │ +│ └──────────────┘ └──────────┘ │ │ │ llama.cpp │ │ Vision │ │ +│ │ │ │ Ornith 35B │ │ mistral, │ │ +│ ┌──────────────┐ ┌──────────┐ │ │ │ Qwen3.6... │ │ qwen3.5:9b │ │ +│ │ n8n │ │jean-router│ │ │ └──────────────┘ └──────────────┘ │ +│ │ Port 5678 │ │ Port 3014│ │ │ │ │ └──────────────┘ └──────────┘ │ │ ┌──────────────┐ ┌──────────────┐ │ -└─────────────────────────────────┘ │ │ kreuzberg │ │ Beszel Agent │ │ - │ │ Port 8300 │ │ Monitoring │ │ - │ │ Doc Extract │ └──────────────┘ │ - │ └──────────────┘ │ - │ RX 7800 XT (VRAM 17GB) │ +│ │ │ │ ComfyUI │ │ effi-stt │ │ +│ ┌──────────────┐ ┌──────────┐ │ │ │ Port 8188 │ │ Port 8200 │ │ +│ │ Beszel Agent │ │ Watchtower│ │ │ │ Image/Audio │ │ Whisper STT │ │ +│ └──────────────┘ └──────────┘ │ │ └──────────────┘ └──────────────┘ │ +└─────────────────────────────────┘ │ │ + │ ┌──────────────┐ ┌──────────────┐ │ + │ │ openedai │ │ kreuzberg │ │ + │ │ Port 8100 │ │ Port 8300 │ │ + │ │ TTS │ │ Doc Extract │ │ + │ └──────────────┘ └──────────────┘ │ + │ RX 7800 XT (VRAM 16GB) │ └─────────────────────────────────────────┘ ``` +**Note :** jean-router gère le routing dynamique entre Jean et Ollama avec gestion automatique de la VRAM partagée. Voir [ARCHITECTURE-JEAN-ROUTER.md](docs/ARCHITECTURE-JEAN-ROUTER.md) pour les détails. + --- ## 📦 Services @@ -56,13 +60,16 @@ Effi est un assistant IA personnel **100% auto-hébergé**, avec mémoire persis | Service | Port | Image | Rôle | RAM/VRAM | Auto-unload | |---------|------|-------|------|----------|-------------| -| **Ollama** | 11434 | Native | LLM inference (gemma4:e4b) | ~10GB | systemd auto-start | +| **Jean** | 8080 | Native | LLM inference (llama.cpp, presets dynamiques) | ~28GB | Arrêt via API | +| **Ollama** | 11434 | Native | LLM vision (mistral, qwen3.5:9b) | ~4-6GB | 5min après dernier usage | | **ComfyUI** | 8188 | `yanwk/comfyui-boot:rocm` | Image + Audio generation | 18GB shm | ✅ après 10min idle | | **effi-stt** | 8200 | Build local | Whisper STT | ~20MB | ✅ après 5min idle | | **openedai-speech** | 8100 | `ghcr.io/matatonic/openedai-speech` | TTS | ~15MB | Non (GPU) | | **kreuzberg** | 8300 | `ghcr.io/kreuzberg-dev/kreuzberg:latest` | Document extraction | ~5MB | ✅ après 10min idle | | **beszel-agent** | - | `henrygd/beszel-agent:latest` | Monitoring | ~5MB | Auto-start | +**Note :** Jean et Ollama partagent la VRAM (RX 7800 XT, 16GB). jean-router gère automatiquement le switch pour éviter la contention mémoire. + ### EffiServer (192.168.1.27) | Service | Port | Image | Rôle | @@ -70,6 +77,8 @@ Effi est un assistant IA personnel **100% auto-hébergé**, avec mémoire persis | **Open WebUI** | 3000 | `ghcr.io/open-webui/open-webui:main` | Interface IA | | **Qdrant** | 6333 | `qdrant/qdrant:latest` | Vector DB (RAG) | | **n8n** | 5678 | `docker.n8n.io/n8nio/n8n:latest` | Automatisation | +| **jean-router** | 3014 | Build local | Routing dynamique Jean/Ollama + gestion VRAM | +| **jean-proxy** | 3012 | Build local | Wake-on-LAN + Jean proxy | | **wol-proxy** | 3011 | Build local | Wake-on-LAN + Ollama proxy | | **beszel-agent** | - | `henrygd/beszel-agent:latest` | Monitoring | @@ -85,9 +94,27 @@ Effi est un assistant IA personnel **100% auto-hébergé**, avec mémoire persis ### 💬 Chat IA - **Interface**: Open WebUI -- **LLM principal**: gemma4:e4b (via Ollama, 9.6GB, 128K context) -- **LLM secondaire**: qwen3:8b (tâches légères) -- **Autres modèles**: qwen3.5, gemma4:26b, deepseek-r1, magistral, etc. +- **LLM principal**: Jean (llama.cpp) avec presets dynamiques + - **ornith-35b-q6** (35B, Q6_K, reasoning) — modèle principal + - **qwen3.6-27b-mtp** (27B, IQ4_XS, reasoning, MTP) + - **qwen3.6-35b-moe-compact** (35B MoE, compact) + - **qwen3.6-35b-moe-compact-reason** (35B MoE, reasoning) + - **qwen3.6-35b-moe-nano** (35B MoE, nano) +- **LLM vision**: Ollama (mistral, qwen3.5:9b) pour les tâches avec images +- **Routing automatique**: jean-router détecte le modèle demandé et route vers Jean ou Ollama +- **Gestion VRAM**: Switch automatique entre Jean et Ollama pour éviter la contention mémoire + +**Note :** Nouveaux presets Jean apparaissent automatiquement dans OpenWebUI sans configuration. Voir [ARCHITECTURE-JEAN-ROUTER.md](docs/ARCHITECTURE-JEAN-ROUTER.md) pour les détails. + +### 🚀 jean-router — Routing dynamique LLM +- **Découverte automatique**: Interroge Jean et Ollama pour lister les modèles disponibles +- **Routing intelligent**: Route vers Jean (presets) ou Ollama (vision) selon le modèle demandé +- **Switch automatique**: Change de preset Jean si nécessaire (via API web) +- **Gestion VRAM**: + - Arrête Jean quand on passe sur Ollama (libère VRAM) + - Garde Ollama en mémoire 5 min avant déchargement automatique + - Évite la contention mémoire entre les deux backends +- **API OpenAI-compatible**: `/v1/models` et `/v1/chat/completions` ### 🎤 Pipeline Vocal - **STT**: Whisper large-v3 via effi-stt (CPU, déchargement auto) @@ -164,6 +191,8 @@ python3 scripts/beszel_client.py --system folzocorp --check stats ## 📚 Documentation +- [Architecture jean-router](./docs/ARCHITECTURE-JEAN-ROUTER.md) — Routing dynamique LLM + gestion VRAM +- [Session 2026-07-20](./docs/05-journal/SESSION-2026-07-20-JEAN-ROUTER.md) — Création de jean-router - [Installation Beszel](./docs/BESZEL-INSTALLATION.md) - [Gestion Mémoire](./docs/MEMOIRE-GESTION.md) - [Session 2026-04-12](./docs/SESSION-2026-04-12-RAM-BESZEL.md) @@ -176,7 +205,8 @@ python3 scripts/beszel_client.py --system folzocorp --check stats | Domaine | Technologies | |---------|-------------| -| **LLM** | Ollama, qwen3:8b | +| **LLM** | Jean (llama.cpp), Ollama (vision) | +| **Routing** | jean-router (Flask, routing dynamique + gestion VRAM) | | **Interface** | Open WebUI | | **STT** | Faster Whisper (CPU) | | **TTS** | XTTS v2 (ROCm GPU) | @@ -238,6 +268,23 @@ effi/ ## 📝 Historique +**Juillet 2026** - jean-router : routing dynamique LLM + gestion VRAM + +### jean-router ✅ +- Création de jean-router (Flask) pour routing dynamique entre Jean et Ollama +- Découverte automatique des presets Jean via `/api/presets` +- Découverte automatique des modèles Ollama via `/api/tags` +- Nouveaux presets Jean automatiquement visibles dans OpenWebUI +- Gestion VRAM : arrêt Jean quand on passe sur Ollama, timer 5min pour Ollama +- Suppression de LiteLLM (une couche de moins, config statique obsolète) +- OpenWebUI pointe directement vers jean-router:3014 + +### Jean (llama.cpp) ✅ +- Migration vers Jean comme backend LLM principal +- Presets dynamiques : ornith-35b-q6, qwen3.6-27b-mtp, qwen3.6-35b-moe-compact... +- Service systemd avec démarrage au boot (sans login) +- API web pour gestion (start/stop/switch presets) + **Avril 2026** - Optimisation RAM + Monitoring + Tool OpenWebUI ### RAM Management ✅ diff --git a/docs/05-journal/SESSION-2026-07-20-JEAN-ROUTER.md b/docs/05-journal/SESSION-2026-07-20-JEAN-ROUTER.md new file mode 100644 index 0000000..cf7d8f2 --- /dev/null +++ b/docs/05-journal/SESSION-2026-07-20-JEAN-ROUTER.md @@ -0,0 +1,256 @@ +# Session 2026-07-20 : jean-router et gestion VRAM dynamique + +## Résumé + +Création de **jean-router**, un proxy dynamique qui remplace LiteLLM pour router intelligemment entre Jean (llama.cpp) et Ollama, avec gestion automatique de la VRAM partagée. + +## Contexte + +### Problème initial +- OpenWebUI ne voyait pas les modèles Jean (Ornith) car configuré uniquement pour Ollama +- LiteLLM avait une configuration statique des modèles (codée en dur dans `litellm/config.yaml`) +- Nouveaux presets Jean nécessitaient de modifier manuellement la config LiteLLM +- Jean et Ollama partagent la même VRAM (RX 7800 XT, 16GB) → contention mémoire + +### Architecture avant +``` +OpenWebUI → LiteLLM:4000 (config statique) + ├→ jean-proxy:3012 → Jean (.11:8080) + └→ wol-proxy:3011 → Ollama (.11:11434) +``` + +**Problèmes :** +- LiteLLM ne faisait pas de pass-through de `/v1/models` +- Configuration statique : `qwen3.6-reason`, `qwen3.6-27b`, etc. codés en dur +- Jean faisait tourner **Ornith** mais LiteLLM attendait Qwen3.6 +- Pas de gestion VRAM : Jean et Ollama pouvaient charger simultanément → OOM + +## Solution : jean-router + +### Architecture après +``` +OpenWebUI → jean-router:3014 (routing dynamique) + ├→ jean-proxy:3012 → Jean (.11:8080) + └→ wol-proxy:3011 → Ollama (.11:11434) +``` + +### Fonctionnalités + +**1. Découverte dynamique des modèles** +- Interroge `/api/presets` de Jean (port 8090) pour lister les presets disponibles +- Interroge `/api/tags` d'Ollama (port 11434) pour lister les modèles +- Expose tout via `/v1/models` (API OpenAI-compatible) +- **Nouveaux presets Jean automatiquement visibles dans OpenWebUI** + +**2. Routing intelligent** +- Si le modèle demandé est un preset Jean → switch automatique + route vers Jean +- Si c'est un modèle Ollama → route vers Ollama +- Sinon → erreur 404 + +**3. Gestion VRAM (RX 7800 XT, 16GB partagés)** + +| Backend | Modèle typique | VRAM utilisée | +|---------|---------------|---------------| +| Jean | Ornith 35B Q6_K | ~28.5GB (déborde en RAM) | +| Ollama | mistral/qwen3.5 | ~4-6GB | + +**Logique de gestion :** + +**Switch Jean → Ollama :** +1. `stop_jean()` arrête Jean via son API web (port 8090) +2. Attend confirmation que Jean est arrêté (max 30s) +3. Route la requête vers Ollama + +**Switch Ollama → Jean :** +1. Programme le déchargement d'Ollama dans 5 minutes (timer) +2. `start_jean()` redémarre Jean +3. Attend que le modèle soit chargé (max 90s) +4. Route la requête vers Jean + +**Timer Ollama :** +- Ollama reste en mémoire pendant **5 minutes** après le dernier usage +- Chaque requête vers Ollama annule le timer +- Si retour sur Ollama avant expiration → déchargement annulé +- Après 5 min d'inactivité → déchargement automatique pour libérer VRAM + +## Fichiers créés/modifiés + +### Nouveau : `jean-router/` +- `app.py` (320 lignes) : Service Flask avec routing dynamique + gestion VRAM +- `Dockerfile` : Container Python 3.12-slim + +### Modifiés : +- `docker-compose.litellm.yml` : + - Ajout service jean-router + - Suppression service litellm + - OpenWebUI pointe directement vers jean-router:3014 +- `litellm/config.yaml` : Supprimé (plus nécessaire) + +### Configuration jean-router +```yaml +environment: + - JEAN_WEB_HOST=${OLLAMA_HOST} + - JEAN_WEB_PORT=8090 + - JEAN_PROXY_HOST=localhost + - JEAN_PROXY_PORT=3012 + - OLLAMA_PROXY_HOST=localhost + - OLLAMA_PROXY_PORT=3011 + - OLLAMA_DIRECT_HOST=${OLLAMA_HOST} + - OLLAMA_DIRECT_PORT=11434 + - ENABLE_OLLAMA=true + - ENABLE_VRAM_MANAGEMENT=true + - OLLAMA_KEEP_ALIVE_MINUTES=5 + - LISTEN_PORT=3014 + - SWITCH_TIMEOUT=90 + - CHECK_INTERVAL=2 + - JEAN_STOP_TIMEOUT=30 + - OLLAMA_UNLOAD_TIMEOUT=15 +``` + +## Déploiement sur effi-server + +### Initialisation du repo git +Le dossier `/home/effi/effi` n'était pas un repo git. Initialisation : +```bash +cd /home/effi/effi +git init +git remote add origin https://gitea.folzocorp.fr/Folzo/Effi.git +git fetch origin +git checkout -b main origin/main +``` + +**Note :** Conflits avec fichiers existants (docker-compose.yml, jean-proxy/, etc.) résolus en les déplaçant temporairement dans `/tmp/effi-backup/`. + +### Commandes de déploiement +```bash +cd /home/effi/effi +git pull +docker compose -f docker-compose.yml -f docker-compose.litellm.yml up -d --build jean-router +docker compose -f docker-compose.yml -f docker-compose.litellm.yml up -d --force-recreate open-webui +docker stop litellm && docker rm litellm # Suppression LiteLLM +``` + +## Tests et validation + +### Modèles visibles dans OpenWebUI + +**Jean (5 presets) :** +- ornith-35b-q6 (actif, Q6_K, reasoning) +- qwen3.6-27b-mtp (IQ4_XS, reasoning) +- qwen3.6-35b-moe-compact +- qwen3.6-35b-moe-compact-reason (reasoning) +- qwen3.6-35b-moe-nano + +**Ollama :** +- mistral (7.2B, Q4_K_M, tools) +- qwen3.5:9b (9.7B, Q4_K_M, vision, thinking) + +### Logs observés + +**Switch Jean → Ollama :** +``` +[VRAM] Switch Jean → Ollama +[VRAM] Arrêt de Jean pour libérer la VRAM... +[VRAM] Jean arrêté via API web +[VRAM] Jean confirmé arrêté +[Router] → Ollama (gemma4:e4b) +``` + +**Switch Ollama → Jean :** +``` +[VRAM] Switch Ollama → Jean +[VRAM] Déchargement Ollama programmé dans 5 minutes +[VRAM] Démarrage de Jean... +[VRAM] Jean prêt après 45s +[Router] → Jean (ornith-35b-q6) +``` + +**Timer Ollama :** +``` +[VRAM] Timer écoulé (5min), déchargement Ollama... +[VRAM] Ollama déchargé via API +``` + +## Avantages + +1. **Dynamique** : Nouveaux presets Jean automatiquement visibles sans modifier de config +2. **Simplifié** : Une couche de moins (LiteLLM supprimé) +3. **VRAM-safe** : Pas de contention mémoire entre Jean et Ollama +4. **Intelligent** : Ollama reste en mémoire 5 min pour les usages répétés +5. **Transparent** : OpenWebUI voit tous les modèles via un seul endpoint + +## Limitations et points d'attention + +### 1. Jean n'a pas d'auto-unload natif +- Ollama décharge automatiquement après `OLLAMA_KEEP_ALIVE` (défaut 5min) +- Jean garde son modèle chargé tant que le service tourne +- **Solution actuelle** : Arrêt complet de Jean via API web + +### 2. Latence de switch +- Jean → Ollama : ~3s (arrêt Jean) +- Ollama → Jean : ~45-90s (démarrage + chargement modèle) +- **Impact** : Première requête après switch plus lente + +### 3. Timer Ollama non-persistant +- Si jean-router redémarre, le timer est perdu +- Ollama restera en mémoire indéfiniment (jusqu'à son propre timeout) +- **Acceptable** : Ollama gère son propre `keep_alive` + +### 4. Pas de failover +- Si Jean échoue (timeout, erreur), pas de fallback automatique vers Ollama +- **À considérer** : Ajouter un fallback dans jean-router si besoin + +## Commits Gitea + +``` +8b7c4fb feat: add VRAM management for Jean/Ollama switching +20767c2 feat: keep Ollama models in memory for 5 minutes +babe4ee chore: add OLLAMA_KEEP_ALIVE_MINUTES=5 to docker-compose +43aacc0 refactor: OpenWebUI points directly to jean-router (no LiteLLM needed) +856ba4a refactor: remove LiteLLM, jean-router handles all routing +fcad415 feat: jean-router — routing dynamique Jean + Ollama +b441f34 fix: jean-router syntax error (missing except block) +``` + +## Réflexions et décisions + +### Pourquoi supprimer LiteLLM ? +- LiteLLM ne faisait pas de pass-through de `/v1/models` +- Configuration statique incompatible avec les presets dynamiques de Jean +- Couche supplémentaire inutile : jean-router fait le même travail +- **Décision** : OpenWebUI pointe directement vers jean-router:3014 + +### Pourquoi garder Ollama en mémoire 5 min ? +- Ollama sert principalement pour la vision (modèles que Jean ne supporte pas) +- Usage typique : quelques requêtes vision consécutives, puis retour à Jean +- 5 min = bon compromis entre réactivité et libération VRAM +- **Configurable** : `OLLAMA_KEEP_ALIVE_MINUTES` dans docker-compose + +### Pourquoi arrêter Jean complètement ? +- Jean n'a pas d'API pour décharger le modèle sans arrêter le service +- Le modèle reste en VRAM tant que llama-server tourne +- **Alternative envisagée** : `systemctl stop jean` (mais nécessite sudo) +- **Solution retenue** : API web de Jean (`/api/stop`) plus simple + +### Architecture future possible +- **JeanOrchestrator** : Jean pourrait gérer lui-même le switch entre presets +- **VRAM monitoring** : jean-router pourrait vérifier la VRAM disponible avant de router +- **Fallback automatique** : Si Jean échoue, router vers Ollama +- **Metrics** : Exposer des statistiques (temps de switch, VRAM utilisée, etc.) + +## Prochaines étapes + +1. **Monitorer** : Vérifier que la gestion VRAM fonctionne bien en production +2. **Ajuster** : Modifier `OLLAMA_KEEP_ALIVE_MINUTES` selon l'usage réel +3. **Documenter** : Mettre à jour `docs/ARCHITECTURE-LITELLM.md` (à renommer `ARCHITECTURE-JEAN-ROUTER.md`) +4. **Tester** : Valider que les nouveaux presets Jean apparaissent bien automatiquement + +## Conclusion + +**jean-router** remplace avantageusement LiteLLM avec : +- Routing dynamique basé sur les presets Jean +- Gestion automatique de la VRAM partagée +- Architecture simplifiée (une couche de moins) +- Nouveaux modèles automatiquement disponibles + +L'architecture est maintenant prête pour Effi : quand un nouveau preset Jean est créé, il apparaît immédiatement dans OpenWebUI sans aucune configuration supplémentaire. diff --git a/docs/ARCHITECTURE-JEAN-ROUTER.md b/docs/ARCHITECTURE-JEAN-ROUTER.md new file mode 100644 index 0000000..6cb1339 --- /dev/null +++ b/docs/ARCHITECTURE-JEAN-ROUTER.md @@ -0,0 +1,313 @@ +# Architecture LLM — jean-router (routing dynamique + gestion VRAM) + +## Vue d'ensemble + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ OpenWebUI (:3000) │ +│ http://192.168.1.27:3000 │ +└────────────────────────┬────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ jean-router (:3014) │ +│ Routing dynamique + Gestion VRAM automatique │ +│ │ +│ • Interroge Jean /api/presets (découverte dynamique) │ +│ • Interroge Ollama /api/tags (modèles vision) │ +│ • Switch automatique de preset Jean │ +│ • Arrête Jean quand on passe sur Ollama (libère VRAM) │ +│ • Garde Ollama 5 min en mémoire avant déchargement │ +└────────┬──────────────────────────────────────┬────────────────┘ + │ │ + ▼ ▼ +┌────────────────────────┐ ┌────────────────────────┐ +│ jean-proxy (:3012) │ │ wol-proxy (:3011) │ +│ + WOL si PC éteint │ │ + WOL si PC éteint │ +└────────┬───────────────┘ └────────┬───────────────┘ + │ │ + ▼ ▼ +┌────────────────────────┐ ┌────────────────────────┐ +│ Jean (.11:8080) │ │ Ollama (.11:11434) │ +│ llama.cpp server │ │ Modèles vision │ +│ Presets dynamiques │ │ mistral, qwen3.5 │ +│ Ornith, Qwen3.6... │ │ │ +└────────────────────────┘ └────────────────────────┘ +``` + +## Composants + +### jean-router (`/jean-router/`) — Proxy dynamique + +**Rôle :** Router intelligemment entre Jean et Ollama avec gestion VRAM + +**Fonctionnalités :** +1. **Découverte dynamique** : Interroge `/api/presets` (Jean) et `/api/tags` (Ollama) +2. **Routing intelligent** : Route vers Jean ou Ollama selon le modèle demandé +3. **Switch automatique** : Change de preset Jean si nécessaire +4. **Gestion VRAM** : Évite la contention mémoire entre Jean et Ollama + +**Endpoints :** +- `GET /v1/models` : Liste tous les modèles (Jean + Ollama) +- `POST /v1/chat/completions` : Route vers le bon backend + +**Configuration :** +```bash +JEAN_WEB_HOST=192.168.1.11 # Jean Web UI (API presets) +JEAN_WEB_PORT=8090 +JEAN_PROXY_HOST=localhost # jean-proxy (backend Jean) +JEAN_PROXY_PORT=3012 +OLLAMA_PROXY_HOST=localhost # wol-proxy (backend Ollama) +OLLAMA_PROXY_PORT=3011 +OLLAMA_DIRECT_HOST=192.168.1.11 # Ollama direct (pour unload) +OLLAMA_DIRECT_PORT=11434 +ENABLE_OLLAMA=true +ENABLE_VRAM_MANAGEMENT=true +OLLAMA_KEEP_ALIVE_MINUTES=5 # Ollama reste 5 min en mémoire +SWITCH_TIMEOUT=90 # Timeout chargement Jean (s) +JEAN_STOP_TIMEOUT=30 # Timeout arrêt Jean (s) +LISTEN_PORT=3014 +``` + +### jean-proxy (`/jean-proxy/`) — Reverse-proxy Jean + WOL + +**Rôle :** Proxy vers Jean avec Wake-on-LAN si le PC gaming est éteint + +**Fonctionnalités :** +- Écoute `:3012` sur le host (`network_mode: host`) +- Health-check : `GET /health` sur Jean +- Si cible DOWN → envoi paquets WOL vers `TARGET_MAC` +- Attente jusqu'à `WAKE_TIMEOUT` (60s) +- Route toutes les requêtes vers `http://${OLLAMA_HOST}:8080` + +**Configuration :** +```bash +TARGET_HOST=192.168.1.11 +TARGET_PORT=8080 +TARGET_MAC=04:D9:F5:D1:EC:DF +LISTEN_PORT=3012 +WAKE_TIMEOUT=60 +CHECK_INTERVAL=5 +BROADCAST_IP=192.168.1.255 +HEALTH_PATH=/health +``` + +### wol-proxy (`/wol-proxy/`) — Reverse-proxy Ollama + WOL + +**Rôle :** Proxy vers Ollama avec Wake-on-LAN + +**Fonctionnalités :** +- Écoute `:3011` sur le host +- Health-check : `GET /api/version` sur Ollama +- Si cible DOWN → envoi paquets WOL +- Route toutes les requêtes vers `http://${OLLAMA_HOST}:11434` + +**Configuration :** +```bash +TARGET_HOST=192.168.1.11 +TARGET_PORT=11434 +TARGET_MAC=04:D9:F5:D1:EC:DF +LISTEN_PORT=3011 +WAKE_TIMEOUT=180 +CHECK_INTERVAL=5 +BROADCAST_IP=192.168.1.255 +``` + +## Gestion VRAM + +### Problème +Jean et Ollama partagent la même carte graphique (RX 7800 XT, 16GB VRAM). Charger les deux simultanément cause une contention mémoire. + +| Backend | Modèle typique | VRAM utilisée | +|---------|---------------|---------------| +| Jean | Ornith 35B Q6_K | ~28.5GB (déborde en RAM) | +| Ollama | mistral 7B Q4_K_M | ~4GB | +| Ollama | qwen3.5:9b Q4_K_M | ~6GB | + +### Solution : Switch automatique + +**Switch Jean → Ollama :** +``` +1. Arrêt de Jean via API web (/api/stop) +2. Attente confirmation (max 30s) +3. Route requête vers Ollama +``` + +**Switch Ollama → Jean :** +``` +1. Programme déchargement Ollama dans 5 min (timer) +2. Démarrage de Jean via API web (/api/start) +3. Attente chargement modèle (max 90s) +4. Route requête vers Jean +``` + +**Timer Ollama :** +- Ollama reste en mémoire pendant **5 minutes** après le dernier usage +- Chaque requête vers Ollama annule le timer +- Si retour sur Ollama avant expiration → déchargement annulé +- Après 5 min d'inactivité → déchargement automatique + +### Logs observés + +**Switch Jean → Ollama :** +``` +[VRAM] Switch Jean → Ollama +[VRAM] Arrêt de Jean pour libérer la VRAM... +[VRAM] Jean arrêté via API web +[VRAM] Jean confirmé arrêté +[Router] → Ollama (gemma4:e4b) +``` + +**Switch Ollama → Jean :** +``` +[VRAM] Switch Ollama → Jean +[VRAM] Déchargement Ollama programmé dans 5 minutes +[VRAM] Démarrage de Jean... +[VRAM] Jean prêt après 45s +[Router] → Jean (ornith-35b-q6) +``` + +**Timer Ollama :** +``` +[VRAM] Timer écoulé (5min), déchargement Ollama... +[VRAM] Ollama déchargé via API +``` + +## Modèles disponibles + +### Jean (presets dynamiques) +Les presets sont découverts automatiquement via `/api/presets` : +- **ornith-35b-q6** (actif, Q6_K, reasoning) +- **qwen3.6-27b-mtp** (IQ4_XS, reasoning) +- **qwen3.6-35b-moe-compact** +- **qwen3.6-35b-moe-compact-reason** (reasoning) +- **qwen3.6-35b-moe-nano** + +**Note :** Nouveaux presets Jean apparaissent automatiquement dans OpenWebUI sans configuration. + +### Ollama (modèles vision) +- **mistral** (7.2B, Q4_K_M, tools) +- **qwen3.5:9b** (9.7B, Q4_K_M, vision, thinking) + +**Note :** Ollama sert principalement pour les modèles avec vision que Jean ne supporte pas. + +## Déploiement + +### Commandes (sur effi-server .27) + +```bash +cd /home/effi/effi +git pull + +# Build et démarrage jean-router +docker compose -f docker-compose.yml -f docker-compose.litellm.yml up -d --build jean-router + +# Recréer open-webui avec nouvelle config +docker compose -f docker-compose.yml -f docker-compose.litellm.yml up -d --force-recreate open-webui + +# Vérifier +docker logs jean-router --tail 20 +curl -s http://localhost:3014/v1/models | jq +``` + +### Tests + +**1. Liste des modèles :** +```bash +curl -s http://localhost:3014/v1/models | python3 -m json.tool +``` + +**2. Requête Jean :** +```bash +curl -X POST http://localhost:3014/v1/chat/completions \ + -H "Content-Type: application/json" \ + -d '{ + "model": "ornith-35b-q6", + "messages": [{"role": "user", "content": "Bonjour"}], + "max_tokens": 50 + }' +``` + +**3. Requête Ollama :** +```bash +curl -X POST http://localhost:3014/v1/chat/completions \ + -H "Content-Type: application/json" \ + -d '{ + "model": "mistral", + "messages": [{"role": "user", "content": "Hello"}], + "max_tokens": 30 + }' +``` + +**4. Vérifier logs VRAM :** +```bash +docker logs jean-router -f | grep -E "VRAM|Router" +``` + +## Avantages vs LiteLLM + +| Aspect | LiteLLM | jean-router | +|--------|---------|-------------| +| Configuration | Statique (config.yaml) | Dynamique (API Jean) | +| Nouveaux presets | Modifier config + restart | Automatique | +| Pass-through /v1/models | Non | Oui | +| Gestion VRAM | Aucune | Automatique | +| Couches | 2 (LiteLLM + jean-proxy) | 1 (jean-router) | +| Complexité | Élevée | Faible | + +## Limitations + +### 1. Latence de switch +- Jean → Ollama : ~3s (arrêt Jean) +- Ollama → Jean : ~45-90s (démarrage + chargement modèle) +- **Impact** : Première requête après switch plus lente + +### 2. Timer Ollama non-persistant +- Si jean-router redémarre, le timer est perdu +- Ollama restera en mémoire indéfiniment (jusqu'à son propre timeout) +- **Acceptable** : Ollama gère son propre `keep_alive` + +### 3. Pas de failover +- Si Jean échoue (timeout, erreur), pas de fallback automatique vers Ollama +- **À considérer** : Ajouter un fallback dans jean-router si besoin + +### 4. Jean n'a pas d'auto-unload natif +- Ollama décharge automatiquement après `OLLAMA_KEEP_ALIVE` (défaut 5min) +- Jean garde son modèle chargé tant que le service tourne +- **Solution actuelle** : Arrêt complet de Jean via API web + +## Évolution possible + +### Court terme +- **Monitorer** : Vérifier que la gestion VRAM fonctionne bien en production +- **Ajuster** : Modifier `OLLAMA_KEEP_ALIVE_MINUTES` selon l'usage réel +- **Tester** : Valider que les nouveaux presets Jean apparaissent bien automatiquement + +### Moyen terme +- **VRAM monitoring** : jean-router pourrait vérifier la VRAM disponible avant de router +- **Fallback automatique** : Si Jean échoue, router vers Ollama +- **Metrics** : Exposer des statistiques (temps de switch, VRAM utilisée, etc.) + +### Long terme +- **JeanOrchestrator** : Jean pourrait gérer lui-même le switch entre presets +- **Multi-GPU** : Si plusieurs GPU, router vers celui avec le plus de VRAM libre +- **Load balancing** : Si plusieurs instances Jean/Ollama, répartir la charge + +## Historique + +**2026-06-27** : Architecture initiale avec LiteLLM +- Routing statique par nom de modèle +- Configuration codée en dur dans `litellm/config.yaml` + +**2026-07-20** : Migration vers jean-router +- Routing dynamique basé sur les presets Jean +- Gestion automatique de la VRAM +- Suppression de LiteLLM (une couche de moins) +- Nouveaux modèles automatiquement disponibles + +## Références + +- **Code jean-router** : `/jean-router/app.py` +- **Docker compose** : `/docker-compose.litellm.yml` +- **Journal de session** : `/docs/05-journal/SESSION-2026-07-20-JEAN-ROUTER.md` +- **Repo Gitea** : https://gitea.folzocorp.fr/Folzo/Effi diff --git a/docs/ARCHITECTURE-LITELLM.md b/docs/ARCHITECTURE-LITELLM.md deleted file mode 100644 index 78c76cf..0000000 --- a/docs/ARCHITECTURE-LITELLM.md +++ /dev/null @@ -1,166 +0,0 @@ -# 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 — 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/v1` -- `OPENAI_API_KEY=sk-effi-litellm-master` -- `ENABLE_OPENAI_API=True` -- `OLLAMA_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 : -```bash -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 : -```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. \ No newline at end of file