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.

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)
| Capa | Qué es | Límite que importa |
|---|---|---|
| Daemon | ollama serve en el puerto 11434 | En Mac, si quieres exponerlo, launchctl setenv OLLAMA_HOST "0.0.0.0:11434" y reinicias la app |
| Chat nativo | POST /api/chat con model + messages | stream por defecto es true; para un loop de tools usa stream: false |
| Tools | Array tools estilo function calling | El modelo propone la llamada; tú ejecutas y devuelves role: "tool" |
| OpenAI compat | http://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) |
| Contexto | Default 4096 tokens | OLLAMA_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.

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:
- Mandas user +
tools. - El assistant responde con
tool_calls(puede ser una o varias en paralelo). - Ejecutas cada función en tu proceso.
- Devuelves mensajes
role: "tool"contool_nameycontent. - 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 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 -vresponde yollama psmuestra el modelo donde esperabas (GPU vs CPU). - Chat de prueba con
stream: falsea/api/chatantes de enchufar tools. - Al menos una tool con schema + allowlist + timeout.
- Tope de iteraciones en el loop.
-
num_ctxconsciente (4096 default; no copies 128k “por si acaso”). - Si usas
/v1, elapi_keydummy 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.
Lecturas relacionadas
Sigue explorando Frameworks y otras piezas para builders.



