Document-RAG-GPT / README.md
merchantkevin
YAML changes
ff730b9
|
Raw
History Blame Contribute Delete
6.54 kB
---
title: Document RAG GPT
emoji: πŸ“„
colorFrom: indigo
colorTo: purple
sdk: gradio
sdk_version: 6.16.0
app_file: app.py
pinned: false
license: mit
---
# πŸ“„ RAG Document Chat
A Retrieval-Augmented Generation (RAG) chat application. Ask questions answered
**strictly from document content**, with guardrails against prompt injection and
out-of-scope queries. The app ships **preloaded with the Constitution of India**
so it works immediately, and you can also upload your own PDF, DOCX, or TXT files.
## Built-in dataset
On startup the app indexes a small corpus (in `data/`):
- `Fundamental-Rights.pdf` β€” Part III, Articles 12–35
- `Directive-Principles.docx` β€” Part IV, Articles 36–51
- `Preamble-and-Fundamental-Duties.txt` β€” Preamble and Article 51A
This exercises all three file loaders (PDF, DOCX, TXT). The bare text of the
Constitution is freely reproducible in India (Copyright Act, 1957, s. 52); it was
compiled from public sources via `build_corpus.py` and is provided for
demonstration (see `indiacode.nic.in` for the authoritative current text).
Sample questions: *"What does Article 21 protect?"*, *"What is the right against
exploitation?"*, *"List the Fundamental Duties"*, *"What does the Preamble say?"*.
An out-of-scope question like *"What's the weather today?"* is correctly refused.
## Document scope (by design)
The assistant answers from one document set at a time:
- **Default:** the built-in Constitution of India corpus described above.
- **After upload:** once you upload and process your own files, the system uses
**only** those documents for the rest of the session; the built-in corpus is no
longer searched.
This single-source behaviour is an intentional design choice β€” when a user brings
their own documents, answers should be grounded **solely** in those documents,
with no blending from an unrelated default corpus. Starting a new session (or
restarting the Space) reverts to the built-in corpus.
## Features
- **Multi-format upload** β€” PDF, DOCX (paragraphs + tables), and TXT.
- **RAG pipeline** β€” chunking β†’ embeddings β†’ vector search β†’ grounded generation.
- **Guardrails**
- Input validation (empty / too-short / too-long).
- Direct prompt-injection detection on the user's query.
- Indirect prompt-injection defence: retrieved text is passed to the model as
clearly delimited, *untrusted* data that it is instructed never to obey.
- Out-of-scope handling: if retrieval similarity is below a threshold, the app
answers "I couldn't find that in the uploaded documents" without calling the LLM.
- **Session isolation** β€” each user's index lives in their own session state.
- **Provider-agnostic LLM** β€” Groq (default, free tier), OpenAI, or Gemini, all via
the OpenAI-compatible API.
- **Conversation memory** β€” the last few turns are sent to the model, so follow-up
questions that refer back are understood in the context of the conversation.
## How it works
1. **Load** documents and extract text (`pypdf`, `python-docx`).
2. **Chunk** text into ~900-character, paragraph-aware pieces with overlap.
3. **Embed** chunks with `all-MiniLM-L6-v2` (Sentence-Transformers), normalized.
4. **Store** vectors in an in-memory FAISS index (`IndexFlatIP` = cosine similarity).
5. **Answer**: validate β†’ injection check β†’ retrieve top-k β†’ scope check β†’
generate a grounded answer from the retrieved excerpts only.
## Project structure
```
app.py # Gradio UI + orchestration (loads built-in corpus at startup)
build_corpus.py # (dev) regenerates the data/ files from public sources
data/ # built-in corpus: Constitution of India (PDF, DOCX, TXT)
rag/
document_loader.py # PDF / DOCX / TXT extraction + validation
chunker.py # paragraph-aware chunking with overlap
embeddings.py # Sentence-Transformers wrapper (lazy singleton)
vector_store.py # FAISS in-memory store
llm.py # OpenAI-compatible client (Groq/OpenAI/Gemini)
guardrails.py # input validation + injection detection
pipeline.py # build_index() and answer()
requirements.txt
.env.example
```
## Local setup
```bash
git clone <your-repo-url>
cd rag-document-chat
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env # then edit .env and add your API key
python app.py # opens at http://127.0.0.1:7860
```
### Getting a free API key (Groq)
Create one at <https://console.groq.com/keys> (free tier, no credit card), then set
`GROQ_API_KEY` in `.env`. To use a different provider, set `LLM_PROVIDER` to
`openai` or `gemini` and provide the matching key.
## Environment variables
| Variable | Default | Description |
|---|---|---|
| `LLM_PROVIDER` | `groq` | `groq`, `openai`, or `gemini` |
| `GROQ_API_KEY` / `OPENAI_API_KEY` / `GEMINI_API_KEY` | β€” | key for the chosen provider |
| `LLM_MODEL` | provider default | override the model name |
| `EMBEDDING_MODEL` | `sentence-transformers/all-MiniLM-L6-v2` | embedding model |
| `RELEVANCE_THRESHOLD` | `0.25` | min cosine score to treat a match as in-scope |
## Deploy to Hugging Face Spaces
1. Create a new Space at <https://huggingface.co/new-space> β€” choose **Gradio** as the SDK.
2. Push this repository to the Space (or upload the files). The included README
front matter sets `sdk: gradio` and `app_file: app.py`. *If the build reports
the SDK version is unavailable, change `sdk_version` to the latest 6.x shown in
the Space settings and update `gradio==` in `requirements.txt` to match.*
3. In **Settings β†’ Variables and secrets**, add a secret named `GROQ_API_KEY`
(and `LLM_PROVIDER` if not using Groq).
4. The Space builds automatically and serves the app.
## Notes & limitations
- The index is in memory and resets when the Space restarts or the session ends.
- Scanned/image-only PDFs yield no text (no OCR); the app reports this clearly.
- The injection filter uses high-signal heuristics; the model-side instruction is
the primary defence against instructions hidden inside documents.
- Conversation memory is applied at the generation step only: recent turns help the
model interpret a follow-up, but retrieval still embeds the raw question. For
follow-ups whose meaning depends entirely on earlier turns, **conversational query rewriting** β€” rephrasing the follow-up into a standalone question before retrieval β€”
would be the more robust approach.