c6deb42adba5098ae646595d59d4604cce600c3a
AfakZaid RAG System
Arabic AI library system with hybrid search, Celery task queue, Cloudflare R2 storage, and Typesense full-text search.
Stack
| Service | Purpose | Port (internal) |
|---|---|---|
| FastAPI | Main API | 8100 |
| PostgreSQL 15 | Metadata, users, sessions | 5432 |
| Qdrant | Vector embeddings | 6333 |
| Redis | Cache + Celery broker | 6379 |
| Ollama | Local LLM (qwen2:7b + nomic-embed-text) | 11434 |
| Typesense 0.25.2 | Arabic full-text search | 8108 |
| Celery | Async task queue (PDF pipeline) | — |
| Flower | Celery monitor dashboard | 5555 |
| Cloudflare R2 | PDF + cover CDN storage | — |
Architecture
Upload → VPS disk → Compress (Ghostscript) → Ingest (Qdrant vectors + PG chunks)
→ Celery task → Meta extract (Ollama) → R2 upload → Typesense index
→ BM25 rebuild
Query → Dense retrieval (Qdrant top 15) → BM25 (rank_bm25 top 15)
→ RRF fusion → CrossEncoder rerank → MMR diversity
→ Ollama qwen2:7b response with Arabic citations
Requirements
- Ubuntu 22.04 or 24.04
- Docker + Docker Compose
- Minimum 8GB RAM (16GB recommended for Ollama)
- 50GB disk space
- Domain with DNS control (for SSL)
- Cloudflare R2 bucket (optional but recommended)
Quick Install
# 1. Clone this repo
git clone https://git.asktam.net/colorlink/afakzaid-rag
cd afakzaid-rag
# 2. Run setup
bash scripts/setup.sh your-domain.com
# 3. Follow post-install steps printed at the end
Manual Install
Step 1 — Prepare directories
mkdir -p /opt/rag /mnt/rag-media/pdfs /mnt/rag-media/covers
mkdir -p /opt/typesense/data /opt/qdrant/storage
cp -r . /opt/rag/
Step 2 — Install Ollama
curl -fsSL https://ollama.ai/install.sh | sh
systemctl enable --now ollama
ollama pull nomic-embed-text
ollama pull qwen2:7b
Step 3 — Create Docker network
docker network create rag-net
Step 4 — Generate secrets
bash scripts/gen-secrets.sh
# Secrets written to /opt/rag/.secrets
# Edit /opt/rag/.secrets to add R2 credentials
Step 5 — Start services
bash scripts/start-all.sh
Step 6 — Setup reverse proxy
bash scripts/setup-nginx.sh your-domain.com
Environment Variables
All secrets stored in /opt/rag/.secrets (chmod 600):
REDIS_PASSWORD= # Redis auth password
QDRANT_API_KEY= # Qdrant service API key
TYPESENSE_API_KEY= # Typesense collection key
FLOWER_USER= # Flower dashboard user
FLOWER_PASSWORD= # Flower dashboard password
CELERY_BROKER_URL= # Auto-generated from REDIS_PASSWORD
RAG_API_KEY= # Public API key for queries
ADMIN_KEY= # Admin endpoint key
JWT_SECRET= # JWT signing secret (64 chars)
R2_ENDPOINT= # https://<account>.r2.cloudflarestorage.com
R2_ACCESS_KEY= # Cloudflare R2 access key ID
R2_SECRET_KEY= # Cloudflare R2 secret key
R2_BUCKET= # R2 bucket name (e.g. library)
R2_PUBLIC_URL= # CDN URL (e.g. https://cdn.yourdomain.com)
PDF Upload Pipeline
- File saved to
/mnt/rag-media/pdfs/ - Ghostscript compression (skipped if file < 200KB or compressed > original)
- Text extraction + chunking (~150 words/chunk, 200w max)
- Dense embeddings via
nomic-embed-text→ Qdrant - Metadata extraction via
qwen2:7b(title, author, category, description) - Upload PDF + cover to Cloudflare R2
- Index in Typesense (Arabic full-text)
- BM25 index rebuild
RAG Query Pipeline
- Dense retrieval: top 15 chunks from Qdrant (threshold 0.20)
- BM25 retrieval: top 15 chunks (Arabic tokenization)
- RRF fusion (k=60)
- CrossEncoder rerank:
cross-encoder/mmarco-mMiniLMv2-L12-H384-v1 - MMR diversity filter (λ=0.6, final 5 chunks)
- Answer generation:
qwen2:7bwith Arabic system prompt - Numbered citations [١][٢] in response
API Endpoints
GET /stats System statistics
POST /query RAG query (requires API key)
GET /api/library List books
POST /api/library/upload Upload single PDF
POST /api/library/bulk-upload Bulk upload PDFs
GET /api/library/{id}/pdf Serve/redirect PDF
GET /api/library/{id}/download Download PDF
GET /library/{id}/cover Book cover image
GET /admin/overview Admin dashboard stats
GET /admin/services Live service health
GET /admin/books Book management
GET /admin/r2-test Test R2 connection
POST /admin/r2-config Save R2 credentials
Security
- All internal service ports blocked from external access via iptables
- Redis password protected
- Qdrant API key enforced
- FastAPI: docs/openapi endpoints disabled in production
- CORS restricted to configured domain
- Rate limiting: login 10/min, query 30/min, ingest 20/min
- JWT fails fast if secret not set
- File uploads: 50MB limit, path traversal protection
File Structure
/opt/rag/
├── api.py FastAPI app + admin endpoints
├── routes.py Library API router
├── brain.py Hybrid RAG pipeline
├── ingest.py PDF ingestion + chunking
├── tasks.py Celery task definitions
├── celery_app.py Celery app configuration
├── compress.py Ghostscript PDF compression
├── storage.py Cloudflare R2 file operations
├── typesense_client.py Typesense search integration
├── meta_extract.py Ollama metadata extraction
├── auth.py JWT authentication
├── worker.py Legacy threading worker (fallback)
├── .secrets All credentials (chmod 600)
└── static/
├── index.html Home page + RAG query interface
├── library.html Book library with grid/list view
├── reader.html PDF reader (redirects to R2)
└── admin.html Admin dashboard
/mnt/rag-media/
├── pdfs/ PDF files (permanent, used for re-ingestion)
└── covers/ Cover images extracted from PDFs
scripts/
├── setup.sh Full automated setup
├── gen-secrets.sh Generate fresh credentials
├── start-all.sh Start all Docker containers
└── setup-nginx.sh Nginx + SSL configuration
Version History
- v1.7.4 — Celery for all uploads, R2 delivery, Typesense search
- v1.7.3 — PDF compression via Ghostscript
- v1.7.2 — Cloudflare R2 redirect for download/read
- v1.7.0 — Typesense + Celery + MinIO (MinIO later disabled)
- v1.5.x — Hybrid RAG (BM25 + dense + RRF + CrossEncoder + MMR)
- v1.0.0 — Initial FastAPI + Qdrant + Ollama
Database Schema
Key tables in PostgreSQL (ragdb):
users— user accounts with roles and permissionsbooks— book metadata (title, author, category, pdf_url, cover_url)chunks— text chunks with book reference (used for BM25)conversations— query historymemory— Mem0 conversation memorybulk_jobs/bulk_job_files— Celery upload job trackingapi_settings— runtime configuration (Claude API key, etc.)favorites— user favorite books
Notes for AI Assistants
When helping install this system on a new server:
- Ollama runs on the HOST (not in Docker) — containers reach it via the Docker gateway IP (usually
172.17.0.1or172.16.x.1) - The Docker image
rag-api-img:secureis pre-built with: Python 3.12, PyTorch, sentence-transformers, Ghostscript, Celery, Typesense client, boto3 - All inter-service communication uses container names (e.g.,
rag-postgres,rag-redis) within the Docker network - The
R2_PUBLIC_URLmust be updated per deployment — point to your CDN domain for that server - Qdrant vectors must be restored from snapshots — they cannot be regenerated without re-uploading all PDFs
- The BM25 index is rebuilt in-memory at startup from the
chunkstable — no migration needed - Typesense collection
booksis auto-created on first startup — data restored viaindex_all_books() - After import, run:
docker exec rag-api python3 -c "from typesense_client import index_all_books; index_all_books()"
Description
Languages
HTML
53.6%
Python
39.2%
Shell
3.7%
JavaScript
3.4%