Türkçe RAG için embedding modeli karşılaştırma harness'i.
Tek bir soruya deneysel cevap arar: ana platformda (enterprise-ai-assistant) hangi embedding modeline geçmeliyiz? Mevcut prod modeli (fastembed BGE-Base 768d) ile yeni adaylar (bge-m3, Qwen3-Embedding) kendi Türkçe corpus'umuz üzerinde recall / MRR / NDCG ile kıyaslanır. "Qwen3 global MMTEB lideri" demek yeterli değil — karar körlemesine değil, bizim verimizdeki ölçümle verilir.
Bu repo bilinçli olarak ana repo'dan ayrıdır. Ana repo'da kararın gerekçesi olarak sadece buraya bir link bırakılır; eval gürültüsü PR akışına girmez.
Embedding modeli değiştirmek ucuz bir konfig değişikliği değildir:
2.0 re-index kuralı: Embedding modeli değişti → eski vektörler hiçbir işe yaramaz. Boyut (768 vs 1024) ya da vektör dağılımı farklıdır; eski ve yeni vektörler aynı uzayda karşılaştırılamaz / karıştırılamaz. Geçiş = tüm corpus'u sıfırdan yeniden embed etmek (re-index).
Maliyeti yüksek ve geri dönüşü zahmetli olduğu için, geçişten önce kendi verimizde ölçüp emin olmak gerekir. Bu repo o ölçümü yapar.
| Anahtar | Model | Boyut | Not |
|---|---|---|---|
bge-m3 |
BAAI/bge-m3 |
1024 | Çok dilli, dense (sentence-transformers) |
qwen3-0.6b |
Qwen/Qwen3-Embedding-0.6B |
1024 | 2026 MMTEB lideri; query'de instruction prompt'u |
baseline-bge-base |
BAAI/bge-base-en-v1.5 |
768 | Mevcut prod baseline (fastembed). İngilizce-odaklı; Türkçede düşük çıkması beklenir |
Yeni model eklemek = eval/providers.py içindeki
REGISTRY'ye tek satır.
Baseline model id'si prod ile birebir aynı olmalı. enterprise-ai-assistant tarafındaki fastembed model adını teyit edip gerekirse
REGISTRY'yi güncelle.
Altın küme, sahibinin yüksek lisans tezinden üretilmiştir — bildiğimiz bir konu olduğu için doğru cevap (relevant) etiketlemesi güvenilirdir.
- corpus (golden_set/corpus.jsonl): tez PDF'i
scripts/build_golden_set.pyile temizlenip ~700 karakterlik pasajlara bölünür ({id, text, source}). Üst bilgi/sayfa numaraları atılır; kapak, içindekiler, dizinler ve kaynakça hariç tutulur. - queries (golden_set/queries.jsonl): Türkçe
factual sorular (
{q_id, query, relevant_ids[]}).relevant_ids, cevabı doğrudan içeren chunk(lar)dır ve tek tek corpus'ta okunarak doğrulanmıştır. - Etiketleme politikası: Bir chunk, soruyu kendi başına karşılayacak kadar açık cevap içeriyorsa relevant sayılır (özet/sonuç + ilgili bölüm gövdesi). Türkçe sorularda Türkçe kaynak chunk'lar hedeflenir.
Gizlilik: Tez savunma öncesi olduğundan bu repo private tutulur. Ham PDF (
Only_tez.pdf) commit edilmez (.gitignore); yalnızca chunk'lanmış metin corpus'ta yer alır. corpus'u yeniden üretmek için ham PDF'i repo köküne koyupbuild_golden_set.pyçalıştır.
corpus ve query aynı normalizasyondan geçer (tutarlılık şart). Varsayılan: NFC + whitespace sadeleştirme + Türkçe-doğru lowercase.
Türkçe lowercase tuzağı:
"I".lower()→"i"(İngilizce). DoğrusuI → ıveİ → i.eval/normalize.pybunu.lower()öncesi elle çevirir. Örn. "IMU" → "ımu"; corpus ve query aynı şekilde dönüştüğü için eşleşme bozulmaz.--no-lowercaseile cased ham metni de deneyebilirsin.
- recall@k — doğru kaynak, ilk k sonuç içinde mi? (kaynak bulma oranı)
- MRR — ilk doğru kaynağın sırası ne kadar üstte? (
1/rankortalaması) - NDCG@10 — sıralama kalitesi; doğru kaynaklar ne kadar üstteyse o kadar yüksek.
RAG için recall@1 (ilk sıraya doğru kaynağı koymak) ve NDCG@10 (sıralama) birlikte bakılması gereken iki ana sinyaldir.
uv ile, Python 3.12:
uv sync
# Tüm modeller + tüm metrikler, tek komut (modeller ilk koşuda iner):
bash scripts/run-all.sh
# veya doğrudan:
uv run python -m eval run \
--corpus golden_set/corpus.jsonl \
--queries golden_set/queries.jsonl \
--models bge-m3,qwen3-0.6b,baseline-bge-baseSonuç results/summary.md (insan-okur tablo + kazanan +
baseline'a göre delta) ve results/raw.json olarak yazılır.
Embedding'in kelime değil anlam yakaladığını somut görmek için:
uv run python scripts/sanity_cosine.py # bge-m3Paraphrase çifti ("Toplantı yarına ertelendi" / "Görüşme bir gün sonraya alındı") yüksek; alakasız çift düşük cosine vermeli. Ortak kelime olmadan yüksek benzerlik = modelin anlamı kavradığının kanıtı.
İlk tur (2026-05-29): 234 chunk corpus, 22 soru, Türkçe lowercase açık, topk=10.
bge-m3 yerel Ollama'dan (F16), Qwen3 + baseline sentence-transformers/fastembed
(full precision). Tam tablo + delta: results/summary.md.
| Model | recall@1 | recall@5 | recall@10 | MRR | NDCG@10 |
|---|---|---|---|---|---|
| bge-m3 | 0.4242 | 0.6061 | 0.7045 | 0.7835 | 0.6552 |
| qwen3-0.6b | 0.1894 | 0.4015 | 0.4621 | 0.4422 | 0.3687 |
| baseline-bge-base | 0.0909 | 0.2424 | 0.3333 | 0.1966 | 0.2153 |
Kazanan: bge-m3 — NDCG@10'da baseline'a göre +0.44, Qwen3'e göre +0.29. recall@1 baseline'ın ~4.6 katı (0.09 → 0.42).
- Migrasyon kararı net: mevcut prod modeli (fastembed BGE-Base 768d, İngilizce-odaklı) Türkçe corpus'ta en zayıfı. bge-m3'e geçiş büyük kazanç.
- Sürpriz: Qwen3-Embedding (2026 MMTEB global lideri) burada bge-m3'ün yarısı kadar. Yani global sıralama Türkçe'ye doğrudan transfer olmadı — körlemesine Qwen3'e geçmek yanlış olurdu. Eval'in amacı tam da buydu.
- Adil olmak için caveat'lar (Qwen3 aleyhine olabilecekler):
- Test edilen en küçük varyant (0.6B); MMTEB lideri skorlar 4B/8B'den. bge-m3 (568M) ise boyutça yakın ve özellikle çok dilli eğitilmiş.
- Türkçe lowercase açıktı.
--no-lowercaseablasyonu koşuldu (results/ablation-nolowercase.md): lowercase tüm cased modellere hafif zarar veriyor, en çok Qwen3'e (+0.056 NDCG@10). Ama Qwen3 no-lowercase'de bile (0.42) bge-m3'ün (0.69) çok altında — confound kararı değiştirmiyor. - Query instruction'ı generic İngilizce; Qwen3 instruction'a duyarlıdır.
Sonuç: bu corpus ve dağıtılabilir 0.6B boyutunda bge-m3 açık tercih. Qwen3 düşünülecekse 4B/8B varyantı + instruction/normalize ablasyonu ile tekrar ölçülmeli; "MMTEB lideri" varsayımına güvenilmemeli. Ayrıca prod'da Türkçe için lowercase'i kapatmak küçük ama tutarlı bir kazanç sağlıyor.
Yeniden üretmek için:
uv run python -m eval run --models bge-m3-ollama,qwen3-0.6b,baseline-bge-base
# tam ST (Ollama yerine) için: --models bge-m3,qwen3-0.6b,baseline-bge-baseeval/ harness: normalize, providers (REGISTRY), embed, retrieve, score, __main__
golden_set/ corpus.jsonl + queries.jsonl (tezden üretilmiş)
scripts/ build_golden_set.py, run-all.sh, sanity_cosine.py
tests/ fixtures/ + test_smoke.py (CI: gerçek modellerle uçtan uca)
results/ summary.md (insan-okur), raw.json (gitignore)
CI (.github/workflows/smoke.yml): minik fixture üzerinde üç modeli de koşturup metriklerin non-zero olduğunu doğrular.
MIT — bkz. LICENSE.