Cambiar tema

Llamadas a la API de Ollama: de curl a la interfaz compatible con OpenAI SDK

Easton editorial illustration: modular AI application workbench

Ese curl en la terminal devolvió un JSON con media palabra: tardé dos horas en darme cuenta de que era respuesta en streaming. Ollama, por defecto, va soltando el contenido poco a poco; cada objeto JSON solo trae unos pocos caracteres.

En despliegue local de LLM, Ollama baja mucho la barrera: descargar, instalar y ejecutar, tres pasos. Pero las llamadas a la API confunden: ¿qué diferencia hay entre la REST API nativa y la interfaz compatible con OpenAI SDK? ¿Cómo se maneja el streaming?

Este artículo recoge los tropiezos que ya pasé: de curl a migración casi sin tocar código con OpenAI SDK, y detalles que la documentación no deja del todo claros.


Ollama tiene dos interfaces de API

Esto me lió un buen rato. Ollama ofrece dos interfaces de API completamente distintas:

REST API nativa: http://localhost:11434/api/*

  • Endpoints: /api/generate (generación de texto), /api/chat (chat), /api/tags (lista de modelos)
  • Respuesta en streaming por defecto (el problema de las tres de la mañana)
  • Llamada HTTP directa, sin SDK

Interfaz compatible con OpenAI: http://localhost:11434/v1/*

  • Endpoints: /v1/chat/completions, /v1/completions, /v1/models
  • Totalmente compatible con OpenAI SDK (Python y JavaScript)
  • Soporta el ecosistema de herramientas OpenAI existente

¿Por qué dos? Cada una tiene su utilidad. La API nativa es más ligera y directa, ideal si escribes tu propio cliente HTTP; la compatible con OpenAI te permite reutilizar código OpenAI casi sin cambios: solo cambias base_url.

Es un diseño inteligente: cubre a quien quiere algo simple y a equipos que ya tienen código sobre el ecosistema OpenAI.


REST API nativa: empezando con curl

La API nativa es bastante directa: una interfaz REST estándar.

Llamada curl básica

El ejemplo más simple — generación de texto:

curl http://localhost:11434/api/generate -d '{
  "model": "llama3.2",
  "prompt": "Why is the sky blue?",
  "stream": false
}'

Fíjate en stream: false. Por defecto Ollama devuelve contenido en streaming; si quieres JSON completo, desactívalo explícitamente. Si no, verás algo así:

{"model":"llama3.2","response":"That","done":false}
{"model":"llama3.2","response":"'","done":false}
{"model":"llama3.2","response":"s","done":false}
{"model":"llama3.2","response":" a","done":false}
...
{"model":"llama3.2","response":"!","done":true}

Cada objeto JSON solo trae unos pocos caracteres, token a token. Eso es NDJSON (Newline-Delimited JSON): una línea, un objeto JSON. Aquella noche a las tres, no lo vi y parseé como JSON normal; solo obtuve “That” del primer objeto.

El modo chat es más práctico

La generación única sirve para tareas simples; el chat es lo que más usarás:

curl http://localhost:11434/api/chat -d '{
  "model": "llama3.2",
  "messages": [
    { "role": "user", "content": "Hello!" }
  ],
  "stream": false
}'

Puedes mantener un array messages con el historial para que el modelo recuerde el contexto. Clave para apps de chat.

Ver modelos instalados

A veces quieres saber qué modelos tienes en local:

curl http://localhost:11434/api/tags

El JSON lista todos los modelos descargados, con tamaño, fecha de modificación y nivel de cuantización. Muy práctico.


Manejo del streaming

Merece un apartado. El streaming de Ollama no devuelve todo de golpe: va token a token.

Streaming en Python

Con requests:

import requests
import json

url = "http://localhost:11434/api/chat"
payload = {
    "model": "llama3.2",
    "messages": [{"role": "user", "content": "Write a short poem"}],
    "stream": True
}

response = requests.post(url, json=payload, stream=True)
for line in response.iter_lines():
    if line:
        chunk = json.loads(line)
        print(chunk.get("message", {}).get("content", ""), end="", flush=True)

La clave es response.iter_lines(): lee el flujo NDJSON línea a línea. Cada chunk puede traer solo unos caracteres; hay que acumularlos.

Streaming en JavaScript

En el frontend, con fetch, es similar:

const response = await fetch('http://localhost:11434/api/chat', {
  method: 'POST',
  body: JSON.stringify({
    model: 'llama3.2',
    messages: [{ role: 'user', content: 'Hello!' }],
    stream: true
  })
});

const reader = response.body.getReader();
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  const chunk = new TextDecoder().decode(value);
  // procesar cada chunk...
}

El streaming es más trabajo que el modo no streaming, pero la UX mejora: ves al modelo «pensar» y escribir en tiempo real, en lugar de esperar y recibir un bloque de texto.


Interfaz compatible con OpenAI SDK: migración casi sin código

Esta parte me gusta especialmente. Ollama ofrece compatibilidad completa con la API de OpenAI; puedes migrar código existente casi sin fricción.

Ejemplo con OpenAI SDK en Python

Usa el SDK oficial de OpenAI:

from openai import OpenAI

client = OpenAI(
    base_url='http://localhost:11434/v1/',
    api_key='ollama'  # en local no se valida; cualquier valor vale
)

response = client.chat.completions.create(
    model="llama3.2",
    messages=[{"role": "user", "content": "Hello!"}]
)

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

El único cambio: base_url y un api_key cualquiera. El resto igual.

Cambiar entre desarrollo y producción

Muy útil: Ollama local en desarrollo, OpenAI en producción:

# .env desarrollo
OPENAI_API_KEY=anyrandomtext
LLM_ENDPOINT="http://localhost:11434/v1"
MODEL=llama3.2

# .env producción
OPENAI_API_KEY=sk-XXXXXXXXXXXXXXXXXXXXXXXX
LLM_ENDPOINT="https://api.openai.com/v1"
MODEL=gpt-3.5-turbo

En el código, lee variables de entorno:

import os
from openai import OpenAI

client = OpenAI(
    base_url=os.getenv('LLM_ENDPOINT'),
    api_key=os.getenv('OPENAI_API_KEY')
)

Desarrollas sin pagar llamadas a OpenAI; al desplegar, cambias variables y listo.

Endpoints soportados

La interfaz compatible de Ollama cubre:

EndpointFunciónSoporte
/v1/chat/completionsGeneración en chatCompleto
/v1/completionsCompletado de textoCompleto
/v1/modelsLista de modelosCompleto
/v1/embeddingsEmbeddings de textoCompleto
/v1/responsesNueva API de respuestasCompleto

También hay /v1/images/generations en experimental; la estabilidad aún no es suficiente.

Alias de modelos

Un truco: puedes aliasar modelos. Si quieres que el código parezca llamar a GPT-3.5:

ollama cp llama3.2 gpt-3.5-turbo

Así, model="gpt-3.5-turbo" usa en realidad llama3.2 local. Muy útil al migrar.


¿Cuál elegir?

Cuándo usar la REST API nativa

Encaja si:

  • Quieres la forma más ligera de llamar
  • No necesitas el ecosistema OpenAI SDK
  • Escribes tu propio cliente HTTP (dispositivos embebidos, entornos especiales)
  • Necesitas control fino del streaming

Es más directa y de bajo nivel. Si dominas HTTP, te resultará natural.

Cuándo usar la interfaz compatible con OpenAI SDK

Encaja si:

  • Ya tienes código con OpenAI SDK
  • Quieres migrar rápido a despliegue local
  • Usas herramientas del ecosistema OpenAI (LangChain, LlamaIndex, etc.)
  • Necesitas alternar desarrollo y producción

En resumen: si prefieres no tocar código, usa la interfaz compatible con OpenAI.

Mi recomendación

En desarrollo suelo usar la compatible con OpenAI SDK: pocos cambios, herramientas disponibles, depuración sencilla. En algunos casos la API nativa gana — un CLI mínimo o un entorno sin OpenAI SDK.


Fragmentos de código útiles

Algunos snippets que uso a menudo.

Chat en streaming con OpenAI SDK (Python)

from openai import OpenAI

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

stream = client.chat.completions.create(
    model="llama3.2",
    messages=[{"role": "user", "content": "Write a poem"}],
    stream=True
)

for chunk in stream:
    if chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)

Conversación completa en JavaScript (API nativa)

async function chat(messages) {
  const response = await fetch('http://localhost:11434/api/chat', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      model: 'llama3.2',
      messages: messages,
      stream: false
    })
  });
  return await response.json();
}

// mantener historial
let conversation = [
  { role: 'user', content: 'Hello!' }
];

const result = await chat(conversation);
conversation.push({
  role: 'assistant',
  content: result.message.content
});

console.log(result.message.content);

Ejemplo con tool calling

Ollama también soporta Function Calling:

from openai import OpenAI

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

tools = [
  {
    "type": "function",
    "function": {
      "name": "get_weather",
      "description": "Get current weather",
      "parameters": {
        "type": "object",
        "properties": {
          "location": {"type": "string"}
        },
        "required": ["location"]
      }
    }
  }
]

response = client.chat.completions.create(
  model="llama3.2",
  messages=[{"role": "user", "content": "What's the weather in Tokyo?"}],
  tools=tools
)

if response.choices[0].message.tool_calls:
  print("Model wants to call:", response.choices[0].message.tool_calls[0].function.name)

En resumen

El diseño de la API de Ollama equilibra bien: REST nativa ligera y directa, y comodidad del ecosistema OpenAI SDK. Cada vía tiene su escenario; depende de lo que necesites.

Si empiezas con Ollama, prueba primero la compatible con OpenAI SDK: arranque rápido, pocos cambios. Cuando domines el terreno, decide si la API nativa encaja mejor.

Y ojo con el streaming: por defecto está activo. Si no lo necesitas, pon stream: false explícitamente — o te quedarás mirando media palabra a las tres de la mañana, como yo.


Referencias

Dos formas de llamar la API de Ollama

Flujo completo de llamada, de la API nativa con curl a la interfaz compatible con OpenAI SDK

⏱️ Estimated time: 10 min

  1. 1

    Step 1: Confirmar que Ollama está instalado y en marcha

    Primero comprueba que Ollama funciona correctamente:

    • En la terminal: ollama list (ver modelos descargados)
    • O visita: http://localhost:11434 (debería devolver Ollama is running)
    • Puerto por defecto: 11434
  2. 2

    Step 2: Elegir el método de llamada

    Elige según tu escenario:

    • REST API nativa: ideal para llamadas ligeras y clientes personalizados
    • Compatible con OpenAI SDK: ideal si ya tienes código OpenAI y quieres migrar rápido
  3. 3

    Step 3: Usar la REST API nativa (con curl)

    La llamada curl más básica:

    • Generación de texto: curl http://localhost:11434/api/generate -d '{"model": "llama3.2", "prompt": "...", "stream": false}'
    • Modo chat: curl http://localhost:11434/api/chat -d '{"model": "llama3.2", "messages": [...], "stream": false}'
    • Nota: responde en streaming por defecto; hay que poner stream: false para desactivarlo
  4. 4

    Step 4: Usar la interfaz compatible con OpenAI SDK

    Llamada con OpenAI SDK en Python:

    • Configura base_url='http://localhost:11434/v1/'
    • api_key puede ser cualquier valor (en local no se valida)
    • El resto del código es idéntico a OpenAI
    • Cambio de entorno: solo modifica base_url (local en desarrollo, OpenAI en producción)
  5. 5

    Step 5: Manejar respuestas en streaming

    Puntos clave del streaming:

    • Python: response.iter_lines() lee NDJSON línea a línea
    • JavaScript: response.body.getReader() para lectura en streaming
    • Cada chunk solo trae unos pocos caracteres; hay que acumular la respuesta completa
    • Sin streaming: pon stream: false para obtener JSON completo

FAQ

¿Qué diferencia hay entre la API nativa de Ollama y la interfaz compatible con OpenAI SDK?
La API nativa es más ligera y directa; responde en streaming por defecto (formato NDJSON) y encaja bien con clientes HTTP personalizados. La interfaz compatible con OpenAI SDK es totalmente compatible con OpenAI SDK; solo hay que cambiar base_url, ideal para migrar código OpenAI existente.
¿Por qué la API de Ollama solo me devolvió media palabra?
Es el comportamiento por defecto del streaming. Ollama usa NDJSON y emite token a token; cada objeto JSON solo contiene unos pocos caracteres. Soluciones:

• Pon stream: false para desactivar el streaming y obtener JSON completo
• O procesa bien el flujo NDJSON: lee línea a línea y acumula el contenido
¿Cómo usar Ollama local en desarrollo y OpenAI en producción?
Usa variables de entorno: en desarrollo LLM_ENDPOINT="http://localhost:11434/v1"; en producción "https://api.openai.com/v1". En el código solo lee base_url de la variable; el resto no cambia.
¿Qué endpoints de OpenAI soporta Ollama?
Soporte completo: /v1/chat/completions, /v1/completions, /v1/models, /v1/embeddings, /v1/responses. Soporte experimental: /v1/images/generations (aún no suficientemente estable).
¿Puedo poner alias a los modelos de Ollama?
Sí. Usa: ollama cp llama3.2 gpt-3.5-turbo. Así, model="gpt-3.5-turbo" en tu código usará en realidad llama3.2 local. Muy útil al migrar código.
¿Ollama soporta tool calling (Function Calling)?
Sí. Puedes usar el parámetro tools vía la interfaz compatible con OpenAI SDK, definir el schema de funciones y el modelo devolverá el campo tool_calls indicando qué función invocar.

7 min de lectura · Publicado el: 3 abr 2026 · Actualizado el: 21 ago 2026

Comentarios

Inicia sesión con GitHub para dejar un comentario

Easton BlogEaston Blog