Qasd (قصد)
Understanding what Arabic shoppers mean, in any dialect.
Type "iPhone" into an Arabic store and people write it six ways: آيفون, أيفون, ايفون, ايفونه, iphone, ayfon. Add a model number and you get ١٥ next to 15. Ask for shoes and an Egyptian types جزمة, someone from the Gulf types جوتي, someone from the Levant types صرماية, and the catalog says حذاء.
A search engine has to understand all of them. Qasd is a small, honest benchmark and toolkit for that problem: Arabic query understanding (normalization, dialects, Arabizi, code-switching) and retrieval (BM25, multilingual embeddings, hybrid search), measured per dialect and per error type.
What's here
| Piece | What it does |
|---|---|
qasd.normalize |
Configurable Arabic normalizer: alef variants, ة/ه, ى/ي, hamza carriers, diacritics, tatweel, Arabic-Indic digits. Every rule is a switch, so its value can be measured. |
qasd.arabizi |
Baseline Arabizi detector and transliterator (sha7en → شاحن). |
qasd.retrieval |
BM25 (pure Python), dense retrieval with EmbeddingGemma 2 via sentence-transformers, and reciprocal-rank fusion. |
qasd.eval |
Recall@k, MRR, nDCG@k, reported overall, by dialect and by phenomenon. |
scripts/generate_queries.py |
LLM-generated dialect queries from product titles, flagged for human review. |
notebooks/finetune_colab.ipynb |
Fine-tunes EmbeddingGemma 2 on Arabic query → product pairs on a Colab GPU. |
Experiments
- Baselines: BM25 with and without normalization, then EmbeddingGemma 2 out of the box.
- Fine-tuning: EmbeddingGemma 2 trained on Arabic dialect query → product pairs, before vs after.
- Matryoshka sizes: how much accuracy 768 → 256 → 128 dimensions costs, against the speed and memory saved.
- Spoken queries: typed vs speech-to-text vs EmbeddingGemma 2's direct audio embeddings. Does the voice path return the same products as the typed one?
Results are reported per dialect and per error type, because an average hides exactly the cases this project is about.
Results
Nothing yet beyond the pipeline check on data/sample (18 synthetic queries). The real eval set is 100–200 hand-checked pairs; see data/README.md.
Quickstart
uv run --extra dev pytest -q # tests, no heavy installs
uv run scripts/run_eval.py --retriever bm25 --show-misses
uv run scripts/run_eval.py --retriever bm25 --no-normalize
uv run --extra dense scripts/run_eval.py --retriever dense # needs torch + the model
Notes
- EmbeddingGemma 2 must run in bfloat16 or float32, never float16.
qasd.retrieval.dense.pick_dtype()handles this. - All data is synthetic or public; no real customer queries. Every query records whether it was hand-written, LLM-generated or human-checked.
See docs/ARCHITECTURE.md, docs/EXPERIMENTS.md and docs/ROADMAP.md.
License: Apache-2.0.
Metadata
Release files for qasd 0.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| qasd-0.0.1.tar.gz | 18.9 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| qasd-0.0.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 29.9 kB
Release files / qasd-0.0.1.tar.gz
| Download URL | qasd-0.0.1.tar.gz |
|---|---|
| Size | 18.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ff4cbd8fbcb81ee5c7d7b7077057fec1bcc739c6e0880bc29f80dcda38dce1e6
|
|
BLAKE2b-256 checksum How to use checksums |
a2c4eebe1e6e4b5595857f39faf0c8bf1d6ddbad4616b6f4be4873e624b721b6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / qasd-0.0.1-py3-none-any.whl
| Download URL | qasd-0.0.1-py3-none-any.whl |
|---|---|
| Size | 11.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a0a2b43ce5df92c8f7437d3674ad5c61f8bf6136270208efc74b2e2682aa018c
|
|
BLAKE2b-256 checksum How to use checksums |
0cb3bcaed1b5f123a0ae28c1e926bce356f475e4740e34a0655b06a00c10b62e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for qasd 0.0.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.