Ollama and opencode setup for the Mac mini
The Ollama LaunchAgent, derived models (mostly to set num_ctx, which Ollama's OpenAI-compatible API won't take per request), and opencode's config, now defaulting to qwen3-coder:30b: qwen3:32b tends to end its turn after thinking without making the tool call it planned. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
e2df9cc0d0
7 changed files with 133 additions and 0 deletions
42
README.md
Normal file
42
README.md
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# local-llm
|
||||
|
||||
Local LLM setup on the Mac mini: [Ollama](https://ollama.com) serving models,
|
||||
[opencode](https://opencode.ai) using them.
|
||||
|
||||
```sh
|
||||
./setup.sh
|
||||
```
|
||||
|
||||
does everything, and is safe to re-run:
|
||||
|
||||
- installs `ollama/com.ollama.server.plist` as the LaunchAgent that runs
|
||||
`ollama serve` (reloaded only when it changed, since that restarts Ollama);
|
||||
- builds each model in `ollama/models/` (pulling its base model if missing);
|
||||
- symlinks `~/.config/opencode/opencode.json` to `opencode/opencode.json`, so
|
||||
edits made either way end up here.
|
||||
|
||||
Needs `brew install ollama opencode`.
|
||||
|
||||
## Models
|
||||
|
||||
| Model | From | Used for |
|
||||
|-------|------|----------|
|
||||
| `qwen3-coder:30b-32k` | `qwen3-coder:30b` | opencode's default |
|
||||
| `qwen3:32b-32k` | `qwen3:32b` | opencode, as an option |
|
||||
| `inspector` | `qwen3:32b` | 16k-context variant |
|
||||
|
||||
Each derived model mostly exists to set `num_ctx`. Ollama's OpenAI-compatible
|
||||
API, which opencode talks to, ignores any context size sent with a request, so
|
||||
without it a long agent prompt is silently cut to Ollama's small default
|
||||
context. A Modelfile's `# name:` line is the name it's created under.
|
||||
|
||||
`qwen3:32b` is a thinking model, and in opencode it often ends its turn after
|
||||
thinking, without making the tool call it planned. `qwen3-coder` was trained
|
||||
for agentic tool use and doesn't think first, so it doesn't have that
|
||||
problem, and it's much faster: a mixture of experts with ~3B active parameters.
|
||||
|
||||
## Server settings
|
||||
|
||||
From the LaunchAgent: listens on all interfaces (`0.0.0.0:11434`), keeps models
|
||||
loaded indefinitely (`OLLAMA_KEEP_ALIVE=-1`), flash attention on. Logs go to
|
||||
`/tmp/ollama.log`.
|
||||
14
ollama/com.ollama.server.plist
Normal file
14
ollama/com.ollama.server.plist
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0"><dict>
|
||||
<key>Label</key><string>com.ollama.server</string>
|
||||
<key>ProgramArguments</key><array><string>/opt/homebrew/bin/ollama</string><string>serve</string></array>
|
||||
<key>EnvironmentVariables</key><dict>
|
||||
<key>OLLAMA_HOST</key><string>0.0.0.0:11434</string>
|
||||
<key>OLLAMA_KEEP_ALIVE</key><string>-1</string>
|
||||
<key>OLLAMA_FLASH_ATTENTION</key><string>1</string>
|
||||
</dict>
|
||||
<key>RunAtLoad</key><true/>
|
||||
<key>KeepAlive</key><true/>
|
||||
<key>StandardErrorPath</key><string>/tmp/ollama.log</string>
|
||||
</dict></plist>
|
||||
3
ollama/models/inspector.Modelfile
Normal file
3
ollama/models/inspector.Modelfile
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# name: inspector
|
||||
FROM qwen3:32b
|
||||
PARAMETER num_ctx 16384
|
||||
5
ollama/models/qwen3-32b-32k.Modelfile
Normal file
5
ollama/models/qwen3-32b-32k.Modelfile
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# name: qwen3:32b-32k
|
||||
# General-purpose thinking model. In opencode it often ends its turn after
|
||||
# thinking without making the tool call it planned; fine for plain chat.
|
||||
FROM qwen3:32b
|
||||
PARAMETER num_ctx 32768
|
||||
5
ollama/models/qwen3-coder-30b-32k.Modelfile
Normal file
5
ollama/models/qwen3-coder-30b-32k.Modelfile
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# name: qwen3-coder:30b-32k
|
||||
# opencode's default: trained for agentic tool calling, no thinking phase,
|
||||
# mixture-of-experts (~3B active params) so it's fast.
|
||||
FROM qwen3-coder:30b
|
||||
PARAMETER num_ctx 32768
|
||||
26
opencode/opencode.json
Normal file
26
opencode/opencode.json
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"provider": {
|
||||
"ollama": {
|
||||
"npm": "@ai-sdk/openai-compatible",
|
||||
"name": "Ollama (local)",
|
||||
"options": {
|
||||
"baseURL": "http://localhost:11434/v1"
|
||||
},
|
||||
"models": {
|
||||
"qwen3-coder:30b-32k": {
|
||||
"name": "Qwen3 Coder 30B (32k context)",
|
||||
"tools": true,
|
||||
"limit": { "context": 32768, "output": 8192 }
|
||||
},
|
||||
"qwen3:32b-32k": {
|
||||
"name": "Qwen3 32B (32k context)",
|
||||
"tools": true,
|
||||
"limit": { "context": 32768, "output": 8192 }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"model": "ollama/qwen3-coder:30b-32k",
|
||||
"small_model": "ollama/qwen3-coder:30b-32k"
|
||||
}
|
||||
38
setup.sh
Executable file
38
setup.sh
Executable file
|
|
@ -0,0 +1,38 @@
|
|||
#!/bin/sh
|
||||
# Makes this Mac match the repo: Ollama's LaunchAgent, the derived models in
|
||||
# ollama/models, and opencode's config (symlinked, so edits land in the repo).
|
||||
# Safe to re-run.
|
||||
set -eu
|
||||
cd "$(dirname "$0")"
|
||||
repo=$(pwd)
|
||||
|
||||
# Ollama server LaunchAgent. Only reloaded when it changed: reloading
|
||||
# restarts Ollama and unloads whatever models are in memory.
|
||||
plist=~/Library/LaunchAgents/com.ollama.server.plist
|
||||
if ! cmp -s ollama/com.ollama.server.plist "$plist"; then
|
||||
echo "Installing $plist"
|
||||
launchctl bootout "gui/$(id -u)" "$plist" 2>/dev/null || true
|
||||
cp ollama/com.ollama.server.plist "$plist"
|
||||
launchctl bootstrap "gui/$(id -u)" "$plist"
|
||||
until curl -fs localhost:11434/api/version >/dev/null; do sleep 1; done
|
||||
fi
|
||||
|
||||
# Derived models: each Modelfile names its model on a "# name:" line.
|
||||
for f in ollama/models/*.Modelfile; do
|
||||
name=$(sed -n 's/^# name: *//p' "$f")
|
||||
base=$(sed -n 's/^FROM *//p' "$f")
|
||||
[ -n "$name" ] && [ -n "$base" ] || { echo "$f: needs '# name:' and FROM lines" >&2; exit 1; }
|
||||
ollama show "$base" >/dev/null 2>&1 || ollama pull "$base"
|
||||
echo "Creating $name from $f"
|
||||
ollama create "$name" -f "$f" >/dev/null
|
||||
done
|
||||
|
||||
# opencode
|
||||
mkdir -p ~/.config/opencode
|
||||
target=~/.config/opencode/opencode.json
|
||||
if [ -e "$target" ] && [ ! -L "$target" ]; then
|
||||
mv "$target" "$target.bak"
|
||||
echo "Moved the old $target to $target.bak"
|
||||
fi
|
||||
ln -sfn "$repo/opencode/opencode.json" "$target"
|
||||
echo "Linked $target"
|
||||
Loading…
Reference in a new issue