Cloud RAG is a document question-answering system built around retrieval-augmented generation.
You upload PDFs, DOCX files, Markdown files, or plain text files, the app chunks and indexes them, and then answers questions using only retrieved evidence from those documents. The current default setup is intentionally cloud-first so indexing and retrieval work do not melt a laptop:
- local file parsing and chunking
- Pinecone for dense retrieval, sparse retrieval, and reranking
- Groq for answer generation
- Streamlit for the interactive UI
The repository still keeps a local fallback path for retrieval and answering, but the default configuration is optimized for an MVP that runs comfortably on lower-end hardware.
This project is designed for small knowledge-base style document Q&A.
Typical use cases:
- ask questions across uploaded policies, notes, or reports
- inspect the supporting passages behind an answer
- compare retrieval strategies with an evaluation script
- keep multiple document collections inside one app
- run a lightweight local workflow or a cloud-backed workflow from the same codebase
What it is not trying to be:
- a full multi-user SaaS app
- a permissions-heavy enterprise document platform
- an OCR-first ingestion pipeline for scanned archives
- PDF, DOCX, TXT, and Markdown ingestion
- stable chunking and metadata persistence in SQLite
- cloud retrieval with Pinecone integrated models
- optional local retrieval fallback with BM25 and FAISS
- reranking before final answer generation
- grounded answers with citations
- background indexing jobs in the Streamlit UI
- collections and query-time filtering
- retrieval result caching
- structured telemetry logs
- evaluation CLI for retrieval and answer checks
| Area | Technology |
|---|---|
| App framework | Python |
| UI | Streamlit |
| LLM orchestration | LangChain |
| Default answer model | Groq llama-3.1-8b-instant |
| Default dense retrieval | Pinecone integrated embeddings with multilingual-e5-large |
| Default sparse retrieval | Pinecone sparse index with pinecone-sparse-english-v0 |
| Default reranker | Pinecone bge-reranker-v2-m3 |
| Local dense fallback | FAISS + Ollama embeddings |
| Local sparse fallback | BM25 via rank-bm25 |
| Local reranker fallback | sentence-transformers cross-encoder |
| Metadata store | SQLite |
| Document parsing | PyMuPDF, pypdf, python-docx |
| Evaluation | custom retrieval metrics + ragas dependency available |
This is the default path in the codebase:
RAG_RETRIEVAL_PROVIDER=pineconeRAG_RERANK_PROVIDER=pineconeRAG_LLM_PROVIDER=groqRAG_ANSWER_MODE=llm
This mode keeps the laptop responsible only for:
- file upload persistence
- text extraction
- chunking
- UI rendering
- orchestration
If you want to run more of the stack locally, the app also supports:
- local sparse retrieval
- local dense retrieval
- local reranking
- local LLM answering with Ollama
That path is useful for offline experiments, but it is slower and heavier on CPU/GPU, especially on laptops.
flowchart TD
A["Upload documents"] --> B["Persist raw files to data/raw"]
B --> C["Extract text and metadata"]
C --> D["Normalize and chunk text"]
D --> E["Store chunks and metadata in SQLite"]
E --> F["Sync dense index"]
E --> G["Sync sparse index"]
F --> H["Retrieve dense candidates"]
G --> I["Retrieve sparse candidates"]
H --> J["Fuse results"]
I --> J
J --> K["Rerank top candidates"]
K --> L["Generate cited answer"]
L --> M["Show answer, sources, metrics, telemetry"]
When you index documents, the pipeline does this:
- Saves the uploaded files under
data/raw/ - Computes a checksum for deduplication
- Extracts text from PDF, DOCX, TXT, or MD files
- Normalizes whitespace and basic formatting noise
- Splits the content into chunks with overlap
- Stores chunks and metadata in SQLite
- Syncs the configured retrieval backend
Stored metadata includes fields like:
doc_idfilenamefile_typepage_numbersection_headingchunk_idcollection_name
When a user asks a question, the pipeline does this:
- Applies any document or collection filters
- Checks the retrieval cache
- Runs dense retrieval, sparse retrieval, or both
- Fuses the candidate lists for hybrid search
- Reranks top candidates if reranking is enabled
- Sends only the selected evidence to the answer model
- Returns an answer with citations and source excerpts
- Logs metrics and telemetry
The answer generator is configured to:
- answer only from supplied context
- cite factual claims with chunk labels like
[C1] - abstain when the retrieved evidence is insufficient
rag/
app/
chains/ # main RAG orchestration
generation/ # answer generation
ingestion/ # document loading and chunking
rerank/ # rerank backends
retrieval/ # dense, sparse, hybrid, Pinecone adapters
storage/ # SQLite metadata store
ui/ # Streamlit app
utils/ # models, runtime checks, background jobs, telemetry
data/
raw/ # uploaded source files
processed/ # telemetry and processed artifacts
eval/ # evaluation datasets
indexes/ # local FAISS/BM25 assets if local retrieval is used
scripts/
build_index.py
run_eval.py
smoke_test.py
tests/
.env.example
plan.md
requirements.txt
- Python 3.9+
pip- a Pinecone account and API key for the default retrieval path
- a Groq API key for the default answer generation path
Optional:
- Ollama, if you want local answering or local dense retrieval
- Tesseract OCR,
Pillow, andpytesseractif you want OCR fallback for scanned PDFs
python3 -m venv .venv
source .venv/bin/activatepip install -r requirements.txtcp .env.example .envAt minimum for the default cloud path, set:
RAG_PINECONE_API_KEYRAG_GROQ_API_KEY
streamlit run app/ui/streamlit_app.pyThen open the local Streamlit URL shown in the terminal.
- Start Streamlit
- Upload one or more documents
- Choose a collection name if you want to group files
- Index the uploaded files
- Ask questions in the chat panel
- Inspect citations, retrieved chunks, and telemetry
Index one file or a directory:
python3 scripts/build_index.py path/to/docsSync retrieval indexes from the stored SQLite chunk corpus without rereading raw files:
python3 scripts/build_index.py --from-dbThis is useful if:
- you changed the retrieval backend
- you need to resync Pinecone
- you want to rebuild from already stored chunks
Key provider settings:
RAG_RETRIEVAL_PROVIDER:pineconeorlocalRAG_RERANK_PROVIDER:pineconeorlocalRAG_LLM_PROVIDER:groqorollamaRAG_ANSWER_MODE:llmorextractive
RAG_ENABLE_DENSE_RETRIEVALRAG_ENABLE_SPARSE_RETRIEVALRAG_ENABLE_RERANKERRAG_DENSE_TOP_KRAG_SPARSE_TOP_KRAG_FUSED_TOP_KRAG_FINAL_CONTEXT_KRAG_CHUNK_SIZERAG_CHUNK_OVERLAP
Important Pinecone settings:
RAG_PINECONE_API_KEYRAG_PINECONE_DENSE_INDEXRAG_PINECONE_SPARSE_INDEXRAG_PINECONE_DENSE_MODELRAG_PINECONE_SPARSE_MODELRAG_PINECONE_RERANK_MODEL
Free-tier indexing controls:
RAG_PINECONE_UPSERT_BATCH_SIZERAG_PINECONE_UPSERT_MAX_BATCH_TOKENSRAG_PINECONE_UPSERT_TOKENS_PER_MINUTERAG_PINECONE_UPSERT_RETRY_ATTEMPTSRAG_PINECONE_UPSERT_RETRY_BASE_DELAY_SECONDS
These exist because Pinecone Starter can rate-limit ingestion. The code batches and throttles uploads to make syncing more reliable on the free tier.
RAG_RETRIEVAL_CACHE_SIZERAG_ENABLE_STRUCTURED_LOGSRAG_TELEMETRY_LOG_PATHRAG_ENABLE_OCRRAG_OCR_LANGUAGERAG_DEFAULT_COLLECTION_NAME
If you want a lighter local dev path without Pinecone or Groq, switch to a simpler local configuration:
RAG_RETRIEVAL_PROVIDER=local
RAG_RERANK_PROVIDER=local
RAG_ENABLE_DENSE_RETRIEVAL=false
RAG_ENABLE_RERANKER=false
RAG_ANSWER_MODE=extractiveThis gives you:
- local sparse retrieval
- extractive answers built directly from retrieved passages
- no Pinecone dependency
- no Groq dependency
If you want local LLM answering too, enable Ollama and point RAG_LLM_PROVIDER=ollama.
The repository includes an evaluation script to compare retrieval strategies and run answer-quality checks.
Run all retrieval baselines:
python3 scripts/run_eval.py --dataset data/eval/sample_questions.jsonl --mode allRun hybrid + rerank and also check answer behavior:
python3 scripts/run_eval.py --dataset data/eval/sample_questions.jsonl --mode hybrid_rerank --with-generationSupported retrieval modes:
densesparsehybridhybrid_rerankall
Expected JSONL row shapes:
{"question":"What is the refund policy?","gold_chunk_ids":["chunk-123","chunk-456"]}{"question":"What is the refund policy?","gold_filenames":["policy.md"],"answer_contains":["30 calendar days"]}The evaluation script reports metrics such as:
HitRate@kRecall@kMRR@knDCG@k- answer-contains pass rate
- refusal accuracy
- citation coverage
Run the unit tests:
python3 -m unittest discover -s tests -p 'test_*.py'Run the smoke test against the bundled sample corpus:
python3 scripts/smoke_test.pyRun smoke test with answer validation:
python3 scripts/smoke_test.py --with-generationThe smoke test forces a lightweight local retrieval mode so it can run offline for basic validation.
The Streamlit UI supports background indexing jobs so large uploads do not block the entire session. Job status is shown in the sidebar.
Each indexed document belongs to a collection. You can:
- assign uploaded files to a collection at index time
- filter questions by collection
- filter questions by specific documents
The app can write structured JSONL telemetry records containing indexing and answer events. This is useful for:
- debugging
- measuring latency
- inspecting abstention behavior
- tracing retrieval settings and filters
- scanned PDFs are not the main target yet; OCR is optional and not enabled by default
- Pinecone Starter can be slow for large ingest jobs because of rate limits
- cloud mode requires internet access and third-party APIs
- local heavy models can still be slow on laptop hardware
- there is no auth, multi-tenancy, or document permission model
- do not commit
.env - rotate API keys if they were ever exposed
- assume uploaded document text is sent to third-party providers when cloud mode is enabled
This project is a practical reference for building a grounded RAG system that is not just a toy chatbot:
- it keeps evidence visible
- it has real retrieval and reranking stages
- it supports evaluation
- it has a usable UI
- it includes a path from local experimentation to cloud-backed retrieval
Add your preferred license here before publishing to GitHub.