An async Retrieval-Augmented Generation (RAG) web application — upload documents, ask questions, get streaming answers grounded in your content.
Warning
This application is a prototype.
Ingest: Upload a document → background worker parses it → chunks it → generates hypothetical questions per chunk (HyPE indexing) → embeds and stores everything in Qdrant. Progress streams back to the UI over SSE.
Chat: Send a message → retrieve relevant chunks from Qdrant → optionally rerank by cross-encoder score → stream an LLM response back over SSE → persist the exchange to the database.
| Layer | Technology |
|---|---|
| API | FastAPI — async, class-based views, JWT auth (httpOnly cookies) |
| Task scheduling | APScheduler |
| Vector store | Qdrant with FastEmbed (BAAI/bge-small-en-v1.5) |
| Document parsing | pypdf, python-docx, openpyxl — PDF, DOCX, XLSX, Markdown |
| Reranking | FastEmbed cross-encoder (Xenova/ms-marco-MiniLM-L-6-v2) |
| Database | SQLite (dev) / PostgreSQL 17 (Docker) |
| LLM providers | Groq, OpenRouter — tenacity retry + SSE streaming |
| Frontend | React 19, TypeScript, Vite 8, TailwindCSS 4, TanStack Query 5, react-router-dom v7 |
- Python 3.13+ and uv
- Node.js 20+
Qdrant runs in local-path mode by default — no separate server required for development.
git clone https://github.com/froggobytes0460/RAGWebApp.git
cd RAGWebApp
uv sync
uv run pre-commit install
cp .env.example .env
# Edit .env — at minimum set LLM__API_KEY and AUTH__JWT_SECRETuv run migrate-run # shortcut via uv_tasks.py
# or: uv run alembic upgrade head# API (FastAPI dev server, logs to logs/api.log)
NO_COLOR=1 uv run fastapi dev backend/api > logs/api.log 2>&1 &
# Frontend dev server (proxies /api → :8000)
cd frontend && npm install && npm run dev# Backend — 80% coverage threshold enforced
uv run test-all # shortcut via uv_tasks.py
# or: uv run pytest -q --tb=short --no-header
# Lint + type-check
uv run pre-commit run --all-filesdocker compose up --build # nginx + api + qdrant + postgres
docker compose downServices: nginx (port 80) → api (:8000) + qdrant + postgres:17. Model caches (hf_cache, docling_cache) are persisted in named volumes.
Add to .env.docker to switch from SQLite/local Qdrant to the containerised backends:
DATABASE__URI=postgresql+asyncpg://user:password@postgres:5432/ragdb
VECTOR_STORE__URL_OR_PATH=http://qdrant:6333/Full reference in .env.example and backend/core/config.py.
| Variable | Default | Notes |
|---|---|---|
AUTH__JWT_SECRET |
(required) | Secret used to sign JWT tokens — keep private |
AUTH__JWT_ALGORITHM |
HS256 |
JWT signing algorithm |
AUTH__ACCESS_TOKEN_TTL_SECONDS |
900 |
Access token lifetime in seconds (default: 15 min) |
AUTH__REFRESH_TOKEN_TTL_SECONDS |
2592000 |
Refresh token lifetime in seconds (default: 30 days) |
AUTH__COOKIE_SECURE |
true |
Set Secure flag on auth cookies; disable only in HTTP dev |
AUTH__COOKIE_SAMESITE |
lax |
SameSite policy: lax, strict, or none |
AUTH__GOOGLE_CLIENT_ID |
(unset) | Google OAuth2 client ID — required to enable Google login |
AUTH__GOOGLE_CLIENT_SECRET |
(unset) | Google OAuth2 client secret |
AUTH__GOOGLE_REDIRECT_URI |
(unset) | Callback URI registered in Google Cloud Console |
LLM__API_KEY |
(required) | API key for the configured LLM provider |
LLM__PROVIDER |
groq |
LLM provider (groq or openrouter) |
LLM__MODEL_NAME |
(required) | Model ID for the chosen provider |
LLM__TEMPERATURE |
0.2 |
Sampling temperature (0.0–1.0) |
LLM__MAX_OUTPUT_TOKEN |
(required) | Maximum tokens in the LLM reply |
DATABASE__URI |
sqlite+aiosqlite:///./rag.db |
Switch to postgresql+asyncpg://... in Docker |
VECTOR_STORE__URL_OR_PATH |
./.qdrant_local/ |
Set to http://qdrant:6333/ in Docker |
VECTOR_STORE__EMBEDDING_MODEL |
BAAI/bge-small-en-v1.5 |
FastEmbed model; must match VECTOR_STORE__VECTOR_SIZE |
VECTOR_STORE__VECTOR_SIZE |
384 |
Must match the chosen embedding model's output dimension |
VECTOR_STORE__API_KEY |
(unset) | Required when connecting to Qdrant Cloud |
TEXT_CHUNK__CHUNK_SIZE |
(required) | Token budget per chunk |
TEXT_CHUNK__CHUNK_OVERLAP |
(required) | Overlap tokens between chunks (must be < CHUNK_SIZE) |
SEARCH__TOP_K |
(required) | Number of chunks to retrieve per query |
SEARCH__SEARCH_TYPE |
similarity |
similarity, mmr, or similarity_score_threshold |
RERANK__ENABLED |
true |
Enable cross-encoder reranking after retrieval |
RERANK__MODEL_NAME |
Xenova/ms-marco-MiniLM-L-6-v2 |
FastEmbed cross-encoder model |
INGEST__MAX_FILE_SIZE |
50 |
Maximum upload size in MB |
INGEST__WORKER_CONCURRENCY |
4 |
Parallel ingestion workers |
INGEST__HYPE_QUESTIONS_PER_CHUNK |
3 |
Hypothetical questions generated per chunk (HyPE indexing) |
JWT tokens issued as
httpOnlycookies.
| Method | Path | Description |
|---|---|---|
POST |
/api/v1/auth/register |
Register a new user (201) |
POST |
/api/v1/auth/login |
Login; sets access_token + refresh_token cookies (200) |
POST |
/api/v1/auth/refresh |
Rotate refresh token (204) |
POST |
/api/v1/auth/logout |
Clear auth cookies (204) |
GET |
/api/v1/auth/me |
Current authenticated user (200) |
GET |
/api/v1/auth/google |
Initiate Google OAuth flow (302) |
GET |
/api/v1/auth/google/callback |
Google OAuth callback (302) |
Requires authentication
| Method | Path | Description |
|---|---|---|
POST |
/api/v1/chats/sessions |
Create a new chat session (201) |
GET |
/api/v1/chats/sessions |
List sessions for the authenticated user (200) |
DELETE |
/api/v1/chats/sessions/{session_id} |
Delete a session and all its data (204) |
Requires authentication
| Method | Path | Description |
|---|---|---|
POST |
/api/v1/chats/{session_id}/documents |
Upload and ingest a document (202) |
GET |
/api/v1/chats/{session_id}/documents |
List documents for a session (200) |
GET |
/api/v1/chats/{session_id}/documents/jobs/{job_id}/progress |
SSE stream — ingestion progress |
DELETE |
/api/v1/chats/{session_id}/documents/{filename} |
Delete a document (202) |
POST |
/api/v1/chats/{session_id}/messages |
Send a message; stream LLM reply as SSE (201) |
GET |
/api/v1/chats/{session_id}/messages |
Retrieve message history (200) |
| Method | Path | Description |
|---|---|---|
GET |
/api/health |
Liveness check — {"status", "version"}, always HTTP 200 |
GET |
/api/health/deep |
Dependency check — {"status", "version", "dependencies": {"database", "vector_store"}} each with status and latency_ms; 503 if any degraded. |
├── backend/
│ ├── api/ # FastAPI routes, schemas, auth, dependencies, app state
│ └── core/
│ ├── auth/ # JWT token helpers, password hashing, OAuth utilities
│ ├── llms/ # Groq + OpenRouter clients, HyPE question generation, RAG prompt template
│ ├── config.py # Pydantic BaseSettings singleton
│ ├── ingest.py # pypdf / python-docx / openpyxl loader + binary-head validation
│ ├── chunking.py # Tokenizer-aware text splitter (LRU-cached)
│ ├── reranker.py # FastEmbed cross-encoder reranking
│ ├── vector_store.py # Qdrant wrapper (FastEmbed)
│ ├── ingestion_worker.py # Background async ingest worker (HyPE + chunking + upsert)
│ ├── database.py # Async SQLAlchemy engine + session factory
│ └── models.py # SQLModel tables (User, ChatSession, ChatMessage, IngestionJob)
├── frontend/
│ └── src/
│ ├── App.tsx # Root component; all app state lives here
│ ├── api.ts # Typed API client (fetch + SSE)
│ ├── types.ts # Shared TypeScript types
│ └── helpers.ts # uid, formatSize, ageText, badge utilities
├── alembic/ # Database migrations
├── tests/
│ ├── api/ # Integration tests (httpx AsyncClient)
│ └── core/ # Unit tests — ingest, chunking, vector store, LLM clients
├── nginx/ # Reverse-proxy config
├── .github/workflows/ # CI: backend
├── Dockerfile # Multi-stage build
└── docker-compose.yml # Full production stack
| Workflow | Trigger | Gates |
|---|---|---|
backend.yaml |
Changes to backend/, tests/, pyproject.toml |
black → basedpyright → pytest ≥ 80% coverage |
- JWT auth — access + refresh tokens issued as
httpOnly, SameSite=lax cookies; refresh rotation on every use. - Extension spoofing protection — binary-head validation runs before any file is parsed.
- Double upload-size enforcement —
Content-Lengthheader check in middleware, plus streaming chunk accumulation in the route handler. - API key validation — Groq and Qdrant Cloud keys validated against strict regex patterns at startup.