Modèles auto-hébergés

Takuto Core peut piloter un serveur de modèle auto-hébergé et compatible OpenAI — LM Studio, Ollama, vLLM, et similaires — via le fournisseur OpenCode. Cela garde le code source et les prompts entièrement sur une infrastructure que vous contrôlez. Cette page couvre la configuration et les modes de défaillance que nous avons rencontrés en branchant LM Studio avec Docker Desktop, pour que vous n’ayez pas à les redécouvrir.

Chaque réglage ci-dessous est éditable depuis Configuration → AI Settings → OpenCode dans le dashboard. Inutile d’éditer config.toml à la main.

Configuration de base

  1. Réglez le fournisseur sur OpenCode dans Configuration → AI Settings.

  2. Renseignez le champ Model. Le flag -m d’OpenCode attend <providerId>/<modelId>, et la config générée par Takuto Core nomme toujours le fournisseur self_hosted ; la valeur doit donc ressembler à :

    self_hosted/<model-id-as-reported-by-the-server>
    Id de modèle rapporté par le serveurÀ régler dans Takuto
    qwen/qwen2.5-vl-7bself_hosted/qwen/qwen2.5-vl-7b
    lmstudio-community/Llama-3.1-8B-Instruct-GGUFself_hosted/lmstudio-community/Llama-3.1-8B-Instruct-GGUF
  3. Réglez la Base URL sur l’endpoint compatible OpenAI, en incluant le /v1 final :

    http://host.docker.internal:1234/v1
  4. Si votre serveur n’a pas besoin d’une vraie clé API (LM Studio, Ollama n’en ont en général pas besoin), cochez Allow shared default token pour que Takuto Core injecte un bearer factice.

  5. Réglez Context limit / Output limit pour correspondre à votre modèle — les endpoints locaux ne peuvent pas les rapporter, donc OpenCode s’appuie sur les valeurs que vous fournissez.

Côté serveur de modèle

Rendez le serveur joignable depuis les containers :

  • LM Studio : activez Developer → Local Server → « Serve on Local Network ». Sans cela, LM Studio ne se lie qu’à 127.0.0.1 et aucun container ne peut l’atteindre.
  • Firewall macOS : soit désactivé, soit avec le serveur de modèle explicitement autorisé.

Vérifiez qu’il est bien lié à toutes les interfaces, pas seulement à la loopback :

lsof -nP -iTCP -sTCP:LISTEN | grep 1234
# Attendu : *:1234 (LISTEN)   — lié à toutes les interfaces
# S’il affiche 127.0.0.1:1234, « Serve on Local Network » est désactivé.

Le sidecar de pont (contournement gVisor)

Vous n’en avez besoin que lorsque le serveur de modèle tourne sur le Mac hôte et que Docker Desktop utilise la pile réseau gVisor (le défaut depuis la 4.34+). Vous n’en avez pas besoin pour les fournisseurs cloud, ni quand le serveur de modèle tourne comme un container dans le réseau compose de Takuto Core — dans ce cas, pointez la Base URL sur le nom du service (par ex. http://lm-studio:1234/v1) et sautez le pont.

Avec gVisor, le trafic des containers vers des IP privées sur le LAN du Mac — y compris host.docker.internal (qui se résout en 192.168.65.254 dans le container) — peut expirer silencieusement, alors que les IP publiques fonctionnent très bien. Vérifiez votre type de réseau :

ps ax | grep com.docker.virtualization | grep -o 'networkType [a-z]*'
# networkType gvisor   ← concerné

Confirmer que c’est bien le problème gVisor

Si le serveur se lie à *:1234, que le firewall est désactivé, que l’internet public fonctionne depuis un container, mais que host.docker.internal:1234 et l’IP LAN du Mac expirent tous deux depuis l’intérieur du container — c’est la pile gVisor.

# L’internet public fonctionne depuis un container :
docker exec <takuto-container> sh -c 'wget -q -O - -T 3 https://1.1.1.1 | head -3'

# host.docker.internal se résout mais les connexions expirent :
docker exec <takuto-container> sh -c 'curl -v -m 3 http://host.docker.internal:1234/v1/models 2>&1 | tail -5'
# * Connection timed out after 3001 milliseconds

Correctif : le sidecar socat lm-bridge

Takuto Core fournit un minuscule container socat qui s’attache à la fois au réseau bridge par défaut (qui atteint l’hôte correctement sous gVisor) et au réseau compose (pour que les workers imbriqués DinD puissent y router). Activez-le avec un seul flag — les cibles make ci-dessous sont l’enrobage de confort du dépôt du moteur Takuto Core (la voie construisez-votre-propre-container) ; sous le capot, LM_BRIDGE=1 se contente de fusionner docker-compose.lm-bridge.yml dans la stack Compose :

make start BACKEND=postgres LM_BRIDGE=1

Cela démarre maestro-lm-bridge à l’IP fixe 172.20.0.250, redirigeant TCP/1234 → host.docker.internal:${LM_HOST_PORT:-1234}. Réglez ensuite la Base URL dans AI Settings sur :

http://172.20.0.250:1234/v1

Pour un port non standard (le 11434 d’Ollama, par exemple) :

LM_HOST_PORT=11434 make start BACKEND=postgres LM_BRIDGE=1

Test rapide une fois le pont en place

docker exec maestro-core-dind-1 docker run --rm --entrypoint /bin/bash \
  maestro:latest -c \
  'exec 3<>/dev/tcp/172.20.0.250/1234;
   printf "GET /v1/models HTTP/1.0\r\nHost: x\r\n\r\n" >&3;
   timeout 5 cat <&3 | head -5'

Un 200 OK suivi de JSON signifie que la voie du worker est saine.

Checklist pour OpenCode error: unknown error

Quand le dashboard affiche le cryptique OpenCode error: unknown error, parcourez ceci dans l’ordre — chaque point est un mode de défaillance réel :

  1. Le champ Model commence par self_hosted/.
  2. Allow shared default token est coché (sauf si vous avez enregistré un bearer par utilisateur). Quand il est désactivé et qu’aucun bearer n’est enregistré, aucun opencode.json n’est monté dans le worker et OpenCode quitte juste après sa migration de base de données du premier lancement.
  3. La Base URL se termine par /v1.
  4. Le serveur de modèle a « Serve on Local Network » activé (*:<port>, pas 127.0.0.1:<port>).
  5. Le réseau Docker Desktop est sain — lancez le test rapide ; s’il expire, utilisez le sidecar LM_BRIDGE ci-dessus.

Si les cinq sont au vert et que l’exécution échoue toujours, lancez opencode run manuellement dans le container avec --print-logs --log-level WARN pour voir la sortie stderr propre d’OpenCode au lieu du « unknown error » masqué.