...

RAG privado en local: cómo chatear con tus documentos y PDFs confidenciales sin enviar datos a internet

Servidor de Datos Local y Documentos Confidenciales para RAG Privado

El auge de la inteligencia artificial ha colocado a empresas y profesionales en una encrucijada crítica: la necesidad de aprovechar el poder de los modelos de lenguaje sin comprometer la confidencialidad de sus datos. Subir balances financieros, historiales médicos o contratos legales a servicios en la nube como ChatGPT o Claude no siempre es una opción viable debido a regulaciones como el RGPD o estrictos acuerdos de no divulgación (NDA).

La solución técnica a este dilema es implementar una arquitectura RAG (Retrieval-Augmented Generation) completamente local. En este tutorial paso a paso, aprenderás a construir un motor de búsqueda semántica y consulta de documentos que corre 100 % en tu propia máquina, sin enviar un solo byte a servidores externos.

Arquitectura básica de un sistema RAG local

Un sistema RAG no reentrena el modelo de IA; en su lugar, actúa como un bibliotecario de alta velocidad. Cuando haces una pregunta, el sistema busca en tu propia base de datos vectorial los fragmentos exactos que contienen la respuesta, y luego se los entrega al modelo para que redacte una síntesis precisa basada exclusivamente en tus hechos.

Diagrama de arquitectura técnica de un pipeline RAG local con ChromaDB y Sentence-Transformers
Figura 1: Arquitectura del pipeline RAG local en 2 fases: Ingesta e indexación de documentos en frío (arriba) y recuperación semántica en caliente con LLM local (abajo).

El flujo se compone de cuatro etapas esenciales:

  • Extracción e Ingesta: Lectura del documento original (PDF, Markdown o texto plano).
  • Fragmentación (Chunking): División del texto en bloques pequeños y coherentes para no saturar la memoria.
  • Vectorización (Embeddings): Transformación de cada fragmento de texto en un vector numérico que captura su significado semántico.
  • Almacenamiento y Recuperación: Guardado de vectores en disco local mediante ChromaDB y búsqueda de coincidencias mediante similitud de coseno.

Paso 1: Preparación del entorno de desarrollo

Para garantizar que todo funcione de manera aislada y limpia, comenzaremos creando un entorno virtual en Python 3.10 o superior e instalando las cuatro dependencias clave: chromadb para la base vectorial, sentence-transformers para los embeddings locales, pypdf para la lectura de documentos y langchain-text-splitters para la fragmentación inteligente.




terminal — bash
$ python -m venv env_rag
$ source env_rag/bin/activate  # En Windows: env_rag\Scripts\activate
$ pip install chromadb sentence-transformers pypdf langchain-text-splitters

💡 Consejo de rendimiento

La primera vez que ejecutes el script, la librería descargará automáticamente el modelo de embeddings all-MiniLM-L6-v2 (~80 MB). Este modelo corre con una velocidad asombrosa en cualquier CPU moderna sin requerir tarjetas gráficas dedicadas.

Paso 2: Código completo de implementación en Python

Crea un archivo llamado rag_privado.py y copia la siguiente implementación estructurada. Cada bloque cuenta con control de flujo para procesar cualquier documento PDF que coloques en la misma carpeta:

rag_privado.py
import os
from pypdf import PdfReader
from langchain_text_splitters import RecursiveCharacterTextSplitter
import chromadb
from chromadb.utils import embedding_functions

# 1. Extracción de texto desde el PDF
def extraer_texto_pdf(ruta_pdf: str) -> str:
    lector = PdfReader(ruta_pdf)
    texto = ""
    for pagina in lector.pages:
        contenido = pagina.extract_text()
        if contenido:
            texto += contenido + "\n"
    return texto

# 2. Fragmentación inteligente (Chunking con solapamiento)
def dividir_en_fragmentos(texto: str, chunk_size: int = 600, overlap: int = 100) -> list[str]:
    divisor = RecursiveCharacterTextSplitter(
        chunk_size=chunk_size,
        chunk_overlap=overlap,
        separators=["\n\n", "\n", ". ", " ", ""]
    )
    return divisor.split_text(texto)

# 3. Inicialización de ChromaDB persistente en disco
cliente_db = chromadb.PersistentClient(path="./mi_base_vectorial")

# Función de embeddings local (descarga automática del modelo MiniLM)
modelo_embeddings = embedding_functions.SentenceTransformerEmbeddingFunction(
    model_name="all-MiniLM-L6-v2"
)

coleccion = cliente_db.get_or_create_collection(
    name="documentos_privados",
    embedding_function=modelo_embeddings
)

# 4. Ingesta e indexación de fragmentos
def indexar_documento(ruta_pdf: str):
    print(f"📄 Procesando: {ruta_pdf}")
    texto = extraer_texto_pdf(ruta_pdf)
    fragmentos = dividir_en_fragmentos(texto)
    
    ids = [f"frag_{i}" for i in range(len(fragmentos))]
    metadatos = [{"fuente": os.path.basename(ruta_pdf), "indice": i} for i in range(len(fragmentos))]
    
    coleccion.add(documents=fragmentos, metadatos=metadatos, ids=ids)
    print(f"✅ {len(fragmentos)} fragmentos indexados correctamente en disco local.")

# 5. Consulta semántica (Búsqueda por significado, no palabras exactas)
def consultar_documentos(pregunta: str, top_k: int = 3):
    resultados = coleccion.query(query_texts=[pregunta], n_results=top_k)
    fragmentos_relevantes = resultados["documents"][0]
    
    print(f"\n🔍 Pregunta: {pregunta}")
    print("=" * 60)
    for i, frag in enumerate(fragmentos_relevantes, 1):
        print(f"[{i}] {frag.strip()}\n")
    return fragmentos_relevantes

# Ejecución de prueba
if __name__ == "__main__":
    # indexar_documento("contrato_confidencial.pdf")
    # consultar_documentos("¿Cuál es la cláusula de terminación anticipada?")
    pass

Paso 3: Cómo funciona la fragmentación con solapamiento (Overlap)

Uno de los errores más comunes en arquitecturas RAG novatas es cortar el texto de forma arbitraria por número de caracteres. Si un párrafo dice: «El plazo de garantía no cubre daños por agua…» y el corte ocurre justo en la palabra «no», el modelo puede entender lo opuesto.

Por esta razón, utilizamos RecursiveCharacterTextSplitter con un chunk_size de 600 caracteres y un overlap de 100 caracteres. El solapamiento asegura que el final de un bloque se repita al inicio del siguiente, preservando el contexto semántico y evitando que las oraciones clave queden mutiladas.

Comparativa técnica: Bases de datos vectoriales para entornos locales

Aunque en esta guía utilizamos ChromaDB por su facilidad de integración en Python, existen varias alternativas sólidas según la escala de tus documentos:

Tecnología Consumo de Memoria Velocidad de Búsqueda Facilidad de Uso Caso de Uso Recomendado
ChromaDB Medio (~250 MB base) Alta (sub-segundo) Muy Alta (Plug & Play) Prototipos rápidos, RAG de escritorio, apps en Python
LanceDB Mínimo (Almacenamiento en disco) Muy Alta (Motor Apache Arrow) Alta Colecciones masivas de documentos en laptops modestas
FAISS (Meta) Variable (Carga completa en RAM) Extrema (GPU / C++) Media (Sin metadatos nativos) Búsquedas masivas de millones de vectores en memoria
Qdrant (Modo local) Medio (Optimizado en Rust) Alta Alta Sistemas que migrarán de local a clúster de producción

Paso 4: Conectar los fragmentos con un modelo de lenguaje local

Una vez que la función consultar_documentos() te devuelve los 3 fragmentos más relevantes de tu PDF, el paso final consiste en pasárselos a un modelo que redacte la respuesta en lenguaje natural. Puedes conectar este script directamente con herramientas locales como Ollama o llama.cpp utilizando el siguiente formato de prompt:

prompt_aumentado.txt
Eres un asistente legal y técnico que responde preguntas basándose ÚNICAMENTE en el siguiente contexto extraído de documentos privados.
Si la respuesta no se encuentra en el contexto, responde honestamente 'La información no aparece en los documentos suministrados'.

CONTEXTO RECUPERADO:
{fragmentos_relevantes}

PREGUNTA DEL USUARIO:
{pregunta}

RESPUESTA:

Buenas prácticas de seguridad y producción

  • Persistencia local garantizada: Al inicializar ChromaDB con PersistentClient(path="./mi_base_vectorial"), los vectores se guardan en una base de datos SQLite embebida en tu disco duro. Puedes apagar tu ordenador y tus datos seguirán ahí sin tener que volver a procesar el PDF.
  • Cero fugas de red: Para auditar que ningún paquete salga a internet, puedes desconectar el Wi-Fi o ejecutar el script con un cortafuegos local; comprobarás que la indexación y la búsqueda vectorial operan al 100 % offline.
  • Documentos multilingües: Si tus PDFs están en español y contienen vocabulario técnico complejo, puedes sustituir el modelo de embeddings por paraphrase-multilingual-MiniLM-L12-v2 cambiando únicamente una línea de código.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Seraphinite AcceleratorOptimized by Seraphinite Accelerator
Turns on site high speed to be attractive for people and search engines.