PTENES
Saltar al contenido
MÓDULO 3.5

🔌 Sirviendo mediante una API local compatible con OpenAI

Hasta ahora conversaste con el modelo por la terminal y por una interfaz. Ahora viene la pieza que conecta el modelo con el resto del mundo: una API HTTP que habla el mismo idioma que OpenAI. Aprende a llamar a tu modelo local desde cualquier app, script o agente y, al final, a exponerlo de forma segura en tu tailnet.

6
Temas
~40
Minutos
Inter.
Nivel
Práctico
Tipo

Contenido detallado

1

🔌 Por qué una API local

Una interfaz de chat es excelente para conversar. Pero una programa no conversa a través de una pantalla; necesita una dirección para enviar texto y recibir una respuesta. Esa dirección es la API. El truco de Ollama es hablar exactamente el mismo "formato" que usa OpenAI: así, cualquier app, script o agente que ya sabe comunicarse con ChatGPT funciona con tu modelo local cambiando una sola línea.

🆕 ¿Eres nuevo por aquí?

"API" (Interfaz de Programación de Aplicaciones) es una puerta de entrada por la que un programa le pide algo a otro. "Endpoint" es la dirección exacta de ese puerto: una URL como http://localhost:11434/v1/chat/completions. "Compatible con OpenAI" quiere decir que el formato de la solicitud y la respuesta es idéntico al de la API de OpenAI: las mismas claves model, messages, choices — solo que servido por tu máquina.

🔑 La idea central

La compatibilidad es la palanca. Con ella obtienes tres cosas a la vez:

  • •Reutilización: las bibliotecas, los SDK y las herramientas hechas para OpenAI funcionan sin reescribir nada.
  • •Portabilidad: cambia entre la nube y el entorno local modificando solo la base_url — el código sigue igual.
  • •Automatización: scripts y agentes (Trilha 4) pasan a usar el modelo sin que tengas que abrir ninguna pantalla.

✗ Sin API (solo chat en pantalla)

  • ✗Cada uso requiere que escribas manualmente
  • ✗Nada de automatización ni programación de tareas
  • ✗Imposible de conectar a otros programas
  • ✗Sin agentes 24/7

✓ Con API local

  • ✓Cualquier script llama al modelo mediante HTTP
  • ✓El mismo SDK de OpenAI, el modelo en tu máquina
  • ✓Base para automatizaciones, RAG y agentes
  • ✓Todo offline, sin pagar por token
API
puerto del modelo
Compatible
formato OpenAI
Cambio
solo la base_url
Desbloquea
scripts y agentes
2

🌐 El endpoint de Ollama

Cuando instalaste Ollama (Módulo 3.1), ya inició un servidor HTTP que escucha en http://localhost:11434. localhost = la propia máquina; 11434 = el puerto. Este servidor ofrece dos conjuntos de rutas: las nativas de Ollama y las Compatibles con OpenAI bajo /v1. Para reutilizar apps y SDK existentes, usa siempre las de /v1.

El recorrido de una solicitud, desde tu programa hasta el modelo y de vuelta:

1

Tu programa arma la solicitud

Un JSON con el model y la lista de messages — exactamente el formato de OpenAI.

2

El servidor Ollama recibe las solicitudes en :11434

Carga el modelo en la memoria (si aún no está) y ejecuta la inferencia localmente.

3

La respuesta vuelve en JSON

En el mismo formato de OpenAI: el texto viene en choices[0].message.content.

🗺️ Las rutas que importan

Objetivo: conocer las direcciones. Las de /v1 son las que usas con SDK listos.

# Rutas nativas de Ollama
POST http://localhost:11434/api/chat
POST http://localhost:11434/api/generate
# Rutas compatibles con OpenAI (usa estas con SDKs/apps)
POST http://localhost:11434/v1/chat/completions
POST http://localhost:11434/v1/embeddings
GET  http://localhost:11434/v1/models

Cómo verificar: ejecuta curl http://localhost:11434/v1/models — debe enumerar, en JSON, los modelos que ya descargaste.

💡 Consejo práctico

Si el puerto 11434 no responde, el servidor no está en ejecución. En Linux/macOS, la aplicación de Ollama ya lo mantiene en funcionamiento; si necesitas iniciarlo manualmente, usa ollama serve. "Connection refused" casi siempre significa esto: el servicio no está activo o está en otra máquina.

3

⌨️ Primera llamada con curl

La forma más rápida de comprobar que la API funciona es el curl — una herramienta de terminal que hace solicitudes HTTP. Sin instalar bibliotecas ni escribir código: solo la terminal y un JSON. Si esto responde, tu API local está funcionando.

⌨️ Ejemplo práctico: chat mediante curl

Objetivo: enviar un mensaje al modelo y recibir la respuesta en JSON. Pégalo en el terminal (con Ollama en ejecución y el modelo qwen2.5:7b ya descargado).

curl http://localhost:11434/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen2.5:7b",
    "messages": [
      {"role": "system", "content": "Responda em PT-BR, conciso."},
      {"role": "user", "content": "Explica qué es una API en 1 frase."}
    ]
  }'

Cómo verificar: la salida es un JSON con un campo choices. El texto de la respuesta está en choices[0].message.content. Si aparece, acabas de servir tu modelo por API.

📊 Anatomía del JSON de respuesta

  • model — qué modelo respondió.
  • choices[0].message.content — el texto generado (es lo que quieres).
  • choices[0].finish_reason — por qué se detuvo (p. ej., stop).
  • usage — conteo de tokens de entrada y salida.
curl
prueba sin código
-d
el cuerpo JSON
messages
system + user
content
la respuesta
4

🐍 Del código: SDK de OpenAI apuntado a lo local

Aquí destaca la compatibilidad. El SDK oficial de OpenAI (la biblioteca openai en Python) funciona con Ollama — basta con cambiar la base_url para tu endpoint local y pasar una api_key cualquiera (Ollama no valida la clave; solo se requiere porque el SDK la pide).

🐍 Ejemplo práctico: chat en Python

Objetivo: llamar al modelo local con el SDK de OpenAI. Instálalo con pip install openai y ejecuta.

# pip install openai
from openai import OpenAI
client = OpenAI(
    base_url="http://localhost:11434/v1",  # apunta al Ollama local
    api_key="ollama",               # cualquier string; Ollama lo ignora
)
resp = client.chat.completions.create(
    model="qwen2.5:7b",
    messages=[{"role": "user", "content": "Di hola en una frase."}],
)
print(resp.choices[0].message.content)

Cómo verificar: el script imprime la frase generada por el modelo. Para volver a la nube, cambia base_url y la clave; el resto del código queda idéntico. Este es el punto central.

💡 Consejo práctico

En JavaScript/TypeScript es la misma historia: el paquete openai acepta baseURL: "http://localhost:11434/v1" e apiKey: "ollama". Guarda la base_url en una variable de entorno (OPENAI_BASE_URL) para alternar entre local y nube sin tocar el código.

✓ Hazlo

  • ✓Usa el nombre del modelo que aparece en ollama list
  • ✓Centraliza base_url en una env var
  • ✓Maneja los timeouts: la 1.ª solicitud carga el modelo (más lento)

✗ Evita

  • ✗Olvidar el /v1 al final de base_url
  • ✗Usar un nombre de modelo que no se descargó
  • ✗Poner una clave real de OpenAI en el código local
5

🌊 Streaming, embeddings y múltiples modelos

La misma API hace otras tres cosas que vas a usar mucho: streaming (la respuesta llega palabra por palabra, como en ChatGPT), embeddings (convertir texto en vectores — la base del RAG, en el próximo módulo) y dirigir según el modelo (basta con cambiar el campo model).

🌊 Streaming: respuesta token a token

Objetivo: mostrar el texto mientras se genera, sin esperar al final. Basta stream=True.

stream = client.chat.completions.create(
    model="qwen2.5:7b",
    messages=[{"role": "user", "content": "Cuenta hasta cinco."}],
    stream=True,  # activa el modo de streaming
)
for chunk in stream:
    print(chunk.choices[0].delta.content or "", end="", flush=True)

Cómo verificar: el texto aparece poco a poco, no de una vez. En curl, lo equivalente es poner "stream": true en el cuerpo JSON.

🧮 Embeddings: el texto se convierte en vector

Objetivo: generar el vector de significado de un texto — el bloque básico del RAG (Módulo 3.6). Usa un modelo de embeddings como nomic-embed-text (descarga con ollama pull nomic-embed-text).

curl http://localhost:11434/v1/embeddings \
  -H "Content-Type: application/json" \
  -d '{
    "model": "nomic-embed-text",
    "input": "texto que se convierte en un vector de significado"
  }'

Cómo verificar: la respuesta incluye data[0].embedding — una larga lista de números. Eso es lo que guarda una base de datos vectorial para buscar por similitud.

🔀 Varios modelos, un solo endpoint

No creas un servidor por modelo. El mismo endpoint sirve a todos los que están descargados; basta con cambiar el campo model: un modelo pequeño y rápido para clasificar, uno más grande para razonar y uno de embedding para RAG. Ollama carga y descarga los modelos según la demanda.

stream
token a token
embeddings
texto → vector
model
dirige la solicitud
1 endpoint
N modelos
6

🔒 Exponiendo en la tailnet de forma segura

Por defecto, la API solo responde en localhost — nadie de afuera puede acceder. Pero en el Módulo 2.4 configuraste una tailnet (red privada vía Tailscale). Al combinar las dos piezas, usas el modelo de casa desde la laptop del trabajo o el celular, todo dentro de tu red privada, sin exponer nada a Internet.

App / Script SDK de OpenAI · curl Otro dispositivo a través de Tailscale (100.x) API local Ollama · :11434 · /v1 Modelo qwen2.5 · gemma · …

Lee de izquierda a derecha: el app/script (morado) activa la API local en el puerto 11434, donde se ejecuta el modelo. La rama en cian muestra otro dispositivo accediendo a la misma API a través de la tailnet, sin exponer nada a la internet pública.

⌨️ Ejemplo práctico: abrir en la tailnet

Objetivo: hacer que Ollama escuche en toda la interfaz (no solo en localhost) para que los otros dispositivos de tu tailnet puedan acceder.

# En la MÁQUINA que sirve el modelo: escucha en todas las interfaces
OLLAMA_HOST=0.0.0.0:11434 ollama serve
# Descubre su IP en la tailnet (100.x.y.z)
tailscale ip -4
# Desde OTRO device en la tailnet, llama usando esta IP
curl http://100.x.y.z:11434/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"qwen2.5:7b","messages":[{"role":"user","content":"oi"}]}'

Cómo verificar: del segundo dispositivo, el curl con la IP 100.x debe responder en JSON. Si no responde, revisa si ambos están en la tailnet (tailscale status).

⚠️ Alerta: nunca lo expongas a Internet pública

OLLAMA_HOST=0.0.0.0 abre el puerto para cualquier red a la que esté conectada la máquina. Dentro de la tailnet esto es seguro: solo tus dispositivos autenticados pueden ver el 100.x. En la Internet abierta, no. La API de Ollama no tiene inicio de sesión ni contraseña: cualquiera que llegue al puerto puede usar (y abusar de) tu modelo.

  • ✗NUNCA hagas port forwarding del puerto 11434 en el router.
  • ✗NUNCA apuntes el puerto a una IP pública.
  • ✓Accede de forma remota SOLO por la tailnet (IP 100.x) — es la forma segura.

✓ Tailnet (seguro)

  • ✓Solo tus dispositivos autenticados
  • ✓Tráfico cifrado de extremo a extremo
  • ✓Ningún puerto abierto en el router

✗ Internet pública (peligrosa)

  • ✗Cualquiera en la web puede acceder al puerto
  • ✗Sin iniciar sesión: API totalmente abierta
  • ✗Modelo secuestrado y la cuenta de la luz la pagas tú

Autoevaluación (opcional): para usar el SDK de OpenAI con tu modelo local, ¿qué ajustas?

localhost
estándar cerrado
0.0.0.0
abre en la tailnet
100.x
IP de Tailscale
Pública
jamás

📌 Resumen del módulo

✓
La API es la puerta al modelo — sin ella, solo puedes conversar manualmente; con ella, scripts y agentes usan el modelo.
✓
Ollama sirve en :11434 con rutas /v1 — formato idéntico al de OpenAI; solo se cambia base_url.
✓
curl prueba, el SDK automatiza — misma biblioteca de OpenAI con base_url local y cualquier api_key.
✓
Streaming, embeddings y N modelos en un endpoint — y el acceso remoto solo por la tailnet, nunca por la internet pública.

Siguiente módulo:

3.6 — Embeddings, memoria y RAG