A retrieval-augmented generation (RAG) pipeline that lets users upload PDF documents and query them in natural language — in Arabic, French, or English — receiving grounded, source-cited answers instead of hallucinated ones.
Traditional LLMs only know what they were trained on. This system solves that by combining a user's own documents with an LLM at query time:
- PDFs are ingested, split into overlapping chunks, and embedded into vectors using a multilingual sentence-transformers model.
- Vectors are stored in ChromaDB, isolated per user.
- When a user asks a question, the most relevant chunks are retrieved via vector similarity search and injected into the LLM's prompt.
- The LLM answers strictly from that retrieved context, citing the source document and page — reducing hallucination and keeping answers auditable.
- Multilingual retrieval and generation — ask questions in Arabic, French, or English; the system answers in the same language.
- Sub-second retrieval on corpora of 1000+ pages, thanks to vector search in ChromaDB.
- JWT-protected user document spaces — each user only sees and queries their own uploaded documents.
- Source-grounded answers — every response cites the originating document and page number.
- Async-friendly document status — documents move through
pending → processing → ready/failedstates, tracked and polled by the frontend.
React (Vite) → Django REST Framework → RAG pipeline → ChromaDB ↓ ↓ JWT auth (SimpleJWT) Hugging Face Inference (embeddings + LLM)
Ingestion pipeline: PDF upload → text extraction (pypdf) → chunking (LangChain RecursiveCharacterTextSplitter) → embedding (sentence-transformers, multilingual model) → storage in a per-user ChromaDB collection.
Query pipeline: question → embedded with the same model → top-k similarity search in ChromaDB → relevant chunks injected into a grounded prompt → answer generated via Hugging Face's Inference Providers router → response returned with cited sources.
| Layer | Technology |
|---|---|
| Backend | Django, Django REST Framework, SimpleJWT |
| Frontend | React (Vite), React Router, Axios |
| Orchestration | LangChain |
| Embeddings | sentence-transformers (paraphrase-multilingual-mpnet-base-v2) |
| Vector store | ChromaDB |
| LLM | Hugging Face Inference Providers |
| Containerization | Docker, docker-compose |
rag-qa-system/ ├── backend/ │ ├── config/ # Django project settings, URLs │ ├── documents/ # Models, serializers, views, auth, upload API │ └── rag/ # Ingestion, retrieval, and generation pipeline └── frontend/ └── src/ ├── api/ # Axios client, auth, documents/query calls ├── components/ # UploadForm, DocumentList, ChatBox └── pages/ # Login, Register, Dashboard
- Python 3.10+
- Node.js 18+
- A Hugging Face account and API token with Inference Providers access (create one here)
cd backend
python -m venv venv
source venv/bin/activate # venv\Scripts\activate on Windows
pip install -r requirements.txtCreate a .env file in backend/:
DJANGO_SECRET_KEY=your-generated-secret-key
DJANGO_DEBUG=True
HF_API_TOKEN=hf_your_token_hereGenerate a secure secret key:
python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"Then run migrations and start the server:
python manage.py makemigrations documents
python manage.py migrate
python manage.py createsuperuser
python manage.py runservercd frontend
npm installCreate a .env file in frontend/:
VITE_API_URL=http://localhost:8000/apiThen start the dev server:
npm run devVisit http://localhost:5173, create an account, log in, upload a PDF, wait for its status to become ready, and start asking questions.
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/auth/register/ |
Create a new user account |
| POST | /api/auth/login/ |
Obtain JWT access/refresh tokens |
| POST | /api/auth/refresh/ |
Refresh an expired access token |
| GET | /api/documents/ |
List the authenticated user's documents |
| POST | /api/documents/ |
Upload a PDF (triggers ingestion) |
| DELETE | /api/documents/{id}/ |
Delete a document and its vectors |
| POST | /api/query/ |
Ask a question against the user's ready documents |
| GET | /api/query/history/ |
List past questions and answers |
- PDF ingestion runs synchronously in the request cycle — large files (100+ pages) will block the upload request until indexing completes. A production version would offload this to a background worker (Celery + Redis).
- Scanned/image-only PDFs without embedded text are not supported (no OCR step yet).
- The embedding model is downloaded and cached in memory on first use per server process, causing a delay on cold start.
This project is for educational and portfolio purposes.