Files
Effi/jean-router/test-capabilities.md

7.6 KiB

Plan de test — Capacités des LLM (vision, thinking)

1. Vérification de /v1/models

# 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
# 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

# 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
# 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.)

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

# 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)

# 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

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

# 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