Cambiar tema

Ollama API en la práctica: guía de clientes con Python y Node.js

Easton editorial illustration: coding assistant migration bridge

Escribes ollama run gemma3 en la terminal y aparece la primera respuesta. El modelo local ya está corriendo.

La pregunta natural: ¿puedo integrarlo en mi proyecto? Sin API Key, sin pagar, todo en local — suena bien.

Revisando la documentación, Ollama ofrece SDK oficiales para Python y JavaScript, e incluso puedes usar el SDK de OpenAI cambiando dos líneas. Más sencillo de lo que parece.

Pero sencillo no significa sin trampas. ¿Cómo acumular streaming? ¿Cómo escribir el Agent Loop para tool calling? ¿Cómo separar razonamiento y respuesta en modo thinking? Son errores que ya cometí.

Este artículo cubre esas trampas. Comparativa Python y Node.js, SDK nativo y compatibilidad OpenAI — una guía completa de clientes.

Si aún no tienes Ollama instalado, empieza por el primer capítulo de la serie: Integración LangChain + Ollama y pon el modelo local en marcha.


Capítulo 1: Fundamentos de la API de Ollama

Primero, cómo funciona la API.

Ollama levanta por defecto un servicio REST en local, en http://localhost:11434/api. Si abres esa URL en el navegador verás el lacónico “Ollama is running” — señal de que todo va bien.

Endpoints principales

Dos endpoints que conviene memorizar:

EndpointUsoCaracterísticas
/api/chatConversación multi-turnAcepta array messages y contexto
/api/generateGeneración simpleDirecto, ideal para tareas puntuales

También está /v1/chat/completions, el endpoint compatible con OpenAI. Si ya tienes un proyecto OpenAI, cambia base_url y listo; lo veremos más adelante.

Probar con curl

Prueba la API de la forma más cruda:

curl http://localhost:11434/api/chat -d '{
  "model": "gemma3",
  "messages": [
    { "role": "user", "content": "¿Por qué el cielo es azul?" }
  ]
}'

La terminal devuelve un montón de JSON. Fíjate en message.content: ahí está la respuesta del modelo.

La estructura de respuesta es algo así:

{
  "model": "gemma3",
  "created_at": "2026-04-18T01:23:45.678Z",
  "message": {
    "role": "assistant",
    "content": "El cielo se ve azul principalmente porque..."
  },
  "done": true
}

El campo done importa. En streaming, cada chunk tiene done: false hasta el último, que lleva true. Lo usarás al procesar streaming.

Respuesta en streaming

Por defecto la API espera a que el modelo termine y devuelve todo de golpe. Para efecto máquina de escribir, añade stream: true:

curl http://localhost:11434/api/chat -d '{
  "model": "gemma3",
  "messages": [{ "role": "user", "content": "¿Por qué el cielo es azul?" }],
  "stream": true
}'

Esta vez salen líneas JSON una a una. Cada línea es un chunk; hay que acumularlos para la respuesta completa.

Procesar chunks a mano es tedioso. Por eso existen los SDK — encapsulan esos detalles.


Capítulo 2: SDK de Python al completo

El SDK de Python lo mantiene el equipo oficial. Instalación trivial:

pip install ollama

Listo para usar. Soporta Python 3.8+, bastante accesible.

Llamada básica

La forma más simple, en una línea:

from ollama import chat

response = chat(
  model='gemma3',
  messages=[{'role': 'user', 'content': '¿Por qué el cielo es azul?'}]
)

print(response.message.content)

Así de simple. chat() es un atajo del SDK que crea un Client por defecto contra Ollama local.

Si Ollama corre en otra máquina, crea tu propio Client:

from ollama import Client

client = Client(host='http://192.168.1.100:11434')
response = client.chat(model='gemma3', messages=[...])

Streaming

El streaming es clave: el usuario ve el texto aparecer poco a poco, no un bloque tras una espera larga.

from ollama import chat

stream = chat(
  model='gemma3',
  messages=[{'role': 'user', 'content': '¿Por qué el cielo es azul?'}],
  stream=True,
)

for chunk in stream:
  print(chunk['message']['content'], end='', flush=True)

Trampa habitual: chunk es un diccionario, no un objeto. Usa chunk['message']['content'], no chunk.message.content. Me costó un rato verlo.

Cliente asíncrono

En arquitectura async — FastAPI, aiohttp — usa el cliente asíncrono:

import asyncio
from ollama import AsyncClient

async def main():
  client = AsyncClient()
  
  # Sin streaming
  response = await client.chat(
    model='gemma3',
    messages=[{'role': 'user', 'content': 'Hola'}]
  )
  print(response.message.content)
  
  # Con streaming
  stream = await client.chat(
    model='gemma3',
    messages=[{'role': 'user', 'content': '¿Por qué el cielo es azul?'}],
    stream=True,
  )
  async for chunk in stream:
    print(chunk['message']['content'], end='', flush=True)

asyncio.run(main())

El streaming async devuelve un async generator; recórrelo con async for. Misma lógica que la versión síncrona, con await y async.

Cloud Models

El SDK también soporta modelos en la nube. Algunos modelos grandes — p. ej. gpt-oss 120B — no caben en local, pero sí en cloud.

from ollama import chat

response = chat(
  model='gpt-oss:120b-cloud',
  messages=[{'role': 'user', 'content': 'Hola'}]
)

El sufijo -cloud en el nombre del modelo usa la API cloud. Necesitas cuenta Ollama cloud y API Key; la configuración difiere de local. Consulta la documentación oficial si te interesa.

Modelos pequeños en local ahorran dinero; grandes en cloud ahorran hardware. Un híbrido razonable.


Capítulo 3: SDK de Node.js al completo

El SDK de Node.js es igual de conciso:

npm i ollama

Soporta Node.js y navegador. En el navegador importa aparte:

// Node.js
import ollama from 'ollama'

// Navegador
import ollama from 'ollama/browser'

Llamada básica

En Node.js todo es async por defecto:

import ollama from 'ollama'

const response = await ollama.chat({
  model: 'gemma3',
  messages: [{ role: 'user', content: '¿Por qué el cielo es azul?' }],
})

console.log(response.message.content)

Comparado con Python: messages como objetos en JS, diccionarios en Python. Mismos nombres de parámetro; cambiar de lenguaje no exige reaprender conceptos.

Streaming

En Node.js el streaming es un async generator nativo:

import ollama from 'ollama'

const stream = await ollama.chat({
  model: 'gemma3',
  messages: [{ role: 'user', content: '¿Por qué el cielo es azul?' }],
  stream: true,
})

for await (const chunk of stream) {
  process.stdout.write(chunk.message.content)
}

Usa process.stdout.write en lugar de console.log: console.log añade salto de línea y no quieres uno por token.

Configuración personalizada

Puedes definir host y headers:

import ollama from 'ollama'

// Host personalizado
const client = new ollama.Ollama({ host: 'http://192.168.1.100:11434' })

// O configuración global
ollama.setDefaultHost('http://192.168.1.100:11434')

// Headers (p. ej. autenticación)
const stream = await ollama.chat({
  model: 'gemma3',
  messages: [{ role: 'user', content: 'Hola' }],
  headers: { Authorization: 'Bearer xxx' },
})

Útil si delante de Ollama hay un proxy con autenticación.

Cancelar generación en streaming

abort() cancela un stream en curso:

import ollama from 'ollama'

const stream = await ollama.chat({
  model: 'gemma3',
  messages: [{ role: 'user', content: 'Escribe un artículo largo...' }],
  stream: true,
})

// El usuario pulsa detener
ollama.abort()

for await (const chunk of stream) {
  // Tras abort, el bucle termina antes
  process.stdout.write(chunk.message.content)
}

Imprescindible en interfaces de chat: el usuario puede parar sin esperar al final.

Versión navegador

Uso similar, con matices:

import ollama from 'ollama/browser'

// En el navegador solo streaming: la API no streaming falla en CORS
const stream = await ollama.chat({
  model: 'gemma3',
  messages: [{ role: 'user', content: 'Hola' }],
  stream: true,
})

for await (const chunk of stream) {
  document.getElementById('output').textContent += chunk.message.content
}

Restricción: obligatorio streaming. Las peticiones no streaming devuelven JSON grande; CORS y timeouts son frecuentes. El streaming por chunks reduce el problema.

Razonable para UI de chat, donde ya quieres efecto streaming.


Capítulo 4: Tool calling en la práctica

El tool calling es la base de los agentes. Ollama permite que el modelo invoque funciones que defines y continúe con el resultado.

En Python puedes pasar funciones directamente; el SDK parsea docstring y tipos de parámetros.

Parseo automático de funciones Python

def get_weather(city: str) -> str:
  """Obtiene el tiempo de una ciudad concreta

  Args:
    city: Nombre de la ciudad, p. ej. Madrid, Barcelona

  Returns:
    Descripción del tiempo
  """
  # Datos simulados
  weather_data = {
    'Madrid': 'Soleado, 18°C',
    'Barcelona': 'Nublado, 22°C',
    'Valencia': 'Lluvia, 26°C',
  }
  return weather_data.get(city, f'Sin datos de tiempo para {city}')

from ollama import chat

response = chat(
  model='qwen3',
  messages=[{'role': 'user', 'content': '¿Qué tiempo hace hoy en Madrid?'}],
  tools=[get_weather],
)

print(response.message.content)

El SDK convierte la función al formato de herramienta: nombre desde la función, descripción desde el docstring, parámetros desde las anotaciones. Sin escribir JSON Schema a mano.

Patrón Agent Loop

No siempre basta una invocación: el modelo puede llamar varias herramientas o encadenarlas. Hace falta un bucle.

Eso es el Agent Loop:

from ollama import chat

def add(a: int, b: int) -> int:
  """Suma dos números"""
  return a + b

def multiply(a: int, b: int) -> int:
  """Multiplica dos números"""
  return a * b

tools = [add, multiply]
tool_map = {'add': add, 'multiply': multiply}

messages = [{'role': 'user', 'content': 'Calcula (3 + 5) * 2'}]

while True:
  response = chat(model='qwen3', messages=messages, tools=tools)

  if response.message.tool_calls:
    # El modelo quiere usar herramientas
    for call in response.message.tool_calls:
      func_name = call.function.name
      func_args = call.function.arguments
      result = tool_map[func_name](**func_args)

      # Añadir resultado al historial
      messages.append({
        'role': 'tool',
        'content': str(result),
        'tool_name': func_name,
      })
  else:
    # Sin tool_calls: terminado
    print(response.message.content)
    break

La lógica:

  1. Enviar mensaje con definición de herramientas
  2. Si hay tool_calls, ejecutar las funciones
  3. Meter resultados en el historial y volver a llamar al modelo
  4. Repetir hasta que no haya más tool_calls

Patrón imprescindible para agentes: defines herramientas y el modelo decide cuándo y cuáles usar.

Modo thinking

Algunos modelos — p. ej. qwen3 — soportan thinking: primero razonan, luego responden.

from ollama import chat

stream = chat(
  model='qwen3',
  messages=[{'role': 'user', 'content': '¿Por qué el cielo es azul?'}],
  stream=True,
  think=True,
)

thinking = ''
content = ''

for chunk in stream:
  if chunk.message.thinking:
    thinking += chunk.message.thinking
  elif chunk.message.content:
    content += chunk.message.content

print('=== Proceso de razonamiento ===')
print(thinking)
print('=== Respuesta final ===')
print(content)

En modo thinking, el chunk incluye el campo thinking. Acumula razonamiento y respuesta por separado.

Útil para ver cómo el modelo llega a la respuesta; apps educativas o depuración de prompts.


Capítulo 5: SDK nativo vs API compatible OpenAI

Tienes dos caminos:

  1. SDK nativo de Ollama (lo anterior)
  2. SDK de OpenAI apuntando a Ollama

La elección depende de tu contexto.

Compatibilidad OpenAI

Si ya tienes un proyecto OpenAI, lo más barato es cambiar base_url:

from openai import OpenAI

client = OpenAI(
  base_url='http://localhost:11434/v1',
  api_key='ollama',  # Obligatorio pero ignorado
)

response = client.chat.completions.create(
  model='gemma3',
  messages=[{'role': 'user', 'content': '¿Por qué el cielo es azul?'}],
)

print(response.choices[0].message.content)

Así de simple. El SDK de OpenAI no sabe que detrás está Ollama; cree hablar con una “API OpenAI”.

En Node.js igual:

import OpenAI from 'openai'

const client = new OpenAI({
  baseURL: 'http://localhost:11434/v1',
  apiKey: 'ollama',
})

const completion = await client.chat.completions.create({
  model: 'gemma3',
  messages: [{ role: 'user', content: '¿Por qué el cielo es azul?' }],
})

console.log(completion.choices[0].message.content)

Comparación

AspectoSDK nativoCompatible OpenAI
Instalaciónpip install ollamaBasta el SDK OpenAI
Tool callingParseo automático de docstringJSON Schema manual
StreamingChunks en formato diccionarioFormato OpenAI estándar
Cloud ModelsNo
Coste migraciónNinguno en proyecto nuevoMínimo en proyecto existente

Recomendación

Proyecto nuevo: SDK nativo.

Motivos:

  • Tool calling más cómodo: pasas funciones Python directamente
  • Más funciones (Cloud Models, thinking)
  • Documentación y ejemplos oficiales

Migrar proyecto OpenAI existente: compatibilidad OpenAI.

Motivos:

  • Dos líneas y funciona
  • Sin reescribir lógica
  • Volver a OpenAI es trivial

En resumen: SDK nativo más completo; compatibilidad OpenAI migración más rápida. Depende de lo que necesites.

He probado ambos. El tool calling nativo ahorra trabajo — no hace falta JSON Schema si el docstring está claro. Pero si el proyecto ya corre sobre OpenAI, no tiene sentido reescribir todo solo por Ollama.


Cierre

Puntos clave:

Llamadas básicas: los SDK de Python y Node.js están bien hechos; pocas líneas bastan. Para streaming, stream=True.

Tool calling: Agent Loop es el patrón central — procesar tool_calls en bucle hasta que el modelo pare. En Python puedes pasar funciones directamente.

Modo thinking: modelos como qwen3 muestran el razonamiento. Trata por separado los campos thinking y content en cada chunk.

Elección de enfoque: proyecto nuevo, SDK nativo; proyecto OpenAI, compatibilidad con un cambio de URL.

Próximos pasos:

  • Si aún no tienes Ollama, empieza por el primer capítulo de la serie
  • Elige nativo u OpenAI según tu proyecto y pruébalo
  • La documentación oficial evoluciona; conviene echarle un vistazo de vez en cuando

Ejecutar LLM en local cada vez es más accesible. Ollama esconde la complejidad tras una API simple. Tú solo necesitas saber llamarla.

Este es el segundo artículo de la serie. El siguiente tratará Modelfile — cómo adaptar el modelo a lo que necesitas.

Desarrollo de clientes Ollama API

Guía completa para llamar la API de modelos locales de Ollama con los SDK de Python o Node.js

⏱️ Estimated time: 45 min

  1. 1

    Step 1: Instalar el SDK y probar una llamada básica

    En Python ejecuta `pip install ollama`; en Node.js, `npm i ollama`.

    Tras instalar, prueba la conexión con el código más simple:
    ```python
    from ollama import chat
    response = chat(model='gemma3', messages=[{'role': 'user', 'content': 'Hola'}])
    print(response.message.content)
    ```

    Asegúrate de que Ollama esté en marcha (puerto 11434 por defecto) y de haber descargado el modelo correspondiente.
  2. 2

    Step 2: Implementar respuesta en streaming

    Activa el streaming para mostrar la salida token a token:

    ```python
    from ollama import chat
    stream = chat(model='gemma3', messages=[...], stream=True)
    for chunk in stream:
    print(chunk['message']['content'], end='', flush=True)
    ```

    Ten en cuenta que chunk es un diccionario; accede con `chunk['message']['content']`.
  3. 3

    Step 3: Configurar tool calling (opcional)

    Define funciones Python como herramientas; el SDK parsea docstring y anotaciones de tipo:

    ```python
    def get_weather(city: str) -> str:
    """Obtiene el tiempo de una ciudad"""
    return f'{city}: soleado'

    response = chat(model='qwen3', messages=[...], tools=[get_weather])
    ```

    Implementa un Agent Loop para gestionar múltiples invocaciones hasta la respuesta final.
  4. 4

    Step 4: Elegir SDK nativo o compatibilidad OpenAI

    Proyectos nuevos: SDK nativo (más funciones: Cloud Models, modo thinking).

    Proyectos OpenAI existentes, solo dos líneas:
    ```python
    client = OpenAI(base_url='http://localhost:11434/v1', api_key='ollama')
    ```

    Coste de migración mínimo; volver a OpenAI cuando quieras.

FAQ

¿Cuál es el puerto y la dirección por defecto de la API de Ollama?
Ollama arranca una REST API en localhost:11434 por defecto. Endpoints principales: /api/chat (multi-turn), /api/generate (generación simple) y /v1/chat/completions (compatibilidad OpenAI).
¿Qué tipo devuelve el streaming del SDK de Python?
El streaming del SDK de Python devuelve diccionarios, no objetos. Accede al contenido con chunk['message']['content'], no con chunk.message.content. El cliente asíncrono devuelve un async generator; recórrelo con async for.
¿Qué limitaciones tiene el SDK de Node.js en el navegador?
En el navegador debes usar streaming (stream: true): las peticiones no streaming devuelven un JSON grande de golpe y suelen fallar por CORS o timeout. La importación también cambia: `import ollama from 'ollama/browser'`.
¿Qué es el patrón Agent Loop?
Agent Loop es el bucle para tool calling: enviar mensaje al modelo → comprobar tool_calls → ejecutar funciones → añadir resultados al historial → volver a llamar al modelo → repetir hasta que no haya más tool_calls. Es la base para construir agentes.
¿Cómo obtener por separado razonamiento y respuesta en modo thinking?
En streaming con thinking, cada chunk puede incluir el campo thinking. Acumula por separado: si chunk.message.thinking, suma al razonamiento; elif chunk.message.content, suma a la respuesta. Modelos como qwen3 lo soportan.
¿SDK nativo o compatibilidad OpenAI?
Proyectos nuevos: SDK nativo (docstring automático en tools, Cloud Models, thinking). Proyectos OpenAI existentes: compatibilidad OpenAI; solo cambia base_url, migración mínima y fácil volver atrás.

10 min de lectura · Publicado el: 18 abr 2026 · Actualizado el: 21 ago 2026

Comentarios

Inicia sesión con GitHub para dejar un comentario

Easton BlogEaston Blog