RAG élesben: chunking, retrieval és őszinte hivatkozások
512 karakternél vakon vágva 32 válaszból 4 eltűnik, mielőtt a retriever látná. Csak a chunker javítása 115-ről 3-ra emel.
Ezen az oldalon
Íme egy valódi kérdés egy valódi assistant valódi felhasználójától: az eval készletemben 20 elem van, ez elég ahhoz, hogy megbízzak a pontszámban. A korpusz tartalmazza a választ — egy egész szakaszt róla. Itt van az a négy töredék, amelyet a retriever ténylegesen betett a promptba.
[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…A négyből három szó közepén kezdődik. Kettő egy másik fejezetből való, más témáról. És az a töredék, amely megválaszolja a kérdést — amely tartalmazza, hogy Húszból tizenhét nem tud különbséget tenni egy 85 %-os és egy 65 %-os model között — 115. helyen jött vissza.
Most ugyanaz a kérdés, ugyanaz az embedding model, ugyanaz a prompt sablon. Egyetlen dolog változott: hogyan vágtuk fel a dokumentumokat.
[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]- helyről 3. helyre. Senki nem nyúlt a modelhez, a prompthoz, a küszöbhöz vagy a helyek számához. Ez a fejezet erről a résről szól, és arról a négy másik pontról, ahol egy retrieval rendszer csendben hazudik neked.
Részletek megjelenítése
Amire ennek a fejezetnek szüksége van a korábbi fejezetekből, és az egyetlen hely, ahol nyelvet vált.
- 1. fejezet definiálta a skalárszorzatot és az L2 normát. Az alábbi küszöbszakasz ez a kettő, és semmi más.
- 8. fejezet szétválasztotta a language model embedding tábláját egy párokon kontrasztívan tanított retrieval embedding modeltől, koszinusz-hasonlóságot mért, és azzal zárta, hogy a 19. fejezet konkrét vágási küszöbhöz érkezik majd. Ez az ígéret itt válik esedékessé. Egyik részét sem ismételjük meg.
- 4. fejezet felépítette a Wilson-intervallumot; 15. fejezet felépítette az evaluation harnesst. Az alábbi táblák mind az előbbit hordozzák, és az utóbbi állította elő őket.
- 16. fejezet beárazta a context windowt. A fejezet végén összeállított prompt 591 tokenbe kerül, és a töredékek ezért a keretért versenyeznek.
Itt minden TypeScript, ahogy 14. fejezet óta, és ez az a fejezet, ahol a szabály igazolja magát: az ingestion sorokból és tárolásból áll, a keresés hálózati hívás, a hivatkozásokkal ellátott prompt összeállítása pedig szerverfeladat. A mérés szándékosan ugyanaz a kód, csak eredményjelzővel körülötte — egy második implementációval pontozott retriever olyan szoftverről ad számot, amelyet nem fogsz shipelni, az alábbi koszinuszküszöb pedig csak azért hihető, mert azt a chunker söpri végig, amely productionben futni fog.
A korpusz, és mi számít helyes válasznak
Link a szakaszhoz: A korpusz, és mi számít helyes válasznakAz alábbiak mind egyetlen korpuszon vannak mérve: a kurzus első tizenhárom fejezetén — 13 dokumentum, 359 067 karakter, 127 szakasz, a front matter és a bibliográfiák eltávolításával. Ez valódi technikai korpusz, prózával, táblákkal, képletekkel és kódblokkokkal, és pontosan olyan dolog, amit emberek betöltenek egy knowledge base-be, majd panaszkodnak rá.
A ground truth 32 kérdés, mindegyikhez egy needle társítva: egy rövid, szó szerinti mondat a korpuszból, amely megválaszolja. Minden needle pontosan egyszer szerepel a 359 067 karakterben, és egyik sem szakaszcím — ez az ellenőrzés számít, mert különben egy chunker, amely minden chunkba bemásolja a címeket, saját magát pontozná. Minden kérdést kétszer teszünk fel: egyszer a kurzus angolján, egyszer pedig úgy, ahogy egy support ticket megfogalmazná: 64 query 32 ground truth felett.
Egy retrieval akkor helyes, ha a visszaadott chunk egészben tartalmazza a needlet. Ez az egyetlen definíció, amely megfelel annak, amire a generatornak szüksége van: egy fél mondat a promptban nem válasz, hanem kockázat.
Az embedding model all-MiniLM-L6-v2 — 384 dimenziós, mean-pooled és normalizált, a kontrasztívan tanított model, amelyet a 8. fejezet mért. A korpusz indexelése CPU-n 20,8 másodperc, 22 ms chunkenként; egy query embeddingje 13 ms.
Chunking hatféleképpen mérve
Link a szakaszhoz: Chunking hatféleképpen mérveHat stratégia három független összetevőből. Blind 512 karakterenként vág, anélkül hogy ránézne a szövegre. Boundaries soha nem vág bekezdésen belül, és csak akkor esik vissza mondathatárra, ha egy bekezdés túllépi a keretet. Header minden chunk elé beteszi a dokumentum címét és a szakaszútvonalat. Overlap az előző chunk utolsó 64 karakterét bemásolja a következőbe.
| stratégia | chunkok | megsemmisített válaszok | 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 mellett az R@20 95 %-os Wilson-intervalluma A esetén [0.471, 0.705], E esetén [0.718, 0.901] — ezek nem fedik egymást, de a többi oszlop nagy része igen, és egy párosítatlan tábla nem tudja szétválasztani őket. Minden stratégia ugyanazokra a querykre válaszol, ezért az őszinte teszt párosított: számold meg az egyik stratégia győzelmeit és vereségeit a másikkal szemben, és futtass előjeltesztet a diszkordáns párokon. Három eredmény éli túl.
A blind chunking a harminckét válaszból négyet egyenesen megsemmisít. Nem rosszul rangsorolja — megsemmisíti. A needle átlóg egy 512 karakteres határon, ezért az indexben egyetlen chunk sem tartalmazza, és ezeknél a queryknél a recall plafonja nulla. Semmilyen reranker nem hozza vissza, semmilyen küszöb nem segít, semmilyen nagyobb model nem segít. Nem tudsz olyan szöveget retrieve-elni, amely sehol sincs egy darabban az indexedben. Ez a RAG leginkább aluljelentett hibája, mert pontosan úgy néz ki, mint egy rossz retriever.
Az overlap ezt javítja, és semmi mást. Minden overlapet használó stratégia nulla választ veszít el, erre való az overlap. A rangsorolást nem javítja: B A-val szemben R@8-nál +8/−6, p = 0.79; R@20-nál +9/−7, p = 0.80. Rosszabb: az overlap hozzáadása a header tetejére aktívan árt — F E-vel szemben +2/−6 R@20-nál —, és az ok mechanikus. Egy chunk vektora a tokenjeinek átlaga, ezért az előző chunk 64 karaktere a szomszéd témája felé húzza ezt az átlagot. Az overlap biztosítás egy szétvágott válasz ellen, precisionben fizetve érte.
A kontextuális header vásárolja meg a retrievalt. E A-val szemben +18/−3 R@20-nál, p = 0.0015. Az ablation pedig azt mondja, nem a boundaries teszi: E C-vel szemben — ugyanazok a vágások, csak a header különbözik — +12/−2, p = 0.0129. Ha egy bekezdés elé beteszed, hogy „Classification, Cross-Entropy, and How Not to Fool Yourself > How many test examples do I need?”, az megmondja az embedding modelnek, hogy miről szól a bekezdés, amit maga a bekezdés gyakran nem mond ki. Ez egy névmásfeloldó dokumentumokhoz.
Ez adja meg a chunker alakját, és egy szabályt, amelyet könnyű elrontani:
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;
}Két szöveg, nem egy. text kerül embeddingbe, headerrel együtt. content csak ennek a chunknak a saját szavai, és ezt idézzük vissza a felhasználónak. Ha text kerül idézésre, a hivatkozás olyan headert mutat, amely a dokumentumnak azon a pontján nincs ott — és overlap esetén egy ismételt farkat is, amely az előző töredékhez tartozik. Így olyan szöveget jelenít meg, amely nincs ott, ahol állítja, ami rosszabb annál, mintha semmit sem mutatna.
A header nincs ingyen. A 940 chunkon át az index 114 275 embedded tokenjéből 24 213-ba kerül: az embeddingért fizetett mennyiség 21,2 %-a olyan header, amelyet te írtál. Emellett a chunkokat az encoder ablakához tolja. all-MiniLM-L6-v2 256 word-piece-t fogad el; az E stratégia 17 chunkja lépi át ezt a vonalat, az F-é 28, és mindegyiket némán truncateli valami, figyelmeztetés nélkül. A tényleges chunkméreted nem a configban lévő szám — hanem annak és az encoder ablakának kisebbike.
Húsz sor BM25, amelyet mindenki átugrik
Link a szakaszhoz: Húsz sor BM25, amelyet mindenki átugrikA dense retrievalnek van egy rendszerszintű gyengesége, és nem finom: jelentést illeszt, ezért közömbös azzal kapcsolatban, hogy pontosan melyik stringet írtad be. Egy cikkszám, egy hibakód, egy rövidítés, egy vezetéknév — egyiknek sincs hasznos embeddingelhető jelentése, és egy hibakód legközelebbi szomszédja a korpuszod összes többi hibakódja.
A klasszikus válasz régebbi mindennél itt, és húsz sor. BM25 úgy pontoz egy dokumentumot, hogy milyen gyakran jelennek meg benne a query kifejezései, minden kifejezést csillapítva, ahogy a gyakorisága nő, és büntetve a hosszú dokumentumokat, amelyek puszta hosszuk miatt gyűjtenek egyezéseket.1 A kifejezés hozzájárulása
ahol a kifejezés darabszáma a dokumentumban, a hossza, az átlagos hossz, és pedig a két szokásos konstans — azt állítja be, milyen gyorsan szűnik meg segíteni az ismétlés, pedig azt, milyen keményen büntetjük a hosszt.
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 felett ez 1,14 ms alatt pontoz egy queryt, index nélkül, két hash mapen túl. És nem múzeumi darab:
| retriever | R@1 | R@4 | R@8 | MRR | költség querynként |
|---|---|---|---|---|---|
| dense (cosine) | 0.094 | 0.422 | 0.578 | 0.280 | 13 ms embeddinghez + 0.3 ms scanhez |
| lexical (BM25) | 0.219 | 0.375 | 0.469 | 0.313 | 1.14 ms |
| hybrid (RRF) | 0.203 | 0.484 | 0.609 | 0.346 | mindkettő |
| hybrid + cross-encoder | 0.312 | 0.578 | 0.703 | 0.447 | + 569 ms |
A BM25 ezen a korpuszon több mint megduplázza a dense retriever top-1 pontosságát, és 8. helyig csúnyán kikap tőle. Más queryken buknak el, és ez az egész érv amellett, hogy mindkettőt futtasd.
Az összeolvasztásuk az a pont, ahol a nyilvánvaló megközelítés rossz. A koszinusztávolságok és a BM25 pontszámok nincsenek ugyanazon a skálán, nem ugyanúgy korlátosak, és querynkénti normalizálásuk miatt a súly attól függ, történetesen mennyire jó volt a legjobb találat. A reciprocal rank fusion eldobja a pontszámokat, és csak a rangokat tartja meg: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);
}És itt a tábla őszinte olvasata fontosabb, mint maga a tábla. A hybrid BM25-öt R@4-nél +10/−3-mal veri, p = 0.09. A dense-t +10/−6-tal veri, p = 0.45. Ezen a korpuszon, 64 queryvel, a hybrid retrieval nem különböztethető meg a dense retrievaltől. Mindkét pontbecslésben és minden recall oszlopban jobb, de a bizonyíték nem éri el a szignifikanciát. Az interneten szinte minden hybrid-search blogposzt a fentihez hasonló táblát közöl intervallum nélkül; ezt mondja az intervallum.
Bi-encoder, cross-encoder, és hol van valójában az emelés
Link a szakaszhoz: Bi-encoder, cross-encoder, és hol van valójában az emelésEddig minden bi-encoder: a query egyedül megy át a modellen, minden chunk egyedül ment át rajta hónapokkal ezelőtt, és a kettő soha nem találkozik, csak skalárszorzatként. Ez teszi lehetővé az indexet — embedding egyszer, újrafelhasználás örökké —, és ez a plafon is. A model soha nem nézi együtt a queryt és a chunkot.
A cross-encoder pontosan ezt teszi: a párt egyetlen inputként kapja, és relevanciapontszámot ad vissza. Semmit nem lehet előre kiszámítani, ezért nem tud indexet rangsorolni — de egy shortlistet újra tud rangsorolni. A hybrid top 25 rerankingje ms-marco-MiniLM-L-6-v2-tel az R@1-et 0.094-ről (dense) 0.312-re, az MRR-t 0.280-ról 0.447-re viszi: ez a fejezet legnagyobb egyedi javulása, és az egyetlen, amely a lista tetejét érinti, nem a farkát.
CPU-n querynként 569 ms-ba kerül, szemben a BM25 1,14 ms-ával és a vektorscan 0,3 ms-ával. Nagyjából kétezerszeres retrieval költség, huszonöt dokumentumért. Ez az egész bi-encoder/cross-encoder trade egyetlen számban, és ezért mindig ugyanolyan alakú az architektúra: olcsó retriever széles recalllal, majd drága scorer egy megfizethető shortlisten. A ColBERT a kettő között ül: tokenenkénti vektorokat számol előre, és késői interakciót végez, amely olcsóbb egy cross-encodernél és élesebb egy skalárszorzatnál.3
L2, koszinusz, és egy küszöb, amelyet még nem érdemeltél ki
Link a szakaszhoz: L2, koszinusz, és egy küszöb, amelyet még nem érdemeltél kiA vector database-ek távolságokat jelentenek, és hogy melyik távolságot, az konfigurációs opció. Normalizált vektorokon a választás kozmetikai, és az azonosságot érdemes egyszer végigcsinálni, mert utána minden azon múlik, hogy a vektorok tényleg egységnyi hosszúak. esetén:
így a koszinusz-távolság pontosan . Ez az 1. fejezet skalárszorzata és normája, beváltva. Ellenőrizve a fenti index két valódi chunkvektorán, majd 40 000 páron:
||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-07Lebegőpontos zajig pontos — és csak azért, mert a vektorok normalizáltak. Hagyd ki a normalizálást, és az azonosság hamis, a küszöböd semmit sem jelent, és a dokumentum által jelentett távolság attól függ, milyen hosszú volt a szövege.
Most a szám, amelyet senki nem vezet le. A retriever mindig visszaad valamit: rendezi az egész indexet, és odaadja a lista tetejét, akár benne van a válasz a korpuszban, akár nincs. A küszöb az egyetlen része a rendszernek, amely nemet tud mondani — és ennek beállításához olyan querykre van szükség, amelyeknek semmit nem kellene visszakapniuk. Itt van harminc: huszonegy olyan dolgokról, amelyeket ez a korpusz valóban nem fed le — streaming, rate limit, prompt caching, JSON sémák, agent loopok, vector database-ek, prompt injection, image generation — és kilenc paelláról, útlevelekről és visszatérítési szabályzatokról. Ugyanazzal az indexszel szemben:
| top-1 koszinusztávolság | |
|---|---|
| in-domain queryk, mind a 64 | átlag 0.445, tartomány 0.270 – 0.721 |
| in-domain, top-1 ténylegesen helyes | átlag 0.370 |
| in-domain, top-1 hibás | átlag 0.452 |
| out-of-domain, mind a 30 | átlag 0.699, tartomány 0.497 – 0.867 |
Az eloszlások elkülönülnek, és átfedik egymást. A legrosszabb in-domain query messzebb van a válaszától (0.721), mint amennyire a legjobb out-of-domain query van egy irreleváns bekezdéstől (0.497), ezért nincs olyan küszöb, amely mindkettőt jól kezeli. Végigsöpörve a valódi kapun — legfeljebb négy chunk megtartása, és csak azoké, amelyek a vágás alatt vannak:
| küszöb | in-domain megválaszolva | ebből a válasz benne volt | out-of-domain megválaszolva |
|---|---|---|---|
| 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 |
| nincs | 64 / 64 | 27 | 30 / 30 |
Az utolsó oszlopot olvasd blöffként. Küszöb nélkül az assistant magabiztos, jól hivatkozott választ ad arra, hogy „hogyan újítom meg a spanyol útlevelemet” egy backpropagationről szóló korpuszból, harmincból harmincszor. 0.675-nél harmincból tízszer teszi ezt. 0.525-nél kétszer, és felad tizenkét olyan kérdést, amelyet meg tudott volna válaszolni.
Ez a trade product döntés, és hogy melyik vége a helyes, attól függ, mennyibe kerül neked egy rossz válasz. Ami nem alku tárgya, az az utolsó oszlop létezése. Ha soha nem mérted a retrieveredet olyan kérdésekkel szemben, amelyeket vissza kellene utasítania, akkor nincs küszöböd — csak egy számod van.
A 0.675-nél megjelenő tíz blöffből kettő megmutatja a két hibamódot.
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."Az első egy near miss: a korpusz részletesen magyarázza a KV cache-t, a query a prompt cache-ről szól, a szavak ugyanazok a szavak, és a 0.497 közelebb van, mint a teljes kísérlet legtöbb helyes in-domain retrievalje. Egy embedding nem tudja, hogy két azonos nevű cache különböző gép. A második szó szerinti egyezés válasz nélkül: a korpusz tartalmazza a pontos „what is the capital of France” kifejezést, példaként egy olyan kérdésre, amelyhez nincs szükség reasoningre. A retrievernek igaza van; a válasz nincs ott. Bármely rendszer, amely az „találtam valami hasonlót” állítást úgy olvassa, hogy „megtaláltam a választ”, erre a bizonyítékra Paris-t fog állítani — vagy rosszabb esetben nem.
Miért nem a model írja a hivatkozást
Link a szakaszhoz: Miért nem a model írja a hivatkozástEgy modelnek nincs külön képessége a tényekre. Egy igaz mondat előállítása és egy hihető mondat előállítása ugyanaz a művelet — a 8. fejezet next-token predictionje —, és ebben a műveletben semmi nem jelöli, melyik melyik. A 2025-ös elemzés, amely újrakeretezte ezt, azt állítja, hogy a training és evaluation pipeline aktívan jutalmazza a találgatást: a benchmarkok bináris pontossággal pontoznak, és nem adnak kreditet a tartózkodásért, ezért egy mindig válaszoló model mindig jobb pontszámot ér el, mint egy azonos model, amely azt mondja, „nem tudom”, amikor nem tudja, és a post-training ennek megfelelően optimalizál.6 A hallucination ebben az olvasatban nem rejtélyes hiba. Ezt kapod, amikor egy feleletválasztós vizsgát úgy pontozol, hogy nincs büntetés a rossz válaszért.
Nézd meg az alakját. Nyolc tanulmányt kérve kontrasztív mondat-embeddingekről, azonosítókkal, Qwen2.5-0.5B-Instruct nyolc sort állított elő tökéletes formátumban. Mind a nyolc azonosító jól formált. Mind a nyolc valódi arXiv-tanulmányra oldódik fel. A nyolcból nulla az állított tanulmány.
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 SomaliEz egy kis model, és az arány a sajátja; egy frontier model sokkal kevesebbet talál ki. A mechanizmus általános, és ez az oka a következő szabálynak. Egy validator, amely azt ellenőrzi, hogy „létezik-e ez az azonosító”, mind a nyolcat átengedi, a felhasználó pedig, aki rákattint, egy valódi archívum valódi oldalára jut, és nincs módja megállapítani, hogy a megfeleltetés kitalált. A hiba nem az azonosítóban vagy a formátumban van. Az asszociációban van — pontosan abban, amit egy language model hihetőség alapján állít elő.
Tehát: a model [1] és [2] értékeket ír, és soha nem ír linket. A számok a szerver által retrieved töredékekre utalnak, a szerver pedig — amely pontosan tudja, melyik dokumentumból és mely offsetekből jött minden szám — utólag csatolja a dokumentumot, a címkét és az URL-t. Nincs mit kitalálnia a modelnek, mert soha nem kérjük tőle azt az egy dolgot, amelyet kitalálna.
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 };
}Futtasd a nyitókérdésen, és a négy chunkból 591-tokenes prompt lesz, valamint egy tábla, amelyet a model soha nem lát:
[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.605A locator az a rész, amelyet az emberek átugranak, majd később már nem tudnak hozzáadni. #char=25873,26272 egy tartomány a dokumentum kanonikus szövegében; PDF esetén ennek megfelelője #page=12, audio vagy video esetén #t=132.4,158.9, táblázatnál egy munkalap és egy A1 tartomány. Ez a kettő nem kitaláció — #page= a PDF Open Parameters, #t= pedig a W3C Media Fragments, amelyet a böngészők natívan tiszteletben tartanak video- és audioelemeknél. Locator nélküli hivatkozás dokumentumnév, a dokumentumnév pedig nem hivatkozás; javaslat arra, hogy a felhasználó menjen és keresse meg.
És amikor semmi nem megy át a küszöbön, a pipeline el sem jut a modelig:
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"Ez olcsóbb és megbízhatóbb visszautasítás, mint bármilyen utasítás egy system promptban, mert két szám összehasonlítása, nem pedig kérés egy probabilisztikus rendszerhez.
Értékeld a retrievert külön a generatortól
Link a szakaszhoz: Értékeld a retrievert külön a generatortólEbben a fejezetben minden mérés a retrievert pontozza, és egyszer sem kér modelt arra, hogy választ írjon. Ez szándékos, és ezt a részt hagyja ki a legtöbb csapat.
Egy RAG rendszernek két hibamódja van, amelyek kívülről azonosnak tűnnek. A retriever nem találta meg a passzust; vagy megtalálta, és a generator figyelmen kívül hagyta, ellentmondott neki, vagy összekeverte valamivel, amit már hitt. Ha csak a végső választ pontozod, a kettő megkülönböztethetetlen, így promptokat hangolsz egy olyan problémára, amely a chunkeredben él. A Recall@k, az MRR és a megsemmisített válaszok száma egyáltalán nem igényel generation hívást, elég olcsók ahhoz, hogy minden deployon fussanak, és a 15. fejezet harnessét jelentik más scoring functionnel — ugyanaz a request, határidő, concurrency és tally, fix kérdéskészleten, nem élő beszélgetésen.
Intervallumokkal jelentsd őket. A 4. fejezet aritmetikája változatlanul érvényes: 64 querynél a 0,5-ös recall 95 %-os Wilson-intervalluma nagyjából ±0,12, ezért egy másiknál négy ponttal előrébb lévő stratégia semmit nem mondott. Használd a párosított tesztet, amikor mindkét stratégia ugyanazokra a kérdésekre válaszol, ami itt mindig igaz — ez alakította az „E jobbnak tűnik A-nál” állítást p = 0.0015-té.
És az utolsó őszinteség: a RAG csökkenti a hallucinationt, de nem szünteti meg. Attól, hogy a helyes passzus bekerül a promptba, a model még nem köteles használni, és a szakirodalom ezt már az eredeti tanulmány óta mondja.7 Két dolog rontja productionben. A hosszú context romlik — egy model a hosszú prompt elején és végén lévő információt megbízhatóbban találja meg, mint a közepén, ezért húsz chunk négy helyett csökkentheti a pontosságot, miközben növeli a számlát; ezt a hatást a 24. fejezet méri. És a retrieval lehet helyes, mégis elégtelen, ahogy a fenti két cache mutatta. A SelfCheckGPT megjelöli azokat az állításokat, amelyek nem élik túl az újramintavételezést;8 a Self-RAG megtanítja a modelt, hogy saját retrieve-and-critique tokeneket bocsásson ki;9 a TruthfulQA eleve olvashatóvá tette ezt a hibamódot.10 Egyik sem zárja be a rést, és az a rendszer, amely a retrieved szöveget bizonyítékként mutatja be, összekeverte a forrással ellátottat az igazzal.
A rendszer fele, amely bármely query előtt fut
Link a szakaszhoz: A rendszer fele, amely bármely query előtt futA retriever egy pipeline látható része, amelynek hibái mind korábban, sötétben történnek. Három tér vissza újra és újra.
Az extraction az a pont, ahol a tartalom meghal. A PDF nem szöveg; rajzolási utasítások halmaza. A kétoszlopos tördelések összekeverednek, a táblák szólevessé válnak, az oldalszintű fejlécek minden chunkba ismétlődnek, a scannelt oldalnak pedig egyáltalán nincs szövege, amíg az OCR nem ad neki valamennyit, confidence értékkel. A fent mért minden azt feltételezte, hogy az extractor elvégezte a munkáját; productionben gyakran nem, és a tünet három réteggel odébb rossz retrievalként jelenik meg.
Az indexen rajta van a model pecsétje, amely építette. Két model embeddingjei nem összehasonlíthatók — nem „kevésbé pontosak”, hanem nem összehasonlíthatók, mert különböző terek pontjai. Ha megváltoztatod az embedding modelt, a store minden vektora szemét, amíg újra nem épül. Ezért a model neve, a dimenziószám, a pipeline verziója és az extractor verziója indexeléskor minden dokumentum mellé íródik. Ezek nélkül upgrade napján nem tudod megmondani, mely dokumentumok elavultak és melyek aktuálisak, egy félig migrált index pedig magabiztos értelmetlenséget ad vissza anélkül, hogy bárhol hiba lenne.
Egy törött dokumentum nem törheti el a mappát, és a számlálóknak azt kell számolniuk, ami történt. Az extractionben elbukó dokumentum failed állapotban végződik az okával együtt, láthatóan és retryolhatóan, miközben a másik kilencvenkilenc kereshető marad; az indexelt chunkok számát pedig a szerver írja ki, amikor végez, nem a kliens jelenti be feltöltéskor. Egy mappa, amely 400 töredéket jelent, de 40-et tart, hazugság, amely csak megválaszolhatatlan kérdésként bukkan felszínre.
Merre megyünk tovább
Link a szakaszhoz: Merre megyünk továbbA fejezet rendszere olyan kérdésekre válaszol, amelyek válaszai le vannak írva. Retrieve-eli őket, rangsorolja őket, visszautasít, amikor nem tud, és hivatkozik arra, hol nézett. Ez a legtöbb, amit az emberek a saját dokumentumaikon futó assistanttól akarnak, és egy konkrét módon korlátos: a retrieval csak azt tudja visszaadni, amit valaki leírt.
Ez meghagyja a másik felet. Amit egy modeltől szeretnél, annak egy része egyáltalán nem tény egy dokumentumban — egy formátum, amelyet tartania kell, egy hangnem, egy taxonómia négyszáz címkével, egy döntési mód, amely tízezer múltbeli példában él, és egyetlen bekezdésben sem. A retrieval ezeket nem tudja leszállítani, mert nincs mit retrieve-elni; egy hosszabb prompt csak a 16. fejezet számláját fizeti ki egy skill leírásáért, nem magáért a skillért.
A 20. fejezet erről a döntésről szól — fine-tune, retrieve vagy prompt —, és az eredménye az, hogy a döntés előbb gazdasági, mint technikai: a három megoldás end to end ugyanazon a kérdésen van beárazva, és a crossover egy tokenszám. A nyitókérdés az, amelyre ez a fejezet nem tud válaszolni. Nem az, hogy hol van leírva a válasz, hanem az, hogy mit teszel, amikor soha nem is volt leírva.
Források és módszer
Link a szakaszhoz: Források és módszerEbben a fejezetben minden mérés egy korpuszt és egy instrumentumot használt, és mindkettő reprodukálható. A korpusz a kurzus 1–13. fejezete, ahogy 2026. szeptember 7-én álltak — 13 dokumentum, 359 067 karakter, 127 szakasz, a front matter és a bibliográfiák eltávolításával. Ezek a fejezetek továbbra is szerkesztés alatt állnak, ezért ugyanennek a szabálynak a mai alkalmazása néhány ezer karakterrel többet számol: a szakaszszám változatlan, és az alábbi következtetések mindegyike is, de a karakterszám pillanatkép, és így is van címkézve. A ground truth 32 kérdés, mindegyikhez egy szó szerinti mondat társítva, amely pontosan egyszer fordul elő a korpuszban, és soha nem szakaszcím; mindegyik két megfogalmazásban szerepel, összesen 64 queryként. A retrieval embeddingek sentence-transformers/all-MiniLM-L6-v2 (384 dimenzió, mean-pooled, L2-normalizált, 256-token window); a reranking cross-encoder/ms-marco-MiniLM-L-6-v2 a top 25 felett; a generation példa Qwen/Qwen2.5-0.5B-Instruct greedy decodinggal. Minden időzítés egyszálú CPU. A fejezet elkészítéséhez nem hívtunk fizetős API-t, ezért minden itt szereplő latency helyi, és így is van címkézve.
A TypeScriptben mutatott chunker az a chunker, amelyet mértünk: az ugyanazt a szabályt és ts/chunk.ts értéket implementáló Python instrumentumot chunkról chunkra összehasonlítottuk a teljes korpuszon, és mind a 940 chunkban, szövegben és offsetben egyezik. Az intervallumok 95 %-os Wilson-intervallumok; a párosított összehasonlítások kétoldali exact sign tesztek a diszkordáns párokon.
A fent idézett mind a tizennégy azonosítót feloldottuk az arXiv API-val, és címenként ellenőriztük 2026. szeptember 7-én — ami, tekintve azt a nyolcat, amely nem az volt, a legkevesebbnek tűnt, amit ez a konkrét fejezet megtehetett.
Hivatkozások
Link a szakaszhoz: Hivatkozások-
Robertson, S. és Zaragoza, H. The Probabilistic Relevance Framework: BM25 and Beyond. Foundations and Trends in Information Retrieval 3(4), pp. 333–389 (2009). A saturation function és a fent használt két konstans forrása, és az a hely, ahol elolvashatod, miért létezik egyáltalán . ↩
-
Cormack, G. V., Clarke, C. L. A. és Büttcher, S. Reciprocal Rank Fusion Outperforms Condorcet and Individual Rank Learning Methods. SIGIR 2009. A az övék, és a módszer lényege, hogy nem igényel kalibrációt az összeolvasztott pontszámskálák között. ↩
-
Khattab, O. és Zaharia, M. ColBERT: Efficient and Effective Passage Search via Contextualized Late Interaction over BERT. arXiv:2004.12832 (2020). A középút a skalárszorzat és a cross-encoder között. Reimers, N. és Gurevych, I., Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks, arXiv:1908.10084 (2019), az a bi-encoder, amelyre a fejezet indexe épül, és amelyet a 8. fejezet mért. ↩
-
Malkov, Yu. A. és Yashunin, D. A. Efficient and Robust Approximate Nearest Neighbor Search using Hierarchical Navigable Small World Graphs. arXiv:1603.09320 (2016). A legtöbb ma árult vector database mögötti gráfindex. ↩
-
Johnson, J., Douze, M. és Jégou, H. Billion-scale Similarity Search with GPUs. arXiv:1702.08734 (2017). FAISS, és a fenti keretes részben mért IVF referencia-implementációja. ↩
-
Kalai, A. T., Nachum, O., Vempala, S. S. és Zhang, E. Why Language Models Hallucinate. arXiv:2509.04664 (2025). Az érv, hogy a hallucinationt olyan bináris pontosságú pontozás hozza létre, amely soha nem jutalmazza a tartózkodást, ezért ez előbb evaluation probléma, mint modelling probléma. ↩
-
Lewis, P., Perez, E., Piktus, A., Petroni, F., Karpukhin, V., Goyal, N., Küttler, H., Lewis, M., Yih, W., Rocktäschel, T., Riedel, S. és Kiela, D. Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks. arXiv:2005.11401 (2020). A tanulmány, amely nevet adott a mintának, és amelyet érdemes elolvasni arról, mit javít és mit nem. Guu et al., REALM: Retrieval-Augmented Language Model Pre-Training, arXiv:2002.08909 (2020), a kortárs munka, amely a retrievert a modellel együtt tanítja, ahelyett hogy utólag csavarozná rá; Karpukhin et al., Dense Passage Retrieval for Open-Domain Question Answering, arXiv:2004.04906 (2020), innen származik a két-encoderes dense retriever, amelyet ebben a fejezetben végig használunk; Izacard és Grave, Leveraging Passage Retrieval with Generative Models for Open Domain Question Answering, arXiv:2007.01282 (2020), pedig a fusion-in-decoder elrendezés, amellyel sok passzust lehet egy generatornak adni. Gao et al., Retrieval-Augmented Generation for Large Language Models: A Survey, arXiv:2312.10997 (2023), mindannak a térképe, ami ezután jött, beleértve a HyDE-ot (Gao et al., Precise Zero-Shot Dense Retrieval without Relevance Labels, arXiv:2212.10496, 2022), amely a kérdés helyett egy hipotetikus választ embedel. ↩
-
Manakul, P., Liusie, A. és Gales, M. J. F. SelfCheckGPT: Zero-Resource Black-Box Hallucination Detection for Generative Large Language Models. arXiv:2303.08896 (2023). Detection újramintavételezéssel, a model belső részeihez való hozzáférés és külső knowledge base nélkül. ↩
-
Asai, A., Wu, Z., Wang, Y., Sil, A. és Hajishirzi, H. Self-RAG: Learning to Retrieve, Generate, and Critique through Self-Reflection. arXiv:2310.11511 (2023). A model megtanítása arra, hogy eldöntse, mikor retrieve-eljen, ahelyett hogy minden körben retrieve-elne. ↩
-
Lin, S., Hilton, J. és Evans, O. TruthfulQA: Measuring How Models Mimic Human Falsehoods. arXiv:2109.07958 (2021). A benchmark, amely olyan kérdésekből épült, ahol a hihető válasz és az igaz válasz eltér; ez az egész nehézség egy mondatban. ↩