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) o ipconfig (Windows).
  • Ambos equipos en la misma red. Una IP 192.168.x.x o 10.x.x.x no es alcanzable desde fuera de casa.

Seguridad

Ollama no tiene autenticación. Abrirlo con 0.0.0.0 lo 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 Ollama

La 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 tcp

Windows

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 abrirlo

setx 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-agent
pi --version   # 0.84.4

03 · 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/models

Deberí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:

CampoValorMotivo
baseUrl…:11434/v1El /v1 final es obligatorio: es la capa compatible con OpenAI. Sin él, todas las peticiones dan 404.
apiopenai-completionsOllama 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.
reasoningtrue / falseSó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 contextWindow y maxTokens, 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íntomaCausaSolución
Connection refused desde el PC nuevoOllama 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 nadaFalta 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ónbaseUrl sin el /v1 final.Debe terminar en :11434/v1.
El servidor rechaza el rol developerServidores compatibles-con-OpenAI antiguos no lo soportan.Añade al proveedor "compat": { "supportsDeveloperRole": false }.
Error con reasoning_effortEl modelo no acepta niveles de razonamiento.Añade "supportsReasoningEffort": false al mismo bloque compat.
Responde, pero nunca usa herramientasEl 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 largascontextWindow 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 otroEl 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-providers

Escribe exactamente el mismo models.json. Ojo: sí rellena enabledModels, con el efecto descrito arriba.

Referencias