A simple end-to-end RAG app — upload a PDF, ask questions, get answers grounded in the document.
pdf-reader/
├── backend/ # FastAPI + LangChain RAG
│ ├── main.py # API routes (/upload, /ask, /health)
│ ├── rag.py # Ingest + retrieval pipeline (FAISS + OpenAI)
│ ├── config.py # Settings from .env
│ ├── uploads/ # Saved PDF files
│ ├── vectorstore/ # Persisted FAISS indexes
│ └── requirements.txt
└── frontend/ # Next.js chat UI
└── src/app/
└── page.tsx # Single-page chat interface
Flow:
- User uploads PDF → backend chunks it, embeds with OpenAI, saves FAISS index locally
- User asks a question → backend retrieves top-k chunks, sends to GPT-4o-mini with a strict RAG prompt
- Answer + source page numbers returned to the UI
- Python 3.11+
- Node.js 18+
- An OpenAI API key
cd backend
# Create and activate a virtual environment
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
# Install dependencies
pip install -r requirements.txt
# Configure your API key
cp .env.example .env
# Edit .env and set OPENAI_API_KEY=sk-...
# Start the API server
.venv/bin/python3 -m uvicorn main:app --reload --port 8000The API is now at http://localhost:8000. Swagger docs: http://localhost:8000/docs
cd frontend
npm install
npm run devThe app is now at http://localhost:3000.
- Open http://localhost:3000
- Drop a PDF onto the upload zone (or click to browse)
- Wait a few seconds for indexing to complete
- Type a question and press Enter (or click Send)
- Embeddings and FAISS indexes are cached in
backend/vectorstore/— re-uploading the same file skips re-embedding. - The LLM is instructed to answer only from the document; it will say so if the answer isn't there.
- To switch models, edit
llm = ChatOpenAI(model=...)inbackend/rag.py.