A local-first, production-style starter for a Multi-Tenant Inference-as-a-Service platform with:
- FastAPI backend
- Ollama for local LLM inference
- Qdrant for tenant-scoped vector storage
- PostgreSQL for tenants, users, and auth metadata
- Prometheus + Grafana for basic observability
- Docker Compose for one-command local startup
This version is designed for learning first and CPU-only machines. You do not need a GPU to begin.
Start in this exact order:
- Git
- Python 3.11
- Docker Desktop
- Ollama
- VS Code with Python extension
Because you currently do not have GPU support, start with one of these:
phi3:minigemma2:2bqwen2.5:1.5b
For the smoothest first run, use:
ollama pull qwen2.5:1.5bThen try:
ollama run qwen2.5:1.5b- Bring up infrastructure with Docker Compose.
- Run the FastAPI app locally.
- Add tenant-aware request handling.
- Add Ollama chat calls.
- Add tenant-scoped Qdrant collections for RAG.
- Add a simple ReAct-style agent.
- Add metrics and dashboarding.
- Add tests and demo data for two tenants.
RAG/
├── backend/
│ ├── app/
│ │ ├── api/
│ │ ├── core/
│ │ ├── models/
│ │ ├── services/
│ │ └── main.py
│ ├── Dockerfile
│ └── requirements.txt
├── docker/
│ └── prometheus.yml
├── docs/
│ ├── ARCHITECTURE.md
│ └── SETUP.md
├── .env.example
└── docker-compose.yml
Copy-Item .env.example .env
ollama pull qwen2.5:1.5b
docker compose up --buildOpen:
- FastAPI docs:
http://localhost:8000/docs - Prometheus:
http://localhost:9090 - Grafana:
http://localhost:3000 - Qdrant:
http://localhost:6333/dashboard
Usage steps:
-
log in as platform admin $adminLogin = Invoke-RestMethod -Method Post -Uri "http://localhost:8000/v1/auth/login" -ContentType "application/json" -Body '{"username":"admin","password":"admin123"}' $adminToken = $adminLogin.access_token
-
Create a tenant $createTenant = Invoke-RestMethod -Method Post -Uri "http://localhost:8000/v1/admin/tenants" -ContentType "application/json" -Headers @{ Authorization = "Bearer $adminToken" } -Body '{ "name": "Tenant C", "slug": "tenant-c", "owner_username": "tenant-c-user", "owner_password": "tenant-c123" }'
-
Login as a user $login = Invoke-RestMethod -Method Post -Uri "http://localhost:8000/v1/auth/login" -ContentType "application/json" -Body '{"username":"tenant-c-user","password":"tenant-c123"}' $tenantToken = $login.access_token
-
verify identity Invoke-RestMethod -Method Get -Uri "http://localhost:8000/v1/auth/me" -Headers @{ Authorization = "Bearer $tenantToken" }
-
Upload document Invoke-RestMethod -Method Post -Uri "http://localhost:8000/v1/documents/upload" -Headers @{ Authorization = "Bearer $tenantToken" } -Form @{ file = Get-Item ".\Kafka_Notes_Merged.pdf" }
-
Ask a question Invoke-RestMethod -Method Post -Uri "http://localhost:8000/v1/ask" -ContentType "application/json" -Headers @{ Authorization = "Bearer $tenantToken" } -Body '{"message":"What does the document say about Kafka?","top_k":3}'
Drawback list
- A document of 500+ pages takes approx. 8-9 minutes to be ingested.