NNESTECHUB
← BACK TO JOURNAL

How to Build a Secure Local Document Search Engine with RAG

Create private conversational search across local PDFs using Ollama, LangChain, ChromaDB, and open-source embeddings—with no per-query API bill.

How to Build a Secure Local Document Search Engine with RAG

A local Retrieval-Augmented Generation system can search contracts, research, and internal PDFs without sending their contents to a hosted model. Documents are chunked, embedded, indexed, retrieved, and passed to a local LLM as grounded context.


The Local RAG Architecture

  1. Load and chunk: Split documents into overlapping passages.
  2. Embed: Convert each passage into a semantic vector.
  3. Index: Store vectors in a local ChromaDB directory.
  4. Retrieve and answer: Supply the closest passages to Ollama.

Step 1: Install the Local Stack

pip install langchain langchain-community langchain-text-splitters chromadb sentence-transformers pypdf ollama
ollama pull llama3

Step 2: Build the Document Index

from langchain_community.document_loaders import PyPDFDirectoryLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores import Chroma

docs = PyPDFDirectoryLoader("./documents").load()
chunks = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200
).split_documents(docs)

embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
Chroma.from_documents(
    documents=chunks,
    embedding=embeddings,
    persist_directory="./chroma_db"
)

Step 3: Query the Index with Ollama

from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.llms import Ollama
from langchain.chains import RetrievalQA

embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
store = Chroma(persist_directory="./chroma_db", embedding_function=embeddings)
chain = RetrievalQA.from_chain_type(
    llm=Ollama(model="llama3"),
    chain_type="stuff",
    retriever=store.as_retriever(search_kwargs={"k": 3})
)

print(chain.run("Summarize the termination clauses."))

Security Checks Before Indexing

  • Bind local services to loopback unless network access is intentionally required.
  • Encrypt the device and protect backups.
  • Exclude secrets and documents users are not authorized to retrieve.
  • Record document sources with each chunk so answers can show citations.

Technical Breakdown

ComponentToolStoragePrivacy
Local LLMOllama / Llama 3Several GBOffline inference
EmbeddingsMiniLMAbout 90 MBLocal processing
Vector DBChromaDBDepends on corpusLocal directory
REFERENCES

Sources and further reading

  1. docs.ollama.com
  2. docs.langchain.com
  3. docs.trychroma.com
JOIN THE CONVERSATION

What do you think?

0 COMMENTS

Be the first person to share a thought.

KEEP READINGExplore all stories →