Production’da RAG: Chunking, retrieval ve dürüst atıflar
512 karakterde kör kesim, 32 yanıttan 4’ünü retriever görmeden öldürür. Sadece chunker’ı düzeltmek rank 115’i 3’e taşır.
Bu sayfada
Gerçek bir assistant’ın gerçek bir kullanıcısından gelen gerçek bir soru: eval set’imde 20 öğe var, skora güvenmek için yeterli mi. Corpus yanıtı içeriyor — hem de bunun için ayrılmış koca bir bölüm. İşte retriever’ın gerçekten prompt’a koyduğu dört fragment.
[1] d=0.578 ship — that set has been used for fitting, and its score stops being
unbiased. Measured on this belt: sweeping the threshold on the
validation set picks 0.196, and the model then scores F1 = 0.4122…
[2] d=0.602 ng when the model is confidently **wrong**. Evaluate both at a few
scores, for an example whose true label is 1: | score | p | …
[3] d=0.613 ard and watch both numbers: | | reward model's score | true quality
| length produced | … The reward went up by a factor of 2.5. The…
[4] d=0.617 | 0.6 | +0.97 | +1.00 | +0.27 | … The reward model is working
perfectly. It has faithfully learned the preferences it was shown…Dördünün üçü kelimenin ortasından başlıyor. İkisi farklı bir konuda farklı bir chapter’dan. Soruyu yanıtlayan fragment ise — Yirmide on yedi, %85’lik bir model ile %65’lik bir modeli ayırt edemez ifadesini içeren parça — rank 115 olarak geri geldi.
Şimdi aynı soru, aynı embedding model, aynı prompt template. Tek şey değişti: dokümanların nasıl kesildiği.
[1] d=0.594 [Classification, Cross-Entropy… > How many test examples do I need?]
Read it backwards, which is how you will use it: ±5 points needs
about 200 examples. ±2 points needs about 1,230…
[2] d=0.598 [Classification, Cross-Entropy… > Three splits, and the leak…]
Why three splits and not two? Because the moment you use a set of
examples to *choose* anything…
[3] d=0.600 [Classification, Cross-Entropy… > How many test examples do I need?]
The honest reading of 17/20 is *somewhere between 64 % and 95 %*.
…Seventeen out of twenty cannot distinguish an 85 % model from a 65 % one.
[4] d=0.605 [Classification, Cross-Entropy… > How many test examples do I need?]
Suppose you score a model on 20 examples and it gets 17 right. You
report 85 %. …Wilson 95% CI : [0.6396, 0.9476]Rank 115’ten rank 3’e. Kimse modele, prompt’a, threshold’a ya da slot sayısına dokunmadı. Bu chapter o fark hakkında ve bir retrieval sisteminin sana sessizce yalan söylediği diğer dört yer hakkında.
Ayrıntıları göster
Bu chapter’ın önceki chapter’lardan ihtiyaç duyduğu şeyler ve dili değiştirdiği tek yer.
- Chapter 1 dot product’ı ve L2 normunu tanımladı. Aşağıdaki threshold bölümü bu ikisidir, başka hiçbir şey değil.
- Chapter 8 bir language model’ın embedding tablosunu, çiftler üzerinde contrastive eğitilmiş bir retrieval embedding model’dan ayırdı, cosine similarity ölçtü ve Chapter 19’un somut bir cut-off ile geleceğini vaat ederek bitti. O vaat burada ödeniyor. Hiçbiri tekrarlanmıyor.
- Chapter 4 Wilson aralığını kurdu; Chapter 15 evaluation harness’ı kurdu. Aşağıdaki her tablo ilkini taşır ve ikincisi tarafından üretildi.
- Chapter 16 context window’u fiyatlandırdı. Bu chapter’ın sonunda birleştirilen prompt 591 token’a mal oluyor ve fragment’ların yarıştığı bütçe bu.
Buradaki her şey, Chapter 14’ten beri olduğu gibi TypeScript ve bu chapter, kuralın kendini hak ettiği yer: ingestion kuyruklar ve storage’dır, search bir network call’dur ve citations içeren bir prompt’u birleştirmek server’ın işidir. Ölçüm, bilerek etrafına scoreboard konmuş aynı koddur — ikinci bir implementation ile skorlanan bir retriever, ship etmediğin yazılım hakkında bir sayıdır ve aşağıdaki cosine threshold ancak production’da çalışacak chunker tarafından süpürüldüğünü izlediğin için inanılırdır.
Corpus ve doğru yanıt sayılan şey
Bölüme bağlantı: Corpus ve doğru yanıt sayılan şeyAşağıdaki her şey tek bir corpus’a karşı ölçülür: bu kursun ilk on üç chapter’ı — front matter ve bibliyografyalar çıkarılmış halde 13 doküman, 359.067 karakter, 127 section. Bu; prose, tablolar, formüller ve code blocks içeren gerçek bir teknik corpus’tur ve insanların knowledge base’e yükleyip sonra şikâyet ettiği şeyin tam olarak aynısıdır.
Ground truth 32 sorudur; her biri bir needle ile eşleştirilmiştir: corpus’tan soruyu yanıtlayan kısa, birebir bir cümle. Her needle 359.067 karakter içinde tam olarak bir kez görünür ve hiçbiri section heading değildir — bu kontrol önemlidir, çünkü headings’i her chunk’a kopyalayan bir chunker aksi halde kendini skorlar. Her soru iki kez sorulur: bir kez kurs İngilizcesiyle, bir kez de bir support ticket’ın ifade edeceği şekilde: 32 ground truth üzerinde 64 query.
Dönen bir chunk needle’ı bütün olarak içerdiğinde retrieval doğru sayılır. Generator’ın ihtiyaç duyduğu şeyle eşleşen tek tanım budur: prompt’ta yarım cümle bir yanıt değildir, bir tehlikedir.
Embedding model all-MiniLM-L6-v2 — 384 dimensions, mean-pooled ve normalised, Chapter 8’in ölçtüğü contrastively trained model. Corpus’u indexlemek CPU’da 20,8 saniye, chunk başına 22 ms sürer; tek bir query’yi embed etmek 13 ms sürer.
Chunking, altı şekilde ölçüldü
Bölüme bağlantı: Chunking, altı şekilde ölçüldüÜç bağımsız bileşenden altı strategy. Blind, metne bakmadan her 512 karakterde keser. Boundaries, paragraph içinde asla kesmez; yalnızca bir paragraph bütçeyi aştığında sentence boundary’ye fallback eder. Header, her chunk’ın başına document title ve section path ekler. Overlap, önceki chunk’ın son 64 karakterini bir sonrakine kopyalar.
| strategy | chunks | answers destroyed | R@1 | R@4 | R@8 | R@20 | MRR |
|---|---|---|---|---|---|---|---|
| A blind 512 | 708 | 4 / 32 | 0.125 | 0.297 | 0.422 | 0.594 | 0.241 |
| B blind + overlap | 809 | 0 | 0.172 | 0.391 | 0.453 | 0.625 | 0.286 |
| C boundaries | 940 | 0 | 0.156 | 0.422 | 0.531 | 0.672 | 0.293 |
| D boundaries + overlap | 940 | 0 | 0.156 | 0.359 | 0.516 | 0.656 | 0.277 |
| E boundaries + header | 940 | 0 | 0.094 | 0.422 | 0.578 | 0.828 | 0.280 |
| F boundaries + header + overlap | 940 | 0 | 0.156 | 0.391 | 0.562 | 0.766 | 0.298 |
64 query ile R@20 üzerindeki %95 Wilson aralığı A için [0.471, 0.705], E için [0.718, 0.901] — bunlar overlap etmiyor, ama diğer column’ların çoğu ediyor ve unpaired bir tablo bunları ayıramaz. Her strategy aynı query’leri yanıtlıyor, bu yüzden dürüst test paired’dır: her strategy’nin diğerine karşı win ve loss’larını say, discordant pair’ler üzerinde sign test çalıştır. Üç sonuç bunu atlatıyor.
Blind chunking, otuz iki yanıtın dördünü baştan yok ediyor. Kötü rank etmiyor — yok ediyor. Needle 512 karakterlik bir boundary’nin iki tarafına yayılıyor, bu yüzden index’te onu içeren hiçbir chunk yok ve bu query’ler için recall ceiling sıfır. Hiçbir reranker onları geri getiremez, hiçbir threshold yardımcı olmaz, daha büyük model yardımcı olmaz. Index’inin herhangi bir yerinde tek parça olmayan metni retrieve edemezsin. Bu, RAG’de en az raporlanan failure’dır, çünkü dışarıdan tamamen kötü bir retriever gibi görünür.
Overlap bunu düzeltir ve başka hiçbir şeyi düzeltmez. Overlap içeren her strategy sıfır yanıt kaybeder; overlap bunun içindir. Ranking’i iyileştirmez: R@8’de B’nin A’ya karşı sonucu +8/−6, p = 0.79; R@20’de +9/−7, p = 0.80. Daha kötüsü, header’ın üstüne overlap eklemek aktif olarak zarar verir — R@20’de F’nin E’ye karşı sonucu +2/−6 — ve nedeni mekaniktir. Bir chunk’ın vector’ü token’larının ortalamasıdır; bu yüzden önceki chunk’tan 64 karakter, o ortalamayı komşunun topic’ine doğru çeker. Overlap, split answer’a karşı precision ile ödenen bir sigortadır.
Retrieval’ı satın alan şey contextual header’dır. E’nin A’ya karşı sonucu R@20’de +18/−3, p = 0.0015. Ablation da işi boundaries’in yapmadığını söyler: E’nin C’ye karşı sonucu — aynı cuts, tek fark header — +12/−2, p = 0.0129. Bir paragraph’ın başına “Classification, Cross-Entropy, and How Not to Fool Yourself > How many test examples do I need?” eklemek, embedding model’a paragraph’ın ne hakkında olduğunu söyler; paragraph’ın kendisi bunu çoğu zaman söylemez. Bu, dokümanlar için bir pronoun resolver’dır.
Bu da chunker’a şeklini ve yanlış anlaması kolay bir kuralı verir:
export interface Chunked {
/** What gets EMBEDDED: contextual header + this chunk's own content. */
text: string;
/** ONLY this chunk's own content: what is quoted back to the user. */
content: string;
section: string;
/** Character range in the document's canonical text. Sliceable. */
from: number;
to: number;
}
export function chunkDocument(doc: string, docTitle: string, target = 512): Chunked[] {
const out: Chunked[] = [];
const heads = [...doc.matchAll(/^## (.+)$/gm)].map((m) => ({ at: m.index!, title: m[1].trim() }));
const spans = heads.length
? heads.map((h, i) => ({ ...h, end: i + 1 < heads.length ? heads[i + 1].at : doc.length }))
: [{ at: 0, title: "", end: doc.length }];
for (const s of spans) {
const header = s.title ? `${docTitle} > ${s.title}` : docTitle;
const skip = /^## .+\n/.exec(doc.slice(s.at, s.end))?.[0].length ?? 0;
const body = doc.slice(s.at + skip, s.end);
const origin = s.at + skip;
// The offset is FOUND in the document, never accumulated: adding up
// lengths drifts by a character wherever a separator was normalised,
// and a citation anchor off by one points at the wrong line.
const emit = (from: number, to: number) => {
const raw = body.slice(from, to);
const lead = raw.length - raw.trimStart().length;
const content = raw.trim();
if (!content) return;
out.push({ text: `[${header}]\n${content}`, content, section: s.title,
from: origin + from + lead, to: origin + from + lead + content.length });
};
let open: [number, number] | null = null;
for (const m of body.matchAll(/[^\n]([^\n]|\n(?!\n))*/g)) { // paragraphs
const [pf, pt] = [m.index!, m.index! + m[0].length];
if (pt - pf > target) { // one huge paragraph
if (open) { emit(open[0], open[1]); open = null; }
let cur: [number, number] | null = null;
for (const sm of body.slice(pf, pt).matchAll(/[^.!?]*[.!?]*\s*/g)) {
if (!sm[0]) continue;
const [sf, st] = [pf + sm.index!, pf + sm.index! + sm[0].length];
if (cur && st - cur[0] > target) { emit(cur[0], cur[1]); cur = null; }
cur = cur ? [cur[0], st] : [sf, st];
}
if (cur) emit(cur[0], cur[1]);
continue;
}
if (open && pt - open[0] > target) { emit(open[0], open[1]); open = null; }
open = open ? [open[0], pt] : [pf, pt];
}
if (open) emit(open[0], open[1]);
}
return out;
}Tek metin değil, iki metin. text embed edilen şeydir; header dahil her şey. content yalnızca bu chunk’ın kendi kelimeleridir ve kullanıcıya quote edilen şey budur. text quote edilirse citation, dokümanın o noktasında bulunmayan bir header gösterir — overlap ile de önceki fragment’a ait tekrar eden bir kuyruk. Böylece söylediği yerde olmayan metni gösterir; bu, hiçbir şey göstermemekten kötüdür.
Header bedava değildir. 940 chunk genelinde index’in 114.275 embedded token’ının 24.213’üne mal olur: embed etmek için ödediğinin %21,2’si kendi yazdığın header’dır. Ayrıca chunk’ları encoder’ın window’una iter. all-MiniLM-L6-v2 256 word-piece kabul eder; strategy E’de bu çizgiyi aşan 17 chunk, F’de 28 chunk vardır ve hepsi hiçbir yerden uyarı gelmeden sessizce truncated olur. Etkili chunk size’ın config’teki sayı değildir — o sayı ile encoder window’unun küçük olanıdır.
Herkesin atladığı yirmi satır BM25
Bölüme bağlantı: Herkesin atladığı yirmi satır BM25Dense retrieval’ın sistematik bir zayıflığı var ve hiç de incelikli değil: anlamı eşleştirir, bu yüzden tam olarak hangi string’i yazdığına kayıtsızdır. Bir parça numarası, hata kodu, acronym, soyadı — bunların hiçbirinin embed edilecek kullanışlı bir anlamı yoktur ve bir error code’un nearest neighbour’ı corpus’undaki diğer her error code’dur.
Klasik yanıt bunların hepsinden eskidir ve yirmi satır sürer. BM25, bir document’ı query’nin terms’lerinin içinde ne kadar sık geçtiğine göre skorlar; frequency arttıkça her term’ü damp eder ve sırf uzunluk yüzünden match biriktiren uzun document’ları cezalandırır.1 Term şu katkıyı yapar
burada term’ün document’taki count’u, document length, average length, ve iki conventional constant’tır — repetition’ın ne kadar hızlı faydasızlaştığını, length’in ne kadar sert cezalandırıldığını belirler.
const toks = (s: string) => s.toLowerCase().match(/[a-z0-9]+/g) ?? [];
export class BM25 {
private tf: Map<string, number>[] = [];
private len: number[] = [];
private idf = new Map<string, number>();
private avg = 0;
private k1: number; private b: number;
constructor(docs: string[], k1 = 1.2, b = 0.75) {
this.k1 = k1; this.b = b;
const df = new Map<string, number>();
for (const d of docs) {
const t = new Map<string, number>(); const ws = toks(d);
for (const w of ws) t.set(w, (t.get(w) ?? 0) + 1);
for (const w of t.keys()) df.set(w, (df.get(w) ?? 0) + 1);
this.tf.push(t); this.len.push(ws.length);
}
this.avg = this.len.reduce((a, b) => a + b, 0) / this.len.length;
const N = docs.length;
for (const [w, n] of df) this.idf.set(w, Math.log(1 + (N - n + 0.5) / (n + 0.5)));
}
scores(query: string): number[] {
const q = toks(query);
return this.tf.map((tf, i) => {
const L = this.len[i]; let s = 0;
for (const w of q) {
const f = tf.get(w); if (!f) continue;
s += (this.idf.get(w) ?? 0) * (f * (this.k1 + 1)) /
(f + this.k1 * (1 - this.b + (this.b * L) / this.avg));
}
return s;
});
}
}940 chunk üzerinde bu, iki hash map dışında hiçbir index olmadan bir query’yi 1,14 ms’de skorlar. Ve bir müze parçası değildir:
| retriever | R@1 | R@4 | R@8 | MRR | cost per query |
|---|---|---|---|---|---|
| dense (cosine) | 0.094 | 0.422 | 0.578 | 0.280 | embed etmek için 13 ms + scan için 0,3 ms |
| lexical (BM25) | 0.219 | 0.375 | 0.469 | 0.313 | 1,14 ms |
| hybrid (RRF) | 0.203 | 0.484 | 0.609 | 0.346 | ikisi de |
| hybrid + cross-encoder | 0.312 | 0.578 | 0.703 | 0.447 | + 569 ms |
BM25 bu corpus’ta dense retriever’ın top-1 accuracy’sini iki katından fazlaya çıkarır ve rank 8’e gelindiğinde ona açık ara kaybeder. Farklı query’lerde başarısız olurlar; ikisini birden çalıştırmanın tüm argümanı budur.
Onları fuse etmek, bariz yaklaşımın yanlış olduğu tek yerdir. Cosine distances ve BM25 scores aynı scale’de değildir, aynı şekilde bounded değildir ve bunları query başına normalise etmek weight’i en iyi hit’in tesadüfen ne kadar iyi olduğuna bağlar. Reciprocal rank fusion score’ları atar ve yalnızca rank’leri tutar:2
/** Reciprocal rank fusion: ranks, not scores. Nothing to calibrate. */
export function rrf(lists: number[][], k = 60): number[] {
const acc = new Map<number, number>();
for (const list of lists)
list.forEach((id, r) => acc.set(id, (acc.get(id) ?? 0) + 1 / (k + r + 1)));
return [...acc.entries()].sort((a, b) => b[1] - a[1]).map(([id]) => id);
}Ve burada tablonun dürüst okuması tablonun kendisinden daha önemlidir. Hybrid, R@4’te BM25’i +10/−3 ile yener, p = 0.09. Dense’i +10/−6 ile yener, p = 0.45. Bu corpus’ta, 64 query ile, hybrid retrieval dense retrieval’dan ayırt edilebilir değildir. Point estimate’lerde ve her recall column’da daha iyidir ama kanıt significance’a ulaşmaz. İnternetteki neredeyse her hybrid-search blog post’u yukarıdaki gibi bir tablo verir ve interval vermez; interval’in söylediği budur.
Bi-encoder, cross-encoder ve lift’in gerçekten nerede olduğu
Bölüme bağlantı: Bi-encoder, cross-encoder ve lift’in gerçekten nerede olduğuBuraya kadarki her şey bir bi-encoder: query model’dan tek başına geçer, her chunk aylar önce tek başına geçmiştir ve ikisi dot product dışında asla buluşmaz. Index’i mümkün kılan şey budur — bir kez embed et, sonsuza dek tekrar kullan — ve ceiling de budur. Model query ile chunk’a hiçbir zaman birlikte bakmaz.
Bir cross-encoder tam olarak bunu yapar: pair’i tek input olarak alır ve relevance score döndürür. Hiçbir şey precompute edilemez, bu yüzden bir index’i rank edemez — ama bir shortlist’i rerank edebilir. Hybrid top 25’i ms-marco-MiniLM-L-6-v2 ile rerank etmek, R@1’i 0.094’ten (dense) 0.312’ye ve MRR’yi 0.280’den 0.447’ye taşır: bu chapter’daki en büyük tek improvement ve listenin tail’i yerine top’una dokunan tek improvement.
CPU’da query başına 569 ms’ye mal olur; BM25 için 1,14 ms ve vector scan için 0,3 ms’ye karşı. Yirmi beş document için retrieval cost’un yaklaşık iki bin katı. Tüm bi-encoder/cross-encoder trade’i tek bir sayıda budur ve mimarinin hep aynı şekilde olmasının nedeni budur: wide recall’a sahip ucuz bir retriever, sonra karşılayabileceğin bir shortlist üzerinde pahalı bir scorer. ColBERT ikisinin arasında durur; per-token vector’leri precompute eder ve cross-encoder’dan daha ucuz, dot product’tan daha keskin bir late interaction yapar.3
L2, cosine ve hak etmediğin bir threshold
Bölüme bağlantı: L2, cosine ve hak etmediğin bir thresholdVector databases distances raporlar ve hangi distance’ın kullanılacağı bir configuration option’dır. Normalised vector’lerde seçim kozmetiktir ve identity’yi bir kez yapmak değerlidir; çünkü bundan sonraki her şey vector’lerin gerçekten unit olmasına bağlıdır. için:
bu yüzden cosine distance tam olarak olur. Chapter 1’in dot product ve norm’u nakde çevrildi. Yukarıdaki index’ten iki gerçek chunk vector üzerinde, sonra 40.000 pair üzerinde kontrol edildi:
||a|| = 1.000000 ||b|| = 1.000000
L2 = 0.795183 L2^2/2 = 0.316158 1 - cos = 0.316158 diff = 7.66e-08
max |L2^2/2 - (1 - cos)| over 200 x 200 pairs = 8.3e-07Floating-point noise’a kadar exact — ve yalnızca vector’ler normalised olduğu için. Normalisation’ı atlarsan identity yanlıştır, threshold’un hiçbir şey ifade etmez ve bir document’ın raporladığı distance metninin ne kadar uzun olduğuna bağlı olur.
Şimdi kimsenin derive etmediği sayı. Bir retriever her zaman bir şey döndürür: bütün index’i sıralar ve answer corpus’ta bir yerde olsun ya da olmasın listenin tepesini sana verir. Threshold, sistemin hayır diyebilen tek parçasıdır — ve bir threshold belirlemek için hiçbir şey döndürmemesi gereken query’lere ihtiyacın vardır. İşte otuz tane: yirmi biri bu corpus’un gerçekten kapsamadığı şeyler hakkında — streaming, rate limits, prompt caching, JSON schemas, agent loops, vector databases, prompt injection, image generation — ve dokuzu paella, pasaportlar ve refund policies hakkında. Aynı index’e karşı:
| top-1 cosine distance | |
|---|---|
| in-domain queries, all 64 | mean 0.445, range 0.270 – 0.721 |
| in-domain, top-1 actually correct | mean 0.370 |
| in-domain, top-1 wrong | mean 0.452 |
| out-of-domain, all 30 | mean 0.699, range 0.497 – 0.867 |
Distributions ayrılıyor ve overlap ediyor. En kötü in-domain query, answer’ından (0.721), en iyi out-of-domain query’nin ilgisiz bir paragraph’tan uzak olduğundan (0.497) daha uzak; bu yüzden hiçbir threshold ikisini de doğru yapamaz. Gerçek gate üzerinde sweep edelim — en fazla dört chunk tut ve sadece cut’ın altındakileri al:
| threshold | in-domain answered | of which the answer was in | out-of-domain answered |
|---|---|---|---|
| 0.400 | 17 / 64 | 6 | 0 / 30 |
| 0.450 | 38 / 64 | 13 | 0 / 30 |
| 0.500 | 50 / 64 | 19 | 1 / 30 |
| 0.525 | 52 / 64 | 20 | 2 / 30 |
| 0.550 | 55 / 64 | 21 | 3 / 30 |
| 0.600 | 60 / 64 | 25 | 5 / 30 |
| 0.675 | 62 / 64 | 27 | 10 / 30 |
| 0.800 | 64 / 64 | 27 | 26 / 30 |
| none | 64 / 64 | 27 | 30 / 30 |
Son column’u blöfler olarak oku. Threshold yokken assistant, “İspanyol pasaportumu nasıl yenilerim” sorusuna backpropagation hakkındaki bir corpus’tan otuzda otuz kez kendinden emin, iyi citation’lı bir yanıt üretir. 0.675’te bunu otuzda on kez yapar. 0.525’te iki kez yapar ve yanıtlayabileceği on iki sorudan vazgeçer.
Bu trade bir product decision’dır ve doğru ucu yanlış bir yanıtın sana maliyetine bağlıdır. Pazarlık konusu olmayan şey, son column’un var olmasıdır. Retriever’ını reddetmesi gereken sorulara karşı hiç ölçmediysen threshold’un yoktur — bir sayın vardır.
0.675’teki on blöften ikisi, bunun başarısız olmasının iki yolunu gösterir.
query: "how much does prompt caching save on a long conversation"
[1] d=0.497 13-inference-optimization > Prefill and decode are two different machines
[2] d=0.532 13-inference-optimization > The cache is also the bill
query: "what is the capital of france"
[1] d=0.671 12-reasoning > The model does not think. It computes for longer.
"…it is why 'think step by step' does nothing for what is the capital of France."İlki bir near miss: corpus KV cache’i ayrıntılı açıklar, query prompt cache hakkındadır, kelimeler aynı kelimelerdir ve 0.497, tüm deneydeki çoğu doğru in-domain retrieval’dan daha yakındır. Bir embedding, aynı ada sahip iki cache’in farklı makineler olduğunu bilmez. İkincisi yanıtı olmayan literal match: corpus, hiç reasoning gerektirmeyen bir question örneği olarak kullanılan “Fransa’nın başkenti nedir” exact phrase’ini içerir. Retriever haklıdır; answer orada değildir. “Benzer bir şey buldum” ifadesini “yanıtı buldum” diye okuyan herhangi bir sistem, bu kanıtla Paris diyecektir — ya da daha kötüsü, demeyecektir.
Citation neden model tarafından yazılmaz
Bölüme bağlantı: Citation neden model tarafından yazılmazBir modelin facts için ayrı bir faculty’si yoktur. Doğru bir sentence üretmekle plausible bir sentence üretmek aynı operation’dır — Chapter 8’in next-token prediction’ı — ve bu operation içinde hangisinin hangisi olduğunu işaretleyen hiçbir şey yoktur. Bunu yeniden çerçeveleyen 2025 analysis, training ve evaluation pipeline’ın aktif olarak tahmin etmeyi ödüllendirdiğini savunur: benchmarks binary accuracy ile skorlar ve abstention için credit vermez; bu yüzden her zaman yanıtlayan bir model, bilmediğinde “bilmiyorum” diyen identical bir modelden her zaman daha yüksek skor alır ve post-training buna göre optimize eder.6 Bu okumada hallucination gizemli bir defect değildir. Yanlış yanıt için ceza olmayan çoktan seçmeli bir exam’ı graded ettiğinde elde ettiğin şeydir.
Şekline bak. Contrastive sentence embeddings üzerine sekiz paper, identifiers ile birlikte istendiğinde, Qwen2.5-0.5B-Instruct perfect format’ta sekiz line üretti. Sekiz identifier’ın tamamı well-formed. Sekizi de arXiv’de gerçek paper’lara resolve oluyor. Sekizinin sıfırı iddia edilen paper.
claimed arXiv:1907.06432 - Contrastive Sentence Embeddings for Text Retrieval
actual A Neural Turing~Machine for Conditional Transition Graph Modeling
claimed arXiv:1809.08669 - Contrastive Learning of Sentence Representations…
actual Collapsing Superstring Conjecture
claimed arXiv:1807.08669 - Contrastive Learning of Sentence Representations…
actual Automatic Speech Recognition for Humanitarian Applications in SomaliBu küçük bir model ve rate kendine özgü; frontier model çok daha az uydurur. Mechanism genelleşir ve sonraki kuralın nedeni budur. “Bu identifier var mı?” diye kontrol eden bir validator sekizinin de geçmesine izin verir; birine tıklayan user da gerçek bir archive’dan gerçek bir page’e iner ve mapping’in uydurulduğunu anlamasının bir yolu yoktur. Failure identifier’da ya da format’ta değildir. Association’dadır — tam da language model’ın plausibility ile ürettiği şeyde.
Yani: model [1] ve [2] yazar, link’i asla yazmaz. Numbers, server’ın retrieved ettiği fragment’lara refer eder; server — her number’ın tam olarak hangi document’tan ve hangi offset’lerden geldiğini bilen taraf — sonradan document, label ve URL’yi ekler. Modelin uyduracağı hiçbir şey yoktur, çünkü uyduracağı tek şey ondan hiçbir zaman istenmez.
export function buildContext(question: string, hits: Scored[]) {
const citations: Citation[] = hits.map((h, i) => ({
index: i + 1,
documentId: h.chunk.documentId,
documentName: h.chunk.documentName,
locatorLabel: label(h.chunk),
fragment: `#char=${h.chunk.locator.flow.from},${h.chunk.locator.flow.to}`,
quote: h.chunk.content, // the OWN content, never `text`
cosineDistance: h.cosineDistance,
}));
const blocks = citations
.map((c) => `[${c.index}] ${c.documentName} - ${c.locatorLabel}\n${c.quote}`)
.join("\n\n");
const prompt =
`Answer using ONLY the numbered sources below. Cite every claim as [n].\n` +
`If the sources do not contain the answer, say so and stop.\n\n` +
`SOURCES\n${blocks}\n\nQUESTION\n${question}`;
return { prompt, citations };
}Opening question üzerinde çalıştırıldığında dört chunk, 591-token prompt’a ve modelin hiç görmediği bir table’a dönüşür:
[1] 04-classification How many test examples do I need? #char=28215,28701 d=0.594
[2] 04-classification Three splits, and the leak… #char=20329,20839 d=0.598
[3] 04-classification How many test examples do I need? #char=25873,26272 d=0.600
[4] 04-classification How many test examples do I need? #char=25554,25871 d=0.605Locator, insanların atladığı ve sonra ekleyemediği parçadır. #char=25873,26272 document’ın canonical text’inde bir range’dir; PDF için karşılığı #page=12, audio veya video için #t=132.4,158.9, spreadsheet için sheet ve A1 range’dir. Bu ikisi invention değildir — #page= PDF Open Parameters’tır ve #t= W3C Media Fragments’tır; browsers tarafından video ve audio elements üzerinde native olarak desteklenir. Locator’sız bir citation bir document name’dir; document name de citation değildir, user’a gidip bakmasını öneren bir işarettir.
Ve threshold’u hiçbir şey geçmediğinde pipeline modele hiç ulaşmaz:
NO ANSWER: nothing under cosine distance 0.675 for "what is the offside rule in football"
NO ANSWER: nothing under cosine distance 0.675 for "how do i renew my spanish passport"
NO ANSWER: nothing under cosine distance 0.675 for "how do i build an agent loop with tools"Bu, system prompt içindeki herhangi bir instruction’dan daha ucuz ve daha güvenilir bir refusal’dır; çünkü probabilistic system’a yapılan bir request değil, iki sayı arasındaki comparison’dır.
Retriever’ı generator’dan ayrı evaluate et
Bölüme bağlantı: Retriever’ı generator’dan ayrı evaluate etBu chapter’daki her measurement retriever’ı skorlar ve bir modelden bir kez bile answer yazmasını istemez. Bu bilinçlidir ve çoğu team’in atladığı parçadır.
Bir RAG system’ın dışarıdan identical görünen iki failure mode’u vardır. Retriever passage’ı bulamamıştır; ya da bulmuştur ama generator onu görmezden gelmiş, onunla çelişmiş veya zaten inandığı bir şeyle harmanlamıştır. Yalnızca final answer’ı skorla ve ikisi ayırt edilemez hale gelir; böylece chunker’ında yaşayan bir problem için prompts tune edersin. Recall@k, MRR ve answer-destroyed count hiçbir generation call’a ihtiyaç duymaz; her deploy’da çalıştırılacak kadar ucuzdur ve farklı bir scoring function ile Chapter 15’teki harness’tır — live conversation yerine fixed question set üzerinde aynı request, deadline, concurrency ve tally.
Bunları intervals ile raporla. Chapter 4’ün arithmetic’i değişmeden geçerlidir: 64 query’de 0.5 recall yaklaşık ±0.12’lik %95 Wilson interval taşır, bu yüzden başka bir strategy’nin dört point önünde olan bir strategy sana hiçbir şey söylememiştir. İki strategy de aynı soruları yanıtladığında paired test kullan; burada her zaman öyledir — “E, A’dan iyi görünüyor” cümlesini p = 0.0015’e çeviren şey budur.
Ve son dürüstlük: RAG hallucination’ı azaltır, ortadan kaldırmaz. Doğru passage’ı prompt’a koymak modeli onu kullanmaya mecbur etmez ve literature bunu original paper’dan beri söylüyor.7 Production’da bunu iki şey daha kötüleştirir. Long contexts degrade olur — model, uzun bir prompt’un başındaki ve sonundaki bilgiyi ortadakinden daha reliable bulur; bu yüzden dört chunk yerine yirmi chunk accuracy’yi düşürürken bill’i artırabilir, Chapter 24’te ölçülen effect budur. Ve retrieval doğru olup yine de insufficient kalabilir; yukarıdaki iki cache’in gösterdiği gibi. SelfCheckGPT resampling’den sağ çıkmayan claims’i flag eder;8 Self-RAG modeli kendi retrieve-and-critique token’larını emit etmek için train eder;9 TruthfulQA failure mode’u baştan legible hale getirdi.10 Hiçbiri gap’i kapatmaz ve retrieved text’i proof olarak sunan bir system, sourced ile true’yu karıştırmıştır.
Herhangi bir query’den önce çalışan sistem yarısı
Bölüme bağlantı: Herhangi bir query’den önce çalışan sistem yarısıRetriever, failures’ı daha önce, karanlıkta gerçekleşen bir pipeline’ın görünür parçasıdır. Üçü tekrar eder.
Extraction, content’in öldüğü yerdir. PDF text değildir; drawing instructions’dır. Two-column layouts iç içe geçer, tablolar word soup’a dönüşür, page headers her chunk’a tekrar eder ve scanned page, OCR ona confidence ile bir şey verene kadar hiç text içermez. Yukarıda ölçülen her şey extractor’ın işini yaptığını varsaydı; production’da çoğu zaman yapmaz ve symptom üç layer ötede kötü retrieval olarak görünür.
Index, onu oluşturan model ile damgalanır. İki modelden gelen embeddings karşılaştırılabilir değildir — “daha az accurate” değil, karşılaştırılabilir değil; çünkü farklı spaces içindeki points’tir. Embedding model değiştiğinde store’daki her vector rebuild edilene kadar çöptür. Bu yüzden model name, dimension count, pipeline version ve extractor version index time’da her document’ın yanına yazılır. Bunlar olmadan, upgrade gününde hangi documents stale, hangileri current bilemezsin ve yarı migrated bir index hiçbir yerde error vermeden confident nonsense döndürür.
Tek bir broken document folder’ı bozmamalı ve counters olanları saymalı. Extraction’da başarısız olan bir document, reason’ı visible ve retryable olacak şekilde failed state’inde biter; diğer doksan dokuz searchable kalır. Indexed chunk sayısı da client upload ederken declare edilmez, server bitirdiğinde yazılır. 400 fragment raporlayıp 40 tutan bir folder, yalnızca yanıtlanamayan bir question olarak yüzeye çıkan bir yalandır.
Bundan sonra nereye gidiyor
Bölüme bağlantı: Bundan sonra nereye gidiyorBu chapter’daki system, yanıtları yazılı olan soruları yanıtlar. Onları retrieve eder, rank eder, yapamadığında reddeder ve nereye baktığını cite eder. İnsanların kendi documents’ları üzerinde bir assistant’tan istediği şeyin çoğu budur ve tek bir specific şekilde bounded’dır: retrieval yalnızca birinin yazdığı şeyi döndürebilir.
Geriye diğer yarı kalır. Bir modelden yapmasını istediğin bazı şeyler document içindeki bir fact değildir — tutması gereken bir format, bir tone, dört yüz label’lı taxonomy, on bin geçmiş example içinde yaşayan ama hiçbir paragraph’ta bulunmayan bir decision method. Retrieval bunları deliver edemez, çünkü retrieve edilecek hiçbir şey yoktur; daha uzun bir prompt yalnızca Chapter 16’nın bill’ini skill’in kendisi yerine skill’in description’ı için öder.
Chapter 20 bu karardır — fine-tune, retrieve ya da prompt — ve finding’i şu: karar teknik olmadan önce ekonomik. Üçü aynı question üzerinde end to end priced edilir ve crossover bir token count’tur. Onu açan question bu chapter’ın yanıtlayamadığı sorudur. Answer nerede yazılı, değil; hiç yazılmadığında ne yaparsın.
Sources and method
Bölüme bağlantı: Sources and methodBu chapter’da ölçülen her şey tek corpus ve tek instrument kullandı; ikisi de reproducible. Corpus, 7 Eylül 2026’daki halleriyle bu course’un chapter 1’den 13’e kadar olan bölümleri — 13 documents, 359.067 characters, 127 sections, front matter ve bibliographies removed. Bu chapters düzenlenmeye devam ediyor; bu yüzden aynı rule’u bugün uygulamak birkaç bin characters daha sayar: section count unchanged ve aşağıdaki her conclusion unchanged, ama character total bir snapshot ve öyle labelled. Ground truth 32 questions; her biri corpus’ta tam olarak bir kez geçen ve asla section heading olmayan verbatim sentence ile paired, 64 query için iki phrasing ile soruldu. Retrieval embeddings sentence-transformers/all-MiniLM-L6-v2 (384 dimensions, mean-pooled, L2-normalised, 256-token window); reranking top 25 üzerinde cross-encoder/ms-marco-MiniLM-L-6-v2; generation example greedy decoding ile Qwen/Qwen2.5-0.5B-Instruct. Tüm timings single-threaded CPU. Bu chapter’ı üretmek için hiçbir paid API çağrılmadı, bu yüzden buradaki her latency local ve öyle labelled.
TypeScript’te gösterilen chunker ölçülen chunker’dır: aynı rule’u ve ts/chunk.ts’i implementation eden Python instrument, tüm corpus üzerinde chunk by chunk karşılaştırıldı ve 940 chunk’ın tamamında texts ve offsets dahil agree ediyor. Intervals %95 Wilson; paired comparisons discordant pairs üzerinde two-sided exact sign tests.
Yukarıda cited edilen on dört identifier’ın tamamı arXiv API’ye karşı resolve edildi ve 7 Eylül 2026’da title by title kontrol edildi — sekizinin öyle olmadığı düşünülünce, bu particular chapter’ın yapabileceği en az şey bu gibi görünüyordu.
Referanslar
Bölüme bağlantı: Referanslar-
Robertson, S. and Zaragoza, H. The Probabilistic Relevance Framework: BM25 and Beyond. Foundations and Trends in Information Retrieval 3(4), pp. 333–389 (2009). Saturation function’ın ve yukarıda kullanılan iki constant’ın kaynağı; ayrıca ’ün neden var olduğunu okumak için yer. ↩
-
Cormack, G. V., Clarke, C. L. A. and Büttcher, S. Reciprocal Rank Fusion Outperforms Condorcet and Individual Rank Learning Methods. SIGIR 2009. onların ve method’un point’i, fuse ettiği score scales arasında hiçbir calibration gerektirmemesidir. ↩
-
Khattab, O. and Zaharia, M. ColBERT: Efficient and Effective Passage Search via Contextualized Late Interaction over BERT. arXiv:2004.12832 (2020). Dot product ile cross-encoder arasındaki middle ground. Reimers, N. and Gurevych, I., Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks, arXiv:1908.10084 (2019), bu chapter’ın index’inin üzerine kurulduğu ve Chapter 8’de ölçülen bi-encoder’dır. ↩
-
Malkov, Yu. A. and Yashunin, D. A. Efficient and Robust Approximate Nearest Neighbor Search using Hierarchical Navigable Small World Graphs. arXiv:1603.09320 (2016). Şu anda satılan çoğu vector database’in arkasındaki graph index. ↩
-
Johnson, J., Douze, M. and Jégou, H. Billion-scale Similarity Search with GPUs. arXiv:1702.08734 (2017). FAISS ve yukarıdaki kutuda ölçülen IVF’nin reference implementation’ı. ↩
-
Kalai, A. T., Nachum, O., Vempala, S. S. and Zhang, E. Why Language Models Hallucinate. arXiv:2509.04664 (2025). Hallucination’ın abstention’ı hiç ödüllendirmeyen binary-accuracy grading tarafından üretildiği ve bu yüzden modelling problem’i olmadan önce evaluation problem’i olduğu argümanı. ↩
-
Lewis, P., Perez, E., Piktus, A., Petroni, F., Karpukhin, V., Goyal, N., Küttler, H., Lewis, M., Yih, W., Rocktäschel, T., Riedel, S. and Kiela, D. Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks. arXiv:2005.11401 (2020). Pattern’a adını veren paper ve neyi düzeltip neyi düzeltmediğini okumak için doğru yer. Guu et al., REALM: Retrieval-Augmented Language Model Pre-Training, arXiv:2002.08909 (2020), retriever’ı modele sonradan bolt etmek yerine onunla jointly train eden contemporaneous work; Karpukhin et al., Dense Passage Retrieval for Open-Domain Question Answering, arXiv:2004.04906 (2020), bu chapter boyunca kullanılan two-encoder dense retriever’ın geldiği yer; Izacard and Grave, Leveraging Passage Retrieval with Generative Models for Open Domain Question Answering, arXiv:2007.01282 (2020), birçok passage’ı tek generator’a besleyen fusion-in-decoder arrangement’tır. Gao et al., Retrieval-Augmented Generation for Large Language Models: A Survey, arXiv:2312.10997 (2023), sonrasında gelen her şeyin map’i; question yerine hypothetical answer embed eden HyDE dahil (Gao et al., Precise Zero-Shot Dense Retrieval without Relevance Labels, arXiv:2212.10496, 2022). ↩
-
Manakul, P., Liusie, A. and Gales, M. J. F. SelfCheckGPT: Zero-Resource Black-Box Hallucination Detection for Generative Large Language Models. arXiv:2303.08896 (2023). Model internals’a access olmadan ve external knowledge base olmadan resampling ile detection. ↩
-
Asai, A., Wu, Z., Wang, Y., Sil, A. and Hajishirzi, H. Self-RAG: Learning to Retrieve, Generate, and Critique through Self-Reflection. arXiv:2310.11511 (2023). Her turn’de retrieve etmek yerine model’i ne zaman retrieve edeceğine karar vermek üzere train etmek. ↩
-
Lin, S., Hilton, J. and Evans, O. TruthfulQA: Measuring How Models Mimic Human Falsehoods. arXiv:2109.07958 (2021). Plausible answer ile true answer’ın farklı olduğu questions’dan kurulan benchmark; tüm zorluğu tek sentence’ta anlatır. ↩