Guía11 min

Agente de IA local con Ollama: loop, tools y API en tu máquina

Resumen

Monta un agente local con Ollama: instalar el daemon en macOS o Linux, chat en localhost:11434, tool calling single-shot y paralelo, compatibilidad OpenAI en /v1, contexto por defecto de 4096 tokens y un loop mínimo en Python sin mandar datos a una API de pago.

OpenAI
Portátil con un daemon local sirviendo un modelo y un loop de agente con herramientas

Qué resuelve

Esta pieza se queda en la decisión práctica: qué instalar, qué riesgo agrega y cómo aplicarlo sin romper operación.

Un agente local no es “descargar un chatbot”. Es un modelo en tu máquina + un loop que llama tools + un contrato HTTP. Ollama cubre las tres: CLI para bajar el modelo, POST /api/chat en http://localhost:11434, tool calling y un espejo parcial de OpenAI en /v1. Cifras y comandos salen de las docs de Ollama del 3 de septiembre de 2026.

Si todavía no tienes claro qué es un agente frente a un chat, empieza por qué es un agente de IA. El bucle con tools contra una API de pago está en crear un agente con Python. Aquí el foco es correr ese bucle en local.

Qué te da Ollama (y qué no)

CapaQué esLímite que importa
Daemonollama serve en el puerto 11434En Mac, si quieres exponerlo, launchctl setenv OLLAMA_HOST "0.0.0.0:11434" y reinicias la app
Chat nativoPOST /api/chat con model + messagesstream por defecto es true; para un loop de tools usa stream: false
ToolsArray tools estilo function callingEl modelo propone la llamada; tú ejecutas y devuelves role: "tool"
OpenAI compathttp://localhost:11434/v1/api_key es obligatorio en el SDK y se ignora; Responses API existe desde Ollama v0.13.3 y no es stateful (previous_response_id no aplica)
ContextoDefault 4096 tokensOLLAMA_CONTEXT_LENGTH en el server, /set parameter num_ctx en el CLI, o options.num_ctx en la API

Ollama no es un orquestador de producción ni un sandbox de código. El modelo vive en tu RAM/GPU; las tools viven en tu proceso. Si una tool puede borrar archivos o pegarle a un webhook, el riesgo es tuyo, no del daemon.

Paso 1: instalar y comprobar GPU

macOS: las docs piden Sonoma (v14) o más nuevo. Apple M series usa CPU y GPU; x86 es solo CPU. Instala arrastrando ollama.dmg a Aplicaciones. El CLI debería quedar en /usr/local/bin. Los modelos ocupan decenas o cientos de GB: si $HOME no alcanza, cambia la ruta de datos antes de hacer pull.

Linux: curl -fsSL https://ollama.com/install.sh | sh, luego ollama serve o el unit systemd (ExecStart=/usr/bin/ollama serve, usuario ollama). CUDA se verifica con nvidia-smi; AMD pide el tarball ROCm extra.

Comprueba dónde cargó el modelo:

ollama ps

La columna Processor dice 100% GPU, 100% CPU o un split 48%/52% CPU/GPU. Si esperabas GPU y ves 100% CPU, el modelo no cabe o los drivers no están.

El quickstart arranca así:

ollama
ollama run gemma4

gemma4:cloud usa el mismo CLI contra la nube de Ollama. Para un agente local de verdad, quédate en el tag sin :cloud.

Instalación del daemon Ollama y un modelo cargado en GPU o CPU

Paso 2: el contrato HTTP del agente

Un chat no es un agente. El agente es el loop: modelo → ¿hay tool_calls? → ejecutas → reinyectas → hasta que content llega sin tools.

Request mínimo (docs de /api/chat):

curl http://localhost:11434/api/chat -d '{
  "model": "gemma4",
  "messages": [{"role": "user", "content": "why is the sky blue?"}],
  "stream": false
}'

Campos útiles del body: tools, format (json o un JSON schema), think (boolean o "high"|"medium"|"low"|"max"), keep_alive (5m o 0 para descargar el modelo), options.num_ctx.

La respuesta trae message.tool_calls[].function.name + arguments, y métricas en nanosegundos: load_duration, prompt_eval_count, prompt_eval_cached_count, eval_count. prompt_eval_cached_count es la señal de que el prefijo se reutilizó; no es magia de “memoria a largo plazo”.

Paso 3: tool calling local (single-shot y paralelo)

Las docs de tool calling usan qwen3 y un schema JSON clásico (type: "function", parameters con required). Flujo:

  1. Mandas user + tools.
  2. El assistant responde con tool_calls (puede ser una o varias en paralelo).
  3. Ejecutas cada función en tu proceso.
  4. Devuelves mensajes role: "tool" con tool_name y content.
  5. El modelo redacta la respuesta final.

El SDK de Python acepta la función directa en tools=[get_temperature] o el schema JSON. En paralelo, las docs piden varias tools a la vez: contestas todas antes del siguiente chat.

Reglas de un agente serio (detalle en function calling confiable):

  • Valida argumentos antes de ejecutar (ciudad string, no SQL).
  • Timeout y presupuesto de tools por turno.
  • Nunca pases una tool de shell sin allowlist. Local no significa inofensivo.

Paso 4: reusar el SDK de OpenAI contra localhost

Si ya tienes un agente escrito contra OpenAI, Ollama documenta el puente:

from openai import OpenAI
client = OpenAI(base_url="http://localhost:11434/v1/", api_key="ollama")

Endpoints soportados en las docs: /v1/chat/completions (streaming, JSON mode, vision, tools, reasoning_effort), /v1/completions, /v1/models, /v1/embeddings, /v1/responses (desde v0.13.3, sin estado entre llamadas). El api_key no autentica: cualquiera que alcance el puerto habla con el modelo. En un VPS, no dejes OLLAMA_HOST=0.0.0.0:11434 abierto a internet.

Este puente no te da la memoria/RAG de un producto: el historial lo armáis vosotros en messages.

Loop de agente local: chat, tool_calls, ejecución en proceso y respuesta

Loop mínimo (Python)

Sustituye el chat de pago del tutorial de Python por Ollama. Pseudocódigo fiel al contrato:

import json, urllib.request

TOOLS = [{
  "type": "function",
  "function": {
    "name": "list_dir",
    "description": "Lista archivos de un directorio permitido",
    "parameters": {
      "type": "object",
      "required": ["path"],
      "properties": {"path": {"type": "string"}}
    }
  }
}]

def chat(messages):
    req = urllib.request.Request(
        "http://localhost:11434/api/chat",
        data=json.dumps({
            "model": "qwen3",
            "messages": messages,
            "tools": TOOLS,
            "stream": False,
        }).encode(),
        headers={"Content-Type": "application/json"},
    )
    with urllib.request.urlopen(req) as res:
        return json.load(res)

messages = [{"role": "user", "content": "¿Qué hay en /tmp/demo?"}]
for _ in range(8):  # tope de pasos
    data = chat(messages)
    msg = data["message"]
    messages.append(msg)
    calls = msg.get("tool_calls") or []
    if not calls:
        print(msg.get("content"))
        break
    for call in calls:
        fn = call["function"]["name"]
        args = call["function"]["arguments"]
        messages.append({"role": "tool", "tool_name": fn, "content": "..."})

Ocho pasos es un techo. Sin techo, un modelo en loop de tools es un calentador de GPU.

Cuándo local gana (y cuándo no)

Local gana si los datos no pueden salir, si el volumen es alto y constante, o si quieres latencia sin round-trip a un proveedor. Eso ya está argumentado en open source vs propietarios. No gana si necesitas el techo de un modelo frontier, evals gestionados o cero operación de GPU.

Hardware: ollama ps miente menos que el marketing del modelo. Si ves split CPU/GPU, espera latencia peor y más RAM. Sube num_ctx solo si el prompt lo necesita: el default 4096 existe porque el contexto largo no es gratis en VRAM.

Checklist de arranque

  • ollama -v responde y ollama ps muestra el modelo donde esperabas (GPU vs CPU).
  • Chat de prueba con stream: false a /api/chat antes de enchufar tools.
  • Al menos una tool con schema + allowlist + timeout.
  • Tope de iteraciones en el loop.
  • num_ctx consciente (4096 default; no copies 128k “por si acaso”).
  • Si usas /v1, el api_key dummy no es un firewall: controla quién llega al puerto.
  • Tag local, no :cloud, si la promesa era privacidad.

FAQ

¿Qué modelo uso? El quickstart enseña gemma4; tool calling se documenta con qwen3. Elige el que ollama ps cargue entero en GPU para tu máquina, no el nombre más famoso.

¿Sirve como drop-in de OpenAI? Para chat, tools, JSON mode y embeddings, las docs dicen que sí en /v1. No copies asunciones de Assistants stateful: Responses en Ollama no guarda conversación.

¿Y n8n / no-code? Esta guía es código + daemon. El camino sin programar está en agente de IA sin programar.


Siguiente paso: instala Ollama, corre ollama run gemma4, y en la misma tarde sustituye el base_url de tu agente Python por http://localhost:11434/v1/. Si quieres el mapa de lecciones, sigue el curso de instalar un agente.