Production में RAG: chunking, retrieval और ईमानदार citations
512 अक्षरों पर अंधा कट लगाते ही 32 में से 4 जवाब retriever से पहले मर जाते हैं; chunker ठीक करने से rank 115 से 3 हो जाता है।
इस पेज पर
यह एक वास्तविक assistant के वास्तविक user का वास्तविक सवाल है: मेरे eval set में 20 items हैं, क्या score पर भरोसा करने के लिए यह काफी है। corpus में जवाब है — उसका पूरा section। ये वे चार fragments हैं जिन्हें retriever ने वास्तव में prompt में रखा।
[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…चार में से तीन mid-word शुरू होते हैं। दो किसी अलग विषय के अलग chapter से हैं। और जो fragment सवाल का जवाब देता है — जिसमें Seventeen out of twenty cannot distinguish an 85 % model from a 65 % one है — वह rank 115 पर लौटा।
अब वही सवाल, वही embedding model, वही prompt template। एक चीज बदली: documents को कैसे काटा गया।
[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 से rank 3। किसी ने model, prompt, threshold या slots की संख्या को नहीं छुआ। यह chapter उसी gap के बारे में है, और उन चार दूसरी जगहों के बारे में जहाँ retrieval system चुपचाप आपसे झूठ बोलता है।
विवरण दिखाएँ
इस chapter को पिछले chapters से क्या चाहिए, और वह एक जगह जहाँ यह भाषा बदलता है।
- Chapter 1 ने dot product और L2 norm को define किया। नीचे का threshold section वही दो चीजें हैं, और कुछ नहीं।
- Chapter 8 ने language model की embedding table को pairs पर contrastively trained retrieval embedding model से अलग किया, cosine similarity मापी, और यह वादा करके खत्म हुआ कि Chapter 19 एक concrete cut-off तक पहुँचेगा। वह वादा यहाँ पूरा होता है। उसमें से कुछ भी दोहराया नहीं गया।
- Chapter 4 ने Wilson interval बनाया; Chapter 15 ने evaluation harness बनाया। नीचे की हर table पहले को साथ रखती है और दूसरे से बनी है।
- Chapter 16 ने context window की कीमत निकाली। इस chapter के अंत में assembled prompt की cost 591 tokens है, और यही वह budget है जिसके लिए fragments compete करते हैं।
यहाँ सब कुछ TypeScript है, जैसा Chapter 14 से है, और यही वह chapter है जहाँ rule खुद को justify करता है: ingestion queues और storage है, search एक network call है, और citations के साथ prompt assemble करना server का काम है। measurement जानबूझकर वही code है जिसके चारों ओर scoreboard है — second implementation से scored retriever उस software के बारे में number है जिसे आप ship नहीं कर रहे, और नीचे का cosine threshold केवल इसलिए believable है क्योंकि आप उसे उसी chunker से sweep होते देखते हैं जो production में चलेगा।
Corpus, और right answer किसे माना गया
सेक्शन का लिंक: Corpus, और right answer किसे माना गयानीचे सब कुछ एक corpus के against measured है: इस course के पहले तेरह chapters — 13 documents, 359,067 characters, 127 sections, जिनसे front matter और bibliographies हटाई गई हैं। यह एक वास्तविक technical corpus है, जिसमें prose, tables, formulas और code blocks हैं, और यह ठीक वैसी चीज है जिसे लोग knowledge base में load करते हैं और फिर शिकायत करते हैं।
Ground truth 32 questions है, हर एक के साथ एक needle: corpus से एक छोटा verbatim sentence जो उसका जवाब देता है। हर needle 359,067 characters में ठीक एक बार आता है, और कोई भी section heading नहीं है — यह check मायने रखता है, क्योंकि वरना हर chunk में headings copy करने वाला chunker खुद को score कर लेता। हर question दो बार पूछा गया है, एक बार course English में और एक बार जैसे support ticket उसे phrase करता है: 32 ground truths पर 64 queries।
Retrieval correct तब है जब returned chunk needle को पूरा contain करे। यही वह एकमात्र definition है जो generator की जरूरत से match करती है: prompt में आधा sentence answer नहीं, hazard है।
Embedding model all-MiniLM-L6-v2 है — 384 dimensions, mean-pooled और normalised, वही contrastively trained model जिसे Chapter 8 ने measured किया। corpus को index करने में CPU पर 20.8 seconds लगते हैं, 22 ms per chunk; एक query embedding में 13 ms लगते हैं।
Chunking, छह तरीकों से measured
सेक्शन का लिंक: Chunking, छह तरीकों से measuredतीन independent ingredients से छह strategies। Blind text को देखे बिना हर 512 characters पर cut करता है। Boundaries paragraph के अंदर कभी cut नहीं करता, केवल तब sentence boundary पर fallback करता है जब कोई paragraph budget से बड़ा हो। Header हर chunk के आगे उसका document title और section path prefix करता है। Overlap पिछले chunk के last 64 characters को next chunk में copy करता है।
| 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 queries के साथ R@20 पर 95 % Wilson interval A के लिए [0.471, 0.705] और E के लिए [0.718, 0.901] है — ये overlap नहीं करते, लेकिन बाकी ज्यादातर columns करते हैं, और unpaired table उन्हें अलग नहीं कर सकती। हर strategy same queries का answer देती है, इसलिए honest test paired है: किसी दूसरी strategy के against हर strategy के wins और losses count करें और discordant pairs पर sign test चलाएँ। तीन results इससे बचते हैं।
Blind chunking बत्तीस में से चार answers को सीधे नष्ट कर देती है। उन्हें खराब rank नहीं करती — नष्ट करती है। needle 512-character boundary को straddle करता है, इसलिए index में कोई chunk उसे contain नहीं करता, और उन queries के लिए recall ceiling zero है। कोई reranker उन्हें recover नहीं करता, कोई threshold help नहीं करता, कोई बड़ा model help नहीं करता। आप वह text retrieve नहीं कर सकते जो आपके index में कहीं भी एक टुकड़े में है ही नहीं। यह RAG की सबसे under-reported failure है, क्योंकि यह बिल्कुल bad retriever जैसी दिखती है।
Overlap इसे ठीक करता है और कुछ नहीं। overlap वाली हर strategy zero answers खोती है, और overlap इसी के लिए है। यह ranking improve नहीं करता: R@8 पर B against A +8/−6 है, p = 0.79; R@20 पर +9/−7 है, p = 0.80। इससे भी खराब, header के ऊपर overlap जोड़ना actively hurt करता है — R@20 पर F against E +2/−6 है — और वजह mechanical है। chunk का vector उसके tokens का mean है, इसलिए पिछले chunk के 64 characters उस mean को neighbour के topic की तरफ खींचते हैं। Overlap split answer के against insurance है, जिसकी कीमत precision में चुकती है।
Contextual header ही retrieval खरीदता है। R@20 पर E against A +18/−3, p = 0.0015 है। और ablation कहता है कि यह boundaries नहीं कर रहीं: E against C — वही cuts, सिर्फ header अलग — +12/−2, p = 0.0129 है। paragraph के आगे “Classification, Cross-Entropy, and How Not to Fool Yourself > How many test examples do I need?” prefix करना embedding model को बताता है कि paragraph किस बारे में है, जो paragraph खुद अक्सर नहीं कहता। यह documents के लिए pronoun resolver है।
यही chunker को उसका shape देता है, और एक rule जिसे गलत करना आसान है:
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;
}दो texts, एक नहीं। text embedded होता है, header सहित। content सिर्फ इस chunk के अपने words हैं, और user को quote back होने वाला text यही है। text quote करें तो citation वह header दिखाता है जो document में उस जगह नहीं है — और overlap के साथ, previous fragment की repeated tail भी। तब वह ऐसा text दिखाता है जो वहाँ नहीं है जहाँ वह कहता है, जो कुछ न दिखाने से भी खराब है।
Header free नहीं है। 940 chunks में यह index के 114,275 embedded tokens में से 24,213 खर्च करता है: embed करने के लिए आप जो pay करते हैं उसका 21.2 % वह header है जिसे आपने खुद लिखा। यह chunks को encoder window के against भी push करता है। all-MiniLM-L6-v2 256 word-pieces accept करता है; strategy E में 17 chunks उस line से ऊपर हैं और F में 28, हर एक बिना किसी warning के silently truncated। आपकी effective chunk size आपके config का number नहीं है — वह उस number और encoder window में से छोटा है।
BM25 की बीस lines, जिन्हें हर कोई skip करता है
सेक्शन का लिंक: BM25 की बीस lines, जिन्हें हर कोई skip करता हैDense retrieval की एक systematic weakness है और वह subtle नहीं है: यह meaning match करता है, इसलिए आपने exactly कौन सा string type किया, उससे indifferent रहता है। part number, error code, acronym, surname — इनमें embed करने लायक useful meaning नहीं होता, और error code का nearest neighbour आपके corpus का हर दूसरा error code होता है।
Classical answer इन सब से पुराना है और बीस lines लेता है। BM25 document को इस पर score करता है कि query के terms उसमें कितनी बार आते हैं, हर term की frequency बढ़ने पर उसे damp करता है और लंबाई के दम पर matches accumulate करने वाले long documents को penalise करता है।1 Term contribute करता है
जहाँ document में term की count है, उसकी length, average length, और तथा दो conventional constants हैं — यह set करता है कि repetition कितनी जल्दी help करना बंद करे, length को कितना punish किया जाए।
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 chunks पर यह दो hash maps से आगे कोई index लिए बिना 1.14 ms में query score करता है। और यह museum piece नहीं है:
| retriever | R@1 | R@4 | R@8 | MRR | cost per query |
|---|---|---|---|---|---|
| dense (cosine) | 0.094 | 0.422 | 0.578 | 0.280 | embed करने के लिए 13 ms + scan के लिए 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 | दोनों |
| hybrid + cross-encoder | 0.312 | 0.578 | 0.703 | 0.447 | + 569 ms |
BM25 इस corpus पर dense retriever की top-1 accuracy को दोगुने से ज्यादा कर देता है, और rank 8 तक उससे बुरी तरह हारता है। वे अलग queries पर fail करते हैं, और दोनों चलाने का पूरा argument यही है।
उन्हें fuse करना वह एक जगह है जहाँ obvious approach गलत है। Cosine distances और BM25 scores same scale पर नहीं हैं, same way bounded नहीं हैं, और per query normalise करने से weight इस पर depend करता है कि best hit कितना अच्छा निकला। Reciprocal rank fusion scores को फेंक देता है और सिर्फ ranks रखता है: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);
}और यहाँ table की honest reading table से ज्यादा important है। Hybrid R@4 पर BM25 को +10/−3, p = 0.09 से beat करता है। यह dense को +10/−6, p = 0.45 से beat करता है। इस corpus पर, 64 queries के साथ, hybrid retrieval dense retrieval से distinguishable नहीं है। यह point estimates और हर recall column पर बेहतर है, और evidence significance तक नहीं पहुँचता। internet पर लगभग हर hybrid-search blog post ऊपर जैसी table report करता है और कोई interval नहीं; interval यही कहता है।
Bi-encoder, cross-encoder, और lift असल में कहाँ है
सेक्शन का लिंक: Bi-encoder, cross-encoder, और lift असल में कहाँ हैअब तक सब कुछ bi-encoder है: query model से अकेले गुजरती है, हर chunk महीनों पहले उसी से अकेले गुजरा, और दोनों dot product के अलावा कभी मिलते नहीं। यही index को possible बनाता है — एक बार embed करें, हमेशा reuse करें — और यही ceiling भी है। model query और chunk को साथ देखकर कभी नहीं सोचता।
Cross-encoder ठीक यही करता है: pair को एक input की तरह लेता है और relevance score return करता है। कुछ precomputed नहीं हो सकता, इसलिए यह index rank नहीं कर सकता — लेकिन shortlist rerank कर सकता है। ms-marco-MiniLM-L-6-v2 के साथ hybrid top 25 को rerank करने से R@1 0.094 (dense) से 0.312 और MRR 0.280 से 0.447 हो जाता है: इस chapter का largest single improvement, और tail के बजाय list के top को touch करने वाला अकेला।
CPU पर यह per query 569 ms cost करता है, BM25 के 1.14 ms और vector scan के 0.3 ms के against। पच्चीस documents के लिए retrieval cost का लगभग दो हजार गुना। यही पूरा bi-encoder/cross-encoder trade एक number में है, और यही वजह है कि architecture हमेशा same shape की होती है: wide recall वाला cheap retriever, फिर afford कर सकने लायक shortlist पर expensive scorer। ColBERT दोनों के बीच बैठता है, per-token vectors precompute करता है और late interaction करता है जो cross-encoder से cheaper और dot product से sharper है।3
L2, cosine, और वह threshold जिसके आप अभी हकदार नहीं
सेक्शन का लिंक: L2, cosine, और वह threshold जिसके आप अभी हकदार नहींVector databases distances report करते हैं, और कौन सा distance हो यह configuration option है। normalised vectors पर choice cosmetic है, और identity एक बार करना worth it है क्योंकि इसके बाद सब कुछ vectors के सच में unit होने पर depend करता है। के लिए:
इसलिए cosine distance बिल्कुल है। यह Chapter 1 का dot product और norm, cash किया हुआ है। ऊपर के index से दो real chunk vectors पर check किया गया, फिर 40,000 pairs पर:
||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 तक exact — और सिर्फ इसलिए क्योंकि vectors normalised हैं। normalisation skip करें और identity false है, आपका threshold meaning नहीं रखता, और document द्वारा reported distance इस पर depend करता है कि उसका text कितना लंबा था।
अब वह number जिसे कोई derive नहीं करता। retriever हमेशा कुछ न कुछ return करता है: यह पूरे index को sort करता है और list का top आपको दे देता है, चाहे answer corpus में कहीं हो या नहीं। threshold system का अकेला हिस्सा है जो नहीं कह सकता है — और उसे set करने के लिए आपको ऐसी queries चाहिए जिन्हें कुछ भी वापस नहीं मिलना चाहिए। यहाँ तीस हैं: इक्कीस उन चीजों के बारे में जिन्हें यह corpus सचमुच cover नहीं करता — streaming, rate limits, prompt caching, JSON schemas, agent loops, vector databases, prompt injection, image generation — और नौ paella, passports और refund policies के बारे में। उसी index के against:
| top-1 cosine distance | |
|---|---|
| in-domain queries, सभी 64 | mean 0.445, range 0.270 – 0.721 |
| in-domain, top-1 वास्तव में correct | mean 0.370 |
| in-domain, top-1 wrong | mean 0.452 |
| out-of-domain, सभी 30 | mean 0.699, range 0.497 – 0.867 |
Distributions अलग होती हैं, और overlap भी करती हैं। सबसे खराब in-domain query अपने answer से (0.721) उस best out-of-domain query से भी दूर है जो irrelevant paragraph से (0.497) है, इसलिए कोई threshold दोनों को सही नहीं करता। इसे real gate पर sweep करते हुए — अधिकतम चार chunks रखें, और सिर्फ वे जो cut के नीचे हों:
| threshold | in-domain answered | जिनमें answer था | 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 |
Last column को bluffs की तरह पढ़ें। बिना threshold assistant “how do I renew my Spanish passport” का confident, well-cited answer backpropagation के corpus से देता है, तीस में तीस बार। 0.675 पर यह तीस में दस बार करता है। 0.525 पर यह दो बार करता है, और उन बारह questions पर हार मान लेता है जिनका answer यह दे सकता था।
यह trade product decision है, और उसका right end इस पर depend करता है कि गलत answer की cost क्या है। जो negotiable नहीं है वह last column का exist करना है। अगर आपने अपने retriever को उन questions के against कभी measure नहीं किया जिन्हें उसे refuse करना चाहिए, तो आपके पास threshold नहीं है — आपके पास एक number है।
0.675 पर दस bluffs में से दो दिखाते हैं कि यह दो तरीकों से fail करता है।
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."पहला near miss है: corpus KV cache को detail में explain करता है, query prompt cache के बारे में है, words वही words हैं, और 0.497 पूरे experiment की अधिकांश correct in-domain retrievals से भी close है। embedding यह नहीं जानता कि same नाम वाले दो caches अलग machines हैं। दूसरा literal match with no answer है: corpus में exact phrase “what is the capital of France” है, reasoning न चाहने वाले question के example के रूप में। retriever सही है; answer वहाँ नहीं है। कोई भी system जो “मुझे कुछ similar मिला” को “मुझे answer मिला” की तरह पढ़ता है, उस evidence पर Paris assert करेगा — या, इससे भी खराब, नहीं करेगा।
Citation model नहीं लिखता, क्यों
सेक्शन का लिंक: Citation model नहीं लिखता, क्योंModel के पास facts के लिए कोई separate faculty नहीं होती। True sentence produce करना और plausible sentence produce करना same operation है — Chapter 8 की next-token prediction — और उस operation में कुछ भी यह mark नहीं करता कि कौन सा कौन है। 2025 analysis जिसने इसे reframe किया, argue करता है कि training और evaluation pipeline actively guessing को reward करती है: benchmarks binary accuracy से score करते हैं और abstention का कोई credit नहीं देते, इसलिए हमेशा answer देने वाला model उसी identical model को outscore करता है जो “I don’t know” कहता है जब उसे नहीं पता, और post-training accordingly optimise करता है।6 इस reading में hallucination कोई mysterious defect नहीं है। यह वह है जो wrong answer पर penalty के बिना multiple-choice exam grade करने से मिलता है।
इसका shape देखें। contrastive sentence embeddings पर आठ papers identifiers के साथ माँगे गए, तो Qwen2.5-0.5B-Instruct ने perfect format में आठ lines produce कीं। सभी आठ identifiers well-formed हैं। सभी आठ arXiv पर real papers resolve करते हैं। आठ में से zero वह paper हैं जिसका claim किया गया।
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 Somaliयह छोटा model है और rate उसका अपना है; frontier model बहुत कम invent करता है। mechanism generalise करता है, और यही वजह है अगले rule की। “क्या यह identifier exist करता है” check करने वाला validator सभी आठ pass कर देता है, और user एक पर click करके real archive की real page पर पहुँचता है, जहाँ mapping invented थी यह बताने का कोई तरीका नहीं। failure identifier या format में नहीं है। यह association में है — ठीक वही चीज जिसे language model plausibility से produce करता है।
तो: model [1] और [2] लिखता है, और link कभी नहीं लिखता। numbers उन fragments को refer करते हैं जिन्हें server ने retrieve किया, और server — जिसे ठीक-ठीक पता है कि हर number किस document और किन offsets से आया — बाद में document, label और URL attach करता है। model के invent करने के लिए कुछ नहीं है क्योंकि उससे वह एक चीज कभी माँगी ही नहीं जाती जिसे वह invent करेगा।
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 पर इसे चलाएँ और चार chunks 591-token prompt और ऐसी table बन जाते हैं जिसे model कभी नहीं देखता:
[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 वह हिस्सा है जिसे लोग skip करते हैं और फिर बाद में add नहीं कर पाते। #char=25873,26272 document के canonical text में range है; PDF के लिए equivalent #page=12 है, audio या video के लिए #t=132.4,158.9, spreadsheet के लिए sheet और A1 range। ये दोनों inventions नहीं हैं — #page= PDF Open Parameters है और #t= W3C Media Fragments, जिन्हें browsers video और audio elements पर natively honour करते हैं। locator के बिना citation document name है, और document name citation नहीं है; यह user को जाकर देखने का suggestion है।
और जब threshold कुछ भी pass नहीं करता, pipeline model तक पहुँचती ही नहीं:
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"यह system prompt में किसी instruction की तुलना में cheaper और ज्यादा reliable refusal है, क्योंकि यह probabilistic system से request के बजाय दो numbers की comparison है।
Retriever को generator से अलग evaluate करें
सेक्शन का लिंक: Retriever को generator से अलग evaluate करेंइस chapter में हर measurement retriever को score करता है और model से answer लिखने को एक बार भी नहीं कहता। यह deliberate है, और most teams यही piece skip करती हैं।
RAG system के दो failure modes बाहर से identical दिखते हैं। retriever ने passage नहीं ढूँढा; या उसने ढूँढ लिया और generator ने उसे ignore किया, contradict किया, या उसे किसी ऐसी चीज के साथ blend कर दिया जिस पर वह पहले से believe करता था। सिर्फ final answer score करें तो दोनों indistinguishable हैं, इसलिए आप prompts को उस problem के against tune करते हैं जो आपके chunker में रहती है। Recall@k, MRR और answer-destroyed count को generation call बिल्कुल नहीं चाहिए, वे हर deploy पर चलाने लायक cheap हैं, और वे Chapter 15 का harness हैं एक अलग scoring function के साथ — वही request, deadline, concurrency और tally, live conversation के बजाय fixed question set पर।
उन्हें intervals के साथ report करें। Chapter 4 की arithmetic unchanged apply होती है: 64 queries पर 0.5 recall लगभग ±0.12 का 95 % Wilson interval carry करता है, इसलिए चार points आगे strategy ने आपको कुछ नहीं बताया। paired test use करें जब भी दोनों strategies same questions answer करती हैं, जो यहाँ हमेशा करती हैं — इसी ने “E looks better than A” को p = 0.0015 में बदला।
और आखिरी honesty: RAG hallucination को reduce करता है, remove नहीं। सही passage को prompt में रखना model को उसे use करने के लिए बाध्य नहीं करता, और literature ने original paper से ही यह कहा है।7 Production में दो चीजें इसे worse करती हैं। Long contexts degrade होते हैं — model long prompt की शुरुआत और अंत में information को middle से ज्यादा reliably पाता है, इसलिए चार की जगह बीस chunks accuracy घटा सकते हैं और bill बढ़ा सकते हैं, एक effect जिसे Chapter 24 में measured किया गया। और retrieval right होकर भी insufficient हो सकता है, जैसा ऊपर के दो caches ने दिखाया। SelfCheckGPT उन claims को flag करता है जो resampling survive नहीं करते;8 Self-RAG model को अपने retrieve-and-critique tokens emit करना train करता है;9 TruthfulQA ने failure mode को पहली जगह legible बनाया।10 इनमें से कोई gap बंद नहीं करता, और retrieved text को proof की तरह present करने वाला system sourced को true समझ बैठा है।
System का वह आधा जो किसी query से पहले चलता है
सेक्शन का लिंक: System का वह आधा जो किसी query से पहले चलता हैRetriever एक pipeline का visible हिस्सा है जिसकी failures पहले, अँधेरे में, होती हैं। उनमें से तीन बार-बार आती हैं।
Extraction वह जगह है जहाँ content मरता है। PDF text नहीं है; वह drawing instructions है। Two-column layouts interleave होते हैं, tables word soup बनते हैं, page headers हर chunk में repeat होते हैं, और scanned page में कोई text नहीं होता जब तक OCR उसे कुछ न दे, confidence के साथ। ऊपर measured सब कुछ मानकर चला कि extractor ने अपना काम किया; production में अक्सर ऐसा नहीं होता, और symptom तीन layers दूर bad retrieval की तरह दिखता है।
Index उस model से stamped होता है जिसने उसे बनाया। दो models की embeddings comparable नहीं हैं — “less accurate” नहीं, comparable नहीं, क्योंकि वे अलग spaces में points हैं। embedding model बदलें और store का हर vector garbage है जब तक rebuild न हो। इसलिए model name, dimension count, pipeline version और extractor version index time पर हर document के beside लिखे जाते हैं। इनके बिना, upgrade day पर, आप नहीं बता सकते कि कौन से documents stale हैं और कौन current, और half-migrated index कहीं error दिए बिना confident nonsense return करता है।
एक broken document folder को break नहीं करना चाहिए, और counters को वही count करना चाहिए जो हुआ। extraction fail करने वाला document अपने reason के साथ failed state में खत्म होता है, visible और retryable, जबकि बाकी निन्यानवे searchable रहते हैं; और indexed chunks की संख्या server finish होने पर लिखता है, client upload करते वक्त declare नहीं करता। 400 fragments report करने और 40 रखने वाला folder एक झूठ है जो सिर्फ unanswerable question के रूप में surface होता है।
यह आगे कहाँ जाता है
सेक्शन का लिंक: यह आगे कहाँ जाता हैइस chapter का system उन questions के answer देता है जिनके answers लिखे हुए हैं। यह उन्हें retrieve करता है, rank करता है, जब नहीं कर सकता तो refuse करता है, और cite करता है कि उसने कहाँ देखा। लोग अपने documents पर assistant से जो चाहते हैं उसका अधिकांश यही है, और यह एक specific way में bounded है: retrieval केवल वही return कर सकता है जो किसी ने लिखा।
जिससे दूसरा आधा बचता है। आप model से जो कुछ करवाना चाहते हैं, उसका कुछ हिस्सा document में fact नहीं होता — कोई format जिसे उसे hold करना है, कोई tone, चार सौ labels वाली taxonomy, decide करने का एक way जो दस हजार past examples में रहता है और किसी paragraph में कहीं नहीं। Retrieval यह deliver नहीं कर सकता, क्योंकि retrieve करने के लिए कुछ नहीं है; longer prompt सिर्फ Chapter 16 का bill किसी skill की description के लिए pay करता है, skill के लिए नहीं।
Chapter 20 वही decision है — fine-tune, retrieve या prompt — और उसकी finding यह है कि decision technical होने से पहले economic है: तीनों की कीमत end to end same question पर निकाली गई है, और crossover एक token count है। जो question उसे खोलता है वह वही है जिसका answer यह chapter नहीं दे सकता। यह नहीं कि answer कहाँ लिखा है, बल्कि जब वह कभी लिखा ही नहीं गया तो आप क्या करते हैं।
Sources and method
सेक्शन का लिंक: Sources and methodइस chapter में measured सब कुछ एक corpus और एक instrument से हुआ, और दोनों reproducible हैं। corpus इस course के chapters 1 से 13 हैं जैसे वे 7 September 2026 को थे — 13 documents, 359,067 characters, 127 sections, front matter और bibliographies हटाई गईं। वे chapters edit होते रहते हैं, इसलिए आज वही rule apply करने पर कुछ हजार characters अधिक count होते हैं: section count unchanged है और नीचे का हर conclusion भी, लेकिन character total snapshot है और उसी तरह labelled है। Ground truth 32 questions है, हर एक के साथ एक verbatim sentence जो corpus में ठीक एक बार आता है और section heading कभी नहीं, 64 queries के लिए दो phrasings में पूछा गया। Retrieval embeddings sentence-transformers/all-MiniLM-L6-v2 हैं (384 dimensions, mean-pooled, L2-normalised, 256-token window); reranking top 25 पर cross-encoder/ms-marco-MiniLM-L-6-v2 है; generation example greedy decoding के साथ Qwen/Qwen2.5-0.5B-Instruct है। सारी timings single-threaded CPU हैं। इस chapter को produce करने के लिए कोई paid API call नहीं किया गया, इसलिए यहाँ की हर latency local है और वैसी ही labelled है।
TypeScript में दिखाया chunker वही chunker है जिसे measured किया गया: same rule और ts/chunk.ts implement करने वाले Python instrument को पूरे corpus पर chunk for chunk compare किया गया और सभी 940 chunks, texts और offsets पर agree करता है। Intervals 95 % पर Wilson हैं; paired comparisons discordant pairs पर two-sided exact sign tests हैं।
ऊपर cited सभी चौदह identifiers arXiv API के against resolve किए गए और 7 September 2026 को title by title check किए गए — जो, उन आठ को देखते हुए जो नहीं थे, इस particular chapter के लिए कम से कम इतना करना तो बनता था।
संदर्भ
सेक्शन का लिंक: संदर्भ-
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 और ऊपर used दो constants का source, और यह पढ़ने की जगह कि exist ही क्यों करता है। ↩
-
Cormack, G. V., Clarke, C. L. A. and Büttcher, S. Reciprocal Rank Fusion Outperforms Condorcet and Individual Rank Learning Methods. SIGIR 2009. उनका है, और method का point यह है कि जिन score scales को यह fuse कर रहा है उनके बीच calibration नहीं चाहिए। ↩
-
Khattab, O. and Zaharia, M. ColBERT: Efficient and Effective Passage Search via Contextualized Late Interaction over BERT. arXiv:2004.12832 (2020). dot product और cross-encoder के बीच का middle ground। Reimers, N. and Gurevych, I., Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks, arXiv:1908.10084 (2019), वह bi-encoder है जिस पर इस chapter का index built है और जिसे Chapter 8 में measured किया गया था। ↩
-
Malkov, Yu. A. and Yashunin, D. A. Efficient and Robust Approximate Nearest Neighbor Search using Hierarchical Navigable Small World Graphs. arXiv:1603.09320 (2016). आज बिक रहे most vector databases के पीछे का graph index। ↩
-
Johnson, J., Douze, M. and Jégou, H. Billion-scale Similarity Search with GPUs. arXiv:1702.08734 (2017). FAISS, और ऊपर के box में measured IVF का reference implementation। ↩
-
Kalai, A. T., Nachum, O., Vempala, S. S. and Zhang, E. Why Language Models Hallucinate. arXiv:2509.04664 (2025). यह argument कि hallucination binary-accuracy grading से produce होती है जो abstention को कभी reward नहीं करती, और इसलिए यह modelling problem होने से पहले evaluation problem है। ↩
-
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). वह paper जिसने pattern को name दिया और जिसे यह समझने के लिए पढ़ना चाहिए कि यह क्या fix करता है और क्या नहीं। Guu et al., REALM: Retrieval-Augmented Language Model Pre-Training, arXiv:2002.08909 (2020), contemporaneous work है जो retriever को model के साथ jointly train करता है बजाय उसे bolt-on करने के; Karpukhin et al., Dense Passage Retrieval for Open-Domain Question Answering, arXiv:2004.04906 (2020), वह जगह है जहाँ से इस chapter में used two-encoder dense retriever आता है; और Izacard and Grave, Leveraging Passage Retrieval with Generative Models for Open Domain Question Answering, arXiv:2007.01282 (2020), कई passages को एक generator को feed करने की fusion-in-decoder arrangement है। Gao et al., Retrieval-Augmented Generation for Large Language Models: A Survey, arXiv:2312.10997 (2023), बाद में आए सब कुछ का map है, जिसमें HyDE शामिल है (Gao et al., Precise Zero-Shot Dense Retrieval without Relevance Labels, arXiv:2212.10496, 2022), जो question के बजाय hypothetical answer embed करता है। ↩
-
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). resampling से detection, model internals या external knowledge base तक access के बिना। ↩
-
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). हर turn पर retrieve करने के बजाय कब retrieve करना है यह decide करने के लिए model को train करना। ↩
-
Lin, S., Hilton, J. and Evans, O. TruthfulQA: Measuring How Models Mimic Human Falsehoods. arXiv:2109.07958 (2021). ऐसे questions से बना benchmark जहाँ plausible answer और true answer अलग होते हैं, जो पूरी difficulty को एक sentence में रख देता है। ↩