feat: jean-router — routing dynamique Jean + Ollama
- jean-router interroge /api/presets pour lister dynamiquement les presets Jean
- Support Ollama via /api/tags pour les modèles vision
- Switch automatique de preset quand nécessaire
- Expose /v1/models avec tous les modèles disponibles
- Route les requêtes vers Jean ou Ollama selon le modèle demandé
- Nouveaux presets Jean automatiquement visibles dans OpenWebUI
Architecture:
open-webui → LiteLLM:4000 → jean-router:3014
├→ jean-proxy:3012 → Jean (.11:8080)
└→ wol-proxy:3011 → Ollama (.11:11434)
This commit is contained in:
@@ -48,6 +48,29 @@ services:
|
|||||||
- BROADCAST_IP=${BROADCAST_IP}
|
- BROADCAST_IP=${BROADCAST_IP}
|
||||||
- HEALTH_PATH=/health
|
- HEALTH_PATH=/health
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────
|
||||||
|
# jean-router — Proxy dynamique qui interroge Jean pour
|
||||||
|
# lister les presets et route les requêtes vers Jean
|
||||||
|
# ou Ollama selon le modèle demandé.
|
||||||
|
# Ecoute :3014 sur le host (network_mode: host)
|
||||||
|
# ─────────────────────────────────────────────────────────
|
||||||
|
jean-router:
|
||||||
|
build: ./jean-router
|
||||||
|
container_name: jean-router
|
||||||
|
restart: unless-stopped
|
||||||
|
network_mode: host
|
||||||
|
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
|
||||||
|
- ENABLE_OLLAMA=true
|
||||||
|
- LISTEN_PORT=3014
|
||||||
|
- SWITCH_TIMEOUT=90
|
||||||
|
- CHECK_INTERVAL=2
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────
|
||||||
# 2. LiteLLM proxy — Endpoint OpenAI unique pour open-webui
|
# 2. LiteLLM proxy — Endpoint OpenAI unique pour open-webui
|
||||||
# Ecoute :4000 sur le host (network_mode: host pour joindre
|
# Ecoute :4000 sur le host (network_mode: host pour joindre
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ RUN pip install --no-cache-dir flask requests
|
|||||||
|
|
||||||
COPY app.py .
|
COPY app.py .
|
||||||
|
|
||||||
ENV LISTEN_PORT=3010
|
ENV LISTEN_PORT=3012
|
||||||
EXPOSE 3010
|
EXPOSE 3012
|
||||||
|
|
||||||
CMD ["python", "app.py"]
|
CMD ["python", "app.py"]
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
FROM python:3.12-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN pip install --no-cache-dir flask requests
|
||||||
|
|
||||||
|
COPY app.py .
|
||||||
|
|
||||||
|
ENV LISTEN_PORT=3014
|
||||||
|
EXPOSE 3014
|
||||||
|
|
||||||
|
CMD ["python", "app.py"]
|
||||||
@@ -0,0 +1,320 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Jean Router — Proxy dynamique pour Jean presets
|
||||||
|
Interroge Jean pour lister les presets disponibles et route les requêtes
|
||||||
|
vers le bon preset en faisant le switch automatique si nécessaire.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
import threading
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from flask import Flask, request, Response, jsonify
|
||||||
|
|
||||||
|
JEAN_WEB_HOST = os.getenv("JEAN_WEB_HOST", "192.168.1.11")
|
||||||
|
JEAN_WEB_PORT = int(os.getenv("JEAN_WEB_PORT", "8090"))
|
||||||
|
JEAN_PROXY_HOST = os.getenv("JEAN_PROXY_HOST", "localhost")
|
||||||
|
JEAN_PROXY_PORT = int(os.getenv("JEAN_PROXY_PORT", "3012"))
|
||||||
|
OLLAMA_PROXY_HOST = os.getenv("OLLAMA_PROXY_HOST", "localhost")
|
||||||
|
OLLAMA_PROXY_PORT = int(os.getenv("OLLAMA_PROXY_PORT", "3011"))
|
||||||
|
ENABLE_OLLAMA = os.getenv("ENABLE_OLLAMA", "true").lower() == "true"
|
||||||
|
LISTEN_PORT = int(os.getenv("LISTEN_PORT", "3014"))
|
||||||
|
SWITCH_TIMEOUT = int(os.getenv("SWITCH_TIMEOUT", "90"))
|
||||||
|
CHECK_INTERVAL = int(os.getenv("CHECK_INTERVAL", "2"))
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
presets_cache = {"data": [], "timestamp": 0}
|
||||||
|
ollama_cache = {"data": [], "timestamp": 0}
|
||||||
|
CACHE_TTL = 30
|
||||||
|
switch_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
|
def get_presets(force_refresh=False):
|
||||||
|
"""Récupère la liste des presets depuis Jean"""
|
||||||
|
now = time.time()
|
||||||
|
if not force_refresh and presets_cache["data"] and (now - presets_cache["timestamp"]) < CACHE_TTL:
|
||||||
|
return presets_cache["data"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"http://{JEAN_WEB_HOST}:{JEAN_WEB_PORT}/api/presets"
|
||||||
|
r = requests.get(url, timeout=3)
|
||||||
|
if r.ok:
|
||||||
|
presets_cache["data"] = r.json()
|
||||||
|
presets_cache["timestamp"] = now
|
||||||
|
return presets_cache["data"]
|
||||||
|
|
||||||
|
|
||||||
|
def get_ollama_models(force_refresh=False):
|
||||||
|
"""Récupère la liste des modèles depuis Ollama"""
|
||||||
|
if not ENABLE_OLLAMA:
|
||||||
|
return []
|
||||||
|
|
||||||
|
now = time.time()
|
||||||
|
if not force_refresh and ollama_cache["data"] and (now - ollama_cache["timestamp"]) < CACHE_TTL:
|
||||||
|
return ollama_cache["data"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"http://{OLLAMA_PROXY_HOST}:{OLLAMA_PROXY_PORT}/api/tags"
|
||||||
|
r = requests.get(url, timeout=3)
|
||||||
|
if r.ok:
|
||||||
|
data = r.json().get("models", [])
|
||||||
|
ollama_cache["data"] = data
|
||||||
|
ollama_cache["timestamp"] = now
|
||||||
|
return data
|
||||||
|
except Exception as e:
|
||||||
|
log.warning(f"[Ollama] Erreur récupération: {e}")
|
||||||
|
|
||||||
|
return ollama_cache["data"]
|
||||||
|
except Exception as e:
|
||||||
|
log.warning(f"[Presets] Erreur récupération: {e}")
|
||||||
|
|
||||||
|
return presets_cache["data"]
|
||||||
|
|
||||||
|
|
||||||
|
def get_active_preset():
|
||||||
|
"""Retourne le preset actuellement actif"""
|
||||||
|
presets = get_presets()
|
||||||
|
for p in presets:
|
||||||
|
if p.get("active"):
|
||||||
|
return p
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def switch_preset(preset_id: str) -> bool:
|
||||||
|
"""Switch vers un preset spécifique par son ID"""
|
||||||
|
presets = get_presets(force_refresh=True)
|
||||||
|
|
||||||
|
for i, p in enumerate(presets):
|
||||||
|
if p["id"] == preset_id:
|
||||||
|
if p.get("active"):
|
||||||
|
log.info(f"[Switch] Preset {preset_id} déjà actif")
|
||||||
|
return True
|
||||||
|
|
||||||
|
log.info(f"[Switch] Activation preset {preset_id} (index {i+1})")
|
||||||
|
try:
|
||||||
|
url = f"http://{JEAN_WEB_HOST}:{JEAN_WEB_PORT}/api/switch"
|
||||||
|
r = requests.post(url, json={"n": i + 1}, timeout=5)
|
||||||
|
if not r.ok:
|
||||||
|
log.error(f"[Switch] Erreur API: {r.text}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"[Switch] Erreur requête: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
log.info(f"[Switch] Attente chargement modèle...")
|
||||||
|
deadline = time.time() + SWITCH_TIMEOUT
|
||||||
|
while time.time() < deadline:
|
||||||
|
try:
|
||||||
|
health_url = f"http://{JEAN_PROXY_HOST}:{JEAN_PROXY_PORT}/health"
|
||||||
|
hr = requests.get(health_url, timeout=2)
|
||||||
|
if hr.ok:
|
||||||
|
models_url = f"http://{JEAN_PROXY_HOST}:{JEAN_PROXY_PORT}/v1/models"
|
||||||
|
mr = requests.get(models_url, timeout=2)
|
||||||
|
if mr.ok:
|
||||||
|
models = mr.json().get("data", [])
|
||||||
|
if models:
|
||||||
|
log.info(f"[Switch] Modèle chargé après {int(time.time() - deadline + SWITCH_TIMEOUT)}s")
|
||||||
|
presets_cache["timestamp"] = 0
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
time.sleep(CHECK_INTERVAL)
|
||||||
|
|
||||||
|
log.error(f"[Switch] Timeout après {SWITCH_TIMEOUT}s")
|
||||||
|
return False
|
||||||
|
|
||||||
|
log.error(f"[Switch] Preset {preset_id} non trouvé")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/v1/models", methods=["GET"])
|
||||||
|
def list_models():
|
||||||
|
"""Expose tous les presets Jean + modèles Ollama comme modèles OpenAI"""
|
||||||
|
presets = get_presets()
|
||||||
|
models = []
|
||||||
|
|
||||||
|
for p in presets:
|
||||||
|
model_id = p["id"]
|
||||||
|
model_name = p.get("name", model_id)
|
||||||
|
|
||||||
|
model_info = {
|
||||||
|
"id": model_id,
|
||||||
|
"object": "model",
|
||||||
|
"created": int(time.time()),
|
||||||
|
"owned_by": "jean",
|
||||||
|
"permission": [],
|
||||||
|
"root": model_id,
|
||||||
|
"parent": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
meta = {}
|
||||||
|
if p.get("quant"):
|
||||||
|
meta["quantization"] = p["quant"]
|
||||||
|
if p.get("reasoning"):
|
||||||
|
meta["reasoning"] = p["reasoning"]
|
||||||
|
if p.get("active"):
|
||||||
|
meta["active"] = True
|
||||||
|
if p.get("bench"):
|
||||||
|
meta["bench"] = p["bench"]
|
||||||
|
|
||||||
|
if meta:
|
||||||
|
model_info["meta"] = meta
|
||||||
|
|
||||||
|
models.append(model_info)
|
||||||
|
|
||||||
|
if ENABLE_OLLAMA:
|
||||||
|
ollama_models = get_ollama_models()
|
||||||
|
for m in ollama_models:
|
||||||
|
model_id = m.get("name", "").replace(":latest", "")
|
||||||
|
if not model_id:
|
||||||
|
continue
|
||||||
|
|
||||||
|
model_info = {
|
||||||
|
"id": model_id,
|
||||||
|
"object": "model",
|
||||||
|
"created": int(time.time()),
|
||||||
|
"owned_by": "ollama",
|
||||||
|
"permission": [],
|
||||||
|
"root": model_id,
|
||||||
|
"parent": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
details = m.get("details", {})
|
||||||
|
meta = {}
|
||||||
|
if details.get("parameter_size"):
|
||||||
|
meta["parameter_size"] = details["parameter_size"]
|
||||||
|
if details.get("quantization_level"):
|
||||||
|
meta["quantization"] = details["quantization_level"]
|
||||||
|
if details.get("family"):
|
||||||
|
meta["family"] = details["family"]
|
||||||
|
if m.get("capabilities"):
|
||||||
|
meta["capabilities"] = m["capabilities"]
|
||||||
|
|
||||||
|
if meta:
|
||||||
|
model_info["meta"] = meta
|
||||||
|
|
||||||
|
models.append(model_info)
|
||||||
|
|
||||||
|
return jsonify({"object": "list", "data": models})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/v1/chat/completions", methods=["POST"])
|
||||||
|
def chat_completions():
|
||||||
|
"""Route les requêtes chat vers Jean ou Ollama selon le modèle"""
|
||||||
|
data = request.get_json()
|
||||||
|
model_id = data.get("model")
|
||||||
|
|
||||||
|
if not model_id:
|
||||||
|
return jsonify({"error": "model parameter required"}), 400
|
||||||
|
|
||||||
|
presets = get_presets()
|
||||||
|
jean_preset_ids = [p["id"] for p in presets]
|
||||||
|
|
||||||
|
if model_id in jean_preset_ids:
|
||||||
|
with switch_lock:
|
||||||
|
active = get_active_preset()
|
||||||
|
if not active or active["id"] != model_id:
|
||||||
|
log.info(f"[Router] Switch Jean requis: {active['id'] if active else 'none'} → {model_id}")
|
||||||
|
if not switch_preset(model_id):
|
||||||
|
return jsonify({"error": f"Failed to switch to preset {model_id}"}), 503
|
||||||
|
|
||||||
|
upstream_url = f"http://{JEAN_PROXY_HOST}:{JEAN_PROXY_PORT}/v1/chat/completions"
|
||||||
|
log.info(f"[Router] → Jean ({model_id})")
|
||||||
|
elif ENABLE_OLLAMA:
|
||||||
|
ollama_models = get_ollama_models()
|
||||||
|
ollama_ids = [m.get("name", "").replace(":latest", "") for m in ollama_models]
|
||||||
|
|
||||||
|
if model_id in ollama_ids:
|
||||||
|
upstream_url = f"http://{OLLAMA_PROXY_HOST}:{OLLAMA_PROXY_PORT}/v1/chat/completions"
|
||||||
|
log.info(f"[Router] → Ollama ({model_id})")
|
||||||
|
else:
|
||||||
|
return jsonify({"error": f"Unknown model: {model_id}"}), 404
|
||||||
|
else:
|
||||||
|
return jsonify({"error": f"Unknown model: {model_id}"}), 404
|
||||||
|
|
||||||
|
headers = dict(request.headers)
|
||||||
|
headers.pop("Host", None)
|
||||||
|
|
||||||
|
try:
|
||||||
|
resp = requests.request(
|
||||||
|
method=request.method,
|
||||||
|
url=upstream_url,
|
||||||
|
headers=headers,
|
||||||
|
json=data,
|
||||||
|
stream=True,
|
||||||
|
timeout=None,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"[Router] Erreur upstream: {e}")
|
||||||
|
return jsonify({"error": str(e)}), 502
|
||||||
|
|
||||||
|
excluded = {"content-encoding", "content-length", "transfer-encoding", "connection"}
|
||||||
|
response_headers = [
|
||||||
|
(name, value) for name, value in resp.headers.items()
|
||||||
|
if name.lower() not in excluded
|
||||||
|
]
|
||||||
|
|
||||||
|
return Response(
|
||||||
|
resp.raw,
|
||||||
|
status=resp.status_code,
|
||||||
|
headers=response_headers,
|
||||||
|
direct_passthrough=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/", defaults={"path": ""}, methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"])
|
||||||
|
@app.route("/<path:path>", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"])
|
||||||
|
def proxy(path):
|
||||||
|
"""Proxy générique pour les autres endpoints"""
|
||||||
|
if path.startswith("v1/chat/completions"):
|
||||||
|
return chat_completions()
|
||||||
|
|
||||||
|
upstream_url = f"http://{JEAN_PROXY_HOST}:{JEAN_PROXY_PORT}/{path}"
|
||||||
|
headers = dict(request.headers)
|
||||||
|
headers.pop("Host", None)
|
||||||
|
|
||||||
|
try:
|
||||||
|
resp = requests.request(
|
||||||
|
method=request.method,
|
||||||
|
url=upstream_url,
|
||||||
|
headers=headers,
|
||||||
|
data=request.get_data(),
|
||||||
|
cookies=request.cookies,
|
||||||
|
params=request.args,
|
||||||
|
allow_redirects=False,
|
||||||
|
stream=True,
|
||||||
|
timeout=None,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
return Response(str(e), status=502)
|
||||||
|
|
||||||
|
excluded = {"content-encoding", "content-length", "transfer-encoding", "connection"}
|
||||||
|
response_headers = [
|
||||||
|
(name, value) for name, value in resp.headers.items()
|
||||||
|
if name.lower() not in excluded
|
||||||
|
]
|
||||||
|
|
||||||
|
return Response(
|
||||||
|
resp.raw,
|
||||||
|
status=resp.status_code,
|
||||||
|
headers=response_headers,
|
||||||
|
direct_passthrough=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
log.info(f"[Router] Démarrage sur port {LISTEN_PORT}")
|
||||||
|
log.info(f"[Router] Jean Web: {JEAN_WEB_HOST}:{JEAN_WEB_PORT}")
|
||||||
|
log.info(f"[Router] Jean Proxy: {JEAN_PROXY_HOST}:{JEAN_PROXY_PORT}")
|
||||||
|
if ENABLE_OLLAMA:
|
||||||
|
log.info(f"[Router] Ollama Proxy: {OLLAMA_PROXY_HOST}:{OLLAMA_PROXY_PORT}")
|
||||||
|
else:
|
||||||
|
log.info(f"[Router] Ollama: désactivé")
|
||||||
|
app.run(host="0.0.0.0", port=LISTEN_PORT, threaded=True)
|
||||||
+12
-98
@@ -1,111 +1,25 @@
|
|||||||
# ═══════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════
|
||||||
# LiteLLM Proxy — Routing pour Effi
|
# LiteLLM Proxy — Routing pour Effi via jean-router
|
||||||
# Endpoint OpenAI-compatible sur :4000 (host network)
|
# Endpoint OpenAI-compatible sur :4000 (host network)
|
||||||
#
|
#
|
||||||
# Backend unique : JeanOrchestrator (port 8080 sur PC gaming .11)
|
# jean-router (:3014) gère dynamiquement :
|
||||||
# via jean-proxy :3012 + WOL auto si PC eteint.
|
# - Liste des presets Jean (avec switch automatique)
|
||||||
#
|
# - Liste des modèles Ollama
|
||||||
# L'orchestrateur gere automatiquement le switch entre :
|
# - Routing vers le bon backend
|
||||||
# - Qwen3.6-35B MoE I-Nano (defaut, 11.7 Go, 100k ctx, ~70 tok/s)
|
#
|
||||||
# - Qwen3.6-27B IQ4_XS (code/analyse, 14.6 Go, 55k ctx, ~60 tok/s)
|
# LiteLLM sert uniquement de proxy OpenAI unifié pour OpenWebUI.
|
||||||
#
|
|
||||||
# Tous les modeles passent par le meme proxy :3012.
|
|
||||||
# L'orchestrateur detecte le modele demande et charge le bon LLM.
|
|
||||||
# Retour automatique au MoE apres 15 min sans 27B.
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
model_list:
|
model_list:
|
||||||
# ═════════════════════════════════════════════════════════
|
- model_name: jean
|
||||||
# Profil 1 — Raisonnement ON (défaut, pour tâches complexes)
|
|
||||||
# Le modele reflechit (jusqu'a 2048 tokens de reasoning_content)
|
|
||||||
# avant de produire sa reponse. Bon pour code, analyse, RAG, rapports.
|
|
||||||
# ═════════════════════════════════════════════════════════
|
|
||||||
- model_name: qwen3.6-reason
|
|
||||||
litellm_params:
|
litellm_params:
|
||||||
model: openai/qwen3.6-reason
|
model: openai/*
|
||||||
api_base: http://localhost:3012/v1
|
api_base: http://localhost:3014/v1
|
||||||
api_key: "null"
|
api_key: "null"
|
||||||
timeout: 600
|
timeout: 600
|
||||||
extra_body:
|
|
||||||
chat_template_kwargs:
|
|
||||||
enable_thinking: true
|
|
||||||
|
|
||||||
- model_name: qwen3.6-27b
|
|
||||||
litellm_params:
|
|
||||||
model: openai/qwen3.6-27b
|
|
||||||
api_base: http://localhost:3012/v1
|
|
||||||
api_key: "null"
|
|
||||||
timeout: 600
|
|
||||||
extra_body:
|
|
||||||
chat_template_kwargs:
|
|
||||||
enable_thinking: true
|
|
||||||
|
|
||||||
# ═════════════════════════════════════════════════════════
|
|
||||||
# Profil 2 — Raisonnement OFF (chat rapide, ~5x moins de latence)
|
|
||||||
# Reponse immediate (TTFT <1s) sans phase de reflexion.
|
|
||||||
# Ideal pour : chat interactif, redaction de mail, extraction
|
|
||||||
# structuree simple, Q/R directes.
|
|
||||||
# ═════════════════════════════════════════════════════════
|
|
||||||
- model_name: qwen3.6-fast
|
|
||||||
litellm_params:
|
|
||||||
model: openai/qwen3.6-fast
|
|
||||||
api_base: http://localhost:3012/v1
|
|
||||||
api_key: "null"
|
|
||||||
timeout: 600
|
|
||||||
extra_body:
|
|
||||||
chat_template_kwargs:
|
|
||||||
enable_thinking: false
|
|
||||||
|
|
||||||
# Alias court "qwen3.6" = fast par defaut (usage chat le plus courant)
|
|
||||||
- model_name: qwen3.6
|
|
||||||
litellm_params:
|
|
||||||
model: openai/qwen3.6
|
|
||||||
api_base: http://localhost:3012/v1
|
|
||||||
api_key: "null"
|
|
||||||
timeout: 600
|
|
||||||
extra_body:
|
|
||||||
chat_template_kwargs:
|
|
||||||
enable_thinking: false
|
|
||||||
|
|
||||||
# ═════════════════════════════════════════════════════════
|
|
||||||
# Profil 3 — Qwen3.6-35B MoE APEX I-Nano (chat Hermes, usage quotidien)
|
|
||||||
# Modele MoE : 35B total / 3B actifs, 11.7 Go, cache KV 5x plus petit
|
|
||||||
# que le 27B -> contexte 100k possible. Cohabite avec ComfyUI.
|
|
||||||
# Route via le meme proxy :3012 que le 27B (orchestrateur fait le switch).
|
|
||||||
# ═════════════════════════════════════════════════════════
|
|
||||||
- model_name: qwen3.6-moe
|
|
||||||
litellm_params:
|
|
||||||
model: openai/qwen3.6-moe
|
|
||||||
api_base: http://localhost:3012/v1
|
|
||||||
api_key: "null"
|
|
||||||
timeout: 600
|
|
||||||
extra_body:
|
|
||||||
chat_template_kwargs:
|
|
||||||
enable_thinking: false
|
|
||||||
|
|
||||||
- model_name: qwen3.6-moe-reason
|
|
||||||
litellm_params:
|
|
||||||
model: openai/qwen3.6-moe-reason
|
|
||||||
api_base: http://localhost:3012/v1
|
|
||||||
api_key: "null"
|
|
||||||
timeout: 600
|
|
||||||
extra_body:
|
|
||||||
chat_template_kwargs:
|
|
||||||
enable_thinking: true
|
|
||||||
|
|
||||||
# ═════════════════════════════════════════════════════════
|
|
||||||
# Backend Ollama — COMMENTE (contention VRAM, voir entete)
|
|
||||||
# Pour reactiver si dispo (autre machine / GPU dedie), decommenter
|
|
||||||
# et s'assurer que le GPU peut charger les deux modeles.
|
|
||||||
# ═════════════════════════════════════════════════════════
|
|
||||||
# - model_name: llama3.1-8b
|
|
||||||
# litellm_params:
|
|
||||||
# model: ollama/llama3.1:8b
|
|
||||||
# api_base: http://localhost:3011
|
|
||||||
# timeout: 600
|
|
||||||
|
|
||||||
litellm_settings:
|
litellm_settings:
|
||||||
drop_params: false # ne pas dropper: on veut passer chat_template_kwargs a Jean
|
drop_params: false
|
||||||
telemetry: false
|
telemetry: false
|
||||||
|
|
||||||
general_settings:
|
general_settings:
|
||||||
|
|||||||
Reference in New Issue
Block a user