Contenido

Ollama + Python y Node.js: Crea tu propio chat de IA local

Actualizado en marzo 2026: Este artículo ha sido revisado con las versiones más recientes de las librerías utilizadas: Ollama Python SDK v0.6.1, Ollama JS SDK v0.6.3, FastAPI v0.115 y Tailwind CSS v4.

Antes de empezar

En el post anterior instalamos Ollama y aprendimos a correr un LLM en nuestra máquina. Si todavía no lo leíste, te recomiendo empezar por ahí — en 10 minutos tendrás todo listo.

Hoy vamos a dar el siguiente paso: construir una aplicación web tipo ChatGPT que se conecta a tu LLM local. Vamos a hacerlo en dos sabores:

  1. Python con FastAPI + la librería oficial de Ollama
  2. Node.js con Express + la librería oficial de Ollama

Ambas versiones usan Tailwind CSS v4 para el frontend y streaming en tiempo real (Server-Sent Events) para que veas las respuestas generándose token por token, igualito a como funciona ChatGPT.


Pre-requisitos

Antes de empezar, asegúrate de tener instalado:

HerramientaVersión mínimaPara qué
Ollamav0.18+Correr el LLM local
Python3.13+Demo en Python
Node.js22+Demo en Node.js
Git2.xClonar los repos

Y necesitas tener al menos un modelo descargado en Ollama:

# Si todavía no lo tienes
ollama run llama3.1
Tip
Puedes verificar que Ollama esté corriendo visitando http://localhost:11434 en tu navegador. Deberías ver “Ollama is running”.

Arquitectura de la app

Antes de meternos al código, veamos cómo funciona esto:

  1. El usuario escribe un mensaje en el browser.
  2. El backend recibe el mensaje y lo envía a Ollama via su API local.
  3. Ollama genera la respuesta token por token (streaming).
  4. El backend retransmite cada token al browser usando Server-Sent Events (SSE).
  5. El browser muestra cada token en tiempo real, como ChatGPT.

Demo 1: Python + FastAPI

Clonar el repositorio

git clone https://github.com/pescarcena/blog-pescarcena-code.git
cd blog-pescarcena-code/ollama-local-app-01

Estructura del proyecto

ollama-local-app-01/
├── app.py                 # Backend FastAPI
├── requirements.txt       # Dependencias Python
├── Dockerfile             # Para correr con Docker
├── templates/
│   └── index.html         # Frontend con Tailwind v4
└── static/                # Archivos estáticos

Instalar dependencias y correr

# Crear entorno virtual
python3 -m venv venv
source venv/bin/activate   # En Windows: venv\Scripts\activate

# Instalar dependencias
pip install -r requirements.txt

# Correr la app
uvicorn app:app --reload

La app estará disponible en http://localhost:8000.

El backend: app.py

Veamos las partes clave del código. El corazón es el endpoint /api/chat:

from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from ollama import chat
import json

app = FastAPI()

class ChatRequest(BaseModel):
    message: str
    model: str = "llama3.1"
    history: list = []

@app.post("/api/chat")
async def chat_endpoint(req: ChatRequest):
    messages = [{"role": m["role"], "content": m["content"]} for m in req.history]
    messages.append({"role": "user", "content": req.message})

    def generate():
        stream = chat(
            model=req.model,
            messages=messages,
            stream=True,
        )
        for chunk in stream:
            content = chunk["message"]["content"]
            yield f"data: {json.dumps({'content': content})}\n\n"
        yield "data: [DONE]\n\n"

    return StreamingResponse(generate(), media_type="text/event-stream")

¿Qué está pasando aquí?

  • ChatRequest: recibe el mensaje del usuario, el modelo a usar y el historial de la conversación.
  • chat(stream=True): le dice a Ollama que nos envíe la respuesta token por token en vez de esperar a que termine.
  • StreamingResponse: FastAPI retransmite cada token al browser como un Server-Sent Event.
  • history: mantiene el contexto de la conversación para que el modelo “recuerde” lo que hablaron antes.

También tenemos un endpoint para listar los modelos disponibles:

@app.get("/api/models")
async def list_models():
    import ollama
    models = ollama.list()
    return {"models": [m.model for m in models.models]}

El frontend: Tailwind CSS v4

El frontend es un solo archivo HTML que usa Tailwind v4 via CDN. Esto lo hace súper ligero — sin build step, sin webpack, sin nada:

<head>
    <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
</head>
Nota
El CDN de Tailwind es perfecto para demos y prototipos. Para producción, usarías el CLI de Tailwind o PostCSS.

La magia del streaming en el frontend está en leer la respuesta como un stream:

const res = await fetch('/api/chat', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ message, model: modelSelect.value, history })
});

const reader = res.body.getReader();
const decoder = new TextDecoder();

while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    const text = decoder.decode(value);
    const lines = text.split('\n');

    for (const line of lines) {
        if (line.startsWith('data: ') && line !== 'data: [DONE]') {
            const data = JSON.parse(line.slice(6));
            fullResponse += data.content;
            aiBubble.textContent = fullResponse;
        }
    }
}

Esto lee los chunks de datos conforme llegan y actualiza el DOM en tiempo real. El resultado es una experiencia fluida donde ves las palabras aparecer una por una.


Demo 2: Node.js + Express

Clonar el repositorio

git clone https://github.com/pescarcena/blog-pescarcena-code.git
cd blog-pescarcena-code/ollama-local-app-02

Estructura del proyecto

ollama-local-app-02/
├── server.js              # Backend Express
├── package.json           # Dependencias Node.js
├── Dockerfile             # Para correr con Docker
└── public/
    └── index.html         # Frontend con Tailwind v4

Instalar dependencias y correr

# Instalar dependencias
npm install

# Correr la app
npm start

# O en modo desarrollo (auto-reload)
npm run dev

La app estará disponible en http://localhost:3000.

El backend: server.js

La versión de Node.js es muy similar en estructura. Acá está el endpoint principal:

import express from 'express';
import { Ollama } from 'ollama';

const app = express();
const ollama = new Ollama();

app.post('/api/chat', async (req, res) => {
    const { message, model = 'llama3.1', history = [] } = req.body;

    const messages = history.map(m => ({ role: m.role, content: m.content }));
    messages.push({ role: 'user', content: message });

    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');

    const stream = await ollama.chat({ model, messages, stream: true });

    for await (const chunk of stream) {
        const content = chunk.message.content;
        res.write(`data: ${JSON.stringify({ content })}\n\n`);
    }

    res.write('data: [DONE]\n\n');
    res.end();
});

La API de la librería ollama para Node.js es prácticamente idéntica a la de Python:

  • ollama.chat({ model, messages, stream: true }) retorna un async iterable.
  • Usamos for await...of para iterar sobre los chunks.
  • Cada chunk se envía al browser como SSE.

El frontend

El frontend es idéntico en funcionalidad al de Python (mismo HTML/JS). La única diferencia visual es el color del tema: indigo para Python y esmeralda para Node.js, para que los distingas fácilmente.


Correr con Docker

Ambos proyectos incluyen un Dockerfile para que puedas correrlos sin instalar dependencias locales.

Python

cd ollama-local-app-01

# Construir la imagen
docker build -t ollama-chat-python .

# Correr el contenedor
docker run -p 8000:8000 --add-host=host.docker.internal:host-gateway ollama-chat-python

Node.js

cd ollama-local-app-02

# Construir la imagen
docker build -t ollama-chat-nodejs .

# Correr el contenedor
docker run -p 3000:3000 --add-host=host.docker.internal:host-gateway ollama-chat-nodejs
Importante
El flag --add-host=host.docker.internal:host-gateway es necesario en Linux para que el contenedor pueda acceder a Ollama que corre en tu host. En macOS y Windows con Docker Desktop, host.docker.internal ya funciona automáticamente.

¿Y esto cómo funcionaría en Kubernetes?

Si estás pensando “oye, ¿puedo meter esto en un cluster de Kubernetes?”, la respuesta es absolutamente sí. De hecho, es un caso de uso muy interesante.

Imagina esta arquitectura:

En Kubernetes podrías:

  • Escalar la app de chat horizontalmente con HPA según la demanda.
  • Dedicar nodos con GPU para correr Ollama y servir el modelo.
  • Usar un Service interno para que la app de chat se comunique con Ollama sin exponer el LLM al exterior.
  • Configurar resource limits para que el modelo no se coma toda la memoria del nodo.
  • Implementar health checks para reiniciar automáticamente si Ollama se queda sin responder.

En un próximo post vamos a abordar exactamente esto: cómo desplegar un LLM con Ollama sobre Kubernetes, incluyendo configuración de GPU, manifiestos, y buenas prácticas para correr modelos en un cluster.


Resumen

Python (FastAPI)Node.js (Express)
Repositorioollama-local-app-01ollama-local-app-02
Ollama SDKPython v0.6.1JS v0.6.3
Puerto80003000
StreamingStreamingResponse + SSEres.write() + SSE
FrontendTailwind v4 CDNTailwind v4 CDN

Ambas versiones hacen exactamente lo mismo: te dan un chat funcional con tu LLM local, con streaming en tiempo real y selector de modelos. Elige la que más se ajuste a tu stack.


Recursos