Retrieval-Augmented Generation pipeline for your own documents: upload PDFs or text files, then ask questions grounded in them. Every answer carries citations back to the source page.
app.py Streamlit UI (chat + file upload)
rag/api.py FastAPI HTTP API
rag/service.py Orchestration (ingest / query)
rag/loader.py PDF & text loading + sentence chunking (with page labels)
rag/embedder.py Gemini embeddings (batched)
rag/storage.py Qdrant vector store
rag/llm.py Gemini answer generation with citations
rag/config.py Settings from environment variables
rag/models.py Pydantic data models
Pipeline: upload, chunk (with page), embed, store in Qdrant, query, retrieve, LLM answer with [n] citations.
Every frontend action is routed through Inngest durable functions: the UI calls the REST API, the REST endpoints fire an event, and the matching Inngest function does the work. Each run is visible (with retries and step history) in the Inngest dashboard, which acts as the observability layer.
-
Install dependencies with
uv:uv sync
-
Create the
.envfile:cp .env.example .env # edit .env and set GEMINI_API_KEY -
Start Qdrant (docker-compose):
docker compose up -d qdrant
Inngest Dev Server (required for functions to execute):
npx inngest-cli devAPI server (REST endpoints + Inngest handlers):
uv run uvicorn rag.api:app --reload --port 8000Streamlit UI:
uv run streamlit run app.pyThe UI talks to the API at RAG_API_URL, which dispatches work to Inngest and
waits for the run to finish before returning. Both servers must be running.
POST /ingest/uploadmultipart file upload, returns{source_id, num_chunks}POST /queryJSON{"question": "...", "top_k": 5}, returns{answer, citations, hits}GET /healthliveness check