A production-ready Retrieval Augmented Generation API that lets you upload PDF documents, ask questions about them, and get accurate answers generated by a local LLM — with full source attribution.
Built with FastAPI, Qdrant, sentence-transformers, and Ollama.
POST /documents → PDF → extract text → chunk → embed → store in Qdrant
POST /query → question → embed → search Qdrant → send context to Ollama → answer
GET /documents → list all uploaded documents and their chunk counts
DELETE /documents → remove a document and all its chunks from Qdrant
GET /health → health check
- Requirements
- Project Structure
- Getting Started
- Running Tests
- API Endpoints
- Request & Response Schemas
- Example Usage
- Docker
- Python 3.12+
- Docker (for Qdrant)
- Ollama (for local LLM inference)
rag-api/
├── .github/
│ └── workflows/
│ └── ci.yml — GitHub Actions CI pipeline
├── app/
│ ├── __init__.py
│ ├── main.py — FastAPI app and route handlers
│ ├── schema.py — Request and response schemas
│ └── services/
│ ├── __init__.py
│ ├── embeddings.py — sentence-transformer embedding model
│ ├── vector_store.py — Qdrant operations
│ ├── document_processor.py — PDF extraction and chunking
│ └── llm.py — Ollama REST API integration
├── test/
│ ├── __init__.py
│ └── test_main.py — full test suite (70 tests)
├── .dockerignore
├── .env
├── .gitignore
├── docker-compose.yml
├── Dockerfile
├── README.md
└── requirements.txt
git clone git@github.com:HonourJAH/rag-api.git
cd rag-apipython3 -m venv venv
source venv/bin/activate # Linux/macOS
venv\Scripts\activate # Windowspip install -r requirements.txtdocker run -d \
--name qdrant \
-p 6333:6333 \
-v qdrant_storage:/qdrant/storage \
qdrant/qdrantQdrant dashboard available at http://localhost:6333/dashboard
Download Ollama from ollama.com then pull a model:
ollama pull mistral # swap mistral for llama3.2, gemma, etc. — whatever you have pulled
ollama serveOllama runs on http://localhost:11434 by default.
To use a different model, update OLLAMA_MODEL in app/services/llm.py:
OLLAMA_MODEL = "llama3.2" # or gemma, phi3, etc.uvicorn app.main:app --reloadAPI available at http://localhost:8000
Interactive docs at http://localhost:8000/docs
Tests use mocked external services — no Qdrant or Ollama required to run them.
pytest test/ -vRun with coverage:
pytest test/ -v --cov=app --cov-report=term-missing| Method | Endpoint | Description | Status Code |
|---|---|---|---|
POST |
/documents |
Upload a PDF and store its chunks | 201 Created |
GET |
/documents |
List all uploaded documents | 200 OK |
DELETE |
/documents/{document_name} |
Delete a document and all its chunks | 200 OK |
POST |
/query |
Ask a question and get an answer | 200 OK |
GET |
/health |
Health check | 200 OK |
Request: multipart/form-data with a PDF file.
Response:
{
"document_name": "policy.pdf",
"chunks_stored": 42,
"message": "Successfully processed and stored 42 chunks from policy.pdf"
}Response:
{
"result": 2,
"documents": [
{ "document_name": "policy.pdf", "chunk_count": 42 },
{ "document_name": "handbook.pdf", "chunk_count": 31 }
]
}Response:
{
"document_name": "policy.pdf",
"chunks_deleted": 42,
"message": "Successfully deleted 42 chunks from 'policy.pdf'"
}Request body:
| Field | Type | Required | Description |
|---|---|---|---|
question |
string |
✅ | The question to ask |
document_name |
string |
❌ | Scope search to a specific document |
top_k |
int |
❌ | Number of chunks to retrieve. Defaults to 5 |
Response:
{
"question": "What is the refund policy?",
"answer": "According to the document, refunds are processed within 14 days..."
}{ "status": "healthy" }curl -X POST http://localhost:8000/documents \
-F "file=@company_policy.pdf"curl http://localhost:8000/documentscurl -X POST http://localhost:8000/query \
-H "Content-Type: application/json" \
-d '{"question": "What is the parental leave policy?"}'curl -X POST http://localhost:8000/query \
-H "Content-Type: application/json" \
-d '{
"question": "What is the refund policy?",
"document_name": "company_policy.pdf",
"top_k": 3
}'curl -X DELETE http://localhost:8000/documents/company_policy.pdfBuild and run the entire API in a container:
docker build -t rag-api .
docker run -d \
--name rag-api \
-p 8000:8000 \
--network host \
rag-api--network host allows the container to reach Qdrant and Ollama running on localhost.
Run with Docker Compose (API + Qdrant together):
docker compose up