Cambiar tema

Embedding con Ollama en la práctica: búsqueda vectorial local y RAG

Easton editorial illustration: MCP trust and permission gateway

Revisé más de 200 PDF buscando un detalle de un documento técnico de hace seis meses. Búsqueda por palabras clave no servía — recordaba la idea, no la frase exacta. Casi una hora hasta dar con él. Pensé: hace falta búsqueda que entienda significado.

Peor: eran documentos internos; subirlos a la nube para vectores estaba prohibido.

Ollama Embedding encaja: local, datos en casa, búsqueda semántica. Probé mxbai, nomic y Qwen3 y comparé bases vectoriales. Muchas trampas — modelo equivocado, base pequeña que no escala.

Este artículo condensa esa experiencia: RAG local de punta a punta, con código.

Modelos Embedding en Ollama

Ollama ofrece varios modelos Embedding; al principio tampoco sabía cuál elegir. La documentación oficial dice que mxbai supera a text-embedding-3-large de OpenAI — suena bien, pero ¿cómo se comporta en la práctica? Probé varios y aquí va la respuesta directa.

Primero la tabla comparativa:

ModeloDimensiónContextoTamañoNotas
mxbai-embed-large1024512 tokens670MOpción general, top MTEB
nomic-embed-text7688192 tokens274MTextos largos, extensión de contexto
Qwen3 Embedding10248192 tokens~600MLanzado en 2026, bueno en chino

mxbai-embed-large es el que más uso. ¿Por qué? Simple, estable. Sus vectores de 1024 dimensiones bastan en la mayoría de escenarios; en MTEB (Massive Text Embedding Benchmark) está muy arriba, de hecho un poco por encima de text-embedding-3-large de OpenAI. Para búsqueda en documentos o código, es una apuesta segura.

nomic-embed-text destaca por 8192 tokens de contexto. Si procesas artículos enteros o conversaciones largas, encaja bien. Es más ligero (274M) y corre más rápido que mxbai. Con 768 dimensiones la expresión semántica es algo menor en teoría; en textos cortos la diferencia es pequeña, en textos largos nomic gana.

Qwen3 Embedding salió en abril de 2026 (Alibaba). En chino va muy bien: probé con artículos técnicos y empareja «mecanismos de tolerancia a fallos en sistemas distribuidos» con «diseño de tolerancia a fallos», donde mxbai flojea. Si tu contenido es mayoritariamente chino, merece la pena.

¿Recomendación? Empieza con mxbai sin complicarte. Para textos largos, nomic; para chino, Qwen3. En pocas palabras: prueba los tres en media hora y decide con datos reales.

Elección de base de datos vectorial

Modelo elegido, ¿dónde guardar los datos? Elegir mal la base vectorial duele más que elegir mal el modelo: pequeña y luego escalas mal; grande y desperdicias recursos. Comparé tres opciones habituales:

BaseEscenarioEscalaNotas
ChromaDBAprendizaje, proyectos personales< 100kListo para usar, cero config
FAISSAlto rendimiento en una máquina100k-1MMeta, open source, muy rápido
MilvusProducción, enterpriseMillones+Distribuido, escalable, completo

ChromaDB es mi recomendación para empezar. Instalación en una línea: pip install chromadb. API clara: guardar y consultar en pocas líneas. Usa índice HNSW; para volúmenes pequeños va sobrado. Limitación: despliegue en una sola máquina; pasado ~100k registros el rendimiento cae.

FAISS es la herramienta clásica de Meta. C++ puro, velocidad real. Con 500k vectores la latencia se mantiene en milisegundos. Pero es más una librería de búsqueda que una base completa: persistencia e índices los gestionas tú. Ideal si te gusta afinar rendimiento o tienes requisitos exigentes.

Milvus sí está pensado para producción: despliegue distribuido, persistencia, varios tipos de índice y versión cloud (Zilliz Cloud). Configuración y coste operativo más altos. Compensa con millones de vectores, alta disponibilidad y equipos.

Mi estrategia: ChromaDB para arrancar rápido; FAISS si el rendimiento apremia; Milvus o cloud en producción seria. No cuentes con migrar de ChromaDB a Milvus sin dolor — formatos y APIs difieren.

Flujo RAG completo

Menos teoría y más código. Implementación local de punta a punta: de PDF a búsqueda semántica con Ollama + ChromaDB.

Preparar el entorno

Instala dependencias:

pip install ollama chromadb langchain langchain-community pypdf

Asegúrate de que Ollama esté en marcha y los modelos descargados:

ollama pull mxbai-embed-large
ollama pull qwen2.5:7b  # para generar respuestas

Implementación en código

import ollama
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
import chromadb

# 1. Cargar PDF
loader = PyPDFLoader("./your_document.pdf")
docs = loader.load()

# 2. Trocear documentos — chunks muy grandes fallan en retrieval; muy pequeños pierden contexto
splitter = RecursiveCharacterTextSplitter(
    chunk_size=800,      # 800 caracteres por fragmento
    chunk_overlap=100,   # 100 de solape para no perder info en los bordes
)
chunks = splitter.split_documents(docs)

# 3. Generar embeddings y guardar en ChromaDB
client = chromadb.Client()
collection = client.create_collection("my_docs")

for i, chunk in enumerate(chunks):
    # Llamada a la API de Ollama
    response = ollama.embed(
        model="mxbai-embed-large",
        input=chunk.page_content,
    )
    embedding = response["embeddings"][0]

    collection.add(
        ids=[str(i)],
        embeddings=[embedding],
        documents=[chunk.page_content],
        metadatas=[{"source": chunk.metadata.get("source", "unknown")}],
    )

print(f"Fragmentos indexados: {len(chunks)}")

# 4. Búsqueda semántica
query = "¿Qué mecanismos de tolerancia a fallos tiene un sistema distribuido?"
query_embedding = ollama.embed(
    model="mxbai-embed-large",
    input=query,
)["embeddings"][0]

results = collection.query(
    query_embeddings=[query_embedding],
    n_results=3,  # top 3 fragmentos
)

# 5. Generar respuesta con el contexto recuperado
context = "\n\n".join(results["documents"][0])
response = ollama.chat(
    model="qwen2.5:7b",
    messages=[
        {
            "role": "system",
            "content": "Responde basándote en el documento. Si no hay información, dilo claramente.",
        },
        {"role": "user", "content": f"Documento:\n{context}\n\nPregunta: {query}"},
    ],
)

print(f"Respuesta: {response['message']['content']}")

Pruébalo. El flujo es directo: trocear → embedding → almacenar → consultar → responder.

Trampas frecuentes:

Primero, no elijas chunk_size al azar. Con 200 caracteres por fragmento obtuve trozos inútiles que no armaban una respuesta. 500-1000 suele ir bien.

Segundo, procesamiento por lotes. Con muchos documentos, llamar a Ollama uno a uno es lento. Agrupa:

# Embeddings en lote — mejora notable de velocidad
batch_texts = [chunk.page_content for chunk in chunks[:50]]
batch_embeddings = ollama.embed(
    model="mxbai-embed-large",
    input=batch_texts,
)["embeddings"]

Tercero, umbral de similitud. ChromaDB devuelve n_results aunque no sean relevantes. A veces conviene filtrar:

# Filtrar por distancia
results = collection.query(
    query_embeddings=[query_embedding],
    n_results=10,
)
# Solo distancia &lt; 0,3 (menor distancia = más similar)
filtered = [
    doc for doc, dist in zip(results["documents"][0], results["distances"][0])
    if dist < 0.3
]

Con este código tienes RAG local. Cambia el PDF y la pregunta; el resto igual.

Ajuste de rendimiento y buenas prácticas

Con el sistema en marcha, toca afinar. Estos parámetros marcan la diferencia.

¿Qué chunk_size?

500-1000 caracteres es mi rango habitual. Muy pequeño: semántica incompleta — una frase partida no encaja con la pregunta. Muy grande: ruido — un chunk mezcla varios temas.

Depende del tipo de documento. Docs técnicos estructurados: por párrafo; conversaciones fragmentadas: ~500 caracteres. Prueba en tu caso; no hay regla universal.

Batch para acelerar

Cada llamada suelta a Ollama paga ida y vuelta. Lotes de 50-100 textos multiplican la velocidad. No excedas el límite de longitud del modelo.

Umbral de similitud

Entre 0,7 y 0,85 según precisión vs recall. Alto (0,85): menos ruido, menos recall. Bajo (0,7): más recall, posible ruido. Corpus limpio y preguntas claras → umbral alto; preguntas abiertas → umbral más bajo.

Consejo: indexa 50-100 documentos, mide retrieval y luego escala. Ajustar con todo el corpus ya dentro cuesta más.

Resumen

En resumen:

Modelo según escenario — mxbai general, nomic textos largos, Qwen3 chino. Base: ChromaDB para empezar, FAISS si el rendimiento apremia, Milvus en producción. El código está listo para adaptar.

Ventajas claras: despliegue local, privacidad, modelos Ollama gratis, ChromaDB fácil de usar. Límites: techo en una sola máquina; millones de vectores piden otra arquitectura.

Corre el código del artículo con unos 50 documentos y mide calidad de retrieval y respuestas antes de escalar.

Para ir más allá con LangChain + Ollama (cadenas de conversación y tool calling), mira la guía de integración de la serie — esta entrega se centra en vectores; juntas cubren el camino completo de apps LLM locales.

Construir un sistema RAG local

Sistema de recuperación vectorial con Ollama + ChromaDB

⏱️ Estimated time: 30 min

  1. 1

    Step 1: Instalar dependencias y modelos

    Ejecuta:

    ```bash
    pip install ollama chromadb langchain langchain-community pypdf
    ollama pull mxbai-embed-large
    ollama pull qwen2.5:7b
    ```

    Asegúrate de que el servicio Ollama esté activo.
  2. 2

    Step 2: Cargar y trocear documentos

    PyPDFLoader + RecursiveCharacterTextSplitter:

    ```python
    loader = PyPDFLoader("./your_document.pdf")
    docs = loader.load()
    splitter = RecursiveCharacterTextSplitter(
    chunk_size=800,
    chunk_overlap=100,
    )
    chunks = splitter.split_documents(docs)
    ```

    chunk_size recomendado: 500-1000 caracteres.
  3. 3

    Step 3: Generar vectores y guardar

    API Ollama + ChromaDB:

    ```python
    client = chromadb.Client()
    collection = client.create_collection("my_docs")

    for i, chunk in enumerate(chunks):
    response = ollama.embed(
    model="mxbai-embed-large",
    input=chunk.page_content,
    )
    embedding = response["embeddings"][0]
    collection.add(
    ids=[str(i)],
    embeddings=[embedding],
    documents=[chunk.page_content],
    )
    ```

    Usa lotes si hay muchos documentos.
  4. 4

    Step 4: Recuperación semántica y respuesta

    Consulta → vector → top-k → LLM:

    ```python
    query_embedding = ollama.embed(
    model="mxbai-embed-large",
    input=query,
    )["embeddings"][0]

    results = collection.query(
    query_embeddings=[query_embedding],
    n_results=3,
    )

    context = "\n\n".join(results["documents"][0])
    response = ollama.chat(
    model="qwen2.5:7b",
    messages=[
    {"role": "system", "content": "Responde basándote en el documento"},
    {"role": "user", "content": f"Documento: {context}\nPregunta: {query}"},
    ],
    )
    ```

    Ajusta umbral de similitud según necesidad.

FAQ

¿Qué modelo Embedding de Ollama es mejor?
Depende del escenario:

• mxbai-embed-large: opción general estable
• nomic-embed-text: textos largos, 8192 tokens
• Qwen3 Embedding: mejor para chino (2026)

Prueba los tres y mide en tus datos.
¿ChromaDB, FAISS o Milvus?
Por escala:

• ChromaDB: empezar, cero config, &lt; 100k registros
• FAISS: rendimiento en una máquina, hasta ~1M
• Milvus: producción distribuida, millones+

Proyectos personales: ChromaDB. Producción: Milvus.
¿Qué chunk_size usar?
500-1000 caracteres. Muy pequeño pierde contexto; muy grande añade ruido. Docs técnicos por párrafo; conversaciones ~500. Ajusta probando.
¿Cómo acelerar la generación de embeddings?
Procesa en lotes de 50-100 textos por llamada a la API Ollama. Controla el tamaño para no superar límites del modelo.
¿Umbral de similitud recomendado?
Entre 0,7 y 0,85. Alto (0,85): más precisión, menos recall. Bajo (0,7): más recall, posible ruido.
¿Ventajas e inconvenientes del RAG local?
Ventajas: privacidad, costo cero en modelos, despliegue simple.

Inconvenientes: rendimiento limitado en una máquina; millones de vectores requieren otra arquitectura; mantenimiento propio.

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

Comentarios

Inicia sesión con GitHub para dejar un comentario

Easton BlogEaston Blog