Stack IA 0 dólares: explicación detallada con ejemplos

Este artículo tiene un vídeo corto:YouTubeTikTok

1. Resumen general

Muchas personas piensan que construir un sistema IA —agente, RAG, chatbot interno— cuesta miles de dólares por mes porque hay que pagar por APIs de OpenAI o Anthropic. En realidad no es así: hay un stack de 8 capas, cada una con una versión gratuita o auto-hospedada, suficiente para que construyas y ejecutes un sistema IA completo con 0 dólares. Este artículo recorre cada capa del stack, explica su función, con ejemplos de código específicos para que puedas visualizar cómo juntarlas. Después de leerlo, sabrás qué herramienta elegir para cada capa y conocerás dónde están los límites reales del “gratuito”.

2. Por qué “0 dólares” no significa siempre gratis

Antes de entrar en detalles, hay que aclarar una cosa: “0 dólares” aquí es costo de software/licencia, no costo de infraestructura ilimitado. El free tier de Vercel tiene límites de ancho de banda, el free tier de Supabase tiene límites de almacenamiento, ejecutar Ollama local cuesta CPU/RAM de tu máquina. Este stack es adecuado para prototipado, aprendizaje, demostración a clientes pequeños, no para escalar a millones de usuarios.

Nota: Si el tráfico real de tu aplicación supera el free tier, aún tendrás que pagar — solo que pagas por infraestructura (compute, almacenamiento) en lugar de pagar al proveedor LLM por token. Esta es una diferencia importante que hay que entender antes de comprometerse con este stack para una producción real.

3. Capa Frontend: Next.js o Streamlit

La capa de interfaz no necesita ser compleja. Si quieres una UI bonita, usa Next.js en el free tier de Vercel. Si solo necesitas un demo rápido para un pipeline IA, Streamlit es una opción mucho más rápida porque no necesitas escribir un frontend separado.

# app.py
import streamlit as st
import requests

st.title("Asistente de preguntas y respuestas sobre documentos internos")

question = st.text_input("Ingresa tu pregunta")

if st.button("Preguntar") and question:
    response = requests.post(
        "http://localhost:8000/ask",
        json={"question": question},
        timeout=30,
    )
    st.write(response.json()["answer"])

Este código crea un campo de entrada y un botón. Cuando haces clic en el botón, llama a la API del backend (ejecutándose localmente) y muestra la respuesta. Ejecuta con streamlit run app.py, Streamlit automáticamente hospeda en localhost:8501. No necesitas escribir React, no necesitas construir pipeline — adecuado para la etapa de validar tu idea antes de invertir en Next.js.

4. Capa Agent Orchestration: LangGraph

Cuando tu sistema no es solo “llamar LLM una vez y devolver resultado” sino que necesita muchos pasos (buscar → sintetizar → verificar → responder), necesitas un orchestrator. LangGraph modela el agente como un grafo con estado, permitiendo bifurcaciones y bucles.

pip install langgraph langchain-ollama
from langgraph.graph import StateGraph, END
from typing import TypedDict

class AgentState(TypedDict):
    question: str
    answer: str

def retrieve_step(state: AgentState) -> AgentState:
    state["answer"] = f"Buscando documentos para: {state['question']}"
    return state

def generate_step(state: AgentState) -> AgentState:
    state["answer"] += " -> respuesta sintetizada"
    return state

graph = StateGraph(AgentState)
graph.add_node("retrieve", retrieve_step)
graph.add_node("generate", generate_step)
graph.set_entry_point("retrieve")
graph.add_edge("retrieve", "generate")
graph.add_edge("generate", END)

app = graph.compile()
result = app.invoke({"question": "¿Cuál es la política de garantía?", "answer": ""})
print(result["answer"])
Buscando documentos para: ¿Cuál es la política de garantía? -> respuesta sintetizada

StateGraph define un flujo de procesamiento con dos nodos: retrieve y generate, cada nodo recibe estado y devuelve un nuevo estado. set_entry_point especifica el nodo inicial, add_edge conecta los pasos. Cuando llamas a app.invoke(...), LangGraph ejecuta secuencialmente a través del grafo y devuelve el estado final. Todo se ejecuta localmente, sin llamadas a ninguna API pagada.

5. Capa RAG Pipeline: Chroma y Qdrant

RAG (Retrieval-Augmented Generation) necesita un vector store para almacenar embeddings y buscar por similitud. Chroma se ejecuta integrado, no necesita un servidor separado — adecuado para prototipado.

pip install chromadb
import chromadb

client = chromadb.PersistentClient(path="./chroma_data")
collection = client.get_or_create_collection(name="policies")

collection.add(
    documents=["Garantía del producto 12 meses desde la fecha de compra.",
               "Devolución dentro de 7 días si el empaque está intacto."],
    ids=["doc1", "doc2"],
)

results = collection.query(query_texts=["política de garantía"], n_results=1)
print(results["documents"])
[['Garantía del producto 12 meses desde la fecha de compra.']]

PersistentClient guarda datos al disco en lugar de solo en RAM, por lo que los datos persisten a través de reinicios. collection.add calcula automáticamente embeddings para los documentos (Chroma tiene una función de embedding predeterminada). collection.query encuentra el documento más cercano en el espacio vectorial a la pregunta. Si el conjunto de datos es más grande, cambias a Qdrant ejecutándose localmente a través de Docker — API similar pero optimizada para escala.

6. Capa LLM: Ollama ejecutando modelo local

Esta es la capa central que te ayuda a escapar de la factura de API. Ollama te permite ejecutar Llama, Mistral, Gemma directamente en tu máquina, sin límite de número de solicitudes.

ollama pull llama3.1
ollama run llama3.1 "Resumen la política de garantía en una oración."
El producto tiene garantía de 12 meses desde la fecha de compra.

El comando ollama pull descarga el modelo a tu máquina (el modelo está cuantizado para ejecutarse en CPU/GPU comunes). El comando ollama run inicia el modelo y envía un prompt, devuelve resultado directamente en la terminal. Ollama también abre una API REST en localhost:11434, así que puedes integrarlo en código Python o Java.

Si tu backend es Spring Boot, llamar a Ollama a través de RestClient es muy simple:

import org.springframework.web.client.RestClient;

public class OllamaClient {

    private final RestClient restClient = RestClient.create("http://localhost:11434");

    public String generate(String model, String prompt) {
        var response = restClient.post()
                .uri("/api/generate")
                .body(new OllamaRequest(model, prompt, false))
                .retrieve()
                .body(OllamaResponse.class);
        return response.response();
    }

    record OllamaRequest(String model, String prompt, boolean stream) {}
    record OllamaResponse(String response) {}
}

OllamaClient encapsula la llamada HTTP a Ollama. RestClient de Spring 6 reemplaza el viejo RestTemplate, escribe más corto. OllamaRequest configura stream = false para recibir la respuesta completa de una vez en lugar de transmitir cada token — más fácil de manejar para el ejemplo demo.

7. Uso de herramientas a través de MCP (Model Context Protocol)

Un agente que solo habla con LLM no puede hacer nada práctico. MCP es un estándar de comunicación que ayuda al agente a llamar herramientas externas — leer archivo, consultar base de datos, llamar API de GitHub — según una interfaz unificada, sin necesidad de escribir integración para cada herramienta.

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/data/docs"]
    },
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"]
    }
  }
}

Este archivo de configuración declara dos servidores MCP: uno para filesystem (leer/escribir archivos en /data/docs), uno para GitHub (crear issue, leer PR). Tu agente (LangGraph, Claude Code, o cualquier cliente que soporte MCP) solo necesita conectarse a estos servidores y llamar a la herramienta según el esquema estándar, sin necesidad de escribir API wrapper para cada servicio.

Nota: El servidor MCP que se ejecuta localmente a través de npx no cuesta licencia, pero algunos servidores requieren token de acceso (por ejemplo, token de GitHub) — ese token aún debe obtenerse del servicio original.

8. Code Agent: Aider

Cuando quieres que el agente escriba y corrija código en el repositorio automáticamente, Aider es una herramienta CLI de código abierto, se combina bien con modelo local a través de Ollama.

pip install aider-chat
aider --model ollama/llama3.1 --file src/main/java/OrderService.java

Este comando abre Aider, especifica usar el modelo llama3.1 a través de Ollama (no a través de API pagada), y precarga el archivo OrderService.java en el contexto. De aquí en adelante escribes solicitud en vietnamita o inglés (“agregar validación para cantidad negativa”), Aider generará diff y auto commit si estás de acuerdo.

9. Capa Data: SQLite, DuckDB, Supabase

No todos los sistemas IA necesitan un cluster Postgres. SQLite es suficiente para almacenamiento estructurado pequeño, DuckDB es potente para análisis de datos de archivos (CSV, Parquet) sin necesidad de un servidor separado.

pip install duckdb
import duckdb

con = duckdb.connect()
result = con.execute("""
    SELECT category, COUNT(*) AS total
    FROM read_csv_auto('orders.csv')
    GROUP BY category
    ORDER BY total DESC
""").fetchall()
print(result)
[('electronics', 120), ('books', 45), ('clothing', 30)]

DuckDB lee archivos CSV directamente a través de read_csv_auto, sin necesidad de ETL o importar a base de datos separada. La consulta SQL se ejecuta casi instantáneamente con cientos de miles de filas — suficiente para la mayoría de casos de uso de análisis internos. Cuando necesitas un backend con API REST y autenticación integradas, el free tier de Supabase proporciona Postgres gestionado con realtime y almacenamiento.

10. Deployment: Docker, Cloudflare Workers, Hugging Face

Containerizar con Docker te ayuda a desplegar consistentemente entre máquina dev y host gratuito.

# docker-compose.yml
services:
  ollama:
    image: ollama/ollama
    ports:
      - "11434:11434"
    volumes:
      - ollama_data:/root/.ollama

  app:
    build: .
    ports:
      - "8000:8000"
    depends_on:
      - ollama

volumes:
  ollama_data:

Este archivo define dos servicios: ollama ejecuta el modelo LLM, app es tu backend, depende de ollama para llamar a inference. docker compose up inicia ambos al mismo tiempo, el volumen ollama_data mantiene el modelo descargado a través de reinicios de contenedor. Cuando necesitas un demo público, empujas la parte frontend a Cloudflare Workers (edge, free tier generoso) y modelo pequeño a Hugging Face Spaces para que otros puedan probar sin configurar su máquina.

11. Integración de todo el stack

Flujo real: usuario escribe pregunta en Streamlit → LangGraph recibe solicitud, llama a Chroma para recuperar contexto relacionado → combina contexto en prompt, envía a Ollama → si necesita herramienta (leer archivo, consultar BD), agente llama a través de MCP → resultado regresa a UI. Todo se ejecuta en una máquina o contenedor, ninguna solicitud va a ninguna API pagada.

def answer_question(question: str) -> str:
    docs = collection.query(query_texts=[question], n_results=2)
    context = "\n".join(docs["documents"][0])
    prompt = f"Basándose en la siguiente información:\n{context}\n\nResponde: {question}"
    import ollama
    response = ollama.generate(model="llama3.1", prompt=prompt)
    return response["response"]

Esta función es el “corazón” de RAG: obtener contexto de Chroma, incrustar en prompt, llamar a Ollama para generar respuesta final. Este es exactamente el patrón básico retrieve-then-generate, no necesita framework complejo si tu caso de uso es simple.

12. Escribir pruebas para la parte de procesamiento de datos

La parte más fácil de probar en el pipeline RAG es usualmente el paso de dividir texto (chunking) antes de incrustar vectores. Si tu backend Java maneja este paso, una simple prueba unitaria ayuda a evitar errores off-by-one al dividir.

import java.util.List;
import java.util.ArrayList;

public class DocumentChunker {

    public List<String> chunk(String text, int maxLength) {
        List<String> chunks = new ArrayList<>();
        for (int i = 0; i < text.length(); i += maxLength) {
            chunks.add(text.substring(i, Math.min(i + maxLength, text.length())));
        }
        return chunks;
    }
}
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;

class DocumentChunkerTest {

    @Test
    void shouldSplitTextIntoFixedSizeChunks() {
        DocumentChunker chunker = new DocumentChunker();

        List<String> chunks = chunker.chunk("Garantía 12 meses para todos los productos", 10);

        assertThat(chunks).hasSizeGreaterThan(1);
        assertThat(chunks.get(0)).hasSize(10);
        assertThat(String.join("", chunks)).isEqualTo("Garantía 12 meses para todos los productos");
    }
}

Esta prueba afirma tres cosas: el texto largo se divide en múltiples fragmentos, el primer fragmento tiene la longitud exacta requerida, y unir todos los fragmentos nuevamente debe dar el texto original exacto — sin pérdida de caracteres. Este es el tipo de bug silencioso que fácilmente sucede cuando escribes la lógica de chunking tú mismo sin pruebas exhaustivas.

13. Errores comunes y cuándo no usar este stack

  • El modelo local es mucho más lento que la API en la nube si la máquina no tiene GPU. Con CPU típica, una solicitud llama3.1 8B podría tomar 5-15 segundos — aceptable para demo, no aceptable para producto que necesita respuesta instantánea.
  • El free tier tiene límite de tasa silencioso. Vercel, Supabase, Cloudflare todos limitan solicitudes/mes; exceder el umbral tu app podría ser bloqueada sin que lo sepas de antemano si no monitoreas el dashboard.
  • Chroma integrado no es adecuado para multi-instancia. Si escalas backend a muchos pods, cada pod tiene su propio archivo Chroma causará inconsistencia — entonces necesitas cambiar a servidor Qdrant o vector store gestionado.
  • El servidor MCP local aumenta la superficie de ataque. Un servidor MCP de filesystem permite al agente leer/escribir archivo — si se expone accidentalmente a internet, este es un agujero de seguridad serio.

Nota: Si estás construyendo un producto con SLA, necesitas baja latencia estable, o tráfico grande desde el inicio, este stack 0 dólares solo debe usarse en la etapa de prototipo. Cuando va en vivo realmente, considera pagar por API LLM gestionada o instancia GPU propia para garantizar calidad de servicio.

14. Conclusión

El stack de cero dólares no es un truco de marketing — es una combinación real de herramientas de código abierto y free tier real, suficiente para que construyas un sistema IA completo con RAG, agente, uso de herramientas y deployment sin necesidad de tarjeta de crédito. La barrera real no es dinero, sino si entiendas claramente cómo conectar cada capa, sepas los límites del free tier, y sepas cuándo cambiar a infraestructura pagada para garantizar calidad. Comienza con este stack para aprender y validar idea, luego actualiza cada capa cuando el producto realmente necesite escalar.

llmragollamalanggraphmcp