Building a Production RAG System for Technical Document Search with Local LLMs

core_marine · Energy · 2026 · source Agentic: no · promotional bias: low · source-backed · extracted with prompt 0.5.4 (schema v2)

Generated from extraction.json — do not edit by hand. Every claim cites verbatim source evidence; stated means the quote directly asserts the claim, implied means it supports it indirectly.

Problem

Domain: Local confidential RAG over offshore engineering project archives

Core Marine needed an internal natural-language search assistant over nearly a decade of highly technical project documents, including OrcaFlex files, while preserving confidentiality through local LLMs and keeping the system fast enough for engineers to use.

Constraints:

Architecture

Core Marine built a local RAG system with Ollama running llama3.2:3b and embeddings, LlamaIndex for retrieval orchestration, ChromaDB backed by SQLite for vector storage, Flask as an API, Streamlit as the user interface, and Azure Blob Storage for original document downloads. The team added aggressive file filtering, batch processing, checkpoints, GPU indexing on a rented RTX 4000 SFF Ada VM, and SAS-token links to serve cited documents directly from Azure while keeping the vector index local.

Diagram confidence: explicit (topology described by the source).

flowchart LR
  streamlit-web-ui[Streamlit Web UI] --> flask-api[Flask API]
  flask-api --> llamaindex-rag[LlamaIndex RAG]
  llamaindex-rag --> ollama-local-models[Ollama Local Models]
  llamaindex-rag --> chromadb-index[ChromaDB Index]
  chromadb-index --> sqlite-storage[SQLite Storage]
  azure-blob-storage[Azure Blob Storage] --> llamaindex-rag
  file-filter-pipeline[File Filter Pipeline] --> batch-indexer[Batch Indexer]
  gpu-vm[GPU VM] --> batch-indexer
  batch-indexer --> llamaindex-rag
  llamaindex-rag --> azure-blob-storage
Drag to pan · scroll or use +/− to zoom · reconstructed from the source's described component topology

Components

Component Role Interacts with
Streamlit Web UI Internal chat interface where engineers ask questions and view answers. Flask API
Flask API Backend API that exposes the RAG system to the Streamlit UI. Streamlit Web UI, LlamaIndex RAG
LlamaIndex RAG Orchestration framework for document indexing, retrieval, and query execution. Flask API, ChromaDB Index, Ollama Local Models, Batch Indexer
Ollama Local Models Local inference and embedding runtime, including llama3.2:3b and nomic-embed-text. LlamaIndex RAG
ChromaDB Index Vector database storing embeddings and supporting similarity search over the document corpus. LlamaIndex RAG, SQLite Storage
SQLite Storage Local persistence layer used under ChromaDB for the vector index. ChromaDB Index
Azure Blob Storage Cloud storage for original project documents served on demand through SAS-token links. LlamaIndex RAG
File Filter Pipeline Preprocessing filters that exclude videos, simulations, backups, archives, temp files, CSVs, JSON, and other low-value expensive files. Batch Indexer
Batch Indexer Processes files in batches, frees memory, saves checkpoints, and resumes after interruptions. File Filter Pipeline, LlamaIndex RAG, GPU VM
GPU VM Rented NVIDIA RTX 4000 SFF Ada machine used to finish indexing within weeks rather than months. Batch Indexer

Data flow

Filtered project files are converted to text where useful, batched through LlamaIndex, embedded with local models, and stored in ChromaDB. Users ask questions in Streamlit, the frontend calls a Flask API, LlamaIndex queries ChromaDB and Ollama, and cited source documents are downloaded directly from Azure Blob Storage through generated SAS-token links rather than copied into local storage.

4 supporting quotes
“It was time to build the backend and frontend. ## Problem 5: the user experience With Flask I built a simple API to access LlamaIndex, which in turn queried ChromaDB and Ollama.”article, stated
“I'm quite a fan of Streamlit for building internal projects of all kinds, so it would be my frontend (and I'd keep using Python). It also has a native widget for Q&A, in the style of any current chat for interacting with an AI.”article, stated
“* Manage memory: Loading all documents into memory and processing them at once is dangerous. Implement batch processing, I used 150 per iteration, with explicit garbage collector calls between batches. Each batch is processed, embedded, stored in ChromaDB, and memory is freed before continuing.”article, stated
“The solution was to serve the original documents directly from Azure Blob Storage (it would have been possible with any other system). For each document cited in a response, the system generates a download link with a SAS token that allows the user to download it directly from the cloud.”article, stated

Why this architecture

The architecture follows from confidentiality and scale constraints. External APIs were off the table, so Ollama handled local models. LlamaIndex simplified RAG orchestration, but default JSON persistence failed at hundreds-of-gigabytes scale, pushing the system to ChromaDB. Original documents stayed in Azure because only the 54GB vector index, 10GB LLM, backend, and frontend needed local disk.

4 supporting quotes
“I needed a local language model, without relying on external APIs, for confidentiality reasons. Ollama emerged as the most mature and easy-to-use option for running LLaMA models locally. I tried several embeddings, and nomic-embed-text offered good performance and quality for technical documents.”article, stated
“After some research, I found a mature open source framework called LlamaIndex. The language I'd use would be Python, I could list many reasons, but the most important one is that I feel comfortable and productive with it. Additionally, both Ollama and LlamaIndex have excellent Python SDKs.”article, stated
“The system became unmanageable: every time the service restarted, it had to reprocess all documents from scratch, which could take days.”article, stated
“What absolutely needed disk space was the ChromaDB vector index, which at 54GB is perfectly manageable on a local disk”article, stated

Models used: llama3.2:3b · nomic-embed-text · Ollama

Scale

Alternatives considered (roads not taken)

Use external LLM APIs

Rejected because: Company confidentiality requirements required local models.

“I needed a local language model, without relying on external APIs, for confidentiality reasons. Ollama emerged as the most mature and easy-to-use option for running LLaMA models locally. I tried several embeddings, and nomic-embed-text offered good performance and quality for technical documents.”article, stated
“I needed a local language model, without relying on external APIs, for confidentiality reasons. Ollama emerged as the most mature and easy-to-use option for running LLaMA models locally. I tried several embeddings, and nomic-embed-text offered good performance and quality for technical documents.”article, stated

Use LlamaIndex default JSON storage

Rejected because: It became unmanageable for hundreds of gigabytes and forced full reprocessing after restarts.

“You just point it to the directory and it takes care of storing all the information inside in JSON format. It's really convenient, works well, unless you're dealing with hundreds of gigabytes of documents.”article, stated
“It's really convenient, works well, unless you're dealing with hundreds of gigabytes of documents. The system became unmanageable: every time the service restarted, it had to reprocess all documents from scratch, which could take days.”article, stated

Index all file types directly

Rejected because: Large binary, simulation, backup, CSV, JSON, and other low-value files exhausted memory or added little retrieval value.

“Documents that added nothing to a RAG system, but that LlamaIndex tried to process as if they were text. If a file weighed several gigabytes, the system tried to load it entirely into memory for processing, which was suicide.”article, stated
“I also removed files that were expensive to process and didn't add value either, like CSVs, JSONs, among others.”article, stated

Store original documents locally with the index

Rejected because: The vector index and models fit locally, while source documents could remain in Azure and be served by SAS-token links.

“What absolutely needed disk space was the ChromaDB vector index, which at 54GB is perfectly manageable on a local disk, the LLM, which takes about 10GB, the backend (a few megabytes), and the frontend (another few megabytes). The rest of the documents stay in Azure, accessible on demand.”article, stated
“The solution was to serve the original documents directly from Azure Blob Storage (it would have been possible with any other system). For each document cited in a response, the system generates a download link with a SAS token that allows the user to download it directly from the cloud.”article, stated

Patterns

rag-retrieval (knowledge-access)

Core Marine built a local RAG system that retrieves project-document chunks and returns answers with references to source documents.

“a tool where you could ask questions in natural language and get answers with references to the original documents.”article, stated
“A RAG involves creating a vector index file containing document embeddings.”article, stated

privacy-preserving-processing (deployment)

The system keeps language model and embedding processing local because the project corpus is confidential.

“without relying on external APIs, for confidentiality reasons.”article, stated
“Ollama + llama3.2:3b”article, stated

batch-offline-processing (indexing)

The indexer processes files in batches, explicitly frees memory, and stores embeddings incrementally.

“Implement batch processing, I used 150 per iteration, with explicit garbage collector calls between batches.”article, stated
“Each batch is processed, embedded, stored in ChromaDB, and memory is freed before continuing.”article, stated

durable-execution (indexing)

The pipeline saves checkpoints so long-running indexing can resume after outages or restarts.

“Each batch can take hours, a power outage or restart can't mean starting from zero.”article, stated
“Implement a checkpoint system that saves the last completed batch, total processed nodes, and a timestamp.”article, stated

observability-tracing (operations)

Monitoring scripts expose indexing progress, speed, checkpoint state, and failed files during long-running RAG builds.

“Add monitoring and information scripts for different states: index-progress, index-watch, index-speed, index-checkpoint, index-failed...”article, stated
“When the RAG is working for hours, you need to know what's happening.”article, stated

Tradeoffs

Use local models

Chose Ollama with local LLaMA models over external LLM APIs.

Rationale: Confidential project data required local processing even though it added hardware and performance constraints.

“I needed a local language model, without relying on external APIs, for confidentiality reasons.”article, stated
“Ollama emerged as the most mature and easy-to-use option for running LLaMA models locally.”article, stated

Move to ChromaDB

Chose a dedicated vector database over LlamaIndex JSON storage and custom checkpointing.

Rationale: ChromaDB enabled batch indexing, resumes across sessions, backup/restore, and large-scale search without corrupt progress.

“After many trials and errors, and reading more about it, I decided to make the leap to a dedicated vector database: ChromaDB.”article, stated
“This allowed indexing the 451GB of documents across multiple sessions, with checkpoints, without losing progress on interruptions”article, stated

Filter aggressively before indexing

Chose exclude low-value or dangerous file classes over attempt complete ingestion of the 1 TB archive.

Rationale: Filtering avoided memory blowups and reduced cost while preserving useful technical documents.

“Documents that added nothing to a RAG system, but that LlamaIndex tried to process as if they were text.”article, stated
“The result was a 54% reduction in the number of files to index.”article, stated

Serve source documents from Azure

Chose SAS-token direct downloads from Azure Blob Storage over copying all original documents onto local disk.

Rationale: The local deployment only needed the vector index, model, backend, and frontend; original documents were too large and could remain available on demand.

“For each document cited in a response, the system generates a download link with a SAS token”article, stated
“The rest of the documents stay in Azure, accessible on demand.”article, stated

Gotchas (what bit them)

Naive ingestion of mixed project files crashed the machine.

Root cause: LlamaIndex tried to treat massive binary or low-value files as text and load multi-gigabyte files into memory.

Resolution: The team added extension/name filters and converted only useful office documents to plain text.

“LlamaIndex ended up overflowing my laptop's RAM within minutes, choking my OS until everything froze.”article, stated
“I added a filtering system to the pipeline that excluded files by extension and by name patterns”article, stated

Convenient local JSON persistence did not survive production scale.

Root cause: The default LlamaIndex storage format required reprocessing all documents on restart and was not optimized for large searches.

Resolution: Core Marine moved vectors into ChromaDB with SQLite persistence.

“every time the service restarted, it had to reprocess all documents from scratch, which could take days.”article, stated
“I decided to make the leap to a dedicated vector database: ChromaDB.”article, stated

Checkpointing is easy to get wrong at large indexing scale.

Root cause: The first custom checkpoint attempt corrupted data and stayed slow.

Resolution: ChromaDB's batch pipeline provided resumable sessions without corrupted progress.

“I added a checkpoint system to save indexing progress.”article, stated
“However, data got corrupted, it was error-prone, and very slow.”article, stated

CPU-only indexing made the schedule untenable.

Root cause: The bottleneck moved from memory and storage to embedding throughput.

Resolution: The team rented an RTX 4000 SFF Ada VM and optimized containers for GPU use.

“Now the bottleneck was neither the RAM, nor the indexing system, nor the files, but the GPU.”article, stated
“it was decided to rent me a virtual machine with an NVIDIA RTX 4000 SFF Ada, which has 20GB of VRAM.”article, stated

Bad files should not stop an indexing batch.

Root cause: Some PDFs, Word docs, spreadsheets, and other documents fail parsing even after filtering.

Resolution: The pipeline logs failures and continues, leaving manual review for later.

“some files got through but failed during parsing, like corrupt PDFs, Word documents with broken macros, spreadsheets with unexpected formats”article, stated
“If a file fails, it's logged and we move on to the next one.”article, stated

Evaluation

Evaluation was operational and experiential: the author benchmarked indexing speed, monitored RAM/GPU/disk bottlenecks, used progress and failure scripts during long jobs, and validated the final system by successfully indexing the corpus and serving a working Streamlit/Flask RAG application.

Metrics: files filtered · documents indexed · vectors indexed · index size · CPU processing time per 500 MB · GPU VRAM · indexing duration · failed file logs · checkpoint progress

3 supporting quotes
“With a quick benchmark, I discovered I would need several months to index all the content with my laptop.”article, stated
“Add monitoring and information scripts for different states: index-progress, index-watch, index-speed, index-checkpoint, index-failed...”article, stated
“After several weeks, between 2 and 3, the indexing process finished without failures.”article, stated

Outcomes

The project began with a 1 TB archive, indexed 451GB into 738,470 vectors, produced a 54GB ChromaDB index, used a 20GB VRAM GPU VM, and completed indexing in roughly two to three weeks at a reported cost of 184 euros.

3 supporting quotes
“738,470 vectors, 54GB of index in ChromaDB, and a RAG system ready to answer questions.”article, stated
“In a couple of hours I had the entire visual part working.”article, stated
“The rest of the documents stay in Azure, accessible on demand.”article, stated