Local RAG System
Project Overview
Local RAG System is a context-aware document ingestion and semantic retrieval application built in Python. It converts uploaded documents into searchable vector embeddings and returns the most relevant text chunks for a user's query. The retrieved results can be used as context by an AI assistant or another downstream application.
The project provides both a Streamlit interface and a FastAPI service. It can store embeddings in PostgreSQL with pgvector or in a local JSON file, making the same retrieval workflow suitable for database-backed and lightweight local setups.
The Problem
Follow-up questions in a conversation are often incomplete when read on their own. A query such as “What does it cost?” may not contain enough information for vector search to retrieve the right document section. RAG applications also need a reliable way to parse different file formats, divide their content into useful chunks, prevent unnecessary re-indexing, and keep separate knowledge bases isolated.
My Solution
I built a modular retrieval pipeline that:
- Accepts TXT, CSV, PDF, and DOCX uploads.
- Extracts and chunks their content using configurable section, line, or word-budget strategies; CSV files are indexed row by row.
- Creates normalized 384-dimensional embeddings with
intfloat/multilingual-e5-small, using the E5passage:andquery:prefixes. - Stores and searches vectors through either PostgreSQL/pgvector or a local JSON/NumPy backend.
- Uses a local GGUF model through
llama-cpp-pythonto turn conversational follow-up questions into standalone search queries. - Filters retrieval by tenant and knowledge-base identifiers and returns ranked chunks with cosine-similarity scores and source metadata.
Key Features
- Two interchangeable vector-storage backends: PostgreSQL with pgvector or local JSON with NumPy
- Multilingual semantic retrieval using the E5 embedding model
- Optional local query rewriting based on up to five previous questions
- Graceful fallback to the original query when the query-rewriter model is unavailable
- TXT, CSV, PDF, and DOCX ingestion
- Configurable chunk size and splitting strategy
- SHA-256 content hashes to skip unchanged chunks during upsert
- Tenant-aware and knowledge-base-aware search filtering
- Selectable top-k retrieval from the Streamlit interface and API
- Knowledge-base statistics plus file-level and knowledge-base-level deletion
- Search results with similarity score, source filename, metadata, and chunk ID
- Separate timing for query rewriting, embedding generation, and vector search
- FastAPI
/searchand/healthendpoints - One launcher for the Streamlit UI and FastAPI service
How It Works
Document upload
|
v
Text extraction and chunking
|
v
E5 passage embeddings
|
v
PostgreSQL/pgvector or local JSON storage
User query + recent history
|
v
Optional local query rewriting
|
v
E5 query embedding
|
v
Tenant/knowledge-base filtered similarity search
|
v
Ranked source chunks for downstream RAG use
Technical Implementation
The PostgreSQL backend creates a dimension-specific vector table and an HNSW cosine index automatically. It uses pgvector's cosine-distance operator for ranked retrieval. The local backend stores the same records in JSON and calculates cosine similarity with NumPy; because the embeddings are normalized, the dot product provides the similarity score.
Each indexed chunk includes its tenant ID, knowledge-base name, source filename, record identifier, content hash, text, and embedding. When a chunk with the same ID and content hash already exists, the system skips re-embedding it.
The query rewriter runs locally from a GGUF model with a 2,048-token context window and a 256 MB inference cache. Rewriting preserves the query's original language and only runs when conversation history and the local model are available.
My Contributions
- Designed the end-to-end ingestion and semantic retrieval workflow
- Built the Streamlit pages for uploading, searching, managing data, and viewing API usage
- Developed the FastAPI search interface and health endpoint
- Implemented PDF, DOCX, CSV, and plain-text extraction and chunking
- Integrated multilingual E5 embeddings with retrieval-specific prefixes
- Implemented PostgreSQL/pgvector search with an HNSW index
- Built the alternative JSON/NumPy vector store for local use
- Added contextual query rewriting with a local GGUF model
- Added tenant and knowledge-base filtering for data separation
- Added content-hash-based update detection and knowledge-base management tools
- Added component-level latency reporting and source-aware search results
- Created a unified launcher for the UI and API processes
Technology Stack
- Python
- Streamlit
- FastAPI and Uvicorn
- Sentence Transformers
intfloat/multilingual-e5-small- llama.cpp via
llama-cpp-python - PostgreSQL and pgvector
- NumPy and JSON
- PyPDF
- python-dotenv
Outcome
The result is a working, locally deployable retrieval layer for RAG applications. Users can ingest documents, organize them by tenant and knowledge base, perform context-aware semantic searches, inspect ranked source chunks, and access retrieval through either a graphical interface or an HTTP API. The modular storage design also allows the project to move between a simple file-based setup and PostgreSQL without changing the ingestion or search experience.
Current Scope
This project provides the retrieval component of a RAG pipeline. It returns relevant document chunks but does not currently send that context to a separate generative model to produce a final answer. Retrieval-quality benchmark scores and production-scale performance results have not yet been measured, so no unverified metrics are claimed here.