Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9439307e6a | ||
|
|
fcaad763da | ||
|
|
e93ab95350 | ||
|
|
dc900172f2 | ||
|
|
697e4e36c9 | ||
|
|
bb15bd8d24 | ||
|
|
62bb38ecff | ||
|
|
06285b8d4f | ||
|
|
f2bcfb98e3 | ||
|
|
4f8a5288ff | ||
|
|
68a76aa254 | ||
|
|
cbb43ad0f3 | ||
|
|
4f95757489 |
+907
-50
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,255 @@
|
|||||||
|
# Plan de test — Capacités des LLM (vision, thinking)
|
||||||
|
|
||||||
|
## 1. Vérification de /v1/models
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Vérifier que les champs sont bien présents
|
||||||
|
curl -s http://localhost:3014/v1/models | python3 -c "
|
||||||
|
import json, sys
|
||||||
|
data = json.load(sys.stdin)['data']
|
||||||
|
for m in data:
|
||||||
|
ctx = m.get('context_length', '?')
|
||||||
|
max_tok = m.get('max_tokens', '?')
|
||||||
|
max_in = m.get('max_input_tokens', '?')
|
||||||
|
caps = m.get('capabilities', [])
|
||||||
|
print(f'{m[\"id\"]:45s} | ctx={str(ctx):>8s} | max_tok={str(max_tok):>8s} | caps={caps}')
|
||||||
|
"
|
||||||
|
|
||||||
|
# Forcer le refresh
|
||||||
|
curl -s "http://localhost:3014/v1/models?refresh=true" | python3 -c "..."
|
||||||
|
```
|
||||||
|
|
||||||
|
**Checklist :**
|
||||||
|
- [ ] Tous les modèles ont `context_length` (pas `?` ni `None`)
|
||||||
|
- [ ] Tous les modèles ont `max_tokens` et `max_input_tokens`
|
||||||
|
- [ ] Les modèles ont `capabilities` (pas vide, min `["completion"]`)
|
||||||
|
- [ ] Le preset "Ornith 1.0 35b Q4 MOE" a des champs valides
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Test de vision (multimodal)
|
||||||
|
|
||||||
|
### 2.1 — Modèles censés supporter la vision (selon `/v1/models`)
|
||||||
|
|
||||||
|
| Modèle Ollama | Vision annoncée |
|
||||||
|
|---------------|----------------|
|
||||||
|
| qwen3.5:9b | vision |
|
||||||
|
| qwen3.5:0.8b | vision |
|
||||||
|
| qwen3.5:4b | vision |
|
||||||
|
| qwen3.5:2b | vision |
|
||||||
|
| qwen3.5 (latest) | vision |
|
||||||
|
| gemma4:12b | vision |
|
||||||
|
| ministral-3:14b | vision |
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Script de test (enregistre comme test_vision.sh)
|
||||||
|
for MODEL in qwen3.5:9b qwen3.5:4b qwen3.5:2b gemma4:12b ministral-3:14b; do
|
||||||
|
echo "=== Test vision: $MODEL ==="
|
||||||
|
curl -s http://localhost:3014/v1/chat/completions \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{
|
||||||
|
\"model\": \"$MODEL\",
|
||||||
|
\"messages\": [{
|
||||||
|
\"role\": \"user\",
|
||||||
|
\"content\": \"Décris ce que tu vois dans l'image.\"
|
||||||
|
}],
|
||||||
|
\"max_tokens\": 100
|
||||||
|
}" | python3 -c "
|
||||||
|
import json, sys
|
||||||
|
d = json.load(sys.stdin)
|
||||||
|
if 'error' in d:
|
||||||
|
print(f' ERREUR: {d[\"error\"]}')
|
||||||
|
else:
|
||||||
|
content = d['choices'][0]['message']['content']
|
||||||
|
print(f' REPONSE: {content[:200]}')
|
||||||
|
"
|
||||||
|
echo ""
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 — Test avec image réelle
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Encoder une image en base64 (necessite une image de test)
|
||||||
|
IMAGE_B64=$(base64 -w0 /path/to/test-image.jpg)
|
||||||
|
|
||||||
|
curl -s http://localhost:3014/v1/chat/completions \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{
|
||||||
|
\"model\": \"qwen3.5:9b\",
|
||||||
|
\"messages\": [{
|
||||||
|
\"role\": \"user\",
|
||||||
|
\"content\": [
|
||||||
|
{\"type\": \"text\", \"text\": \"Décris cette image en une phrase.\"},
|
||||||
|
{\"type\": \"image_url\", \"image_url\": {\"url\": \"data:image/jpeg;base64,$IMAGE_B64\"}}
|
||||||
|
]
|
||||||
|
}],
|
||||||
|
\"max_tokens\": 200
|
||||||
|
}" | python3 -m json.tool
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Test de thinking/reasoning
|
||||||
|
|
||||||
|
### 3.1 — Modèles censés supporter le thinking
|
||||||
|
|
||||||
|
| Modèle | Thinking annoncé |
|
||||||
|
|--------|-----------------|
|
||||||
|
| qwen3.5:9b | thinking |
|
||||||
|
| qwen3.5:0.8b | thinking |
|
||||||
|
| qwen3.5:4b | thinking |
|
||||||
|
| qwen3.5:2b | thinking |
|
||||||
|
| qwen3.5 (latest) | thinking |
|
||||||
|
| qwen3:8b | thinking |
|
||||||
|
| deepseek-r1:14b | thinking |
|
||||||
|
| deepseek-r1 | thinking |
|
||||||
|
| gemma4:12b | thinking |
|
||||||
|
| gemma4:26b | thinking |
|
||||||
|
| gemma4:e4b | thinking |
|
||||||
|
| magistral | thinking |
|
||||||
|
| gpt-oss | thinking |
|
||||||
|
| gemma4 (latest) | thinking |
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test de raisonnement
|
||||||
|
for MODEL in deepseek-r1:14b qwen3.5:9b qwen3:8b gemma4:12b; do
|
||||||
|
echo "=== Test thinking: $MODEL ==="
|
||||||
|
curl -s http://localhost:3014/v1/chat/completions \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{
|
||||||
|
\"model\": \"$MODEL\",
|
||||||
|
\"messages\": [{
|
||||||
|
\"role\": \"user\",
|
||||||
|
\"content\": \"Si un train part de Paris à 100 km/h et un autre de Lyon à 150 km/h, distance 400 km, combien de temps avant qu'ils se croisent ? Explique ton raisonnement étape par étape.\"
|
||||||
|
}],
|
||||||
|
\"max_tokens\": 500
|
||||||
|
}" | python3 -c "
|
||||||
|
import json, sys
|
||||||
|
d = json.load(sys.stdin)
|
||||||
|
if 'error' in d:
|
||||||
|
print(f' ERREUR: {d[\"error\"]}')
|
||||||
|
else:
|
||||||
|
content = d['choices'][0]['message']['content']
|
||||||
|
print(f' REPONSE: {content[:300]}')
|
||||||
|
# Vérifier si la réponse contient un raisonnement structuré
|
||||||
|
has_reasoning = any(kw in content.lower() for kw in ['étape', 'd\'abord', 'premièrement', 'step', 'first', 'raisonn'])
|
||||||
|
print(f' STRUCTURE_VISIBLE: {has_reasoning}')
|
||||||
|
"
|
||||||
|
echo ""
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 — Test thinking avec le paramètre OpenAI `reasoning_effort`
|
||||||
|
|
||||||
|
> Pour les modèles Jean avec `reasoning: "on"` (Ornith, qwen3.6-27b-mtp, etc.)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s http://localhost:3014/v1/chat/completions \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"model": "ornith-35b-q6",
|
||||||
|
"messages": [{"role": "user", "content": "Combien de 'r' y a-t-il dans le mot \"strawberry\" ?"}],
|
||||||
|
"max_tokens": 300
|
||||||
|
}' | python3 -c "
|
||||||
|
import json, sys
|
||||||
|
d = json.load(sys.stdin)
|
||||||
|
print(json.dumps(d, indent=2)[:2000])
|
||||||
|
"
|
||||||
|
# Le modèle devrait répondre : 3 (s-t-r-a-w-b-e-r-r-y → 3 'r')
|
||||||
|
# Si thinking est actif, il devrait montrer son raisonnement
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Test des modèles sans certaines capacités
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Tester un modèle sans vision — lui envoyer une image devrait échouer
|
||||||
|
curl -s http://localhost:3014/v1/chat/completions \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"model": "mistral",
|
||||||
|
"messages": [{
|
||||||
|
"role": "user",
|
||||||
|
"content": [
|
||||||
|
{"type": "text", "text": "Décris cette image."},
|
||||||
|
{"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,..."}}
|
||||||
|
]
|
||||||
|
}],
|
||||||
|
"max_tokens": 100
|
||||||
|
}' | python3 -c "
|
||||||
|
import json, sys
|
||||||
|
d = json.load(sys.stdin)
|
||||||
|
print(f'Status attendu: ERREUR → Reçu: {\"error\" in d}')
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Test de routing (Jean ↔ Ollama)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Tester un modèle Jean (doit déclencher le switch VRAM)
|
||||||
|
curl -s http://localhost:3014/v1/chat/completions \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"model": "ornith-35b-q6",
|
||||||
|
"messages": [{"role": "user", "content": "Bonjour, es-tu fonctionnel ?"}],
|
||||||
|
"max_tokens": 50
|
||||||
|
}' | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('choices',[{}])[0].get('message',{}).get('content','ERREUR:'+json.dumps(d))[:300])"
|
||||||
|
|
||||||
|
# Puis tester un modèle Ollama (doit déclencher le switch inverse)
|
||||||
|
curl -s http://localhost:3014/v1/chat/completions \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"model": "mistral",
|
||||||
|
"messages": [{"role": "user", "content": "Bonjour, es-tu fonctionnel ?"}],
|
||||||
|
"max_tokens": 50
|
||||||
|
}' | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('choices',[{}])[0].get('message',{}).get('content','ERREUR:'+json.dumps(d))[:300])"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Synthèse
|
||||||
|
|
||||||
|
| Check | Méthode |
|
||||||
|
|-------|---------|
|
||||||
|
| `/v1/models` a `max_tokens` + `max_input_tokens` | curl + jq |
|
||||||
|
| `/v1/models` a `capabilities` non-vide | curl + jq |
|
||||||
|
| Preset "Ornith 1.0 35b Q4 MOE" existe avec champs valides | curl + jq |
|
||||||
|
| Modèle `vision=true` accepte image sans erreur | curl + image base64 |
|
||||||
|
| Modèle `vision=false` rejette image | curl + image base64 |
|
||||||
|
| Modèle `thinking=true` montre raisonnement structuré | curl + analyse réponse |
|
||||||
|
| Switch Jean→Ollama fonctionne | 2 requêtes séquentielles |
|
||||||
|
| `?refresh=true` force la mise à jour du cache | 2 requêtes avec timestamp |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Mise à jour OpenFox
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openfox update # télécharge la nouvelle version
|
||||||
|
kill $(lsof -t -i :10369) # tue l'ancien processus (sinon EADDRINUSE)
|
||||||
|
openfox # redémarre
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Mise à jour jean-router sur effi-server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Depuis la machine locale
|
||||||
|
scp jean-router/app.py effi-server:/home/effi/effi/jean-router/app.py
|
||||||
|
|
||||||
|
# Sur effi-server
|
||||||
|
ssh effi-server
|
||||||
|
cd /home/effi/effi
|
||||||
|
docker build -t effi-jean-router ./jean-router
|
||||||
|
docker stop jean-router && docker rm jean-router
|
||||||
|
docker run -d --name jean-router --network host \
|
||||||
|
--env-file /home/effi/effi/.env \
|
||||||
|
-e JEAN_WEB_HOST=192.168.1.11 \
|
||||||
|
-e OLLAMA_DIRECT_HOST=192.168.1.11 \
|
||||||
|
effi-jean-router
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user