自托管模型

Takuto Core 可以通过 OpenCode 提供商驱动一个自托管、OpenAI 兼容的模型服务器 —— LM Studio、Ollama、vLLM 等。这让源代码和 prompt 完全留在你掌控的基础设施上。本页讲解 设置过程,以及我们用 Docker Desktop 接 LM Studio 时撞上的几种失败模式,省得你再去重新 推导一遍。

下面每一项设置都可从仪表盘的 Configuration → AI Settings → OpenCode 编辑。无需 手工编辑 config.toml

基本设置

  1. Configuration → AI Settings 中把提供商设为 OpenCode

  2. 设置 Model 字段。OpenCode 的 -m 标志期望的格式是 <providerId>/<modelId>, 而 Takuto Core 生成的配置总是把提供商命名为 self_hosted,因此该值必须形如:

    self_hosted/<model-id-as-reported-by-the-server>
    服务器报告的模型 id在 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. Base URL 设为 OpenAI 兼容的端点,包含末尾的 /v1

    http://host.docker.internal:1234/v1
  4. 如果你的服务器不需要真实的 API key(LM Studio、Ollama 通常不需要),勾选 Allow shared default token,让 Takuto Core 注入一个占位 bearer。

  5. Context limit / Output limit 设成与你的模型相符 —— 本地端点无法报告这些 值,所以 OpenCode 依赖你提供的数值。

模型服务器一侧

让服务器可从 container 访问:

  • LM Studio: 打开 Developer → Local Server → “Serve on Local Network”。不开 它,LM Studio 只绑定到 127.0.0.1,没有任何 container 能访问到它。
  • macOS 防火墙: 要么关闭,要么明确放行该模型服务器。

确认它绑定到了所有网络接口,而不只是回环:

lsof -nP -iTCP -sTCP:LISTEN | grep 1234
# Want: *:1234 (LISTEN)   — bound to all interfaces
# If it shows 127.0.0.1:1234, "Serve on Local Network" is OFF.

桥接 sidecar(gVisor 变通方案)

只有当模型服务器跑在宿主 Mac 上而且 Docker Desktop 使用 gVisor 网络栈(4.34+ 的 默认设置)时,你才需要它。 对于云提供商你需要它;当模型服务器作为一个 container 跑在 Takuto Core 的 compose 网络内部时也不需要 —— 那种情况下把 Base URL 指向服务名(例如 http://lm-studio:1234/v1)并跳过这个桥接。

在 gVisor 下,从 container 到 Mac 局域网上私有 IP 的流量 —— 包括 host.docker.internal(在 container 内部解析为 192.168.65.254)—— 可能会静默超时, 而公网 IP 却一切正常。检查你的网络类型:

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

确认这是 gVisor 问题

如果服务器绑定到 *:1234、防火墙关着、从 container 访问公网正常,但从 container 内部 访问 host.docker.internal:1234 和 Mac 的局域网 IP 都超时 —— 那就是 gVisor 栈。

# Public internet works from a container:
docker exec <takuto-container> sh -c 'wget -q -O - -T 3 https://1.1.1.1 | head -3'

# host.docker.internal resolves but connections time out:
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

解决办法:lm-bridge socat sidecar

Takuto Core 附带了一个小巧的 socat container,它同时接入默认的 bridge 网络 (在 gVisor 下能正确访问宿主)和 compose 网络(这样 DinD 嵌套的 worker 也能路由到它)。 用一个标志就能把它启动起来 —— 下面的 make 目标是 Takuto Core 引擎仓库提供的便捷封装 (即自建 container 的路径);在底层,LM_BRIDGE=1 只是把 docker-compose.lm-bridge.yml 合并进 Compose 栈:

make start BACKEND=postgres LM_BRIDGE=1

那会在固定 IP 172.20.0.250 上启动 takuto-lm-bridge,把 TCP/1234 转发到 host.docker.internal:${LM_HOST_PORT:-1234}。然后把 AI Settings 里的 Base URL 设为:

http://172.20.0.250:1234/v1

对于非默认端口(比如 Ollama 的 11434):

LM_HOST_PORT=11434 make start BACKEND=postgres LM_BRIDGE=1

桥接起来后做一次冒烟测试

docker exec takuto-core-dind-1 docker run --rm --entrypoint /bin/bash \
  takuto: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'

一个 200 OK 后面跟着 JSON,就说明 worker 路径是健康的。

OpenCode error: unknown error 排查清单

当仪表盘显示晦涩的 OpenCode error: unknown error 时,按顺序逐项排查 —— 每一项都是 真实的失败模式:

  1. Model 字段以 self_hosted/ 开头。
  2. Allow shared default token 已勾选(除非你保存了一个按用户的 bearer)。当它关着 且没有保存 bearer 时,不会有 opencode.json 被挂载进 worker,OpenCode 会在它的 首次运行数据库迁移之后立刻退出。
  3. Base URL/v1 结尾。
  4. 模型服务器开启了 “Serve on Local Network”(是 *:<port>,而非 127.0.0.1:<port>)。
  5. Docker Desktop 网络是健康的 —— 跑一遍冒烟测试;如果超时,就用上面的 LM_BRIDGE sidecar。

如果这五项都绿了运行却仍然失败,就在 container 内部用 --print-logs --log-level WARN 手动运行 opencode run,以查看 OpenCode 自己的 stderr,而不是那条被隐藏的 “unknown error”。