Spaces:
Sleeping
Sleeping
Commit ยท
092fe97
1
Parent(s): 47279ca
First commit
Browse files- README.md +481 -9
- app.py +364 -0
- requirements.txt +123 -0
- retrieval.py +237 -0
README.md
CHANGED
|
@@ -1,12 +1,484 @@
|
|
| 1 |
---
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
---
|
| 11 |
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
license: mit
|
| 3 |
+
task_categories:
|
| 4 |
+
- question-answering
|
| 5 |
+
- text-retrieval
|
| 6 |
+
language:
|
| 7 |
+
- ar
|
| 8 |
+
tags:
|
| 9 |
+
- aqeedah
|
| 10 |
+
- islamic-theology
|
| 11 |
+
- arabic
|
| 12 |
+
- rag
|
| 13 |
+
- faiss
|
| 14 |
+
- hybrid-search
|
| 15 |
+
size_categories:
|
| 16 |
+
- n<1K
|
| 17 |
+
pretty_name: Aqeedah RAG Dataset
|
| 18 |
+
configs:
|
| 19 |
+
- config_name: default
|
| 20 |
+
data_files:
|
| 21 |
+
- split: train
|
| 22 |
+
path: data/train-*
|
| 23 |
+
dataset_info:
|
| 24 |
+
features:
|
| 25 |
+
- name: content
|
| 26 |
+
dtype: string
|
| 27 |
+
- name: meta
|
| 28 |
+
struct:
|
| 29 |
+
- name: author_name
|
| 30 |
+
dtype: string
|
| 31 |
+
- name: doc_name
|
| 32 |
+
dtype: string
|
| 33 |
+
- name: paragraph_number
|
| 34 |
+
dtype: int64
|
| 35 |
+
- name: embeddings
|
| 36 |
+
list: float64
|
| 37 |
+
splits:
|
| 38 |
+
- name: train
|
| 39 |
+
num_bytes: 43231133
|
| 40 |
+
num_examples: 5419
|
| 41 |
+
download_size: 30017136
|
| 42 |
+
dataset_size: 43231133
|
| 43 |
---
|
| 44 |
|
| 45 |
+
|
| 46 |
+
<div align="center">
|
| 47 |
+
<img src="https://upload.wikimedia.org/wikipedia/en/a/ae/Najran_University_Logo.svg" alt="Najran University Logo" width="200"/>
|
| 48 |
+
|
| 49 |
+
# Aqeedah AI Assistant ๏ฟฝ
|
| 50 |
+
|
| 51 |
+
**A Research Initiative by Najran University, Kingdom of Saudi Arabia**
|
| 52 |
+
|
| 53 |
+
[](https://huggingface.co/datasets/abdullah-alamodi/aqeedah-rag-dataset)
|
| 54 |
+
[](https://huggingface.co/spaces/abdullah-alamodi/aqeedah-ai)
|
| 55 |
+
[](https://opensource.org/licenses/MIT)
|
| 56 |
+
</div>
|
| 57 |
+
|
| 58 |
+
---
|
| 59 |
+
|
| 60 |
+
## ๐ Quick Start
|
| 61 |
+
|
| 62 |
+
### Try the Live Demo
|
| 63 |
+
|
| 64 |
+
**๐ Live Chatbot:** [https://huggingface.co/spaces/abdullah-alamodi/aqeedah-ai](https://huggingface.co/spaces/abdullah-alamodi/aqeedah-ai)
|
| 65 |
+
|
| 66 |
+
### Run Locally
|
| 67 |
+
|
| 68 |
+
1. **Clone the repository:**
|
| 69 |
+
```bash
|
| 70 |
+
git clone https://github.com/Abdullah-Alamodi/aqeedah-ai.git
|
| 71 |
+
cd aqeedah-ai
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
2. **Install dependencies:**
|
| 75 |
+
```bash
|
| 76 |
+
pip install -r requirements.txt
|
| 77 |
+
```
|
| 78 |
+
|
| 79 |
+
3. **Set up environment variables:**
|
| 80 |
+
Create a `.env` file in the project root:
|
| 81 |
+
```env
|
| 82 |
+
GEMINI_API_KEY=your_gemini_api_key_here
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
4. **Run the chatbot:**
|
| 86 |
+
```bash
|
| 87 |
+
python app.py
|
| 88 |
+
```
|
| 89 |
+
|
| 90 |
+
The app will launch at `http://localhost:7860`
|
| 91 |
+
|
| 92 |
+
### Project Structure
|
| 93 |
+
|
| 94 |
+
```
|
| 95 |
+
aqeedah-ai/
|
| 96 |
+
โโโ app.py # Gradio chatbot interface
|
| 97 |
+
โโโ retrieval.py # Hybrid RAG retrieval system
|
| 98 |
+
โโโ requirements.txt # Python dependencies
|
| 99 |
+
โโโ rag_playground.ipynb # Dataset preparation notebook
|
| 100 |
+
โโโ aqeedah_kb.json # Source data (5419 paragraphs)
|
| 101 |
+
โโโ aqeedah_kb/ # Original source documents (DOCX/PDF)
|
| 102 |
+
โโโ .env # API keys (not in git)
|
| 103 |
+
โโโ .gitignore
|
| 104 |
+
โโโ pyproject.toml # Project configuration
|
| 105 |
+
โโโ README.md # This file
|
| 106 |
+
```
|
| 107 |
+
|
| 108 |
+
**Key Features:**
|
| 109 |
+
- โ
**Simple Structure**: Only 2 main Python files (`app.py` + `retrieval.py`)
|
| 110 |
+
- โ
**Cloud-First**: Loads dataset from HuggingFace (no local .bin files)
|
| 111 |
+
- โ
**Production-Ready**: Deployed on HF Spaces with Gradio
|
| 112 |
+
- โ
**Hybrid Retrieval**: BM25 + Dense embeddings (AraBERT)
|
| 113 |
+
|
| 114 |
+
---
|
| 115 |
+
|
| 116 |
+
# Aqeedah RAG Dataset ๐
|
| 117 |
+
|
| 118 |
+
A curated Arabic Islamic theology (Aqeedah) dataset with pre-computed FAISS embeddings, designed for advanced Retrieval-Augmented Generation (RAG) applications in Islamic scholarly research.
|
| 119 |
+
|
| 120 |
+
## ๐ Dataset Description
|
| 121 |
+
|
| 122 |
+
This dataset represents a specialized collection of 5419 paragraphs from authoritative Islamic theology texts, meticulously compiled and structured for computational analysis. The corpus focuses specifically on Aqeedah (Islamic creed), covering foundational topics in Islamic belief and theology.
|
| 123 |
+
|
| 124 |
+
**Key Features:**
|
| 125 |
+
- **Authentic Arabic content** with complete diacritics (Tashkeel) preserved for linguistic accuracy
|
| 126 |
+
- **Pre-computed semantic embeddings** (768-dimensional dense vectors) using state-of-the-art Arabic language models
|
| 127 |
+
- **Rich scholarly metadata** including source document names, author attributions, and precise paragraph references
|
| 128 |
+
- **Optimized FAISS index** for millisecond-scale semantic similarity search
|
| 129 |
+
- **Hybrid retrieval support** combining traditional keyword-based (BM25) and modern neural approaches
|
| 130 |
+
|
| 131 |
+
## ๐ Research Context
|
| 132 |
+
|
| 133 |
+
This dataset was developed as part of an academic research initiative at **Najran University, Kingdom of Saudi Arabia**, under the supervision of **Dr. Alya Alamodi**, a distinguished scholar holding a Ph.D. in Islamic Theology (Aqeedah). Dr. Alamodi's expertise in classical Islamic sciences combined with modern computational approaches has shaped the careful curation and theological accuracy of this corpus.
|
| 134 |
+
|
| 135 |
+
The technical implementation and AI infrastructure were designed and developed by **Abdullah Alamodi**, M.Sc. candidate in Artificial Intelligence at **IU International University of Applied Sciences, Germany**. This collaboration represents an interdisciplinary effort bridging traditional Islamic scholarship with cutting-edge natural language processing and information retrieval technologies.
|
| 136 |
+
|
| 137 |
+
### Research Objectives
|
| 138 |
+
|
| 139 |
+
1. **Democratizing Access**: Making authoritative Aqeedah knowledge computationally accessible for researchers and students
|
| 140 |
+
2. **Semantic Search**: Enabling meaning-based retrieval beyond keyword matching in classical Arabic texts
|
| 141 |
+
3. **AI-Assisted Learning**: Supporting intelligent question-answering systems for Islamic education
|
| 142 |
+
4. **Scholarly Validation**: Establishing benchmarks for Arabic NLP in religious domain-specific applications
|
| 143 |
+
|
| 144 |
+
## ๐ Source Texts
|
| 145 |
+
|
| 146 |
+
This dataset comprises carefully selected paragraphs from the following authoritative Islamic theology works:
|
| 147 |
+
|
| 148 |
+
1. **ุดุฑุญ ุงูุทุญุงููุฉ** (Sharh al-Tahawiyyah) - ุตุฏุฑ ุงูุฏูู ู
ุญู
ุฏ ุจู ุนูุงุก ุงูุฏูู ุนูู ุจู ู
ุญู
ุฏ ุงุจู ุฃุจู ุงูุนุฒ ุงูุญููู (Volumes 1-2)
|
| 149 |
+
2. **ูุชุงุจ ุงูุชูุญูุฏ** (Kitab al-Tawhid) - ู
ุญู
ุฏ ุจู ุนุจุฏ ุงูููุงุจ
|
| 150 |
+
3. **ุดุฑุญ ุงูุนููุฏุฉ ุงููุงุณุทูุฉ** (Sharh al-Aqidah al-Wasitiyyah) - ู
ุญู
ุฏ ุจู ุตุงูุญ ุจู ู
ุญู
ุฏ ุงูุนุซูู
ูู (Volumes 1-2)
|
| 151 |
+
4. **ุงูููู ุงูู
ููุฏ ุนูู ูุชุงุจ ุงูุชูุญูุฏ** (Al-Qawl al-Mufid 'ala Kitab al-Tawhid) - ู
ุญู
ุฏ ุจู ุตุงูุญ ุจู ู
ุญู
ุฏ ุงูุนุซูู
ูู (Volumes 1-4)
|
| 152 |
+
5. **ุงูููู ุงูุณุฏูุฏ ุดุฑุญ ูุชุงุจ ุงูุชูุญูุฏ** (Al-Qawl al-Sadid Sharh Kitab al-Tawhid) - ุนุจุฏ ุงูุฑุญู
ู ุจู ูุงุตุฑ ุงูุณุนุฏู
|
| 153 |
+
6. **ุฃุตูู ุงูุฅูู
ุงู** (Usul al-Iman) - ุนุจุฏ ุงูุนุฒูุฒ ุจู ุนุจุฏ ุงููู ุจู ุจุงุฒ
|
| 154 |
+
7. **ุงููุฌูุฒ ูู ุนููุฏุฉ ุงูุณูู ุงูุตุงูุญ ุฃูู ุงูุณูุฉ ูุงูุฌู
ุงุนุฉ** (Al-Wajiz fi Aqidah al-Salaf al-Salih) - ุนุจุฏ ุงููู ุจู ุนุจุฏ ุงูุญู
ูุฏ ุงูุฃุซุฑู
|
| 155 |
+
8. **ุงูุฅุณูุงู
ุฃุตููู ูู
ุจุงุฏุฆู** (Al-Islam: Usuluhu wa Mabadi'uhu) - ู
ุญู
ุฏ ุจู ุนุจุฏ ุงููู ุจู ุตุงูุญ ุงูุณุญูู
|
| 156 |
+
9. **ูุชุงูู ููุฑ ุนูู ุงูุฏุฑุจ** (Fatawa Nur 'ala al-Darb) - ู
ุญู
ุฏ ุจู ุตุงูุญ ุจู ู
ุญู
ุฏ ุงูุนุซูู
ูู (Volumes 1-4)
|
| 157 |
+
|
| 158 |
+
## ๐๏ธ Dataset Structure
|
| 159 |
+
|
| 160 |
+
### Data Fields
|
| 161 |
+
|
| 162 |
+
- `paragraph_text` (string): The Arabic text content with complete diacritical marks
|
| 163 |
+
- `doc_name` (string): Title of the source Islamic text
|
| 164 |
+
- `author_name` (string): Name of the classical or contemporary scholar
|
| 165 |
+
- `paragraph_number` (int): Sequential paragraph identifier within the source document
|
| 166 |
+
- `embeddings` (list of float): Pre-computed 768-dimensional embedding vector (L2-normalized)
|
| 167 |
+
|
| 168 |
+
### Data Splits
|
| 169 |
+
|
| 170 |
+
This dataset contains a single split with 5419 carefully selected paragraphs from verified Islamic theology sources.
|
| 171 |
+
|
| 172 |
+
## ๐ค Embedding Model
|
| 173 |
+
|
| 174 |
+
**Model**: [`aubmindlab/bert-base-arabertv02`](https://huggingface.co/aubmindlab/bert-base-arabertv02)
|
| 175 |
+
|
| 176 |
+
**Technical Specifications**:
|
| 177 |
+
- Architecture: BERT-Base (12 layers, 768 hidden dimensions)
|
| 178 |
+
- Pre-training: Arabic Wikipedia + other Arabic corpora
|
| 179 |
+
- Embedding Dimension: 768
|
| 180 |
+
- Text Normalization: Light preprocessing (preserves diacritics for theological accuracy)
|
| 181 |
+
- Pooling Strategy: Attention-masked average pooling
|
| 182 |
+
- Vector Normalization: L2 normalization for cosine similarity compatibility
|
| 183 |
+
|
| 184 |
+
## ๐ Usage
|
| 185 |
+
|
| 186 |
+
### Installation
|
| 187 |
+
|
| 188 |
+
```bash
|
| 189 |
+
pip install datasets faiss-cpu torch transformers pyarabic rank-bm25
|
| 190 |
+
```
|
| 191 |
+
|
| 192 |
+
### Quick Start
|
| 193 |
+
|
| 194 |
+
```python
|
| 195 |
+
from datasets import load_dataset
|
| 196 |
+
import torch
|
| 197 |
+
from transformers import AutoTokenizer, AutoModel
|
| 198 |
+
import pyarabic.araby as araby
|
| 199 |
+
|
| 200 |
+
# Load dataset with FAISS index
|
| 201 |
+
dataset = load_dataset("abdullah-alamodi/aqeedah-rag-dataset")
|
| 202 |
+
|
| 203 |
+
# Load the embedding model
|
| 204 |
+
model_name = "aubmindlab/bert-base-arabertv02"
|
| 205 |
+
tokenizer = AutoTokenizer.from_pretrained(model_name)
|
| 206 |
+
model = AutoModel.from_pretrained(model_name)
|
| 207 |
+
|
| 208 |
+
# Add FAISS index for fast retrieval
|
| 209 |
+
dataset['train'].add_faiss_index(column="embeddings")
|
| 210 |
+
|
| 211 |
+
# Helper function for embedding
|
| 212 |
+
def get_embedding(text):
|
| 213 |
+
normalized = araby.normalize_hamza(text)
|
| 214 |
+
text_input = f"query: {normalized}"
|
| 215 |
+
|
| 216 |
+
inputs = tokenizer([text_input], padding=True, truncation=True,
|
| 217 |
+
max_length=512, return_tensors='pt')
|
| 218 |
+
|
| 219 |
+
with torch.no_grad():
|
| 220 |
+
outputs = model(**inputs)
|
| 221 |
+
|
| 222 |
+
# Average pooling
|
| 223 |
+
embeddings = outputs.last_hidden_state.mean(dim=1)
|
| 224 |
+
embeddings = torch.nn.functional.normalize(embeddings, p=2, dim=1)
|
| 225 |
+
|
| 226 |
+
return embeddings[0].numpy()
|
| 227 |
+
|
| 228 |
+
# Search example
|
| 229 |
+
query = "ู
ุง ู
ุนูู ุดูุงุฏุฉ ุฃู ูุง ุฅูู ุฅูุง ุงูููุ"
|
| 230 |
+
query_embedding = get_embedding(query)
|
| 231 |
+
|
| 232 |
+
# Find top 5 similar documents
|
| 233 |
+
scores, retrieved = dataset['train'].get_nearest_examples(
|
| 234 |
+
"embeddings",
|
| 235 |
+
query_embedding,
|
| 236 |
+
k=5
|
| 237 |
+
)
|
| 238 |
+
|
| 239 |
+
# Display results
|
| 240 |
+
for i, (score, text, meta) in enumerate(zip(
|
| 241 |
+
scores,
|
| 242 |
+
retrieved['content'],
|
| 243 |
+
retrieved['meta']
|
| 244 |
+
)):
|
| 245 |
+
print(f"ุงููุต ุงูู
ุณุชุฑุฌุน ููุณุคุงู {i+1}".center(80, '-'))
|
| 246 |
+
print(f"Score: {score:.4f}")
|
| 247 |
+
print(f"Document: {meta['doc_name']} by {meta['author_name']}")
|
| 248 |
+
print(f"Paragraph: {meta['paragraph_number']}")
|
| 249 |
+
print(f"Text: {text[:200]}...")
|
| 250 |
+
print("\n")
|
| 251 |
+
```
|
| 252 |
+
|
| 253 |
+
### Hybrid Search (BM25 + Dense)
|
| 254 |
+
|
| 255 |
+
```python
|
| 256 |
+
from rank_bm25 import BM25Okapi
|
| 257 |
+
import numpy as np
|
| 258 |
+
import pyarabic.araby as araby
|
| 259 |
+
from datasets import load_dataset
|
| 260 |
+
from transformers import AutoTokenizer, AutoModel
|
| 261 |
+
import torch
|
| 262 |
+
|
| 263 |
+
# Load dataset with FAISS index
|
| 264 |
+
dataset = load_dataset("abdullah-alamodi/aqeedah-rag-dataset")
|
| 265 |
+
|
| 266 |
+
# Load the embedding model
|
| 267 |
+
model_name = "aubmindlab/bert-base-arabertv02"
|
| 268 |
+
tokenizer = AutoTokenizer.from_pretrained(model_name)
|
| 269 |
+
model = AutoModel.from_pretrained(model_name)
|
| 270 |
+
|
| 271 |
+
# Add FAISS index for fast retrieval
|
| 272 |
+
dataset['train'].add_faiss_index(column="embeddings")
|
| 273 |
+
|
| 274 |
+
# Helper function for embedding
|
| 275 |
+
def get_embedding(text):
|
| 276 |
+
normalized = araby.normalize_hamza(text)
|
| 277 |
+
text_input = f"query: {normalized}"
|
| 278 |
+
|
| 279 |
+
inputs = tokenizer([text_input], padding=True, truncation=True,
|
| 280 |
+
max_length=512, return_tensors='pt')
|
| 281 |
+
|
| 282 |
+
with torch.no_grad():
|
| 283 |
+
outputs = model(**inputs)
|
| 284 |
+
|
| 285 |
+
# Average pooling
|
| 286 |
+
embeddings = outputs.last_hidden_state.mean(dim=1)
|
| 287 |
+
embeddings = torch.nn.functional.normalize(embeddings, p=2, dim=1)
|
| 288 |
+
|
| 289 |
+
return embeddings[0].numpy()
|
| 290 |
+
|
| 291 |
+
# Prepare BM25 index
|
| 292 |
+
def normalize_for_bm25(text):
|
| 293 |
+
text = araby.normalize_hamza(text)
|
| 294 |
+
text = araby.strip_diacritics(text)
|
| 295 |
+
text = araby.strip_tatweel(text)
|
| 296 |
+
return text
|
| 297 |
+
|
| 298 |
+
corpus = [normalize_for_bm25(doc['content']) for doc in dataset['train']]
|
| 299 |
+
tokenized = [doc.split() for doc in corpus]
|
| 300 |
+
bm25 = BM25Okapi(tokenized)
|
| 301 |
+
|
| 302 |
+
# Search function
|
| 303 |
+
def hybrid_search(query, top_k=5):
|
| 304 |
+
# BM25 search
|
| 305 |
+
norm_query = normalize_for_bm25(query)
|
| 306 |
+
bm25_scores = bm25.get_scores(norm_query.split())
|
| 307 |
+
bm25_top = np.argsort(bm25_scores)[::-1][:top_k]
|
| 308 |
+
|
| 309 |
+
# Dense search
|
| 310 |
+
query_emb = get_embedding(query)
|
| 311 |
+
scores, faiss_results = dataset['train'].get_nearest_examples(
|
| 312 |
+
"embeddings", query_emb, k=top_k
|
| 313 |
+
)
|
| 314 |
+
|
| 315 |
+
# Extract FAISS indices (they're already sorted by score)
|
| 316 |
+
# Since get_nearest_examples returns actual data, we need to track indices differently
|
| 317 |
+
# Simple approach: just combine the unique results
|
| 318 |
+
|
| 319 |
+
# Get unique indices from both methods
|
| 320 |
+
bm25_indices = set(bm25_top.tolist())
|
| 321 |
+
|
| 322 |
+
# For FAISS, we'll use the returned results directly
|
| 323 |
+
# Combine: prioritize FAISS results, then add BM25-only results
|
| 324 |
+
combined_results = []
|
| 325 |
+
seen_content = set()
|
| 326 |
+
|
| 327 |
+
# Add FAISS results first
|
| 328 |
+
for content, meta in zip(faiss_results['content'], faiss_results['meta']):
|
| 329 |
+
if content not in seen_content:
|
| 330 |
+
combined_results.append({'content': content, 'meta': meta})
|
| 331 |
+
seen_content.add(content)
|
| 332 |
+
|
| 333 |
+
# Add unique BM25 results
|
| 334 |
+
for idx in bm25_top:
|
| 335 |
+
doc = dataset['train'][int(idx)]
|
| 336 |
+
if doc['content'] not in seen_content:
|
| 337 |
+
combined_results.append(doc)
|
| 338 |
+
seen_content.add(doc['content'])
|
| 339 |
+
if len(combined_results) >= top_k * 2: # Get up to 2x results
|
| 340 |
+
break
|
| 341 |
+
|
| 342 |
+
return combined_results[:top_k * 2] # Return more results for better coverage
|
| 343 |
+
|
| 344 |
+
# Example usage
|
| 345 |
+
results = hybrid_search("ู
ุง ูู ุฃุฑูุงู ุงูุฅูู
ุงูุ", top_k=5)
|
| 346 |
+
for i, res in enumerate(results):
|
| 347 |
+
print(f"Result {i+1}: {res['content']}\n")
|
| 348 |
+
```
|
| 349 |
+
|
| 350 |
+
## ๐ Dataset Statistics
|
| 351 |
+
|
| 352 |
+
- **Total paragraphs**: 5419
|
| 353 |
+
- **Language**: Classical and Modern Standard Arabic (ar)
|
| 354 |
+
- **Domain**: Islamic Theology (Aqeedah)
|
| 355 |
+
- **Source texts**: 17 volumes from 9 distinct scholarly works
|
| 356 |
+
- **Average text length**: ~951 characters per paragraph
|
| 357 |
+
- **Embedding coverage**: 100% of corpus
|
| 358 |
+
|
| 359 |
+
## ๐ฏ Intended Use
|
| 360 |
+
|
| 361 |
+
### Primary Applications
|
| 362 |
+
|
| 363 |
+
- โ
**Scholarly RAG Systems**: Building question-answering systems for Islamic theology education
|
| 364 |
+
- โ
**Semantic Search**: Enabling meaning-based retrieval in classical Arabic religious texts
|
| 365 |
+
- โ
**Educational Technology**: Supporting AI-powered learning platforms for Aqeedah studies
|
| 366 |
+
- โ
**Research Tools**: Facilitating computational analysis of Islamic theological discourse
|
| 367 |
+
|
| 368 |
+
### Research Domains
|
| 369 |
+
|
| 370 |
+
- Arabic Natural Language Processing (NLP)
|
| 371 |
+
- Information Retrieval in Religious Texts
|
| 372 |
+
- Cross-lingual Semantic Search
|
| 373 |
+
- Domain-Specific Language Models
|
| 374 |
+
|
| 375 |
+
---
|
| 376 |
+
|
| 377 |
+
## ๐ Deployment to Hugging Face Spaces
|
| 378 |
+
|
| 379 |
+
### Prerequisites
|
| 380 |
+
1. Create a Hugging Face account at [huggingface.co](https://huggingface.co)
|
| 381 |
+
2. Get a Gemini API key from [Google AI Studio](https://aistudio.google.com/app/apikey)
|
| 382 |
+
|
| 383 |
+
### Deployment Steps
|
| 384 |
+
|
| 385 |
+
1. **Create a new Space:**
|
| 386 |
+
- Go to [huggingface.co/spaces](https://huggingface.co/spaces)
|
| 387 |
+
- Click "Create new Space"
|
| 388 |
+
- Name: `aqeedah-ai`
|
| 389 |
+
- SDK: Select "Gradio"
|
| 390 |
+
- License: MIT
|
| 391 |
+
|
| 392 |
+
2. **Upload files:**
|
| 393 |
+
```bash
|
| 394 |
+
# Clone your HF Space
|
| 395 |
+
git clone https://huggingface.co/spaces/abdullah-alamodi/aqeedah-ai
|
| 396 |
+
cd aqeedah-ai
|
| 397 |
+
|
| 398 |
+
# Copy necessary files
|
| 399 |
+
cp /path/to/aqeedah-ai/app.py .
|
| 400 |
+
cp /path/to/aqeedah-ai/retrieval.py .
|
| 401 |
+
cp /path/to/aqeedah-ai/requirements.txt .
|
| 402 |
+
|
| 403 |
+
# Commit and push
|
| 404 |
+
git add .
|
| 405 |
+
git commit -m "Initial deployment"
|
| 406 |
+
git push
|
| 407 |
+
```
|
| 408 |
+
|
| 409 |
+
3. **Set up secrets:**
|
| 410 |
+
- Go to your Space settings
|
| 411 |
+
- Navigate to "Repository secrets"
|
| 412 |
+
- Add secret: `GEMINI_API_KEY` = your_api_key
|
| 413 |
+
|
| 414 |
+
4. **Your Space will automatically build and deploy!** ๐
|
| 415 |
+
|
| 416 |
+
### Required Files for HF Spaces
|
| 417 |
+
- `app.py` - Main Gradio application
|
| 418 |
+
- `retrieval.py` - RAG retrieval logic
|
| 419 |
+
- `requirements.txt` - Python dependencies
|
| 420 |
+
|
| 421 |
+
**Note:** The dataset is automatically loaded from HuggingFace, so no need to upload the data files!
|
| 422 |
+
|
| 423 |
+
---
|
| 424 |
+
|
| 425 |
+
## โ ๏ธ Limitations & Considerations
|
| 426 |
+
|
| 427 |
+
### Scope Limitations
|
| 428 |
+
- **Domain Specificity**: Exclusively focused on Islamic theology (Aqeedah); not suitable for general Arabic NLP tasks
|
| 429 |
+
- **Language**: Limited to Arabic; no multilingual support
|
| 430 |
+
- **Corpus Size**: 5419 paragraphs represent a focused collection, not exhaustive coverage of all Aqeedah literature
|
| 431 |
+
- **Temporal Coverage**: Focuses on established scholarly works; may not include the most recent publications
|
| 432 |
+
|
| 433 |
+
### Theological Considerations
|
| 434 |
+
- This dataset is curated for academic and educational purposes
|
| 435 |
+
- Users should consult qualified Islamic scholars for authoritative religious guidance
|
| 436 |
+
- The dataset represents specific theological perspectives within Sunni Islamic tradition (Ahl al-Sunnah wa al-Jama'ah)
|
| 437 |
+
|
| 438 |
+
### Technical Limitations
|
| 439 |
+
- Embeddings are model-specific (AraBERT v2); transfer to other models may require re-encoding
|
| 440 |
+
- FAISS index optimized for CPU inference; GPU acceleration requires additional configuration
|
| 441 |
+
- Diacritic preservation may affect compatibility with some NLP tools trained on non-diacritized text
|
| 442 |
+
|
| 443 |
+
## ๐ License
|
| 444 |
+
|
| 445 |
+
**MIT License** - This dataset is freely available for academic research, educational purposes, and commercial applications with proper attribution.
|
| 446 |
+
|
| 447 |
+
## ๐ Citation
|
| 448 |
+
|
| 449 |
+
If you use this dataset in your research or applications, please cite:
|
| 450 |
+
|
| 451 |
+
```bibtex
|
| 452 |
+
@dataset{aqeedah_rag_dataset_2025,
|
| 453 |
+
title={Aqeedah RAG Dataset: Arabic Islamic Theology Corpus with Pre-computed Embeddings},
|
| 454 |
+
author={Alamodi, Alya and Alamodi, Abdullah},
|
| 455 |
+
year={2025},
|
| 456 |
+
institution={Najran University, Saudi Arabia},
|
| 457 |
+
publisher={Hugging Face},
|
| 458 |
+
howpublished={\url{https://huggingface.co/datasets/abdullah-alamodi/aqeedah-rag-dataset}},
|
| 459 |
+
note={Curated by Dr. Alya Alamodi (Najran University), Technical Implementation by Abdullah Alamodi (IU International University of Applied Sciences)}
|
| 460 |
+
}
|
| 461 |
+
```
|
| 462 |
+
|
| 463 |
+
## ๐ฅ Contributors
|
| 464 |
+
|
| 465 |
+
**Principal Investigator & Theological Curation:**
|
| 466 |
+
**Dr. Alya Alamodi**
|
| 467 |
+
Ph.D. in Islamic Theology (Aqeedah)
|
| 468 |
+
Najran University, Kingdom of Saudi Arabia
|
| 469 |
+
|
| 470 |
+
**Technical Development & AI Implementation:**
|
| 471 |
+
**Abdullah Alamodi**
|
| 472 |
+
M.Sc. Candidate in Artificial Intelligence
|
| 473 |
+
IU International University of Applied Sciences, Germany
|
| 474 |
+
|
| 475 |
+
## ๐ง Contact
|
| 476 |
+
|
| 477 |
+
For questions regarding:
|
| 478 |
+
- **Theological content and scholarly interpretation**: Contact Dr. Alya Alamodi via Najran University
|
| 479 |
+
- **Technical implementation and AI methodology**: Contact Abdullah Alamodi
|
| 480 |
+
- **General inquiries**: Open an issue on the dataset repository
|
| 481 |
+
|
| 482 |
+
---
|
| 483 |
+
|
| 484 |
+
**Acknowledgments**: This work was supported by the academic resources of Najran University and developed with computational infrastructure provided by IU International University of Applied Sciences.
|
app.py
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Aqeedah AI Chatbot - Gradio Interface
|
| 3 |
+
======================================
|
| 4 |
+
|
| 5 |
+
A Retrieval-Augmented Generation (RAG) chatbot for Islamic theology (Aqeedah)
|
| 6 |
+
questions, powered by Gemini and deployed on Hugging Face Spaces.
|
| 7 |
+
|
| 8 |
+
Author: Abdullah Alamodi
|
| 9 |
+
Dataset: https://huggingface.co/datasets/abdullah-alamodi/aqeedah-rag-dataset
|
| 10 |
+
Space: https://huggingface.co/spaces/abdullah-alamodi/aqeedah-ai
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
import os
|
| 14 |
+
import gradio as gr
|
| 15 |
+
from google import genai
|
| 16 |
+
from dotenv import load_dotenv
|
| 17 |
+
from retrieval import HybridRetriever
|
| 18 |
+
|
| 19 |
+
# Load environment variables
|
| 20 |
+
load_dotenv()
|
| 21 |
+
|
| 22 |
+
# --- Configuration ---
|
| 23 |
+
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
|
| 24 |
+
# Options: "gemini-2.0-flash-exp", "gemini-1.5-flash", "gemini-1.5-pro"
|
| 25 |
+
GEMINI_MODEL = "gemini-2.5-flash"
|
| 26 |
+
|
| 27 |
+
# System prompt template
|
| 28 |
+
SYSTEM_PROMPT_TEMPLATE = """
|
| 29 |
+
# ุงูุชุนููู
ุงุช
|
| 30 |
+
1. ุฃุฌุจ **ููุท** ุนูู ุงูุฃุณุฆูุฉ ุงูู
ุชุนููุฉ ุจู **ุงูุนููุฏุฉ ุงูุฅุณูุงู
ูุฉ**.
|
| 31 |
+
2. ูุง ุชุณุชุฎุฏู
ุฃู ู
ุนุฑูุฉ ู
ุณุจูุฉ ุฎุงุฑุฌ ุงูุณูุงู.
|
| 32 |
+
3. ุฅุฐุง ูู
ุชุฌุฏ ุงูุฅุฌุงุจุฉ ูู ุงูุณูุงู โ ูู ุจูุถูุญ: **"ูู
ุฃุฌุฏ ุงูุฅุฌุงุจุฉ ูู ุงููุต ุงูู
ูุฏู
"**.
|
| 33 |
+
4. ุฅุฐุง ูุงู ุงูุณุคุงู ุฎุงุฑุฌ ู
ูุถูุน ุงูุนููุฏุฉ ุงูุฅุณูุงู
ูุฉ โ ูู ุจูุถูุญ: **"ูุฐุง ุงูุณุคุงู ุฎุงุฑุฌ ูุทุงู ุงูุนููุฏุฉ ุงูุฅุณูุงู
ูุฉุ ููุง ุฃุณุชุทูุน ุงูุฅุฌุงุจุฉ ุนููู"**.
|
| 34 |
+
5. ูู ุญุงูุฉ ูุฌูุฏ ุงุฌุงุจุฉ ููุงู ุงูุณุคุงู ู
ุชุนููุงู ุจุงูุนููุฏุฉ ุงูุฅุณูุงู
ูุฉุ ุงุจุฏุฃ ูู ุฅุฌุงุจุฉ ุจุงูุตูุบุฉ:
|
| 35 |
+
**"ุงูุญู
ุฏ ูููุ ูุงูุตูุงุฉ ูุงูุณูุงู
ุนูู ุฑุณูู ุงูููุ ูุนูู ุขูู ูุตุญุจู ูู
ู ูุงูุงูุ ุฃู
ุง ุจุนุฏ:"**
|
| 36 |
+
6. ุงุฐูุฑ ุงูู
ุตุงุฏุฑ ุงูุชู ุงุณุชูุฏุช ุฅูููุง ูู ุฅุฌุงุจุชู ู
ู ุงูุณูุงู ุงูู
ูุฏู
ู
ุซู ุงุณู
ุงููุชุงุจ ุงู ุงุณู
ุงูุดูุฎ ุงู ุงู
ูู.
|
| 37 |
+
|
| 38 |
+
---
|
| 39 |
+
|
| 40 |
+
## ุงูุณูุงู:
|
| 41 |
+
{context}
|
| 42 |
+
|
| 43 |
+
---
|
| 44 |
+
|
| 45 |
+
## ุงูุณุคุงู:
|
| 46 |
+
{query}
|
| 47 |
+
|
| 48 |
+
---
|
| 49 |
+
## ุงูุฅุฌุงุจุฉ:
|
| 50 |
+
ุงูุญู
ุฏ ูููุ ูุงูุตูุงุฉ ูุงูุณูุงู
ุนูู ุฑุณูู ุงูููุ ูุนูู ุขูู ูุตุญุจู ูู
ู ูุงูุงูุ ุฃู
ุง ุจุนุฏ:
|
| 51 |
+
... ุฃูู
ู ุงุฌุงุจุชู ููุง ุจูุงุกู ุนูู ุงูุณูุงู ุฃุนูุงู.
|
| 52 |
+
"""
|
| 53 |
+
|
| 54 |
+
# --- Initialize Global Components ---
|
| 55 |
+
print("๐ Initializing Aqeedah AI Chatbot...")
|
| 56 |
+
|
| 57 |
+
# Initialize Gemini client
|
| 58 |
+
if not GEMINI_API_KEY:
|
| 59 |
+
print("โ ๏ธ WARNING: GEMINI_API_KEY not found in environment variables!")
|
| 60 |
+
print(" Please set GEMINI_API_KEY in .env file or HF Spaces secrets")
|
| 61 |
+
gemini_client = None
|
| 62 |
+
else:
|
| 63 |
+
gemini_client = genai.Client(api_key=GEMINI_API_KEY)
|
| 64 |
+
print(f"โ Gemini client initialized (model: {GEMINI_MODEL})")
|
| 65 |
+
|
| 66 |
+
# Initialize retriever
|
| 67 |
+
retriever = HybridRetriever(
|
| 68 |
+
embedding_model="aubmindlab/bert-base-arabertv02",
|
| 69 |
+
dataset_name="abdullah-alamodi/aqeedah-rag-dataset"
|
| 70 |
+
)
|
| 71 |
+
|
| 72 |
+
print("โ
Chatbot initialized successfully!\n")
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
# --- Core Function ---
|
| 76 |
+
|
| 77 |
+
def answer_question(question, top_k=4):
|
| 78 |
+
"""
|
| 79 |
+
Answer a question using RAG with Gemini.
|
| 80 |
+
|
| 81 |
+
Args:
|
| 82 |
+
question (str): User's question in Arabic
|
| 83 |
+
top_k (int): Number of documents to retrieve
|
| 84 |
+
|
| 85 |
+
Returns:
|
| 86 |
+
tuple: (answer, sources_list, context_preview)
|
| 87 |
+
"""
|
| 88 |
+
if not question or question.strip() == "":
|
| 89 |
+
return "โ ๏ธ ุงูุฑุฌุงุก ุฅุฏุฎุงู ุณุคุงู", "", ""
|
| 90 |
+
|
| 91 |
+
# Check if Gemini is available
|
| 92 |
+
if gemini_client is None:
|
| 93 |
+
return "โ ๏ธ ุฎุทุฃ: ูู
ูุชู
ุชูููู Gemini API. ุงูุฑุฌุงุก ุงูุชุญูู ู
ู ู
ุชุบูุฑุงุช ุงูุจูุฆุฉ.", "", ""
|
| 94 |
+
|
| 95 |
+
try:
|
| 96 |
+
# Step 1: Retrieve relevant documents
|
| 97 |
+
print(f"\n๐ Searching for: {question}")
|
| 98 |
+
results = retriever.search(question, top_k=top_k)
|
| 99 |
+
|
| 100 |
+
if not results:
|
| 101 |
+
return "โ ๏ธ ูู
ุฃุฌุฏ ูุชุงุฆุฌ ุฐุงุช ุตูุฉ ุจุณุคุงูู ูู ูุงุนุฏุฉ ุงูุจูุงูุงุช", "", ""
|
| 102 |
+
|
| 103 |
+
# Step 2: Build context from retrieved documents
|
| 104 |
+
context_parts = []
|
| 105 |
+
sources_list = []
|
| 106 |
+
|
| 107 |
+
for i, result in enumerate(results, 1):
|
| 108 |
+
doc_name = result['meta']['doc_name']
|
| 109 |
+
author_name = result['meta']['author_name']
|
| 110 |
+
paragraph_num = result['meta']['paragraph_number']
|
| 111 |
+
content = result['content']
|
| 112 |
+
|
| 113 |
+
# Add to context
|
| 114 |
+
context_parts.append(
|
| 115 |
+
f"ุงูู
ุตุฏุฑ {i}:\n"
|
| 116 |
+
f"ุงููุชุงุจ: {doc_name}\n"
|
| 117 |
+
f"ุงูู
ุคูู: {author_name}\n"
|
| 118 |
+
f"ุงูููุฑุฉ ุฑูู
: {paragraph_num}\n"
|
| 119 |
+
f"ุงููุต: {content}\n"
|
| 120 |
+
)
|
| 121 |
+
|
| 122 |
+
# Add to sources list
|
| 123 |
+
sources_list.append({
|
| 124 |
+
"rank": i,
|
| 125 |
+
"document": doc_name,
|
| 126 |
+
"author": author_name,
|
| 127 |
+
"paragraph": paragraph_num,
|
| 128 |
+
"preview": content[:200] + "..." if len(content) > 200 else content
|
| 129 |
+
})
|
| 130 |
+
|
| 131 |
+
context = "\n---\n\n".join(context_parts)
|
| 132 |
+
|
| 133 |
+
# Step 3: Generate answer with Gemini
|
| 134 |
+
print(f"๐ค Generating answer with Gemini...")
|
| 135 |
+
prompt = SYSTEM_PROMPT_TEMPLATE.format(context=context, query=question)
|
| 136 |
+
|
| 137 |
+
response = gemini_client.models.generate_content(
|
| 138 |
+
model=GEMINI_MODEL,
|
| 139 |
+
contents=prompt
|
| 140 |
+
)
|
| 141 |
+
|
| 142 |
+
answer = response.text
|
| 143 |
+
|
| 144 |
+
# Step 4: Format output
|
| 145 |
+
print(f"โ Answer generated successfully")
|
| 146 |
+
|
| 147 |
+
# Create a nicely formatted sources display
|
| 148 |
+
sources_md = "### ๐ ุงูู
ุตุงุฏุฑ ุงูู
ุณุชุฎุฏู
ุฉ:\n\n"
|
| 149 |
+
for src in sources_list:
|
| 150 |
+
sources_md += f"**{src['rank']}.** {src['document']}\n"
|
| 151 |
+
sources_md += f" - ุงูู
ุคูู: {src['author']}\n"
|
| 152 |
+
sources_md += f" - ุงูููุฑุฉ: {src['paragraph']}\n\n"
|
| 153 |
+
|
| 154 |
+
return answer, sources_md, context
|
| 155 |
+
|
| 156 |
+
except Exception as e:
|
| 157 |
+
error_msg = f"โ ๏ธ ุญุฏุซ ุฎุทุฃ ุฃุซูุงุก ู
ุนุงูุฌุฉ ุงูุณุคุงู:\n\n{str(e)}"
|
| 158 |
+
print(f"โ Error: {e}")
|
| 159 |
+
|
| 160 |
+
# Check if it's a rate limit error
|
| 161 |
+
if "429" in str(e) or "RESOURCE_EXHAUSTED" in str(e):
|
| 162 |
+
error_msg = """## โ ๏ธ ุชู
ุชุฌุงูุฒ ุงูุญุฏ ุงูู
ุณู
ูุญ ู
ู ุงูุทูุจุงุช (API Rate Limit Exceeded)
|
| 163 |
+
|
| 164 |
+
**ุนุฐุฑุงู! ููุฏ ุชู
ุงุณุชููุงุฏ ุญุตุฉ Gemini API ุงูู
ุฌุงููุฉ.**
|
| 165 |
+
|
| 166 |
+
### ๐ ุงูุญููู ุงูู
ุชุงุญุฉ:
|
| 167 |
+
|
| 168 |
+
**ุงูุญู ุงูุฃูู: ุงูุชุธุฑ ุฏูููุฉ ูุงุญุฏุฉ ุซู
ุฃุนุฏ ุงูู
ุญุงููุฉ**
|
| 169 |
+
- ุงูุชุธุฑ 60 ุซุงููุฉ
|
| 170 |
+
- ุฃุนุฏ ุฅุฏุฎุงู ุงูุณุคุงู ู
ุฑุฉ ุฃุฎุฑู
|
| 171 |
+
|
| 172 |
+
**ุงูุญู ุงูุซุงูู: ูู
ุจุชูุฒูู ุงูููุฏ ูุชุดุบููู ู
ุญููุงู**
|
| 173 |
+
|
| 174 |
+
1. ูู
ุจุชุญู
ูู ุงูููุฏ ู
ู GitHub:
|
| 175 |
+
```bash
|
| 176 |
+
git clone https://github.com/Abdullah-Alamodi/aqeedah-ai.git
|
| 177 |
+
cd aqeedah-ai
|
| 178 |
+
```
|
| 179 |
+
|
| 180 |
+
2. ูู
ุจุฅูุดุงุก ู
ูู `.env` ูุฃุถู ู
ูุชุงุญ Gemini API ุงูุฎุงุต ุจู:
|
| 181 |
+
```
|
| 182 |
+
GEMINI_API_KEY=your_api_key_here
|
| 183 |
+
```
|
| 184 |
+
ุงุญุตู ุนูู ุงูู
ูุชุงุญ ู
ู: [Google AI Studio](https://aistudio.google.com/app/apikey)
|
| 185 |
+
|
| 186 |
+
3. ูู
ุจุชุซุจูุช ุงูู
ุชุทูุจุงุช:
|
| 187 |
+
```bash
|
| 188 |
+
pip install -r requirements.txt
|
| 189 |
+
```
|
| 190 |
+
|
| 191 |
+
4. ุดุบูู ุงูุชุทุจูู:
|
| 192 |
+
```bash
|
| 193 |
+
python app.py
|
| 194 |
+
```
|
| 195 |
+
|
| 196 |
+
---
|
| 197 |
+
|
| 198 |
+
**๐ GitHub Repository:** [Abdullah-Alamodi/aqeedah-ai](https://github.com/Abdullah-Alamodi/aqeedah-ai)
|
| 199 |
+
|
| 200 |
+
**๐ก ู
ูุงุญุธุฉ:** ุงูุชุดุบูู ุงูู
ุญูู ูุนุทูู ุญุตุฉ API ู
ุณุชููุฉ ูุฃุฏุงุก ุฃูุถู!
|
| 201 |
+
"""
|
| 202 |
+
|
| 203 |
+
return error_msg, "", ""
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
# --- Gradio Interface ---
|
| 207 |
+
|
| 208 |
+
def create_interface():
|
| 209 |
+
"""Create and configure the Gradio interface."""
|
| 210 |
+
|
| 211 |
+
with gr.Blocks(
|
| 212 |
+
title="ู
ุณุงุนุฏ ุงูุนููุฏุฉ ุงูุฅุณูุงู
ูุฉ | Aqeedah AI Assistant",
|
| 213 |
+
theme=gr.themes.Soft(),
|
| 214 |
+
css="""
|
| 215 |
+
.rtl { direction: rtl; text-align: right; }
|
| 216 |
+
.container { max-width: 900px; margin: auto; }
|
| 217 |
+
"""
|
| 218 |
+
) as interface:
|
| 219 |
+
|
| 220 |
+
# Header
|
| 221 |
+
gr.Markdown("""
|
| 222 |
+
<div align="center">
|
| 223 |
+
<img src="https://upload.wikimedia.org/wikipedia/en/a/ae/Najran_University_Logo.svg" alt="Najran University Logo" width="200">
|
| 224 |
+
</div>
|
| 225 |
+
|
| 226 |
+
<div style="text-align: center;">
|
| 227 |
+
<h1>Aqeedah-Ai ๐</h1>
|
| 228 |
+
<p>ูุธุงู
ุฐูุงุก ุงุตุทูุงุนู ููุฅุฌุงุจุฉ ุนูู ุฃุณุฆูุฉ ุงูุนููุฏุฉ ุงูุฅุณูุงู
ูุฉ ุจูุงุกู ุนูู ู
ุตุงุฏุฑ ุนูู
ูุฉ ู
ูุซููุฉ</p>
|
| 229 |
+
</div>
|
| 230 |
+
|
| 231 |
+
---
|
| 232 |
+
|
| 233 |
+
<div style="text-align: center; padding: 15px; background-color: rgba(255, 243, 205, 0.3); border: 2px solid #ff9800; border-radius: 8px; margin: 10px 0;">
|
| 234 |
+
<h3 style="color: #ff6b6b; margin-top: 0;">โ ๏ธ ุชุญุฐูุฑ ูุงู
| Important Warning</h3>
|
| 235 |
+
<p style="margin: 10px 0; line-height: 1.8;">
|
| 236 |
+
<strong>๐ฌ ูุฐุง ูุธุงู
ุชุฌุฑูุจู ููุจุญุซ ุงูุนูู
ู ููุท</strong><br>
|
| 237 |
+
<strong>๐ฌ This is an Experimental Research Chatbot</strong>
|
| 238 |
+
</p>
|
| 239 |
+
<p style="margin: 10px 0; line-height: 1.8; direction: rtl; text-align: center;">
|
| 240 |
+
โ ๏ธ <strong>ูุง ุชุนุชู
ุฏ ุนูู ุฅุฌุงุจุงุช ูุฐุง ุงููุธุงู
ูู ุงูุฃู
ูุฑ ุงูุดุฑุนูุฉ</strong><br>
|
| 241 |
+
ูุฌุจ ุงูุฑุฌูุน ุฅูู ุงูุนูู
ุงุก ุงูู
ุนุชู
ุฏูู (ุงูู
ุดุงูุฎ) ูููุชุงูู ูุงูุฃุญูุงู
ุงูุดุฑุนูุฉ
|
| 242 |
+
</p>
|
| 243 |
+
<p style="margin: 10px 0; line-height: 1.8;">
|
| 244 |
+
โ ๏ธ <strong>Do not rely on this system for religious rulings</strong><br>
|
| 245 |
+
Always refer to authorized Islamic scholars (Sheikh) for religious guidance
|
| 246 |
+
</p>
|
| 247 |
+
</div>
|
| 248 |
+
|
| 249 |
+
""")
|
| 250 |
+
|
| 251 |
+
gr.Markdown("### ๐ฌ ุงุทุฑุญ ุณุคุงูู")
|
| 252 |
+
|
| 253 |
+
with gr.Row():
|
| 254 |
+
with gr.Column(scale=4):
|
| 255 |
+
question_input = gr.Textbox(
|
| 256 |
+
label="ุงูุณุคุงู (Question)",
|
| 257 |
+
placeholder="ู
ุซุงู: ู
ุง ู
ุนูู ุดูุงุฏุฉ ุฃู ูุง ุฅูู ุฅูุง ุงูููุ",
|
| 258 |
+
lines=3,
|
| 259 |
+
elem_classes=["rtl"]
|
| 260 |
+
)
|
| 261 |
+
with gr.Column(scale=1):
|
| 262 |
+
top_k_slider = gr.Slider(
|
| 263 |
+
minimum=2,
|
| 264 |
+
maximum=8,
|
| 265 |
+
value=4,
|
| 266 |
+
step=1,
|
| 267 |
+
label="ุนุฏุฏ ุงูู
ุตุงุฏุฑ (Sources)",
|
| 268 |
+
info="Number of documents to retrieve"
|
| 269 |
+
)
|
| 270 |
+
|
| 271 |
+
submit_btn = gr.Button("๐ ุงุจุญุซ ูุงุฌุจ | Search & Answer", variant="primary", size="lg")
|
| 272 |
+
|
| 273 |
+
gr.Markdown("### ๐ ุงูุฅุฌุงุจุฉ (Answer)")
|
| 274 |
+
answer_output = gr.Textbox(
|
| 275 |
+
label="",
|
| 276 |
+
lines=10,
|
| 277 |
+
elem_classes=["rtl"],
|
| 278 |
+
show_label=False
|
| 279 |
+
)
|
| 280 |
+
|
| 281 |
+
gr.Markdown("### ๐ ุงูู
ุตุงุฏุฑ ุงูู
ุณุชุฎุฏู
ุฉ (Sources)")
|
| 282 |
+
sources_output = gr.Markdown(
|
| 283 |
+
value="",
|
| 284 |
+
elem_classes=["rtl"]
|
| 285 |
+
)
|
| 286 |
+
|
| 287 |
+
# Advanced: Show context (collapsible)
|
| 288 |
+
with gr.Accordion("๐ ุนุฑุถ ุงูุณูุงู ุงููุงู
ู (Show Full Context)", open=False):
|
| 289 |
+
context_output = gr.Textbox(
|
| 290 |
+
label="ุงูุณูุงู ุงูู
ุณุชุฎุฏู
",
|
| 291 |
+
lines=15,
|
| 292 |
+
elem_classes=["rtl"]
|
| 293 |
+
)
|
| 294 |
+
|
| 295 |
+
# Example questions
|
| 296 |
+
gr.Markdown("### ๐ก ุฃู
ุซูุฉ ููุฃุณุฆูุฉ (Example Questions)")
|
| 297 |
+
gr.Examples(
|
| 298 |
+
examples=[
|
| 299 |
+
["ู
ุง ู
ุนูู ุดูุงุฏุฉ ุฃู ูุง ุฅูู ุฅูุง ุงููู ูุฃู ู
ุญู
ุฏุง ุฑุณูู ุงููู ุตูู ุงููู ุนููู ูุณูู
ุ"],
|
| 300 |
+
["ู
ุง ุงููุฑู ุจูู ุงูุฅุณูุงู
ูุงูุฅูู
ุงูุ"],
|
| 301 |
+
["ู
ุงู
ุนูู ูููููููู: (ูููู ู
ูุนูููู ุงูุฑููุจููุจููููุฉู ููููุง ู
ูุฑูุจููุจูุ ููู
ูุนูููู ุงููุฎูุงูููู ููููุง ู
ูุฎูููููู)ุ"],
|
| 302 |
+
["ู
ุงูู
ูุตูุฏ ุจุงูุงูู
ุงู ุจุงููุฏุฑ ุฎูุฑู ูุดุฑูุ"],
|
| 303 |
+
["ู
ุงูู
ูุตูุฏ ุจุงููุดุฑุฉุ"],
|
| 304 |
+
["ู
ุง ุฃูุณุงู
ุณุจ ุงูุฏูุฑุ"],
|
| 305 |
+
["ู
ุงุญูู
ููู: ู
ุง ุดุงุก ุงููู ูุดุฆุชุ"],
|
| 306 |
+
["ู
ุง ุตูุงุช ูู
ูุฒุงุช ุฃูู ุงูุณูุฉ ูุงูุฌู
ุงุนุฉุ"],
|
| 307 |
+
["ููู ุชุญูู ู
ุญุจุฉ ุงูุฑุณูู ุตูู ุงููู ุนููู ูุณูู
ุ"],
|
| 308 |
+
["ู
ู ูู ุงููุฑูุฉ ุงููุงุฌูุฉ ูู
ุง ุตูุงุชูุง ูู
ุง ุฃุจุฑุฒ ุฎุตุงุฆุตูุงุ"]
|
| 309 |
+
],
|
| 310 |
+
inputs=question_input,
|
| 311 |
+
label=""
|
| 312 |
+
)
|
| 313 |
+
|
| 314 |
+
# Footer
|
| 315 |
+
gr.Markdown("""
|
| 316 |
+
---
|
| 317 |
+
<div style="text-align: center; padding: 10px; background-color: rgba(100, 100, 100, 0.1); border-radius: 5px; margin: 10px 0;">
|
| 318 |
+
<strong>๐ Researcher name | ุงุณู
ุงูุจุงุญุซุฉ:</strong><br>
|
| 319 |
+
<strong>Dr. Alya Alamodi</strong> - Ph.D. in Islamic Theology (Aqeedah)<br>
|
| 320 |
+
Najran University, Kingdom of Saudi Arabia<br><br>
|
| 321 |
+
<strong>๐ค AI Architecture & Development:</strong> <em>Abdullah Alamodi, M.Sc. AI (IU International University, Germany)</em>
|
| 322 |
+
</div>
|
| 323 |
+
|
| 324 |
+
<div style="text-align: center; font-size: 0.9em; color: #666;">
|
| 325 |
+
<p>๐ <strong>Dataset:</strong> <a href="https://huggingface.co/datasets/abdullah-alamodi/aqeedah-rag-dataset" target="_blank">abdullah-alamodi/aqeedah-rag-dataset</a></p>
|
| 326 |
+
<p>๐ค <strong>Model:</strong> Gemini 2.5 Flash + AraBERT v2</p>
|
| 327 |
+
<p>โ ๏ธ <strong>Disclaimer:</strong> This is an AI research tool. Please consult qualified Islamic scholars for authoritative religious guidance.</p>
|
| 328 |
+
<p>๐ <strong>License:</strong> MIT | <strong>Sources:</strong> Authenticated Sunni Islamic theology texts</p>
|
| 329 |
+
</div>
|
| 330 |
+
""")
|
| 331 |
+
|
| 332 |
+
# Connect components
|
| 333 |
+
submit_btn.click(
|
| 334 |
+
fn=answer_question,
|
| 335 |
+
inputs=[question_input, top_k_slider],
|
| 336 |
+
outputs=[answer_output, sources_output, context_output]
|
| 337 |
+
)
|
| 338 |
+
|
| 339 |
+
# Also trigger on Enter key
|
| 340 |
+
question_input.submit(
|
| 341 |
+
fn=answer_question,
|
| 342 |
+
inputs=[question_input, top_k_slider],
|
| 343 |
+
outputs=[answer_output, sources_output, context_output]
|
| 344 |
+
)
|
| 345 |
+
|
| 346 |
+
return interface
|
| 347 |
+
|
| 348 |
+
|
| 349 |
+
# --- Launch App ---
|
| 350 |
+
|
| 351 |
+
if __name__ == "__main__":
|
| 352 |
+
app = create_interface()
|
| 353 |
+
|
| 354 |
+
# Launch configuration
|
| 355 |
+
# Use localhost for local development, 0.0.0.0 for deployment
|
| 356 |
+
is_hf_space = os.getenv("SPACE_ID") is not None
|
| 357 |
+
|
| 358 |
+
app.launch(
|
| 359 |
+
server_name="0.0.0.0" if is_hf_space else "127.0.0.1",
|
| 360 |
+
server_port=7860,
|
| 361 |
+
share=False,
|
| 362 |
+
show_error=True,
|
| 363 |
+
inbrowser=True # Auto-open browser on local
|
| 364 |
+
)
|
requirements.txt
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
aiofiles==24.1.0
|
| 2 |
+
aiohappyeyeballs==2.6.1
|
| 3 |
+
aiohttp==3.13.2
|
| 4 |
+
aiosignal==1.4.0
|
| 5 |
+
annotated-doc==0.0.3
|
| 6 |
+
annotated-types==0.7.0
|
| 7 |
+
anyio==4.11.0
|
| 8 |
+
asttokens==3.0.0
|
| 9 |
+
attrs==25.4.0
|
| 10 |
+
audioop-lts==0.2.2
|
| 11 |
+
brotli==1.1.0
|
| 12 |
+
cachetools==6.2.1
|
| 13 |
+
certifi==2025.10.5
|
| 14 |
+
charset-normalizer==3.4.4
|
| 15 |
+
click==8.3.0
|
| 16 |
+
colorama==0.4.6
|
| 17 |
+
comm==0.2.3
|
| 18 |
+
datasets==4.3.0
|
| 19 |
+
debugpy==1.8.17
|
| 20 |
+
decorator==5.2.1
|
| 21 |
+
dill==0.4.0
|
| 22 |
+
executing==2.2.1
|
| 23 |
+
faiss-cpu==1.12.0
|
| 24 |
+
fastapi==0.120.4
|
| 25 |
+
ffmpy==0.6.4
|
| 26 |
+
filelock==3.20.0
|
| 27 |
+
frozenlist==1.8.0
|
| 28 |
+
fsspec==2025.9.0
|
| 29 |
+
google-auth==2.42.0
|
| 30 |
+
google-genai==1.47.0
|
| 31 |
+
gradio==5.49.1
|
| 32 |
+
gradio-client==1.13.3
|
| 33 |
+
groovy==0.1.2
|
| 34 |
+
h11==0.16.0
|
| 35 |
+
hf-xet==1.2.0
|
| 36 |
+
httpcore==1.0.9
|
| 37 |
+
httpx==0.28.1
|
| 38 |
+
huggingface-hub==0.36.0
|
| 39 |
+
idna==3.11
|
| 40 |
+
ipykernel==7.1.0
|
| 41 |
+
ipython==9.6.0
|
| 42 |
+
ipython-pygments-lexers==1.1.1
|
| 43 |
+
jedi==0.19.2
|
| 44 |
+
jinja2==3.1.6
|
| 45 |
+
joblib==1.5.2
|
| 46 |
+
jupyter-client==8.6.3
|
| 47 |
+
jupyter-core==5.9.1
|
| 48 |
+
lxml==6.0.2
|
| 49 |
+
markdown-it-py==4.0.0
|
| 50 |
+
markupsafe==3.0.3
|
| 51 |
+
matplotlib-inline==0.2.1
|
| 52 |
+
mdurl==0.1.2
|
| 53 |
+
mpmath==1.3.0
|
| 54 |
+
multidict==6.7.0
|
| 55 |
+
multiprocess==0.70.16
|
| 56 |
+
nest-asyncio==1.6.0
|
| 57 |
+
networkx==3.5
|
| 58 |
+
nltk==3.9.2
|
| 59 |
+
numpy==2.3.4
|
| 60 |
+
orjson==3.11.4
|
| 61 |
+
packaging==25.0
|
| 62 |
+
pandas==2.3.3
|
| 63 |
+
parso==0.8.5
|
| 64 |
+
pillow==11.3.0
|
| 65 |
+
platformdirs==4.5.0
|
| 66 |
+
prompt-toolkit==3.0.52
|
| 67 |
+
propcache==0.4.1
|
| 68 |
+
psutil==7.1.2
|
| 69 |
+
pure-eval==0.2.3
|
| 70 |
+
pyarabic==0.6.15
|
| 71 |
+
pyarrow==22.0.0
|
| 72 |
+
pyasn1==0.6.1
|
| 73 |
+
pyasn1-modules==0.4.2
|
| 74 |
+
pydantic==2.11.10
|
| 75 |
+
pydantic-core==2.33.2
|
| 76 |
+
pydub==0.25.1
|
| 77 |
+
pygments==2.19.2
|
| 78 |
+
python-dateutil==2.9.0.post0
|
| 79 |
+
python-docx==1.2.0
|
| 80 |
+
python-dotenv==1.2.1
|
| 81 |
+
python-multipart==0.0.20
|
| 82 |
+
pytz==2025.2
|
| 83 |
+
pyyaml==6.0.3
|
| 84 |
+
pyzmq==27.1.0
|
| 85 |
+
rank-bm25==0.2.2
|
| 86 |
+
regex==2025.10.23
|
| 87 |
+
requests==2.32.5
|
| 88 |
+
rich==14.2.0
|
| 89 |
+
rsa==4.9.1
|
| 90 |
+
ruff==0.14.3
|
| 91 |
+
safehttpx==0.1.7
|
| 92 |
+
safetensors==0.6.2
|
| 93 |
+
scikit-learn==1.7.2
|
| 94 |
+
scipy==1.16.3
|
| 95 |
+
semantic-version==2.10.0
|
| 96 |
+
sentence-transformers==5.1.2
|
| 97 |
+
setuptools==80.9.0
|
| 98 |
+
shellingham==1.5.4
|
| 99 |
+
six==1.17.0
|
| 100 |
+
sniffio==1.3.1
|
| 101 |
+
stack-data==0.6.3
|
| 102 |
+
starlette==0.49.1
|
| 103 |
+
sympy==1.14.0
|
| 104 |
+
tenacity==9.1.2
|
| 105 |
+
threadpoolctl==3.6.0
|
| 106 |
+
tokenizers==0.22.1
|
| 107 |
+
tomlkit==0.13.3
|
| 108 |
+
torch==2.9.0
|
| 109 |
+
torchvision==0.24.0
|
| 110 |
+
tornado==6.5.2
|
| 111 |
+
tqdm==4.67.1
|
| 112 |
+
traitlets==5.14.3
|
| 113 |
+
transformers==4.57.1
|
| 114 |
+
typer==0.20.0
|
| 115 |
+
typing-extensions==4.15.0
|
| 116 |
+
typing-inspection==0.4.2
|
| 117 |
+
tzdata==2025.2
|
| 118 |
+
urllib3==2.5.0
|
| 119 |
+
uvicorn==0.38.0
|
| 120 |
+
wcwidth==0.2.14
|
| 121 |
+
websockets==15.0.1
|
| 122 |
+
xxhash==3.6.0
|
| 123 |
+
yarl==1.22.0
|
retrieval.py
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Aqeedah RAG Retrieval System
|
| 3 |
+
============================
|
| 4 |
+
|
| 5 |
+
This module implements a hybrid retrieval system combining BM25 (sparse) and
|
| 6 |
+
dense embeddings (FAISS) for Arabic Islamic theology (Aqeedah) question-answering.
|
| 7 |
+
|
| 8 |
+
The system loads pre-computed embeddings from HuggingFace and performs efficient
|
| 9 |
+
semantic search over a corpus of authenticated Islamic scholarly texts.
|
| 10 |
+
|
| 11 |
+
Author: Abdullah Alamodi
|
| 12 |
+
Dataset: https://huggingface.co/datasets/abdullah-alamodi/aqeedah-rag-dataset
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
import numpy as np
|
| 16 |
+
import torch
|
| 17 |
+
import pyarabic.araby as araby
|
| 18 |
+
from rank_bm25 import BM25Okapi
|
| 19 |
+
from transformers import AutoTokenizer, AutoModel
|
| 20 |
+
from datasets import load_dataset
|
| 21 |
+
from tqdm import tqdm
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
# --- Normalization Functions ---
|
| 25 |
+
|
| 26 |
+
def normalize_for_bm25(text):
|
| 27 |
+
"""
|
| 28 |
+
Aggressive normalization for keyword matching.
|
| 29 |
+
|
| 30 |
+
Args:
|
| 31 |
+
text (str): Arabic text to normalize
|
| 32 |
+
|
| 33 |
+
Returns:
|
| 34 |
+
str: Normalized text suitable for BM25 indexing
|
| 35 |
+
"""
|
| 36 |
+
text = araby.normalize_hamza(text) # Normalize alef, hamza
|
| 37 |
+
text = araby.strip_diacritics(text) # Strip all diacritics
|
| 38 |
+
text = araby.strip_tatweel(text) # Strip tatweel (elongation)
|
| 39 |
+
return text
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def normalize_for_dense(text):
|
| 43 |
+
"""
|
| 44 |
+
Light normalization for dense embeddings, preserves diacritics.
|
| 45 |
+
|
| 46 |
+
Args:
|
| 47 |
+
text (str): Arabic text to normalize
|
| 48 |
+
|
| 49 |
+
Returns:
|
| 50 |
+
str: Lightly normalized text suitable for dense embeddings
|
| 51 |
+
"""
|
| 52 |
+
text = araby.normalize_hamza(text) # Only normalize alef, hamza
|
| 53 |
+
return text
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
# --- Helper function for pooling embeddings ---
|
| 57 |
+
|
| 58 |
+
def _average_pool(last_hidden_states, attention_mask):
|
| 59 |
+
"""
|
| 60 |
+
Performs average pooling on the last hidden states, respecting the attention mask.
|
| 61 |
+
|
| 62 |
+
Args:
|
| 63 |
+
last_hidden_states: Model output hidden states
|
| 64 |
+
attention_mask: Attention mask tensor
|
| 65 |
+
|
| 66 |
+
Returns:
|
| 67 |
+
torch.Tensor: Pooled embeddings
|
| 68 |
+
"""
|
| 69 |
+
last_hidden = last_hidden_states.masked_fill(~attention_mask[..., None].bool(), 0.0)
|
| 70 |
+
return last_hidden.sum(dim=1) / attention_mask.sum(dim=1)[..., None]
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
# --- HybridRetriever Class ---
|
| 74 |
+
|
| 75 |
+
class HybridRetriever:
|
| 76 |
+
"""
|
| 77 |
+
Hybrid retrieval system combining BM25 and dense embeddings.
|
| 78 |
+
|
| 79 |
+
This class loads the Aqeedah dataset from HuggingFace, creates a BM25 index,
|
| 80 |
+
and loads pre-computed FAISS embeddings for efficient hybrid search.
|
| 81 |
+
|
| 82 |
+
Args:
|
| 83 |
+
embedding_model (str): HuggingFace model name for query encoding
|
| 84 |
+
dataset_name (str): HuggingFace dataset name
|
| 85 |
+
batch_size (int): Batch size for encoding (if needed)
|
| 86 |
+
use_gpu (bool): Whether to use GPU if available
|
| 87 |
+
"""
|
| 88 |
+
|
| 89 |
+
def __init__(
|
| 90 |
+
self,
|
| 91 |
+
embedding_model="aubmindlab/bert-base-arabertv02",
|
| 92 |
+
dataset_name="abdullah-alamodi/aqeedah-rag-dataset",
|
| 93 |
+
batch_size=32,
|
| 94 |
+
use_gpu=True
|
| 95 |
+
):
|
| 96 |
+
self.dataset_name = dataset_name
|
| 97 |
+
self.batch_size = batch_size
|
| 98 |
+
self.device = torch.device("cuda" if (torch.cuda.is_available() and use_gpu) else "cpu")
|
| 99 |
+
|
| 100 |
+
print(f"๐ Initializing HybridRetriever")
|
| 101 |
+
print(f" Device: {self.device}")
|
| 102 |
+
print(f" Embedding model: {embedding_model}")
|
| 103 |
+
print(f" Dataset: {dataset_name}")
|
| 104 |
+
|
| 105 |
+
# --- Load Dataset from HuggingFace ---
|
| 106 |
+
print(f"\n๐ฅ Loading dataset from HuggingFace...")
|
| 107 |
+
self.dataset = load_dataset(dataset_name, split="train")
|
| 108 |
+
self.documents = [
|
| 109 |
+
{
|
| 110 |
+
"content": doc["content"],
|
| 111 |
+
"meta": doc["meta"]
|
| 112 |
+
}
|
| 113 |
+
for doc in self.dataset
|
| 114 |
+
]
|
| 115 |
+
print(f" โ Loaded {len(self.documents)} documents")
|
| 116 |
+
|
| 117 |
+
# --- Load Dense Model ---
|
| 118 |
+
print(f"\n๐ค Loading embedding model...")
|
| 119 |
+
self.dense_tokenizer = AutoTokenizer.from_pretrained(embedding_model)
|
| 120 |
+
self.dense_model = AutoModel.from_pretrained(embedding_model).to(self.device).eval()
|
| 121 |
+
print(f" โ Model loaded on {self.device}")
|
| 122 |
+
|
| 123 |
+
# --- Create BM25 Index ---
|
| 124 |
+
print(f"\n๐ Building BM25 index...")
|
| 125 |
+
bm25_corpus = [normalize_for_bm25(doc['content']) for doc in self.documents]
|
| 126 |
+
tokenized_corpus = [doc.split() for doc in tqdm(bm25_corpus, desc=" Tokenizing")]
|
| 127 |
+
self.bm25 = BM25Okapi(tokenized_corpus)
|
| 128 |
+
print(f" โ BM25 index created")
|
| 129 |
+
|
| 130 |
+
# --- Load FAISS Index from HuggingFace ---
|
| 131 |
+
print(f"\nโก Loading FAISS index...")
|
| 132 |
+
try:
|
| 133 |
+
# Try to load pre-computed index from dataset
|
| 134 |
+
self.dataset.load_faiss_index("embeddings", "embeddings_index")
|
| 135 |
+
print(f" โ FAISS index loaded from HuggingFace")
|
| 136 |
+
except Exception as e:
|
| 137 |
+
print(f" โ ๏ธ Could not load FAISS index from HF: {e}")
|
| 138 |
+
print(f" ๐ Creating FAISS index from embeddings column...")
|
| 139 |
+
# Create index from embeddings column if not available
|
| 140 |
+
self.dataset.add_faiss_index(column="embeddings")
|
| 141 |
+
print(f" โ FAISS index created")
|
| 142 |
+
|
| 143 |
+
print(f"\nโ
HybridRetriever initialized successfully!\n")
|
| 144 |
+
|
| 145 |
+
def search(self, query, top_k=5):
|
| 146 |
+
"""
|
| 147 |
+
Perform hybrid search combining BM25 and dense retrieval.
|
| 148 |
+
|
| 149 |
+
Args:
|
| 150 |
+
query (str): Arabic query text
|
| 151 |
+
top_k (int): Number of top results to return
|
| 152 |
+
|
| 153 |
+
Returns:
|
| 154 |
+
list: List of retrieved documents with metadata
|
| 155 |
+
"""
|
| 156 |
+
# A. Sparse Search (BM25)
|
| 157 |
+
norm_query_bm25 = normalize_for_bm25(query)
|
| 158 |
+
tokenized_query = norm_query_bm25.split()
|
| 159 |
+
bm25_scores = self.bm25.get_scores(tokenized_query)
|
| 160 |
+
bm25_top_k_indices = np.argsort(bm25_scores)[::-1][:top_k]
|
| 161 |
+
|
| 162 |
+
# B. Dense Search (FAISS)
|
| 163 |
+
norm_query_dense = f"query: {normalize_for_dense(query)}"
|
| 164 |
+
inputs = self.dense_tokenizer(
|
| 165 |
+
[norm_query_dense],
|
| 166 |
+
max_length=512,
|
| 167 |
+
padding=True,
|
| 168 |
+
truncation=True,
|
| 169 |
+
return_tensors='pt'
|
| 170 |
+
).to(self.device)
|
| 171 |
+
|
| 172 |
+
with torch.no_grad():
|
| 173 |
+
outputs = self.dense_model(**inputs)
|
| 174 |
+
|
| 175 |
+
query_embedding = _average_pool(outputs.last_hidden_state, inputs['attention_mask'])
|
| 176 |
+
query_embedding = torch.nn.functional.normalize(query_embedding, p=2, dim=1)
|
| 177 |
+
query_embedding = query_embedding.cpu().numpy()[0]
|
| 178 |
+
|
| 179 |
+
# Search using HuggingFace dataset's FAISS index
|
| 180 |
+
scores, retrieved = self.dataset.get_nearest_examples(
|
| 181 |
+
"embeddings",
|
| 182 |
+
query_embedding,
|
| 183 |
+
k=top_k
|
| 184 |
+
)
|
| 185 |
+
|
| 186 |
+
# Get FAISS indices (retrieve actual indices from the dataset)
|
| 187 |
+
# Since get_nearest_examples returns the actual data, we need to find indices
|
| 188 |
+
faiss_contents = retrieved['content']
|
| 189 |
+
faiss_indices = []
|
| 190 |
+
for content in faiss_contents:
|
| 191 |
+
# Find index of this content in original documents
|
| 192 |
+
for idx, doc in enumerate(self.documents):
|
| 193 |
+
if doc['content'] == content:
|
| 194 |
+
faiss_indices.append(idx)
|
| 195 |
+
break
|
| 196 |
+
|
| 197 |
+
# C. Combine results (union of BM25 and FAISS)
|
| 198 |
+
fused_indices = list(set(bm25_top_k_indices.tolist()) | set(faiss_indices))
|
| 199 |
+
|
| 200 |
+
# Return unique results
|
| 201 |
+
results = []
|
| 202 |
+
seen_contents = set()
|
| 203 |
+
for idx in fused_indices:
|
| 204 |
+
if idx < len(self.documents):
|
| 205 |
+
content = self.documents[idx]['content']
|
| 206 |
+
if content not in seen_contents:
|
| 207 |
+
results.append(self.documents[idx])
|
| 208 |
+
seen_contents.add(content)
|
| 209 |
+
|
| 210 |
+
return results[:top_k * 2] # Return up to 2x top_k for better coverage
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
# --- Example Usage ---
|
| 214 |
+
|
| 215 |
+
if __name__ == "__main__":
|
| 216 |
+
# Initialize retriever
|
| 217 |
+
retriever = HybridRetriever()
|
| 218 |
+
|
| 219 |
+
# Example queries
|
| 220 |
+
test_queries = [
|
| 221 |
+
"ู
ุง ู
ุนูู ุดูุงุฏุฉ ุฃู ูุง ุฅูู ุฅูุง ุงููู ูุฃู ู
ุญู
ุฏุง ุฑุณูู ุงููู ุตูู ุงููู ุนููู ูุณูู
ุ",
|
| 222 |
+
"ู
ุง ุงููุฑู ุจูู ุงูุฅุณูุงู
ูุงูุฅูู
ุงูุ",
|
| 223 |
+
"ู
ุง ูู ุฃุฑูุงู ุงูุฅูู
ุงูุ"
|
| 224 |
+
]
|
| 225 |
+
|
| 226 |
+
for query in test_queries:
|
| 227 |
+
print(f"\n{'='*80}")
|
| 228 |
+
print(f"โ Query: {query}")
|
| 229 |
+
print(f"{'='*80}")
|
| 230 |
+
|
| 231 |
+
results = retriever.search(query, top_k=3)
|
| 232 |
+
|
| 233 |
+
for i, result in enumerate(results, 1):
|
| 234 |
+
print(f"\n[{i}] {result['meta']['doc_name']}")
|
| 235 |
+
print(f" Author: {result['meta']['author_name']}")
|
| 236 |
+
print(f" Paragraph: {result['meta']['paragraph_number']}")
|
| 237 |
+
print(f" Preview: {result['content'][:150]}...")
|