RAG de PDF completamente local con LangChain, Ollama y Chroma
Una aplicación RAG en PDF completamente local: LangChain ingiere los archivos, Ollama los integra, Chroma almacena los vectores, y las respuestas funcionan sin conexión sin necesidad de claves de API en la nube.
Fuentes: Implement RAG with Langchain and local Ollama models
Crea una pipeline RAG completamente local para PDFs con LangChain, Ollama y ChromaDB: chatea con documentos sin necesidad de APIs en la nube ni de que los datos salgan de la máquina.
Por qué es importante ser local
Los LLM en la nube son prácticos, pero cada consulta envía texto a través de la red de terceros. Los contratos legales, los registros médicos y los informes internos a menudo no pueden aceptar ese compromiso. Una solución local evita fugas de datos mientras permite hacer preguntas y respuestas en lenguaje natural con tus propios archivos.
Qué obtendrás
- Ingestión de archivos PDF en un almacén vectorial persistente
- Respuestas a preguntas en lenguaje natural basadas en esos documentos
- Ejecución sin conexión en el momento de la inferencia: no se requieren claves API una vez que se han descargado los modelos
Stack: LangChain para la orquestación, Ollama para el chat local y los embeddings, ChromaDB como almacén vectorial.
Arquitectura
Ruta de ingesta: los bytes del PDF entran en un cargador basado en PyPDF, se dividen en fragmentos y luego Ollama incrusta cada fragmento en Chroma.
Ruta de consulta: una pregunta llega al recuperador, los fragmentos coincidentes se convierten en contexto y el modelo de chat local Ollama escribe la respuesta.
Dos fases mantienen los costos bajo control:
- Ingestión — una vez (o cuando cambian los documentos): analizar, dividir en fragmentos, incrustar, guardar
- Recuperación — con cada consulta: los mejores fragmentos se convierten en contexto para el LLM
Pagas por los costos de incrustación cuando cambia el contenido; las consultas interactivas siguen siendo rápidas.
Requisitos previos
Instala Ollama y descarga los modelos utilizados aquí:
ollama pull embeddinggemma:latest # embedding model
ollama pull gemma4:latest # chat model
gemma4 está ajustado para seguir instrucciones específicas. Para un uso con menos recursos, pruebe gemma2:2b; para un razonamiento más potente, utilice gemma4:27b — actualice las cadenas de configuración en consecuencia.
Configuración del proyecto
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
Paquetes clave de los requisitos:
langchain
langchain-chroma
langchain-community
langchain-ollama
langchain-text-splitters
pypdf
chromadb
python-dotenv
Sobrescriba los valores predeterminados mediante el entorno sin editar código:
OLLAMA_BASE_URL=http://localhost:11434
OLLAMA_MODEL=gemma4:latest
OLLAMA_EMBED_MODEL=embeddinggemma:latest
Proceso de ingesta (src/ingest.py)
Cargar documentos
def load_documents(pdf_path: str | Path) -> list:
path = Path(pdf_path)
loader = (
PyPDFLoader(str(path))
if path.is_file()
else PyPDFDirectoryLoader(str(path))
)
return loader.load()py
Un helper sirve para un archivo o directorio individual. Los cargadores de LangChain siempre devuelven una lista uniforme de Document en ambos casos.
Dividir en fragmentos
def split_documents(documents: list) -> list:
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
)
return splitter.split_documents(documents)
RecursiveCharacterTextSplitter prefiere dividir el texto por párrafos, luego por oraciones y finalmente por palabras para que los límites semánticos se mantengan. Una superposición de 200 caracteres permite que una oración que atraviesa un límite esté presente en ambos fragmentos adyacentes, lo cual reduce los fallos en la recuperación en los bordes.
Incorporar y persistir
def build_vector_store(chunks: list) -> Chroma:
embeddings = OllamaEmbeddings(model=EMBEDDING_MODEL, base_url=OLLAMA_BASE_URL)
VECTOR_STORE_DIR.mkdir(parents=True, exist_ok=True)
return Chroma.from_documents(
documents=chunks,
embedding=embeddings,
collection_name=COLLECTION_NAME,
persist_directory=str(VECTOR_STORE_DIR),
)
Ollama proporciona embeddings localmente en el puerto 11434. Chroma los almacena en vector_store/ (SQLite junto con archivos de índice HNSW) para que los reinicios del proceso no obliguen a realizar una nueva incorporación completa.
Cadena de recuperación (src/retrieval.py)
LangChain LCEL combina los pasos mediante el operador |.
Plantilla de prompt
RAG_PROMPT = ChatPromptTemplate.from_messages([
(
"system",
"You are an assistant that answers questions based on the provided context "
"from PDF documents. Use only the information in the context to answer. "
"If the answer is not in the context, say so clearly.\n\nContext:\n{context}"
),
("human", "{question}"),
])
El mensaje del sistema ancla las respuestas al contexto recuperado; es la principal medida de seguridad de RAG contra las alucinaciones generadas únicamente a partir del entrenamiento previo.
Construir la cadena
def build_rag_chain(k: int = 4):
store = load_vector_store()
retriever = store.as_retriever(search_kwargs={"k": k})
llm = ChatOllama(model=MODEL_NAME, base_url=OLLAMA_BASE_URL)
return (
{"context": retriever | _format_docs, "question": RunnablePassthrough()}
| RAG_PROMPT
| llm
| StrOutputParser()
)
De izquierda a derecha: el recuperador selecciona los k fragmentos más similares mediante la similitud de embeddings, los asistentes formatan esos fragmentos en una única cadena de contexto, el plantilla de prompt combina ese contexto con la pregunta del usuario y ChatOllama genera la respuesta.
Bucle interactivo de preguntas
# Index a directory of PDFs (default: data/pdfs/)
python main.py ingest
Una interfaz CLI sencilla (o un envoltorio HTTP) carga la cadena una sola vez y procesa las preguntas hasta que se cierre. Mantenga k ajustable para poder equilibrar el tamaño del contexto con la latencia.
Ejecución de principio a fin
# Index a specific file
python main.py ingest --path /path/to/contract.pdf
# Ask a one-off question
python main.py query "What are the payment terms?"
# Interactive session
python main.py chat
Dirija la entrada hacia su carpeta de PDF, confirme que el directorio del almacén de vectores esté completo y luego realice consultas. Todo, incluidos los embeddings, los vectores y la generación, permanece en localhost.
Conclusión
El RAG local ya no es algo exótico. Ollama proporciona los modelos, Chroma almacena los vectores y LangChain conecta el grafo. Para corpus privados, ese trío es suficiente para crear un asistente de documentos útil sin necesidad de facturas por uso en la nube ni de rutas de datos hacia el exterior.
Lecturas relacionadas
- LLMs locales con Ollama: CLI, API y redes WSL — Elija modelos adecuados con llmfit, inspeccione las etiquetas mediante ollama show y acceda a una API de Ollama alojada en Windows desde WSL usando la IP del gateway.
- RAG para políticas de recursos humanos adaptado a entornos de producción con LangChain y LangGraph — Procesamiento de datos, MMR, reescritura consciente del historial, respuestas basadas en información concreta, medidas de seguridad, evaluación, citas y orquestación del grafo para un asistente de políticas de recursos humanos.