# EmpathRAG
**Emotion-Aware Retrieval-Augmented Generation with Safety Guardrails for Student Mental Health**
[](https://python.org)
[](https://pytorch.org)
[](LICENSE)
[]()
[](https://umd.edu)
*MSML641 · Applied Machine Learning · University of Maryland · Spring 2025*
---
Standard RAG systems treat every message identically as a neutral information request. A student in crisis and a student asking for study tips go through the same retrieval pipeline, with no emotional awareness and no safety gate.
**EmpathRAG fixes this.** It is a 5-stage NLP pipeline that classifies a student's emotional state *before* retrieval, rewrites queries based on that state, and intercepts crisis-level messages with a trained NLI classifier before the language model is ever invoked.
---
## Pipeline
```
Student Message
│
├──▶ [1] Emotion Classifier RoBERTa + LoRA (CPU)
│ distress · anxiety · frustration · neutral · hopeful
│
├──▶ [2] Safety Guardrail DeBERTa-v3 NLI (CPU)
│ ⚠ If crisis → return 988 lifeline · STOP · generator never runs
│
├──▶ [3] Query Router Deterministic templates + session tracker
│ Rewrites query with emotion context · tracks 6 trajectory states
│
├──▶ [4] FAISS Retrieval all-mpnet-base-v2 · 1,674,369 vectors
│ Emotion-match re-ranking · SQLite metadata sidecar
│
└──▶ [5] Mistral 7B Generator Q4_K_M GGUF · 28/33 GPU layers
Sliding 3-turn memory · 2-paragraph empathetic response
```
---
## Results
| Metric | Value | Target | |
|---|---|---|---|
| RoBERTa Emotion F1 (weighted) | **0.7127** | > 0.55 | ✅ |
| DeBERTa Crisis Recall (held-out NLI test set, 23K samples) | **0.9629** | > 0.80 | ✅ |
| DeBERTa Crisis Recall (30 adversarial probes, 6 categories) | **0.75** | — | ✅ |
| DeBERTa Crisis Precision | **0.7951** | > 0.65 | ✅ |
| BERTScore F1 | **0.8266** | > 0.72 | ✅ |
| Wilcoxon p-value (Full vs BM25) | **3.62e-08** | < 0.05 | ✅ |
| Euphemistic recall — NLI vs keyword | **100% vs 20%** | — | 🔑 |
### Ablation — Emotion Alignment Score (3-condition)
| Condition | Retrieval | Emotion Conditioning | Score |
|---|---|---|---|
| A — BM25 baseline | Sparse | None | 0.30 |
| C — Dense RAG | FAISS semantic | None | 0.50 |
| D — **Full EmpathRAG** | FAISS + emotion | Query rewrite + re-rank | **0.88** |
> Emotion conditioning contributes **+0.38** over pure dense retrieval (C→D). Wilcoxon signed-rank: p = 3.62e-08.
> **Note:** Condition B (DPR two-tower baseline) was descoped — deprioritised to increase adversarial probe depth. A ColBERT-scale index would be required for DPR to offer meaningful gains over FAISS flat-L2 at 1.67M vectors.
---
## Key Finding
The NLI-framed safety guardrail outperforms a keyword filter across every adversarial probe category that matters:
| Probe Category | EmpathRAG (NLI) | Keyword Filter |
|---|---|---|
| Direct crisis language | 100% | 80% |
| Euphemistic / indirect | **100%** | 20% |
| Negation bypass | **100%** | 60% |
| Bait-and-switch | 40% | 20% |
Keyword filters miss indirect phrasing. NLI understands semantic entailment. This is the core research finding.
---
## Demo
> 🔄 **Demo recording in progress** — Loom walkthrough coming soon.
The Gradio demo shows:
- Real-time emotion timeline with trajectory detection across turns
- Safety guardrail panel with Integrated Gradients token attribution highlights
- Multi-turn conversation memory (sliding 3-turn window)
- Session ID for human evaluation correlation
To run locally (requires models — see [Setup](#setup)):
```bash
python demo/app.py
```
---
## VRAM Budget (RTX 3060, 6 GB)
| Component | Device | VRAM |
|---|---|---|
| RoBERTa + DeBERTa | CPU | 0 MB |
| Sentence Transformer | GPU (encode only) | 440 MB |
| Mistral 7B Q4_K_M | GPU resident | 3,870 MB |
| KV cache + compute | GPU | ~630 MB |
| **Total peak** | | **~4,940 MB** ✅ |
---
## Repo Structure
```
Empath-RAG/
├── src/
│ ├── pipeline/
│ │ ├── pipeline.py # 5-stage orchestrator · conversation memory
│ │ ├── query_router.py # Emotion-conditioned query rewriting
│ │ └── session_tracker.py # Trajectory detection (6 states)
│ ├── models/
│ │ ├── guardrail_ig.py # DeBERTa NLI + Integrated Gradients
│ │ └── annotate_corpus.py # Corpus emotion annotation (Colab A100)
│ └── data/
│ ├── preprocess.py # GoEmotions 27→5 label collapse
│ ├── build_faiss_index.py # FAISS IVFFlat builder
│ └── build_nli_pairs.py # NLI training pair construction
├── demo/
│ └── app.py # Gradio demo · emotion timeline · crisis panel
├── eval/
│ ├── run_ablation.py # Conditions A / C / D
│ ├── run_bertscore.py # BERTScore F1
│ ├── run_wilcoxon.py # Wilcoxon signed-rank test
│ ├── run_adversarial.py # NLI vs keyword filter · 30 probes
│ ├── test_prompts.json # 50 prompts · 10 per emotion class
│ └── adversarial_probes.json # 30 probes · 6 categories × 5
├── notebooks/
│ ├── colab_emotion_classifier.ipynb # RoBERTa + LoRA · A100
│ └── colab_deberta_guardrail.ipynb # DeBERTa NLI · A100 · bf16
└── data/
└── MANIFEST.md # Dataset download instructions
```
---
## Setup
**Requirements:** Python 3.12 · CUDA 12.1 · 6 GB VRAM · 16 GB RAM · ~15 GB disk
> ⚠️ Install order is critical — do not skip steps.
```bash
# 1. Clone
git clone https://github.com/MukulRay1603/Empath-RAG.git
cd Empath-RAG
python -m venv venv
venv\Scripts\activate # Windows
# 2. PyTorch with CUDA — install FIRST
pip install torch==2.5.1+cu121 --index-url https://download.pytorch.org/whl/cu121
# 3. llama-cpp-python CUDA wheel — install SECOND
pip install "llama_cpp_python==0.3.4" --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cu121
# 4. Everything else
pip install -r requirements.txt
# 5. Force correct numpy (faiss requires < 2.0)
pip install "numpy==1.26.4" --force-reinstall
```
**Model downloads** — see [`data/MANIFEST.md`](data/MANIFEST.md) for dataset download instructions.
Mistral 7B GGUF → download `mistral-7b-instruct-v0.2.Q4_K_M.gguf` from [TheBloke/Mistral-7B-Instruct-v0.2-GGUF](https://huggingface.co/TheBloke/Mistral-7B-Instruct-v0.2-GGUF) → place at `models/generator/mistral-7b-instruct-v0.2.Q4_K_M.gguf`
---
## Datasets
| Dataset | Role | License |
|---|---|---|
| [GoEmotions](https://huggingface.co/datasets/google-research-datasets/go_emotions) | Emotion classifier training | Apache 2.0 |
| [Reddit Mental Health](https://zenodo.org/records/3941387) | FAISS retrieval corpus | CC BY 4.0 |
| [Suicide Detection](https://www.kaggle.com/datasets/nikhileswarkomati/suicide-watch) | Guardrail NLI training | Public |
| [Empathetic Dialogues](https://huggingface.co/datasets/facebook/empathetic_dialogues) | BERTScore gold references | CC BY-NC 4.0 |
---
## Evaluation Status
| Task | Status |
|---|---|
| Emotion Classifier (RoBERTa F1 = 0.7127) | ✅ Complete |
| Safety Guardrail (DeBERTa recall = 0.9629) | ✅ Complete |
| BERTScore Evaluation (F1 = 0.8266) | ✅ Complete |
| Wilcoxon Test (p = 3.62e-08) | ✅ Complete |
| Adversarial Probe Evaluation (30 probes) | ✅ Complete |
| Human Evaluation (8–10 raters) | 🔄 In Progress |
| Loom Demo Recording | 🔄 In Progress |
---
## Known Limitations
- **Bait-and-switch probes:** Positive openers cause the guardrail to misclassify follow-up crisis content (40% recall). Most dangerous documented failure mode.
- **Domain transfer false positives:** Academic hyperbole ("this thesis is killing me") fires the guardrail at high confidence — trained on r/SuicideWatch, never saw graduate student language.
- **Generator quality:** Mistral 7B Q4_K_M produces adequate but not optimal empathetic responses. Architecture supports drop-in model replacement.
- **Faithfulness gap:** No automated faithfulness metric deployed — RAGAS and DeepEval both produced degenerate scores with a small model judge. BERTScore reported instead.
---
## v2 Safety Direction
EmpathRAG Core is being hardened as a guarded conversational RAG research system,
not a production counseling replacement. The Core plan prioritizes fail-closed
safety loading, multi-level triage, private-by-default demo behavior, curated
resource retrieval, and stronger safety evaluation. Start with
[`docs/README.md`](docs/README.md) and
[`docs/planning/MASTER_CHECKLIST.md`](docs/planning/MASTER_CHECKLIST.md).
---
## License
MIT License — see [`LICENSE`](LICENSE) for details.
Dataset licenses vary — see Datasets table above. Mistral 7B: Apache 2.0.