Metadata-Version: 2.5
Name: parsrag
Version: 0.2.0
Summary: Build, evaluate, and ship Retrieval-Augmented Generation (RAG) for Persian text — offline-first, with a free Persian course.
Project-URL: Homepage, https://github.com/snikookar/Persian-RAG-Academy
Project-URL: Documentation, https://snikookar.github.io/Persian-RAG-Academy/
Project-URL: Repository, https://github.com/snikookar/Persian-RAG-Academy
Project-URL: Issues, https://github.com/snikookar/Persian-RAG-Academy/issues
Project-URL: Changelog, https://github.com/snikookar/Persian-RAG-Academy/blob/main/CHANGELOG.md
Author: Samane Nikookar
License-Expression: MIT
License-File: LICENSE
Keywords: bm25,course,embeddings,farsi,llm,nlp,persian,rag,retrieval,retrieval-augmented-generation
Classifier: Development Status :: 3 - Alpha
Classifier: Framework :: FastAPI
Classifier: Framework :: Pydantic :: 2
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Education
Classifier: Intended Audience :: Science/Research
Classifier: Natural Language :: English
Classifier: Natural Language :: Persian
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Education
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Text Processing :: Indexing
Classifier: Topic :: Text Processing :: Linguistic
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: numpy>=1.24
Requires-Dist: pydantic>=2.5
Provides-Extra: agent
Requires-Dist: langchain-core>=0.3; extra == 'agent'
Requires-Dist: langgraph>=0.2; extra == 'agent'
Provides-Extra: all
Requires-Dist: beautifulsoup4>=4.12; extra == 'all'
Requires-Dist: faiss-cpu>=1.8; extra == 'all'
Requires-Dist: fastapi>=0.110; extra == 'all'
Requires-Dist: httpx>=0.27; extra == 'all'
Requires-Dist: langchain-core>=0.3; extra == 'all'
Requires-Dist: langgraph>=0.2; extra == 'all'
Requires-Dist: ollama>=0.3; extra == 'all'
Requires-Dist: pgvector>=0.3; extra == 'all'
Requires-Dist: psycopg[binary]>=3.1; extra == 'all'
Requires-Dist: pypdf>=4.0; extra == 'all'
Requires-Dist: python-docx>=1.1; extra == 'all'
Requires-Dist: python-multipart>=0.0.9; extra == 'all'
Requires-Dist: sentence-transformers>=3.0; extra == 'all'
Requires-Dist: uvicorn>=0.29; extra == 'all'
Provides-Extra: api
Requires-Dist: fastapi>=0.110; extra == 'api'
Requires-Dist: python-multipart>=0.0.9; extra == 'api'
Requires-Dist: uvicorn>=0.29; extra == 'api'
Provides-Extra: dev
Requires-Dist: fpdf2>=2.7; extra == 'dev'
Requires-Dist: matplotlib>=3.7; extra == 'dev'
Provides-Extra: docs
Requires-Dist: mkdocs-material<10,>=9.5; extra == 'docs'
Requires-Dist: mkdocs<2,>=1.6; extra == 'docs'
Provides-Extra: faiss
Requires-Dist: faiss-cpu>=1.8; extra == 'faiss'
Provides-Extra: loaders
Requires-Dist: beautifulsoup4>=4.12; extra == 'loaders'
Requires-Dist: pypdf>=4.0; extra == 'loaders'
Requires-Dist: python-docx>=1.1; extra == 'loaders'
Provides-Extra: ollama
Requires-Dist: ollama>=0.3; extra == 'ollama'
Provides-Extra: openai
Requires-Dist: httpx>=0.27; extra == 'openai'
Provides-Extra: pgvector
Requires-Dist: pgvector>=0.3; extra == 'pgvector'
Requires-Dist: psycopg[binary]>=3.1; extra == 'pgvector'
Provides-Extra: st
Requires-Dist: sentence-transformers>=3.0; extra == 'st'
Description-Content-Type: text/markdown

# Persian RAG Academy

**[فارسی](https://github.com/snikookar/Persian-RAG-Academy/blob/main/README.fa.md)** · **[Documentation (Persian)](https://snikookar.github.io/Persian-RAG-Academy/)** · **[Lesson summaries in English](https://github.com/snikookar/Persian-RAG-Academy/blob/main/docs/en/index.md)** · **[Changelog](https://github.com/snikookar/Persian-RAG-Academy/blob/main/CHANGELOG.md)**

[![CI](https://github.com/snikookar/Persian-RAG-Academy/actions/workflows/ci.yml/badge.svg)](https://github.com/snikookar/Persian-RAG-Academy/actions/workflows/ci.yml)
[![Coverage](https://img.shields.io/endpoint?url=https%3A%2F%2Fsnikookar.github.io%2FPersian-RAG-Academy%2Fbadges%2Fcoverage.json)](https://github.com/snikookar/Persian-RAG-Academy/actions/workflows/ci.yml)
[![mypy](https://img.shields.io/badge/types-mypy%20checked-2a6db2.svg)](https://github.com/snikookar/Persian-RAG-Academy/actions/workflows/ci.yml)
[![Docs](https://github.com/snikookar/Persian-RAG-Academy/actions/workflows/docs.yml/badge.svg)](https://snikookar.github.io/Persian-RAG-Academy/)
[![PyPI](https://img.shields.io/pypi/v/parsrag.svg)](https://pypi.org/project/parsrag/)
[![Python](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12-blue.svg)](https://github.com/snikookar/Persian-RAG-Academy/blob/main/pyproject.toml)
[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/snikookar/Persian-RAG-Academy/blob/main/LICENSE)

**Build, evaluate and ship Retrieval-Augmented Generation (RAG) for Persian text — a free
15-lesson course in Persian, and `parsrag`, the library you rebuild along the way.**

Every lesson has a Persian explanation with diagrams and measured results, starter code with
TODOs, and an automatic grader with bilingual hints. The finished library is the reference
solution. Everything — tests, grader, CI, examples — runs **offline, with zero API keys and no
model downloads**; real models (any OpenAI-compatible API, Ollama, sentence-transformers) are
optional extras.

## Why this exists

Most RAG tutorials assume English. Persian text breaks a naive pipeline in quiet ways: nothing
crashes, the right document simply is not found. Three examples from the lessons:

| Problem | Before | After | Lesson |
|---|---|---|---|
| **Arabic vs Persian letters.** «كتاب» (Arabic KAF, YEH) and «کتاب» look identical but are different strings, so a rules document written on an Arabic keyboard never matches the question. | `كتاب هاي  من ۱۲٣` | `کتاب‌های من 123` — one spelling, ZWNJ restored, one digit system (`parsrag.normalize`) | [02](https://github.com/snikookar/Persian-RAG-Academy/blob/main/lessons/02-normalization/README.fa.md) |
| **Broken PDF text.** Many PDF producers store glyph shapes (presentation forms) and some store each line in *visual* order. Copy-paste looks fine; search finds nothing. | `.ﺪﻨﮐﯽﻣ ﺐﺼﻧ ﻪﻧﺎﺒﺷ ﺖﻧﺎﻣﺍ ﯼﺍﺮﺑ ﺪﻨﻤﺷﻮﻫ ٔﻪﺴﻔﻗ ﮏﯾ` (word recall 0.07) | `یک قفسهٔ هوشمند برای امانت شبانه نصب می‌کند.` (word recall 1.00) | [03](https://github.com/snikookar/Persian-RAG-Academy/blob/main/lessons/03-loading-documents/README.fa.md) |
| **Colloquial questions.** Users write «چقدره»، «میشه»، «تو تهران»; documents say «چقدر است»، «می‌شود»، «در تهران». | `هزینهٔ پیک تو تهران چقدره و مرجوعی تا کی میشه؟` | `هزینهٔ پیک در تهران چقدر است و مرجوعی تا کی می‌شود؟` + split into two sub-questions | [11](https://github.com/snikookar/Persian-RAG-Academy/blob/main/lessons/11-agentic-rag/README.fa.md) |

On the course questions, normalisation moves hit@1 of the naive pipeline from 0.81 to 0.90, and
switching off only the letter unification step drops it straight back to 0.81. The lessons
measure every other decision the same way.

## Architecture

The full `parsrag` pipeline as assembled by `PersianRAG` (lesson 13). Every box is a
replaceable component with an offline default.

```mermaid
flowchart LR
    subgraph Indexing
        F["Files<br/>PDF · DOCX · HTML · MD · TXT"] --> L["loaders<br/>(03) bidi + PDF repair"]
        L --> N["normalize<br/>(02)"]
        N --> C["chunking<br/>(04) recursive + headings"]
        C --> E["embeddings<br/>(05)"]
        E --> S[("stores<br/>(06) memory · FAISS · pgvector")]
        C --> B[("BM25 index<br/>(07)")]
    end
    subgraph Answering
        Q["Question"] --> QN["normalize"]
        QN --> H["hybrid retrieval<br/>(07) BM25 + dense + RRF"]
        S --> H
        B --> H
        H --> R["rerank<br/>(08) 20 → 4"]
        R --> SEC["injection guard (14, opt-in)<br/>detect · sanitise · sandbox"]
        SEC --> G["grounded generation<br/>(09) citations · «نمی‌دانم»"]
        G --> A["Answer + citations"]
    end
    A -.-> EV["eval (10)<br/>metrics · bootstrap · CI gate"]
    Q -.-> AG["agent (11)<br/>rewrite · grade · retry"]
    AG -.-> H
    A --> API["api (12) FastAPI · SSE · cache<br/>cli + web UI (13)"]
```

### Course map

```mermaid
flowchart TB
    L00["00 Setup<br/>FakeLLM · HashingEmbedding"] --> L01["01 RAG in 50 lines"]
    L01 --> L02["02 Persian normalisation"] --> L03["03 Loading documents<br/>PDF · RTL"]
    L03 --> L04["04 Chunking"] --> L05["05 Embeddings<br/>benchmark"] --> L06["06 Vector stores"]
    L06 --> L07["07 Hybrid search<br/>BM25 + RRF"] --> L08["08 Reranking"]
    L08 --> L09["09 Grounded generation"] --> L10["10 Evaluation<br/>CI gate"]
    L10 --> L11["11 Agentic RAG<br/>LangGraph"] --> L12["12 Production API<br/>FastAPI · Docker"]
    L12 --> L13["13 Capstone<br/>your documents + UI"]
    L13 --> L14["14 RAG security<br/>prompt injection in documents"]
```

## Results

> **Read these numbers for what they are.** They come from the repository's small synthetic
> Persian corpus (25 original documents, 59 answerable questions plus 46 answerable
> paraphrases) with offline, deterministic models (`HashingEmbedding`, an extractive `FakeLLM`,
> a rule-based agent LLM). They show *how* to measure and *which way* each choice moves the
> numbers — not how a neural model would score. To benchmark real embedding models on the same
> questions, open the [Colab notebook](https://github.com/snikookar/Persian-RAG-Academy/blob/main/notebooks/benchmark_real_embeddings.ipynb) (free GPU,
> both question sets) or run [`examples/benchmark_embeddings.py`](https://github.com/snikookar/Persian-RAG-Academy/blob/main/examples/benchmark_embeddings.py)
> on your own machine, and send the measured rows to lesson 05's results table. All charts are regenerated by
> [`examples/make_readme_charts.py`](https://github.com/snikookar/Persian-RAG-Academy/blob/main/examples/make_readme_charts.py); the numbers behind them
> are in [`docs/eval/readme_numbers.json`](https://github.com/snikookar/Persian-RAG-Academy/blob/main/docs/eval/readme_numbers.json).

| | |
|---|---|
| ![Normalisation impact](https://raw.githubusercontent.com/snikookar/Persian-RAG-Academy/main/docs/images/readme_normalization.png) | ![PDF repair](https://raw.githubusercontent.com/snikookar/Persian-RAG-Academy/main/docs/images/readme_pdf_repair.png) |
| **Normalisation (02).** Unifying Arabic/Persian letters is the step that matters: without it hit@1 stays at the raw-text level (0.81 → 0.90 with it). | **PDF repair (03).** NFKC fixes logical-order PDFs (0.07 → 0.94 word recall) but not visual-order ones (0.08); order repair and ZWNJ recovery bring both to 1.00. |
| ![Chunking strategies](https://raw.githubusercontent.com/snikookar/Persian-RAG-Academy/main/docs/images/readme_chunking.png) | ![Hybrid search](https://raw.githubusercontent.com/snikookar/Persian-RAG-Academy/main/docs/images/readme_hybrid.png) |
| **Chunking (04).** Structure-aware chunks with the heading prepended beat fixed windows at every size (MRR 0.66 → 0.82 at 200 characters). | **Hybrid search (07).** RRF over BM25 and dense is the best untuned method on the original questions (MRR 0.93). Paraphrases halve every lexical method — the gap only a real model closes. |
| ![Reranking](https://raw.githubusercontent.com/snikookar/Persian-RAG-Academy/main/docs/images/readme_rerank.png) | ![Agent vs pipeline](https://raw.githubusercontent.com/snikookar/Persian-RAG-Academy/main/docs/images/readme_agent.png) |
| **Reranking (08).** Reranking 20 candidates lifts R@1 0.86 → 0.88 (original) and 0.33 → 0.37 (paraphrase); lesson 10's paired bootstrap shows the interval contains 0. | **Agent vs pipeline (11).** The agent answers more paraphrases (0.22 → 0.35) at ~4× the LLM calls; on the original set its answer-F1 gain (+0.025) is not significant. |

The end-to-end pipeline is gated in CI against [`docs/eval/baseline.json`](https://github.com/snikookar/Persian-RAG-Academy/blob/main/docs/eval/baseline.json)
(`python -m parsrag.eval gate`); the full report is in [`docs/eval/report.md`](https://github.com/snikookar/Persian-RAG-Academy/blob/main/docs/eval/report.md).

## Quick start

### a) Learner: take the course

```bash
git clone https://github.com/snikookar/Persian-RAG-Academy.git
cd Persian-RAG-Academy
python -m venv .venv && source .venv/bin/activate
pip install -e ".[loaders]" pytest ruff
python -m grader run 00          # then open lessons/00-setup/README.fa.md
```

Each lesson's `starter/` has TODOs; `python -m grader run NN` tests your version and prints a
Persian/English hint for every failing check. `python -m grader list` shows all lessons.

### b) Library user

```bash
pip install parsrag                       # core: pydantic + numpy only
pip install "parsrag[loaders,openai]"     # PDF/DOCX/HTML + any OpenAI-compatible model
```

> `parsrag` is not on PyPI yet; until the first release, install from a clone with
> `pip install -e .`.

```python
from parsrag import PersianRAG

rag = PersianRAG()                                   # the course defaults, fully offline
rag.add_documents(["data/fixtures/corpus"])          # folders, files or Document objects
answer = rag.ask("هزینهٔ پیک در تهران چقدر است؟")
print(answer.text)                                   # هزینهٔ پیک در تهران 200 هزار تومان است.
for c in answer.citations:
    print(c.doc_id, c.section)                       # kahroba-shipping شیوه‌های ارسال سفارش
print(rag.ask("پایتخت فرانسه کجاست؟").text)          # نمی‌دانم
```

The default LLM is the offline extractive `FakeLLM`; pass `llm=OpenAICompatibleLLM(...)` or
configure `parsrag.toml` for a real model ([lesson 13](https://github.com/snikookar/Persian-RAG-Academy/blob/main/lessons/13-capstone/README.fa.md)).
The same pipeline from the command line:

```bash
parsrag init                                        # commented parsrag.toml
parsrag ingest docs/ --calibrate dev.jsonl          # index + calibrate «نمی‌دانم»
parsrag ask "شرایط مرجوعی کالا چیست؟"
parsrag eval test.jsonl --baseline baseline.json    # lesson 10 report + regression gate
```

### c) Service: API + web UI

```bash
docker compose up --build        # API + PostgreSQL/pgvector → http://localhost:8000
```

Or without Docker: `pip install -e ".[api,loaders]"` and `parsrag serve --docs data/fixtures/corpus`.
Endpoints: `GET /health`, `POST /ingest`, `POST /ingest/files`, `GET /documents`, `POST /ask`,
`POST /ask/stream` (Server-Sent Events) and OpenAPI docs at `/docs`
([lesson 12](https://github.com/snikookar/Persian-RAG-Academy/blob/main/lessons/12-production-api/README.fa.md)). The right-to-left web UI streams answers
and opens every citation at its exact span in the source document:

![parsrag web UI: streamed answer with clickable citations and the source passage](https://raw.githubusercontent.com/snikookar/Persian-RAG-Academy/main/docs/images/capstone_ui.png)

## Lessons

The lessons are in Persian. **[Read the lesson summaries in English](https://github.com/snikookar/Persian-RAG-Academy/blob/main/docs/en/index.md)** — one
short page per lesson with what you build, the key ideas and the measured results (also on the
[docs site](https://snikookar.github.io/Persian-RAG-Academy/en/)).

| # | Lesson | درس | GitHub | Docs |
|---|---|---|---|---|
| 00 | Setup, offline tools, grader | آماده‌سازی محیط | [README](https://github.com/snikookar/Persian-RAG-Academy/blob/main/lessons/00-setup/README.fa.md) | [site](https://snikookar.github.io/Persian-RAG-Academy/lessons/00-setup/) |
| 01 | RAG in 50 lines | RAG در ۵۰ خط | [README](https://github.com/snikookar/Persian-RAG-Academy/blob/main/lessons/01-rag-in-50-lines/README.fa.md) | [site](https://snikookar.github.io/Persian-RAG-Academy/lessons/01-rag-in-50-lines/) |
| 02 | Persian normalisation | نرمال‌سازی متن فارسی | [README](https://github.com/snikookar/Persian-RAG-Academy/blob/main/lessons/02-normalization/README.fa.md) | [site](https://snikookar.github.io/Persian-RAG-Academy/lessons/02-normalization/) |
| 03 | Loading Persian documents (PDF, DOCX, HTML, RTL) | بارگذاری اسناد فارسی | [README](https://github.com/snikookar/Persian-RAG-Academy/blob/main/lessons/03-loading-documents/README.fa.md) | [site](https://snikookar.github.io/Persian-RAG-Academy/lessons/03-loading-documents/) |
| 04 | Chunking Persian text | قطعه‌بندی متن فارسی | [README](https://github.com/snikookar/Persian-RAG-Academy/blob/main/lessons/04-chunking/README.fa.md) | [site](https://snikookar.github.io/Persian-RAG-Academy/lessons/04-chunking/) |
| 05 | Embeddings and a retrieval benchmark | برداری‌سازی و بنچمارک بازیابی | [README](https://github.com/snikookar/Persian-RAG-Academy/blob/main/lessons/05-embeddings/README.fa.md) | [site](https://snikookar.github.io/Persian-RAG-Academy/lessons/05-embeddings/) |
| 06 | Vector stores (in-memory, FAISS, pgvector) | انباره‌های برداری | [README](https://github.com/snikookar/Persian-RAG-Academy/blob/main/lessons/06-vector-stores/README.fa.md) | [site](https://snikookar.github.io/Persian-RAG-Academy/lessons/06-vector-stores/) |
| 07 | Hybrid search (Persian BM25 + dense + RRF) | جست‌وجوی ترکیبی | [README](https://github.com/snikookar/Persian-RAG-Academy/blob/main/lessons/07-hybrid-search/README.fa.md) | [site](https://snikookar.github.io/Persian-RAG-Academy/lessons/07-hybrid-search/) |
| 08 | Reranking | بازرتبه‌بندی | [README](https://github.com/snikookar/Persian-RAG-Academy/blob/main/lessons/08-reranking/README.fa.md) | [site](https://snikookar.github.io/Persian-RAG-Academy/lessons/08-reranking/) |
| 09 | Grounded generation, citations, honest «I don't know» | تولید مستند با ارجاع و «نمی‌دانم» | [README](https://github.com/snikookar/Persian-RAG-Academy/blob/main/lessons/09-grounded-generation/README.fa.md) | [site](https://snikookar.github.io/Persian-RAG-Academy/lessons/09-grounded-generation/) |
| 10 | Evaluation and a CI quality gate | ارزیابی و دروازهٔ CI | [README](https://github.com/snikookar/Persian-RAG-Academy/blob/main/lessons/10-evaluation/README.fa.md) | [site](https://snikookar.github.io/Persian-RAG-Academy/lessons/10-evaluation/) |
| 11 | Agentic RAG with LangGraph | RAG عامل‌محور با LangGraph | [README](https://github.com/snikookar/Persian-RAG-Academy/blob/main/lessons/11-agentic-rag/README.fa.md) | [site](https://snikookar.github.io/Persian-RAG-Academy/lessons/11-agentic-rag/) |
| 12 | Production API (FastAPI, streaming, cache, Docker) | API تولیدی | [README](https://github.com/snikookar/Persian-RAG-Academy/blob/main/lessons/12-production-api/README.fa.md) | [site](https://snikookar.github.io/Persian-RAG-Academy/lessons/12-production-api/) |
| 13 | Capstone: RAG over your own documents + UI | پروژهٔ پایانی | [README](https://github.com/snikookar/Persian-RAG-Academy/blob/main/lessons/13-capstone/README.fa.md) | [site](https://snikookar.github.io/Persian-RAG-Academy/lessons/13-capstone/) |
| 14 | RAG security: prompt injection hidden in documents | امنیت RAG: تزریق پرامپت در اسناد | [README](https://github.com/snikookar/Persian-RAG-Academy/blob/main/lessons/14-rag-security/README.fa.md) | [site](https://snikookar.github.io/Persian-RAG-Academy/lessons/14-rag-security/) |

## FAQ

**Do I need an API key or a GPU?** No. Every lesson, test and example runs offline on a laptop.
Real models are optional and only used when you pass a flag such as `--model st:NAME` or `--llm NAME`.

**Do I need to read Persian?** The lessons are in Persian; the code, docstrings and this README
are in English, so the library itself is usable without it.

**Why are the benchmark numbers so high on the original questions and so low on paraphrases?**
The original questions reuse the documents' wording, which favours lexical methods; the
paraphrase set was written to expose that. Neural embeddings are expected to narrow the gap —
measure it with the [Colab notebook](https://github.com/snikookar/Persian-RAG-Academy/blob/main/notebooks/benchmark_real_embeddings.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/snikookar/Persian-RAG-Academy/blob/main/notebooks/benchmark_real_embeddings.ipynb) or
`examples/benchmark_embeddings.py`.

**Can I use `parsrag` for non-Persian text?** The pipeline is generic, but normalisation,
tokenisation, the stop-word list and the prompts are Persian-specific.

**Is `HashingEmbedding` a real embedding model?** No — it hashes words and character n-grams into
a vector. It is deterministic and dependency-free, which is what tests and CI need. Lesson 05
explains when to replace it.

**Where does the sample data come from?** Every document and question in `data/fixtures/` was
written for this repository (a fictional shop «کهربا» and library «مهرگان»); no copyrighted text.

## Contributing

Contributions are welcome — bug reports, lesson feedback, fixes, new lessons. Read
[CONTRIBUTING.md](https://github.com/snikookar/Persian-RAG-Academy/blob/main/CONTRIBUTING.md) (how lessons are structured, fixture-writing rules, running the
grader and the eval gate) and the [Code of Conduct](https://github.com/snikookar/Persian-RAG-Academy/blob/main/CODE_OF_CONDUCT.md). Security issues:
see [SECURITY.md](https://github.com/snikookar/Persian-RAG-Academy/blob/main/SECURITY.md).

Looking for a first task? See
[good first contributions](https://github.com/snikookar/Persian-RAG-Academy/blob/main/CONTRIBUTING.md#help-wanted--good-first-contributions).

## License

[MIT](https://github.com/snikookar/Persian-RAG-Academy/blob/main/LICENSE). All Persian sample data in `data/fixtures/` was written originally for this
repository and is covered by the same license. The web UI bundles the Vazirmatn font
(© The Vazirmatn Project Authors), licensed under the
[SIL Open Font License 1.1](https://github.com/snikookar/Persian-RAG-Academy/blob/main/src/parsrag/api/static/fonts/OFL.txt).

## Cite this project

If you use the course or `parsrag` in teaching or research, please cite it
([CITATION.cff](https://github.com/snikookar/Persian-RAG-Academy/blob/main/CITATION.cff)):

```bibtex
@software{nikookar_persian_rag_academy_2026,
  author  = {Nikookar, Samane},
  title   = {Persian RAG Academy: a course and library for Retrieval-Augmented Generation on Persian text},
  year    = {2026},
  version = {0.1.0},
  url     = {https://github.com/snikookar/Persian-RAG-Academy},
  license = {MIT}
}
```
