RAG: getirim iyi olduğunda çalışır.
RAG bir mimari değil, bir ödünleşme. Çoğu ekip modeli suçlarken aslında getirim katmanını hiç ölçmemiş oluyor. Aşağısı, üretimde çalışan bir sistemin nasıl kurulduğu ve nerede kırıldığı üzerine.
Bir dil modeli yalnızca eğitildiği şeyi bilir. Şirketinin dünkü politika değişikliğini, müşterinin sözleşme maddesini, dün akşam kapanan destek talebini bilmez ve bilemez. RAG — Retrieval-Augmented Generation, Türkçesiyle getirimle desteklenmiş üretim — bu boşluğu kapatmanın en yaygın yolu: modeli yeniden eğitmek yerine, soru sorulduğu anda ilgili belgeleri bulup bağlam penceresine koymak ve cevabı onların üzerinden yazdırmak.
Mekanik üç adımda özetlenebilir. Getir: kullanıcının sorgusuyla bilgi tabanını ara, en alakalı parçaları seç. Ekle: bu parçaları isteme (prompt) iliştir. Üret: modelden yalnızca verilen bağlama dayanarak cevaplamasını iste. Kazanç açık — bilgi tabanı güncellendiğinde model yeniden eğitilmez, sadece indeks yenilenir; cevap kaynağa dayandığı için alıntılanabilir ve denetlenebilir.
Fikir 2020'de Lewis ve arkadaşlarının makalesiyle isimlendirildi, ama yeni değildi. Basit de görünüyor. Üretimde basit değil.
Çünkü bir RAG sistemi kurup üretime aldıysan ve model "halüsinasyon görüyor" diyorsan, muhtemelen model halüsinasyon görmüyor. Ona yanlış belge veriliyor ve o da elindekiyle en makul cümleyi kuruyor. Dil modeli, bağlam penceresine ne koyduysan onun üzerinden akıl yürütür. Yanlış paragrafı verip doğru cevap beklemek, yanlış dosyayı açıp derleyiciden farklı bir program çıkmasını beklemeye benziyor. Sistemin kalitesi neredeyse tamamen getirim katmanının kalitesine bağlı ve o katman genellikle hiç ölçülmüyor.
Üretim seviyesinde RAG'de model bir bileşen, getirim ise sistemin kendisi.
01 / ÇerçeveYanlış soruyla başlamak
Çoğu ekip şu soruyla başlıyor: "Hangi vektör veritabanını kullanmalıyım?" Bu, yanlış yerden giriş. Doğru sıra tersine işliyor:
- Kullanıcı hangi soruları soruyor? Gerçek sorgu dağılımını topla. Sentetik sorularla tasarlanan sistemler üretimde çöküyor, çünkü insanlar yazım hatası yapar, kısaltma kullanır, bağlamı eksik verir.
- Cevap belgede nerede duruyor? Cevap tek bir paragrafta mı, yoksa üç ayrı bölümün sentezi mi? Bu, parçalama stratejini belirler, tersi değil.
- Yanlış cevabın maliyeti ne? Bir destek botunun yanlışı ile bir sözleşme analiz aracının yanlışı aynı şey değil. Bu, eşik değerlerini ve "bilmiyorum" davranışını belirler.
- Ancak bundan sonra altyapı seçilir.
02 / GetirimKelime, vektör ve ikisinin toplamı
Yaygın yanılgı: gömme (embedding) tabanlı arama, anahtar kelime aramasının modern ve üstün halidir. Değil. İkisi farklı hata modlarına sahip ve hataları birbirini örtüyor.
Seyrek getirim (BM25)
BM25, kelime sıklığı ve belge uzunluğunu tartan klasik bir sıralama fonksiyonu. Nadir tokenlarda çok güçlü: ürün kodları, hata mesajları, kişi adları, mevzuat maddeleri. ORA-01555 aradığında BM25 tam isabet verir, gömme modeli ise bunu "veritabanı hatası" semantik uzayına yerleştirip yakın ama yanlış on belge getirir.
Yoğun getirim (embedding + ANN)
Yoğun getirim eşanlamlıyı ve parafrazı yakalar. "Ödememi nasıl iptal ederim" sorgusu, içinde "iptal" kelimesi hiç geçmeyen "aboneliğinizi sonlandırma" başlıklı belgeye ulaşır. BM25 bunu ıskalar.
Hibrit ve füzyon
Pratikte ikisi paralel çalıştırılıp sonuçlar birleştirilir. En dayanıklı yöntem Reciprocal Rank Fusion: skorları normalize etmeye çalışmaz, sadece sıralamalara bakar. Bu yüzden farklı ölçeklerdeki sistemleri güvenle harmanlar.
# Reciprocal Rank Fusion — skor değil, sıra birleştirir. # k=60 literatürde standart; sıralamanın kuyruğunu yumuşatır. def rrf(rankings, k=60): scores = defaultdict(float) for ranking in rankings: # [bm25_ids, dense_ids, ...] for rank, doc_id in enumerate(ranking): scores[doc_id] += 1.0 / (k + rank + 1) return sorted(scores, key=scores.get, reverse=True) bm25 = opensearch.search(query, size=50) # seyrek dense = qdrant.search(embed(query), limit=50) # yoğun candidates = rrf([bm25, dense])[:30] # reranker'a gidecek
Bir kurumsal doküman aramasında yalnız yoğun getirimden hibrite geçiş, Recall@20'yi 0.71'den 0.89'a taşıdı. Değişen tek şey BM25'in eklenmesiydi. Model, gömme modeli, parça boyutu — hepsi aynı kaldı.
03 / HazırlıkParçalama, gerçekten önemli
Parçalama (chunking) bir ön işleme detayı gibi görünür, oysa getirimin çözünürlüğünü belirleyen şeydir. İndeksteki parça, aramanın erişebileceği en küçük birimdir: bir cevap iki parçaya bölünmüşse hiçbir sıralama algoritması onu bütün halinde geri getiremez. Sistemin tavanı, belgeyi kestiğin anda çizilmiş olur.
Yine de en yaygın varsayılan, metni 512 token'lık sabit pencerelere bölmek. Bu, belgenin anlamını değil karakter sayısını dikkate alan bir karar. Sonucunda maddenin şartı bir parçada, hükmü diğerinde kalır; tablonun başlığı gövdesinden kopar; "bu durumda" diye başlayan paragraf, hangi duruma işaret ettiğini kaybeder. Getirim doğru parçayı bulur, cevap yine de eksiktir.
Daha iyi yaklaşımlar:
- Yapıya duyarlı bölme. Markdown başlıkları, HTML bölümleri, PDF'te yazı tipi boyutu hiyerarşisi. Belgenin kendi yapısı zaten bir bölme sinyali.
- Bağlamsal başlık ekleme. Her parçanın başına ait olduğu belge ve bölüm başlığını yaz. Parça artık kendi başına anlamlı: "Ürün İadeleri > Süre Sınırı — Kargo teslim tarihinden itibaren 14 gün..."
- Üst-alt parça ayrımı. Küçük parçaları indeksle (isabet keskin olsun), ama modele büyük üst parçayı gönder (bağlam yeterli olsun). Getirim ve okuma için farklı granülerlik kullan.
- Tabloları metne çevirme. Ham CSV satırları gömme uzayında anlamsızdır. Her satırı cümleye dönüştür veya tabloyu bir LLM'e özetlettir, ikisini birden indeksle.
04 / KesinlikYeniden sıralama katmanı
Getirim geniş ağ atar, yeniden sıralama (reranking) ağı daraltır. Aradaki fark mimari: bi-encoder sorgu ve belgeyi ayrı ayrı kodlar (bu yüzden hızlıdır, önceden indekslenebilir), cross-encoder ikisini birlikte okur (bu yüzden çok daha doğru ama yavaştır).
Doğru kompozisyon: bi-encoder ile 50 aday çek, cross-encoder ile ilk 5'i seç. Cross-encoder'ı tüm korpusa çalıştırmak imkânsız, ama 50 adaya çalıştırmak birkaç yüz milisaniye.
# Aşama 1: geniş, ucuz. Aşama 2: dar, pahalı, doğru. candidates = hybrid_search(query, k=50) pairs = [(query, c.text) for c in candidates] scores = cross_encoder.predict(pairs) # bge-reranker, Cohere Rerank... top = [c for c, s in sorted(zip(candidates, scores), key=lambda x: x[1], reverse=True) if s > 0.35][:5] # Eşiğin altında hiçbir şey yoksa: uydurma. Söyle ki bilmiyorsun. if not top: return ABSTAIN
Son üç satır, çoğu sistemde yok. Eşik altı sonuç geldiğinde yine de modele veriliyor ve model kibarca uyduruyor. "Bilmiyorum" bir başarısızlık değil, bir özellik — ve ölçülmesi gereken bir metrik.
05 / BütçeBağlam, bedava değil
Bağlam penceresi bir milyon tokena çıktı diye her şeyi içine atmak cazip. Üç sebeple kötü fikir:
- Ortadaki kayıp. Uzun bağlamlarda modeller baştaki ve sondaki bilgiyi hatırlar, ortadakini kaçırır. On beşinci belgeye gömülü doğru cevap, pratikte yok sayılır.
- Gürültü, dikkati seyreltir. Beş alakalı belge yerine yirmi belge verdiğinde, on beş alakasız belge modeli yanlış çıkarıma çeker. Kesinlik (precision), hatırlamadan (recall) daha kıymetlidir — bir noktadan sonra.
- Maliyet ve gecikme. Token başına ödüyorsun ve kullanıcı bekliyor.
ideal parça sayısı
kesme eşiği
bağlama
06 / ÖlçümÖlçmediğin şeyi düzeltemezsin
En yaygın hata: uçtan uca cevap kalitesine bakıp getirimi hiç ölçmemek. Cevap kötü çıktığında suçlanacak model var, düzeltilecek bir sayı yok. Katmanları ayrı ölç.
| Metrik | Ne ölçer | Ne zaman düşer |
|---|---|---|
| Recall@k | Doğru belge ilk k içinde mi? | Parçalama ya da gömme kötü. Getirim katmanına bak. |
| MRR / nDCG | Doğru belge kaçıncı sırada? | Reranker yok ya da zayıf. |
| Faithfulness | Cevap, verilen bağlamdan mı türedi? | Model uyduruyor. Eşik ve çekimserlik davranışına bak. |
| Answer relevance | Cevap, sorulan soruya mı cevap veriyor? | İstem (prompt) ya da sorgu anlama katmanı zayıf. |
| Abstention rate | Ne sıklıkla "bilmiyorum" diyor? | Sıfırsa şüphelen. Sistem asla bilmediğini itiraf etmiyor demektir. |
Altın küme (golden set) şart. Elli soru ve elle işaretlenmiş doğru belgeleriyle başla. Elli, bindir. Bu kümeyi her değişiklikte çalıştır — parça boyutunu değiştirdiğinde, gömme modelini yükselttiğinde, istemi düzelttiğinde. Ragas, TruLens ve DeepEval bunu otomatikleştirir, ama araç değil disiplin belirleyici.
07 / KırılmaNerede kırılıyor
Sorgu ile belge aynı dili konuşmuyor
Kullanıcı "faturamı göremiyorum" der, belge "invoice retrieval failure" der. Çözüm: HyDE (varsayımsal cevap üretip onu gömmek), sorgu genişletme, ya da çok dilli gömme modeli. Türkçe için ek bir zorluk: token ekonomisi Türkçe aleyhine, yani aynı anlam daha çok token yiyor ve parça sınırları daha erken geliyor.
Çok atlamalı sorular
"X politikası, Y bölgesindeki çalışanlar için Z tarihinden sonra ne söylüyor?" Cevap tek parçada yok. Naif RAG burada çuvallar. Çözüm ajanlı getirim: model sorguyu alt sorulara böler, sırayla arar, ara sonuçları biriktirir.
Zaman ve yetki
İki sinsi hata. Birincisi, indeks eski belgeyi de yenisini de tutuyor ve model beş yıl önceki politikayı alıntılıyor. İkincisi, kullanıcının görmemesi gereken belge getirime giriyor. Her ikisi de metadata filtreleme ile çözülür ve her ikisi de gömme benzerliğiyle çözülmez. Filtre, aramadan önce uygulanmalı.
# Yetki ve tazelik, benzerlik hesabından önce gelir. results = qdrant.search( vector=embed(query), query_filter=Filter(must=[ FieldCondition(key="acl", match=MatchAny(any=user.groups)), FieldCondition(key="valid_until", range=Range(gte=now())), ]), limit=50, )
08 / YığınNe kullanılıyor
Kategori kategori, üretimde sık gördüklerim ve seçim kriteri:
| Katman | Seçenekler | Kritere göre |
|---|---|---|
| Vektör deposu | Qdrant, Weaviate, Milvus, pgvector, Vespa | Zaten Postgres varsa pgvector yeter. Filtreli arama ağırsa Qdrant. Milyar ölçekte Vespa/Milvus. |
| Seyrek arama | OpenSearch, Elasticsearch, Tantivy | BM25'i atlama. Çoğu yığında zaten var. |
| Gömme | BGE-M3, E5, Voyage, Cohere Embed, OpenAI | Türkçe için çok dilli modeller (BGE-M3, multilingual-E5) tek dilli İngilizce modelleri açık ara geçer. |
| Yeniden sıralama | bge-reranker, Cohere Rerank, Jina | En yüksek getiri/maliyet oranlı tek ekleme. Önce bunu yap. |
| Ayrıştırma | Unstructured, Docling, LlamaParse | PDF ve tablo işi kirli. Buraya harcanan zaman geri döner. |
| Orkestrasyon | LlamaIndex, Haystack, ya da düz kod | Prototipte çerçeve, üretimde çoğu ekip düz koda dönüyor. Soyutlama hata ayıklamayı zorlaştırıyor. |
| Değerlendirme | Ragas, TruLens, DeepEval, Phoenix | Hangisi olduğu önemli değil. Olmaması ölümcül. |
Bir reranker eklemek, gömme modelini büyütmekten neredeyse her zaman daha çok kazandırır. Ve çok daha ucuzdur.
Son bir şey. RAG'in modası geçmiş olduğu, uzun bağlamın onu gereksiz kıldığı düzenli aralıklarla söyleniyor. Bu yanlış çerçeve. Uzun bağlam, neyi okuyacağına karar verme problemini çözmüyor; sadece erteliyor. Kurumsal bir bilgi tabanı terabaytlarca. Soru hep aynı kalıyor: bu sorgu için hangi beş paragraf?
Cevabı iyi verirsen sistem çalışır. Kötü verirsen, dünyanın en iyi modeli sana kendinden emin bir yanlış yazar.