Cómo dejar pi apuntando a un servidor Ollama que corre en otra máquina de la red local. De cero a un modelo funcionando en un PC nuevo. Verificado con pi 0.84.4 y un servidor con qwen3:8b.
El cliente no guarda pesos ni ejecuta el modelo: sólo habla HTTP contra el puerto 11434 del servidor, usando su capa compatible con OpenAI (/v1).
Antes de empezar
- El servidor Ollama ya instalado en la otra máquina, con al menos un modelo descargado (
ollama pull qwen3:8b). - Su IP en la red local. En el propio servidor:
ipconfig getifaddr en0(macOS),hostname -I(Linux) oipconfig(Windows). - Ambos equipos en la misma red. Una IP
192.168.x.xo10.x.x.xno es alcanzable desde fuera de casa.
Seguridad
Ollama no tiene autenticación. Abrirlo con
0.0.0.0lo deja accesible para cualquiera en tu LAN, que es lo que queremos. Lo que nunca debes hacer es abrir el puerto 11434 en el router hacia internet: sería un servidor de inferencia gratuito y anónimo para quien lo encuentre.
01 · Hacer que Ollama escuche en la red
En el servidor Ollama
Por defecto Ollama sólo acepta conexiones de 127.0.0.1, así que desde otro PC no responde. Hay que fijar OLLAMA_HOST y reiniciar el servicio.
macOS
# La app de Ollama lee las variables de launchd
launchctl setenv OLLAMA_HOST "0.0.0.0:11434"
# Salir de Ollama desde la barra de menús y volver a abrirlo
osascript -e 'quit app "Ollama"'
open -a OllamaLa primera conexión entrante puede disparar el diálogo del firewall de macOS: acepta.
Linux (systemd)
sudo systemctl edit ollama.service
# Añade en el editor que se abre:
# [Service]
# Environment="OLLAMA_HOST=0.0.0.0:11434"
sudo systemctl daemon-reload
sudo systemctl restart ollama
# Si usas ufw, permite el puerto sólo desde la LAN
sudo ufw allow from 192.168.0.0/16 to any port 11434 proto tcpWindows
setx OLLAMA_HOST "0.0.0.0:11434"
# Regla de firewall limitada a la red local
New-NetFirewallRule -DisplayName "Ollama LAN" -Direction Inbound `
-Protocol TCP -LocalPort 11434 -Action Allow -Profile Private
# Cierra Ollama desde la bandeja del sistema y vuelve a abrirlosetx sólo afecta a procesos nuevos: hay que cerrar Ollama del todo, no sólo la ventana.
02 · Instalar pi
En el PC nuevo
Elige una vía. La de curl es la oficial y no necesita Node instalado.
# macOS / Linux
curl -fsSL https://pi.dev/install.sh | sh
# Windows (PowerShell)
powershell -c "irm https://pi.dev/install.ps1 | iex"
# o vía npm — requiere Node >= 22.19.0
npm install -g --ignore-scripts @earendil-works/pi-coding-agentpi --version # 0.84.403 · Confirmar que se llega al servidor
En el PC nuevo
Este paso separa un problema de red de un problema de configuración. Hazlo antes de tocar ningún fichero: si falla aquí, no sigas.
# Sustituye la IP por la de tu servidor
curl -s http://192.168.68.107:11434/api/tags
# Y el endpoint compatible con OpenAI, que es el que usa pi
curl -s http://192.168.68.107:11434/v1/modelsDeberías ver un JSON con tus modelos, del estilo {"models":[{"name":"qwen3:8b",…}]}. Si sale Connection refused o se queda colgado, vuelve al paso 01: casi siempre es OLLAMA_HOST sin aplicar o el firewall.
04 · Declarar el proveedor en models.json
En el PC nuevo
pi lee los proveedores propios de ~/.pi/agent/models.json (en Windows, %USERPROFILE%\.pi\agent\models.json). El fichero no existe hasta que lo creas.
{
"providers": {
"ollama": {
"baseUrl": "http://192.168.68.107:11434/v1",
"api": "openai-completions",
"apiKey": "ollama",
"compat": {
"supportsDeveloperRole": false,
"supportsReasoningEffort": true,
"maxTokensField": "max_tokens"
},
"models": [
{
"id": "qwen3.5:9b",
"name": "Qwen3.5 9B (Ollama)",
"reasoning": true,
"defaultReasoningLevel": "medium",
"input": ["text", "image"],
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 },
"contextWindow": 32768,
"maxTokens": 8192,
"limitSource": "manual"
},
{
"id": "qwen3:8b",
"name": "Qwen3 8B (Ollama)",
"reasoning": true,
"defaultReasoningLevel": "medium",
"input": ["text"],
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 },
"contextWindow": 32768,
"maxTokens": 8192,
"limitSource": "manual"
},
{
"id": "qwen2.5-coder:7b",
"name": "Qwen2.5 Coder 7B (Ollama)",
"reasoning": false,
"input": ["text"],
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 },
"contextWindow": 32768,
"maxTokens": 8192,
"limitSource": "manual"
}
]
}
}
}Las cuatro decisiones que importan:
| Campo | Valor | Motivo |
|---|---|---|
baseUrl | …:11434/v1 | El /v1 final es obligatorio: es la capa compatible con OpenAI. Sin él, todas las peticiones dan 404. |
api | openai-completions | Ollama no habla el protocolo nativo de Anthropic ni el de Google. Este es el único que encaja. |
apiKey | "ollama" | Relleno. Ollama lo ignora, pero pi oculta del selector los modelos sin credencial configurada, así que sin este campo el proveedor carga y aun así no aparece. |
reasoning | true / false | Sólo true si el modelo declara la capacidad thinking. Ponerlo a ciegas hace que pi mande parámetros que el modelo no entiende. |
Valores por defecto
Si omites
contextWindowymaxTokens, pi asume 128000 y 16384. Para un modelo local suele ser optimista: el paso 06 explica cómo leer los reales del propio servidor.
05 · Verificar de punta a punta
En el PC nuevo
Tres comprobaciones, de menos a más exigente. La tercera es la que de verdad importa si vas a usar pi como agente.
# 1 · ¿pi ve el modelo?
pi --list-models ollama
# provider model context max-out thinking images
# ollama qwen3:8b 41.0K 16.4K yes no
# 2 · ¿responde?
pi --provider ollama --model qwen3:8b --no-session -nt -p "Di solo: OK"
# 3 · ¿sabe usar herramientas? (lo que hace útil a un agente)
echo "hola" > prueba.txt
pi --provider ollama --model qwen3:8b --no-session \
-p "Lee prueba.txt y dime su contenido exacto."La tercera debe leer el fichero de verdad, no inventárselo. Si el modelo responde pero nunca invoca la herramienta, no admite tool calling: compruébalo con /api/show y usa otro modelo.
Ya puedes lanzar la sesión interactiva. Dentro de pi, /model relee models.json cada vez que lo abres, así que puedes editar el fichero sin reiniciar.
pi --provider ollama --model qwen3:8b
# Alternar con Ctrl+P entre lo local y lo de la nube
pi --models "anthropic/*,ollama/*"06 · Generarlo todo con un script (opcional)
Si el servidor tiene varios modelos, escribir el JSON a mano se hace pesado y es fácil equivocarse en el contexto. Este script pregunta al servidor por sus modelos, lee de cada uno su ventana de contexto y sus capacidades reales, y escribe models.json conservando los proveedores que ya tuvieras.
#!/usr/bin/env bash
# Uso: ./pi-ollama-setup.sh <ip-o-host> [puerto] [nombre-proveedor]
set -euo pipefail
HOST="${1:?Uso: $0 <ip-o-host> [puerto] [nombre-proveedor]}"
PORT="${2:-11434}"
PROVIDER="${3:-ollama}"
BASE="http://${HOST}:${PORT}"
OUT="$HOME/.pi/agent/models.json"
command -v python3 >/dev/null || { echo "Falta python3"; exit 1; }
echo "==> Probando ${BASE} ..."
curl -sf --max-time 5 "${BASE}/api/tags" >/dev/null \
|| { echo "ERROR: no se llega a ${BASE}. Revisa OLLAMA_HOST y el firewall."; exit 1; }
echo "==> Leyendo modelos y metadatos ..."
python3 - "$BASE" "$PROVIDER" "$OUT" <<'PY'
import json, sys, os, urllib.request
base, provider, out = sys.argv[1], sys.argv[2], sys.argv[3]
def get(path, payload=None):
data = json.dumps(payload).encode() if payload else None
req = urllib.request.Request(base + path, data=data,
headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=30) as r:
return json.load(r)
models = []
for m in get("/api/tags").get("models", []):
mid = m["name"]
caps, ctx = [], None
try:
show = get("/api/show", {"model": mid})
caps = show.get("capabilities", []) or []
arch = show.get("model_info", {}).get("general.architecture")
if arch:
ctx = show.get("model_info", {}).get(f"{arch}.context_length")
except Exception:
pass
ctx = ctx or 128000
models.append({
"id": mid,
"name": f"{mid} (Ollama)",
"reasoning": "thinking" in caps,
"input": ["text", "image"] if "vision" in caps else ["text"],
"cost": {"input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0},
"contextWindow": ctx,
"maxTokens": min(16384, ctx // 2),
})
print(f" - {mid} ctx={ctx} caps={','.join(caps) or 'none'}")
if not models:
sys.exit("No hay modelos en el servidor. Haz 'ollama pull <modelo>' primero.")
data = {"providers": {}}
if os.path.exists(out):
try:
with open(out) as f:
data = json.load(f)
except Exception:
pass
os.replace(out, out + ".bak")
data.setdefault("providers", {})[provider] = {
"baseUrl": f"{base}/v1",
"api": "openai-completions",
"apiKey": "ollama",
"models": models,
}
os.makedirs(os.path.dirname(out), exist_ok=True)
with open(out, "w") as f:
json.dump(data, f, indent=2)
os.chmod(out, 0o600)
print(f"==> Escrito {out} ({len(models)} modelo/s en '{provider}')")
PY
echo "==> Verificando con pi ..."
pi --list-models "$PROVIDER"Ejecución:
chmod +x pi-ollama-setup.sh
./pi-ollama-setup.sh 192.168.68.107
# ==> Probando http://192.168.68.107:11434 ...
# ==> Leyendo modelos y metadatos ...
# - qwen3:8b ctx=40960 caps=completion,tools,thinking
# ==> Escrito /Users/tu-usuario/.pi/agent/models.json (1 modelo/s en 'ollama')Guarda una copia en .bak antes de sobrescribir. En Windows, ejecútalo desde WSL o Git Bash.
Cuando algo no funciona
| Síntoma | Causa | Solución |
|---|---|---|
| Connection refused desde el PC nuevo | Ollama sigue escuchando sólo en localhost, o el firewall bloquea el 11434. | Repite el paso 01 y confirma que reiniciaste el servicio, no sólo la ventana. |
pi --list-models ollama no muestra nada | Falta apiKey, o el JSON tiene un error de sintaxis. | Añade "apiKey": "ollama". Valida con python3 -m json.tool ~/.pi/agent/models.json. |
| 404 en cada petición | baseUrl sin el /v1 final. | Debe terminar en :11434/v1. |
El servidor rechaza el rol developer | Servidores compatibles-con-OpenAI antiguos no lo soportan. | Añade al proveedor "compat": { "supportsDeveloperRole": false }. |
Error con reasoning_effort | El modelo no acepta niveles de razonamiento. | Añade "supportsReasoningEffort": false al mismo bloque compat. |
| Responde, pero nunca usa herramientas | El modelo no tiene la capacidad tools. | Compruébalo con curl -s $BASE/api/show -d '{"model":"…"}' y cambia de modelo. |
| Se corta o alucina en conversaciones largas | contextWindow declarado mayor que el num_ctx real que sirve Ollama. | Baja el valor, o sube OLLAMA_CONTEXT_LENGTH en el servidor. |
| Dejó de funcionar de un día para otro | El router dio otra IP al servidor por DHCP. | Reserva la IP en el router, o usa el nombre de host (mi-servidor.local) en baseUrl. |
Dos detalles que muerden
No añadas enabledModels a settings.json
En ~/.pi/agent/settings.json ese campo actúa como filtro exclusivo: vacío o ausente, ves todos los modelos; en cuanto metes una entrada, pi oculta todo lo demás. Si añades sólo el modelo de Ollama, desaparecen los de Anthropic del selector. Déjalo como está.
El asistente interactivo, si prefieres no editar JSON
Existe una extensión de la comunidad que hace esto mismo con un asistente en pantalla, incluido el autodescubrimiento de modelos:
pi install npm:pi-setup-custom-providers
# y dentro de pi:
/setup-custom-providersEscribe exactamente el mismo models.json. Ojo: sí rellena enabledModels, con el efecto descrito arriba.
Referencias
- Documentación oficial del esquema:
packages/coding-agent/docs/models.mden earendil-works/pi - Instalación: pi.dev
- Versión web de esta guía: https://claude.ai/code/artifact/a185a35d-a3bb-4c96-83f9-41ced4fc0c4f