5 Downloads Updated 2 weeks ago
ollama run arelgendy/qmodel:8.5
Updated 2 weeks ago
2 weeks ago
b5eb7d03a18e · 16GB
Specialized Qur’an, Hadith & Fatwa Knowledge System with Multi-Vector RAG & Dual LLM Support
A production-ready Retrieval-Augmented Generation system specialized exclusively in authenticated Islamic knowledge. No hallucinations, no outside knowledge—only content from verified sources.
/fatwa/{id}, /fatwa/search, and /fatwa/sources﴿...﴾, ❝...❞, «...») are checked against the retrieved passages; near-copies are restored to the canonical text, ungrounded quotes are removed/v1/chat/completions and /v1/modelspassage: and query: prefixes for optimal vector similarityBAAI/bge-reranker-v2-m3 rescoring and confidence gating (on by default)# Download code + search index from Hugging Face (skips the 15 GB GGUF)
huggingface-cli download aelgendy/QModel --exclude "models/*" --local-dir QModel && cd QModel
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
# Configure (choose one backend)
# Option A — GGUF (self-contained, default):
export LLM_BACKEND=gguf
export GGUF_MODEL_PATH=./models/Qwen2.5-14B-Instruct-Q8_0.gguf
huggingface-cli download bartowski/Qwen2.5-14B-Instruct-GGUF Qwen2.5-14B-Instruct-Q8_0.gguf --local-dir ./models
# Option B — Ollama:
ollama pull arelgendy/qmodel:8.5 # or: ollama pull hf.co/aelgendy/QModel
export LLM_BACKEND=ollama
export OLLAMA_MODEL=arelgendy/qmodel:8.5
# Make sure Ollama is running: ollama serve
# Option C — HuggingFace (production):
export LLM_BACKEND=hf
export HF_MODEL_NAME=Qwen/Qwen2.5-14B-Instruct
# Run
python main.py
# Query
curl "http://localhost:8000/ask?q=What%20does%20Islam%20say%20about%20mercy?"
API docs: http://localhost:8000/docs
Pre-built data files are included:
- metadata.json — 56,250 documents (6,236 Quran verses + 46,416 hadiths from 15 collections + 3,598 fatwas)
- QModel.index — FAISS search index (~220 MB, 56,250 vectors, 1024-d)
To rebuild after dataset changes:
python build_index.py
Fatwas are read from data/fatwas/ (hand-collected) and data/fatwas_scraped/ (scraper output), de-duplicated by id and content; files listed in FATWA_EXCLUDE in build_index.py are skipped. To add fatwas without re-embedding the Qur’an and hadith:
python data_pipeline/scrape_fatwas.py --source islamweb --start 8001 --end 12000 --delay 1.0
python scripts/update_fatwa_index.py # writes QModel.fatwa-update.index / metadata.fatwa-update.json
python scripts/update_fatwa_index.py --in-place # or replace the live files (previous pair saved as .bak, overwriting older .bak)
Records without answer text, and scraped listing pages (e.g. audio-lecture indexes), are skipped.
# Basic question
curl "http://localhost:8000/ask?q=What%20does%20Islam%20say%20about%20mercy?"
# Word frequency
curl "http://localhost:8000/ask?q=How%20many%20times%20is%20mercy%20mentioned?"
# Authentic Hadiths only
curl "http://localhost:8000/ask?q=prayer&source_type=hadith&grade_filter=sahih"
# Quran text search
curl "http://localhost:8000/quran/search?q=bismillah"
# Quran topic search
curl "http://localhost:8000/quran/topic?topic=patience&top_k=5"
# Quran word frequency
curl "http://localhost:8000/quran/word-frequency?word=mercy"
# Single chapter
curl "http://localhost:8000/quran/chapter/2"
# Exact verse
curl "http://localhost:8000/quran/verse/2:255"
# Root-word analysis
curl "http://localhost:8000/quran/root/رحم"
# Hadith text search
curl "http://localhost:8000/hadith/search?q=actions+are+judged+by+intentions"
# Hadith topic search (Sahih only)
curl "http://localhost:8000/hadith/topic?topic=fasting&grade_filter=sahih"
# Verify Hadith authenticity
curl "http://localhost:8000/hadith/verify?q=Actions%20are%20judged%20by%20intentions"
# Browse a collection
curl "http://localhost:8000/hadith/collection/bukhari?limit=5"
# Fatwa topic search (with scholar filter)
curl "http://localhost:8000/fatwa/search?q=الوصية&source=BinBaz&top_k=3"
# Fatwa direct ID lookup
curl "http://localhost:8000/fatwa/fatwa_binbaz_4210"
# List loaded Fatwa collections
curl "http://localhost:8000/fatwa/sources"
# Streaming (OpenAI-compatible)
curl -X POST http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"QModel","messages":[{"role":"user","content":"What does Islam say about charity?"}],"stream":true}'
All configuration via environment variables (.env file or exported directly):
| Backend | Pros | Cons | When to Use |
|---|---|---|---|
| GGUF | Self-contained, no external daemon, bundles with the repo/Space | Larger image/repo size | HF Spaces, offline/portable deployment |
| Ollama | Fast setup, no GPU, free | Smaller models, requires separate daemon | Development, testing |
| HuggingFace | Larger models, better quality | Requires GPU or significant RAM | Production |
LLM_BACKEND=gguf
GGUF_MODEL_PATH=./models/Qwen2.5-14B-Instruct-Q8_0.gguf
GGUF_N_CTX=8192
GGUF_N_GPU_LAYERS=-1 # -1 = offload all layers to GPU, 0 = CPU-only
Requires the .gguf file to be present at GGUF_MODEL_PATH — download with:
huggingface-cli download bartowski/Qwen2.5-14B-Instruct-GGUF Qwen2.5-14B-Instruct-Q8_0.gguf --local-dir ./models
| Model | Size (Q8_0) |
RAM / VRAM | Generation Speed | Best Use Case |
|---|---|---|---|---|
| Qwen2.5-14B-Instruct (Default) | ~15 GB | ~16 GB | Blazing (~55 tok/s) | Production, Hugging Face Hub, and local GPU deployment |
ollama pull arelgendy/qmodel:8.5
LLM_BACKEND=ollama
OLLAMA_HOST=http://localhost:11434
OLLAMA_MODEL=arelgendy/qmodel:8.5
Requires ollama serve running. The model is published in two places, both built from Modelfile (chat template, system prompt and sampling settings):
| Source | Pull | Tags |
|---|---|---|
| ollama.com | ollama pull arelgendy/qmodel:8.5 |
release versions (8.5, latest) |
| Hugging Face | ollama pull hf.co/aelgendy/QModel |
quantization only (Q8_0); reads the repo’s template, system and params files |
ollama run arelgendy/qmodelon its own gives the bare language model, without retrieval, citation checks or verse links. For grounded answers, run the QModel API withLLM_BACKEND=ollamaand send questions to it (or to Open-WebUI connected to it).
LLM_BACKEND=hf
HF_MODEL_NAME=Qwen/Qwen2.5-14B-Instruct
HF_DEVICE=auto # auto | cuda | cpu
HF_MAX_NEW_TOKENS=2048
| Variable | Default | Description |
|---|---|---|
| Backend | ||
LLM_BACKEND |
gguf |
gguf, ollama, or hf |
GGUF_MODEL_PATH |
./models/Qwen2.5-14B-Instruct-Q8_0.gguf |
Path to local .gguf file |
GGUF_N_CTX |
8192 |
Context window size |
GGUF_N_GPU_LAYERS |
-1 |
Layers to offload to GPU (-1 = all, 0 = CPU-only) |
OLLAMA_HOST |
http://localhost:11434 |
Ollama server URL |
OLLAMA_MODEL |
qwen2.5:14b |
Ollama model name |
HF_MODEL_NAME |
Qwen/Qwen2.5-14B-Instruct |
HuggingFace model ID |
HF_DEVICE |
auto |
auto, cuda, or cpu |
HF_MAX_NEW_TOKENS |
2048 |
Max output length |
| Embedding & Data | ||
EMBED_MODEL |
intfloat/multilingual-e5-large |
Embedding model |
FAISS_INDEX |
QModel.index |
Index file path |
METADATA_FILE |
metadata.json |
Dataset file |
| Retrieval | ||
TOP_K_SEARCH |
20 |
Candidate pool (5–100) |
TOP_K_RETURN |
5 |
Results shown to user (1–20) |
RERANK_ALPHA |
0.6 |
Dense vs Sparse weight (0.0–1.0) |
FAST_QUERY_REWRITE |
true |
Rewrite queries without the LLM (regex intent + thesaurus terms), saving ~2 s per question. The LLM rewrite still runs for follow-up questions with chat history |
USE_RERANKER |
true |
Cross-encoder reranker (true/false). Also drives the confidence gate: off-topic questions refused 92% with it vs 36% without |
RERANK_MODEL |
BAAI/bge-reranker-v2-m3 |
Cross-encoder model ID |
AYAH_WINDOW_SIZE |
1 |
Number of neighboring verses for context expansion |
ENABLE_HADITH_DEDUPLICATION |
true |
Cluster near-identical Hadith narrations |
TAFSIR_MAX_CHARS |
1200 |
Max Tafsir characters provided in LLM context |
| Generation | ||
TEMPERATURE |
0.2 |
Creativity (0.0–1.0, use 0.1–0.2 for religious) |
MAX_TOKENS |
2048 |
Max response length |
SHOW_THINKING |
false |
Include chain-of-thought/reasoning output (true/false) |
| Safety | ||
RERANK_MIN_SCORE |
0.05 |
Confidence gate with reranker on: min cross-encoder relevance (0–1) of the best retrieved passage to call the LLM |
DENSE_MIN_SCORE |
0.80 |
Confidence gate with reranker off: min dense cosine similarity (multilingual-e5-large scale) |
HADITH_BOOST |
0.08 |
Score boost for hadith on hadith queries |
QURAN_BOOST |
0.12 |
Score boost for Quran verses on tafsir/quran queries |
| Other | ||
CACHE_SIZE |
512 |
Query response cache entries |
CACHE_TTL |
3600 |
Cache expiry in seconds |
ALLOWED_ORIGINS |
* |
CORS origins |
MAX_EXAMPLES |
3 |
Few-shot examples in system prompt |
Self-contained (GGUF, e.g. HF Spaces)
LLM_BACKEND=gguf
GGUF_MODEL_PATH=./models/Qwen2.5-14B-Instruct-Q8_0.gguf
GGUF_N_CTX=8192
GGUF_N_GPU_LAYERS=-1
TEMPERATURE=0.2
Development (Ollama)
LLM_BACKEND=ollama
OLLAMA_HOST=http://localhost:11434
OLLAMA_MODEL=arelgendy/qmodel:8.5
TEMPERATURE=0.2
ALLOWED_ORIGINS=*
Production (HuggingFace + GPU)
LLM_BACKEND=hf
HF_MODEL_NAME=Qwen/Qwen2.5-14B-Instruct
HF_DEVICE=cuda
TOP_K_SEARCH=30
TEMPERATURE=0.1
USE_RERANKER=true
ALLOWED_ORIGINS=yourdomain.com,api.yourdomain.com
USE_RERANKER=true (the default: better ranking and a far more reliable confidence gate, ~1 s per answer), increase TOP_K_SEARCH, use TEMPERATURE=0.1TOP_K_SEARCH and TOP_K_RETURN, reduce MAX_TOKENS, use OllamaRERANK_MIN_SCORE / DENSE_MIN_SCORE, lower TEMPERATURE — check the trade-off with python -m eval.run_eval --show-failureseval/golden.jsonl holds 147 labelled queries (Qur’an topic/text/reference, Hadith topic/text/authenticity, Fatwa, and out-of-scope negatives). Each case lists the dataset ids that count as a correct hit; where one source is decisive (5:90 for “is alcohol forbidden?”, while 2:219 alone would mislead), it is also listed under required and scored as complete@k. eval/run_eval.py runs them through retrieval (no LLM generation) using the local index:
python -m eval.run_eval --out eval/results/baseline.json # full pipeline, k = TOP_K_RETURN
python -m eval.run_eval --mode hybrid --k 20 # hybrid_search() alone
python -m eval.run_eval --only fatwa,negative --show-failures # subset + list misses
python -m eval.run_eval --compare eval/results/baseline.json # diff against a previous run
It reports Hit@1/3/k, MRR and Recall@k overall, by category and by language, plus the confidence gate’s false-reject rate (answer found but refused) and false-accept rate (out-of-scope queries let through). Run it before and after any retrieval or config change; env vars such as USE_RERANKER=false or TOP_K_SEARCH=100 apply as usual.
eval/run_answers.py runs the same golden set through the full pipeline with the LLM and checks what users read:
python -m eval.run_answers --limit 5 # smoke test
python -m eval.run_answers --only hadith_text,fatwa # subset
python -m eval.run_answers --out eval/results/answers.json # full run (~1 h at ~25 s/answer)
required sources are citedEvery answer is saved in the report for manual review.
The labels themselves need expert review: eval/label_review.csv lists every golden label (question, labelled source and its text) with columns for a reviewer’s verdict. Categories with 5–11 cases move 10–20 points on a single case, so read per-category numbers with that in mind.
docker-compose.yml is production-safe by default: it does not bind-mount
the repo, and it mounts QModel.index/metadata.json read-only so a dataset
update doesn’t require an image rebuild. docker-compose.override.yml is
picked up automatically for local dev and layers a full repo bind-mount on
top for live code reload.
cp .env.example .env # Configure backend (see Configuration section)
# Development (live code reload via docker-compose.override.yml)
docker-compose up
# Production (base file only, no bind-mount — runs the image's own code)
docker-compose -f docker-compose.yml up -d
docker build -t qmodel .
# With Ollama backend
docker run -p 8000:8000 \
--env-file .env \
--add-host host.docker.internal:host-gateway \
qmodel
# With HuggingFace backend
docker run -p 8000:8000 \
--env-file .env \
--env HF_TOKEN=your_token_here \
qmodel
# .env
LLM_BACKEND=gguf
GGUF_MODEL_PATH=./models/Qwen2.5-14B-Instruct-Q8_0.gguf
GGUF_N_CTX=8192
GGUF_N_GPU_LAYERS=-1
The .gguf file must be present under ./models/ before building/running the
image (see download command in GGUF Backend) —
no external daemon or network access needed at runtime, which is what makes
this backend suitable for bundling directly into an HF Space.
# .env
LLM_BACKEND=ollama
OLLAMA_HOST=http://host.docker.internal:11434
OLLAMA_MODEL=arelgendy/qmodel:8.5
Requires Ollama running on the host (ollama serve).
# .env
LLM_BACKEND=hf
HF_MODEL_NAME=Qwen/Qwen2.5-14B-Instruct
HF_DEVICE=auto
# Pass HF token
export HF_TOKEN=hf_xxxxxxxxxxxxx
docker-compose up
The shipped Dockerfile installs the CPU-only torch wheel (paired with
faiss-cpu) to keep the image small. That build cannot use a GPU even if one
is passed through. For real GPU acceleration, build a variant that installs
the CUDA-enabled torch wheel instead (drop the --extra-index-url
https://download.pytorch.org/whl/cpu line in the builder stage), then add:
services:
qmodel:
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
docker-compose -f docker-compose.yml up -d so the dev-only
docker-compose.override.yml (full repo bind-mount) isn’t applied — the
base file already omits it, restarts with on-failure:5, and mounts the
data files read-only.ALLOWED_ORIGINS instead of *QModel is fully OpenAI-compatible and works out of the box with Open-WebUI.
# Start QModel
python main.py
# Start Open-WebUI
docker run -d -p 3000:8080 --name open-webui ghcr.io/open-webui/open-webui:latest
http://localhost:8000/v1QModelversion: '3.8'
services:
qmodel:
build: .
ports:
- "8000:8000"
environment:
- LLM_BACKEND=ollama
- OLLAMA_HOST=http://ollama:11434
ollama:
image: ollama/ollama:latest
ports:
- "11434:11434"
web-ui:
image: ghcr.io/open-webui/open-webui:latest
ports:
- "3000:8080"
depends_on:
- qmodel
| Feature | Status |
|---|---|
| Chat | ✅ Full support |
| Streaming | ✅ stream: true |
| Multi-turn context | ✅ Handled by Open-WebUI |
| Temperature | ✅ Configurable |
| Token limits | ✅ max_tokens |
| Model listing | ✅ /v1/models |
| Source attribution | ✅ x_metadata.sources |
main.py ← FastAPI app + router registration
build_index.py ← Dataset compilation, E5 asymmetric embedding & FAISS indexing
app/
config.py ← Config class (env vars)
llm.py ← LLM providers (GGUF, Ollama, HuggingFace)
cache.py ← TTL-LRU async cache
arabic_nlp.py ← Arabic normalization, stopwords, Quranic root index
search.py ← Multi-vector FAISS + BM25, RRF, clustering, windowing, reranker
analysis.py ← Intent detection, analytics, counting
prompts.py ← Prompt engineering (persona, anti-hallucination)
models.py ← Pydantic schemas (Quran, Hadith, Fatwa, Chat)
state.py ← AppState, lifespan, multi-turn RAG pipeline
routers/
quran.py ← 6 Quran endpoints
hadith.py ← 5 Hadith endpoints
fatwa.py ← 3 Fatwa endpoints
chat.py ← /ask + OpenAI-compatible chat
ops.py ← health, models, debug scores
tests/
test_enhancements.py ← Unit tests for multi-vector, windowing, clustering, roots
test_fatwa.py ← Unit tests for fatwa endpoints & RAG integration
intfloat/multilingual-e5-large (passage: prefix for indexing, query: prefix for inference)IndexFlatIP for inner-product dense vector retrieval + inverted index for BM25history to resolve ambiguous pronouns and anaphoric references into standalone topical queries.query_ar) and English (query_en) query vectors using multilingual-e5-large with the mandatory query: prefix.AYAH_WINDOW_SIZE) for complete theological context.BAAI/bge-reranker-v2-m3 (USE_RERANKER, on by default). Five of those slots are reserved for the best BM25-only hits, which RRF would otherwise rank below anything both dense searches found. Each passage is scored with its text in the query’s language first, since the reranker truncates at 512 tokens and a long diacritized verse fills that on its own.RERANK_MIN_SCORE (or dense similarity DENSE_MIN_SCORE without the reranker). Context carries copyable citation lines: [REF] (verse + URLs), [HADITH_REF] (collection and number in English and Arabic, grade), [FATWA_REF] (source, scholar, URL).[REF] / [HADITH_REF] / [FATWA_REF] lines in the context| Operation | Time | Backend |
|---|---|---|
| Query (cached) | ~50ms | Both |
| Query (Ollama) | 400–800ms | Ollama |
| Query (HF GPU) | 500–1500ms | CUDA |
| Query (HF CPU) | 2–5s | CPU |
ollama serve # Ensure Ollama is running on host
# In Docker, use OLLAMA_HOST=http://host.docker.internal:11434
export HF_TOKEN=hf_xxxxxxxxxxxxx # Set token for gated models
HF_MODEL_NAME=mistralai/Mistral-7B-Instruct-v0.2neural-chatMAX_TOKENS to 1024docker-compose.ymlThis is expected — QModel rejects low-confidence queries. Try:
- More specific queries
- Lower RERANK_MIN_SCORE (reranker on) or DENSE_MIN_SCORE (reranker off) in .env
- Check raw scores: GET /debug/scores?q=your+query
docker-compose down && docker system prune
# Or change port: ports: ["8001:8000"]
This project uses open-source data from: - Qur’an JSON — Open source - Hadith API — Open source
See individual repositories for license details.
Made with ❤️ for Islamic scholarship.
Version 8.5.0 | October 2026 | Production-Ready