5. ünite / 10 · 4 saat
RAG uçtan uca: belgeden kaynaklı cevap
Parçalama, gömme, vektör arama, yeniden sıralama, eşik ve kaynak gösteren cevap. Modelin bilmediği notlardan doğru cevap almak.
Bu ünitenin sonunda
- RAG hattının beş adımını sırayla açıklamak
- Parça boyu, örtüşme ve k seçiminin etkisini ölçmek
- Hibrit yeniden sıralamanın neden işe yaradığını göstermek
- Cevap notlarda yoksa "bilmiyorum" diyen bir eşik kurmak
Ders notları
Model eğitildiği tarihten sonraki ya da hiç görmediği belgeleri (dersin notları, şirketin yönetmeliği) bilemez. RAG (retrieval-augmented generation) soruyu cevaplamadan önce ilgili belge parçalarını bulur ve prompta koyar; model cevabı bu parçalara dayanarak yazar ve kaynağını gösterir.
Hat
- Parçala: belgeleri birkaç yüz tokenlık parçalara böl. Örtüşme, bir cümlenin iki parçanın sınırında kaybolmasını önler.
- Göm: her parçayı bir vektöre çevir ve bir dizinde sakla (bir kez, çevrimdışı).
- Ara: sorunun vektörüne en yakın k parçayı bul.
- Yeniden sırala: geniş bir aday kümesini daha pahalı ama daha isabetli bir ölçütle yeniden puanla. Bu derste vektör benzerliği ile anahtar kelime örtüşmesinin karışımı (hibrit) kullanılır; gerçek sistemlerde bir çapraz kodlayıcı model de olabilir.
- Üret: seçilen parçaları etiketlerle prompta koy, "yalnız bu notlara dayan, kaynağı [kimlik] olarak yaz, notlarda yoksa söyle" kuralıyla modeli çağır.
Eşik ve "bilmiyorum"
En yakın parça bile soruya uzaksa model yine de bir şey yazmaya çalışır. Benzerlik eşiğinin altında kalan parçaları atmak ve hiçbir parça kalmazsa "notlarda yok" demek uydurmayı kökten azaltır. Eşik çok yüksekse doğru cevaplar da kaybolur; doğru değer bir değerlendirme kümesiyle (7. ünite) seçilir.
Bu sayfadaki mini RAG
Sekiz kısa Türkçe ders notu, öğretim gömmesi ve şablonla çalışan bir cevap adımı tarayıcıda çalışır. Cevap adımı gerçek bir model değildir: seçilen parçalardan soruya en çok örtüşen cümleleri alıntılar ve kaynağını ekler. Gerçek bir uygulamada aynı prompt Anthropic API'ye gider; kod örneği bunu gösterir.
Simülasyon
Öğretim modeli: bu sayfadaki simülasyonlar gerçek bir dil modeli değildir. Küçük, deterministik, tarayıcıda çalışan modellerdir; kavramı göstermek için yazıldılar. Site hiçbir dış LLM servisine istek göndermez.
Uçtan uca mini RAG: ders notlarından cevap
Sekiz kısa Türkçe ders notu parçalara bölünür, öğretim gömmesiyle vektöre çevrilir, soruna en yakın parçalar bulunur, isteğe bağlı olarak anahtar kelime örtüşmesiyle yeniden sıralanır ve kaynak gösteren bir cevap kurulur. Parça boyu, örtüşme, k ve eşiği değiştir; eşiğin altında sistem "notlarda yok" der. Cevap adımı gerçek bir model değil, alıntı yapan bir şablondur.
RAG'ı 3B izle: sorgu → komşular → yeniden sıralama → cevap
Mini RAG'ın parçaları 3B gömme uzayında. Oynat'a bas: sorgu merkezden yerine uçar, vektör arama 2k adayı bulur, en yakın k parça seçilir, hibrit yeniden sıralama sırayı değiştirir, eşiği geçen parçalar yeşile döner ve kaynaklı cevap kurulur. k, eşik ve yeniden sıralamayı değiştir; Adım düğmesiyle tek tek ilerle.
Kendin dene
Mini RAG'da dört hazır soruyu dene ve hangisinin "notlarda yok" cevabı aldığını açıkla. Parça boyunu 15'e indir, örtüşmeyi 0 yap: "Motor neden doğrudan pine bağlanmaz?" sorusunun cevabı bozuldu mu? Yeniden sıralamayı kapatıp aç ve tabloda sıranın nasıl değiştiğine bak. Son olarak eşiği 0,5'e çıkar ve hangi doğru cevapların kaybolduğunu yaz.
Kod
Bunları kendi bilgisayarında çalıştır. Anahtar ANTHROPIC_API_KEY ortam değişkeninden okunur; koda asla yazma.
import pathlib, re
import numpy as np
import anthropic
from sentence_transformers import SentenceTransformer # yerel gömme modeli
client = anthropic.Anthropic() # anahtar ANTHROPIC_API_KEY ortam değişkeninden
embedder = SentenceTransformer("sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
def chunk(text, size=60, overlap=15):
words = text.split()
step = size - overlap
return [" ".join(words[i:i + size]) for i in range(0, max(1, len(words) - overlap), step)]
# 1) Dizinleme: her not parçalanır, her parça kaynağıyla birlikte gömülür
chunks = []
for path in sorted(pathlib.Path("notlar").glob("*.txt")):
for i, c in enumerate(chunk(path.read_text(encoding="utf-8"))):
chunks.append({"id": f"{path.stem}#{i}", "text": c})
E = embedder.encode([c["text"] for c in chunks], normalize_embeddings=True)
def words(s): return set(re.findall(r"\w+", s.lower()))
def retrieve(question, k=4, keep=2, threshold=0.35):
q = embedder.encode([question], normalize_embeddings=True)[0]
scores = E @ q
top = [(float(scores[i]), chunks[i]) for i in np.argsort(-scores)[:k]]
# 2) Yeniden sıralama: vektör puanı + anahtar kelime örtüşmesi
qw = words(question)
rer = sorted(top, key=lambda p: -(0.7 * p[0] + 0.3 * len(qw & words(p[1]["text"])) / max(1, len(qw))))
return [c for s, c in rer[:keep] if s >= threshold] # eşik altı: kanıt yok
def answer(question):
found = retrieve(question)
if not found:
return "Bu sorunun cevabı notlarda yok."
context = "\n".join(f'<belge id="{c["id"]}">{c["text"]}</belge>' for c in found)
response = client.messages.create(
model="claude-sonnet-5-5", max_tokens=600,
system="Yalnız <belge> içeriğine dayanarak cevap ver. Her cümlenin sonuna [id] ile kaynak yaz. Belgelerde yoksa bilmediğini söyle.",
messages=[{"role": "user", "content": f"{context}\n\n<soru>{question}</soru>"}],
)
return "".join(b.text for b in response.content if b.type == "text")
print(answer("Dönem projesi ne zaman teslim edilir?"))// Kelime sayısıyla parçala; örtüşme, sınırda bölünen cümleyi iki parçada da tutar
function chunkWords(text, size, overlap) {
const w = text.split(/\s+/).filter(Boolean)
const out = []
for (let start = 0; start < w.length; start += size - overlap) {
out.push({ start, text: w.slice(start, start + size).join(' ') })
if (start + size >= w.length) break
}
return out
}
const tokens = (s) => new Set(s.toLocaleLowerCase('tr-TR').match(/[\p{L}\p{N}]+/gu) ?? [])
// Vektör puanı gelmiş adayları sorudaki kelimelerle yeniden sırala
function rerank(question, candidates, alpha = 0.7) {
const q = tokens(question)
return candidates
.map((c) => {
const overlap = [...q].filter((x) => tokens(c.text).has(x)).length / Math.max(1, q.size)
return { ...c, final: +(alpha * c.score + (1 - alpha) * overlap).toFixed(3) }
})
.sort((a, b) => b.final - a.final)
}
const note = "Dönem projesi 12. haftanın cuma günü saat 17.00'ye kadar teslim edilir. Geç teslimde her gün için yüzde 10 kesilir."
console.log(chunkWords(note, 8, 2))
console.log(rerank('proje ne zaman teslim edilir', [
{ id: 'kurallar#0', score: 0.52, text: note },
{ id: 'pwm#1', score: 0.55, text: 'Servo motorlar 50 Hz darbelerle konum alır.' },
]))Sık yapılan hatalar
- Parçaları çok küçük seçmek: cevap iki parçaya bölünür ve hiçbiri tek başına yeterli olmaz.
- Eşik koymamak: ilgisiz parçalarla da cevap üretilir ve uydurma artar.
- Kaynak göstermemek: kullanıcı cevabı doğrulayamaz, hata ayıklamak zorlaşır.
- Belgeler güncellenince dizini yeniden üretmeyi unutmak.
Yazılım mühendisliği bağlantısı
RAG bir veri hattıdır: içeri alma, dizinleme ve sorgu ayrı bileşenlerdir ve ayrı test edilir.
- Arama adımını üretimden bağımsız ölç: altın kümede doğru parça ilk k içinde mi (isabet@k)?
- Belge sürümü ve dizin oluşturma tarihi her cevabın kaydında bulunsun; yanlış cevabın eski bir dizinden mi geldiği anlaşılsın.
Kendini sına
Parçalar arasında örtüşme neden bırakılır?
Benzerlik eşiğinin altında hiç parça kalmadığında doğru davranış nedir?
Hibrit yeniden sıralama hangi iki sinyali birleştirir?
Arama adımının kalitesini üretimden bağımsız ölçmek için ne kullanılır?
Bu üniteden sonra bitirme projesinde
- Ara kontrol: RAG hattı: Parçalama, gömme ve arama çalışıyor; isabet@k altın kümede ölçüldü; şemalı cevap ve doğrulama; mimari belgesi (bileşenler, veri akışı).
