A self-hosted, air-gapped Retrieval-Augmented Generation (RAG) system with hybrid search capabilities. Designed for organizations with strict data sovereignty requirements.
A production-ready, self-hosted RAG system for secure document retrieval and question-answering entirely within your infrastructureβno external API calls, no data leaving your premises.
- π Data Sovereignty: All data stays on-premises
- π« Zero External Calls: Complete network isolation
- π° Cost Effective: No ongoing API costs
- π§ High Quality: Open-weight models matching cloud AI capabilities
- π Multi-Format: Supports PDF, DOCX, and TXT documents
| Feature | Description |
|---|---|
| Hybrid Search | Combines semantic (dense) and keyword (sparse) search using BGE-M3 |
| Reciprocal Rank Fusion | Intelligent result merging for optimal retrieval |
| Source Citations | Every answer includes references to source documents |
| Self-Hosted LLM | Qwen3-8B via Ollamaβno API keys required |
| Network Isolation | Built-in verification of zero external calls |
User Interface (Next.js/React/API Clients)
β HTTP/REST API
FastAPI Backend
β
ββββββββ΄βββββββ
β β
BGE-M3 Model Qdrant Vector DB
(Embeddings) (Dense + Sparse)
β β
ββββββββ¬βββββββ
β
Qwen3-8B (Ollama)
LLM Inference
Document Ingestion:
- Extract text (PDF/DOCX/TXT)
- Chunk into 500-character segments (50-char overlap)
- Generate BGE-M3 embeddings (dense + sparse)
- Store in Qdrant
Query Execution:
- Generate query embeddings (dense + sparse)
- Parallel dense and sparse search
- Merge results using Reciprocal Rank Fusion
- Generate answer with Qwen3-8B LLM
- Return answer + source citations
| Component | Technology | Why |
|---|---|---|
| Embeddings | BAAI/bge-m3 | Dual dense + sparse vectors, 100+ language support |
| Vector DB | Qdrant | Native hybrid search, production-ready |
| LLM | Qwen3-8B (Ollama) | Fully open-source, local inference, ~GPT-3.5 quality |
| Backend | FastAPI | High-performance async API |
| Parsing | PyPDF2, python-docx | Reliable text extraction |
- Python 3.10+
- Docker (for Qdrant)
- Ollama (for Qwen3-8B)
- 8GB+ RAM recommended
- 10GB+ free disk space
git clone https://github.com/yourusername/rag-project.git
cd rag-projectcd backend
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txtdocker run -d -p 6333:6333 -p 6334:6334 \
-v $(pwd)/qdrant_storage:/qdrant/storage \
--name qdrant \
qdrant/qdrant# Download from https://ollama.com/download
# Pull Qwen3-8B
ollama pull qwen3:8b
# Start Ollama service
ollama servecp .env.example .env
# Edit .env with your settings.env.example:
QDRANT_HOST=localhost
QDRANT_PORT=6333
QDRANT_COLLECTION_NAME=my_documents
EMBEDDING_MODEL=BAAI/bge-m3
OLLAMA_BASE_URL=http://localhost:11434
CHAT_MODEL=qwen3:8b
MAX_FILE_SIZE=10485760
ALLOWED_EXTENSIONS=.pdf,.docx,.txt
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:8000uvicorn app.main:app --reload --host 0.0.0.0 --port 8000curl http://localhost:8000/Expected response:
{
"status": "healthy",
"service": "Document RAG API",
"version": "3.0.0"
}GET /POST /upload
Content-Type: multipart/form-data
Parameters: file (PDF, DOCX, TXT)Response:
{
"status": "success",
"document_id": "550e8400-e29b-41d4-a716-446655440000",
"filename": "policy.pdf",
"total_chunks": 15
}POST /query
Content-Type: application/json
{
"question": "What is the vacation policy?",
"top_k": 3
}Response:
{
"answer": "According to the employee handbook, employees receive 15 days of paid vacation per year.",
"sources": [
{
"source_index": 1,
"filename": "policy.pdf",
"rrf_score": 0.0325,
"content_preview": "Employees receive 15 days of paid vacation..."
}
]
}GET /documentsDELETE /documents/{document_id}GET /network-statusimport requests
BASE_URL = "http://localhost:8000"
# Upload document
with open("policy.pdf", "rb") as f:
response = requests.post(f"{BASE_URL}/upload", files={"file": f})
doc_id = response.json()["document_id"]
# Query
response = requests.post(
f"{BASE_URL}/query",
json={"question": "What is the vacation policy?", "top_k": 3}
)
print(f"Answer: {response.json()['answer']}")
print(f"Sources: {response.json()['sources']}")# Upload
curl -X POST "http://localhost:8000/upload" -F "file=@test.pdf"
# Query
curl -X POST "http://localhost:8000/query" \
-H "Content-Type: application/json" \
-d '{"question": "Test question"}'
# List documents
curl http://localhost:8000/documents
# Network status
curl http://localhost:8000/network-statuscd backend
# Run full test suite
python test_suite.py
# Run specific tests
pytest tests/test_rag_service.py -v
# Test network isolation (critical)
pytest tests/test_rag_service.py::test_no_external_calls -v| Operation | Average Time |
|---|---|
| Upload (10-page PDF) | 12s |
| Query (hybrid search) | 8-12s |
| LLM Generation | 5-8s |
Scaling:
- 1,000 chunks: ~5MB, ~12s query time
- 10,000 chunks: ~50MB, ~13s query time
- 100,000 chunks: ~500MB, ~15s query time
Qdrant Connection Failed:
docker ps | grep qdrant
docker run -d -p 6333:6333 --name qdrant qdrant/qdrantOllama Connection Failed:
curl http://localhost:11434/api/tags
ollama pull qwen3:8bOut of Memory:
# Reduce chunk size in config.py
chunk_size = 300Slow Queries:
# Use CPU mode
device = "cpu"
# Reduce retrieval
top_k = 2- β Documents never leave your infrastructure
- β Embeddings generated locally
- β LLM inference runs locally
- β No usage data sent to third parties
- β Built-in network isolation verification
- Run on air-gapped network
- Add JWT/API key authentication
- Enable rate limiting
- Implement logging and monitoring
- Regular Qdrant snapshots
Completed:
- β Document upload (PDF, DOCX, TXT)
- β Hybrid search with RRF
- β Qwen3-8B LLM integration
- β Network isolation verification
- β Production FastAPI backend
Planned:
- Streaming responses
- OCR for scanned documents
- Semantic chunking
- Metadata filtering
- Cross-encoder re-ranking
- Web UI (Next.js/React)
- Docker Compose setup
- Kubernetes Helm chart
- Fork the repository
- Create a feature branch:
git checkout -b feature/your-feature - Commit changes:
git commit -m "Add feature" - Push:
git push origin feature/your-feature - Open a Pull Request
MIT License - see LICENSE file for details
- Issues: GitHub Issues
- Documentation: Project Wiki
Built with β€οΈ for data sovereignty