RAG في الإنتاج: Chunking والاسترجاع والاستشهادات الصادقة
القطع الأعمى عند 512 حرفاً يُفقد 4 من 32 إجابة قبل الاسترجاع. إصلاح chunker وحده ينقل الرتبة من 115 إلى 3.
في هذه الصفحة
إليك سؤالاً حقيقياً من مستخدم حقيقي لمساعد حقيقي: مجموعة التقييم لدي تضم 20 عنصراً، هل يكفي ذلك للوثوق بالنتيجة. يحتوي المتن على الإجابة — قسماً كاملاً منها. وهذه هي المقاطع الأربعة التي وضعها المسترجِع فعلاً في 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…ثلاثة من الأربعة تبدأ في منتصف كلمة. اثنان من فصل مختلف عن موضوع مختلف. والمقطع الذي يجيب عن السؤال — ذاك الذي يحتوي على Seventeen out of twenty cannot distinguish an 85 % model from a 65 % one — عاد عند الرتبة 115.
الآن السؤال نفسه، وembedding model نفسه، وقالب prompt نفسه. شيء واحد تغيّر: طريقة قطع المستندات.
[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]من الرتبة 115 إلى الرتبة 3. لم يمس أحد النموذج، ولا prompt، ولا العتبة، ولا عدد الخانات. هذا الفصل عن تلك الفجوة، وعن أربعة مواضع أخرى يكذب فيها نظام الاسترجاع عليك بهدوء.
عرض التفاصيل
ما يحتاجه هذا الفصل من الفصول السابقة، والموضع الوحيد الذي يغيّر فيه اللغة.
- الفصل 1 عرّف الضرب النقطي ومعيار L2. قسم العتبة أدناه هو هذان الشيئان، ولا شيء غيرهما.
- الفصل 8 فصل بين جدول embedding في نموذج اللغة وبين embedding model للاسترجاع مدرَّب تباينياً على أزواج، وقاس cosine similarity، وانتهى بوعد أن يصل الفصل 19 إلى حدّ قطع ملموس. يحين موعد ذلك الوعد هنا. لا يُعاد أيّ منه.
- الفصل 4 بنى فترة Wilson؛ و**الفصل 15** بنى evaluation harness. كل جدول أدناه يحمل الأولى وأنتجه الثاني.
- الفصل 16 سعّر context window. يبلغ prompt المُركّب في نهاية هذا الفصل 591 tokens، وهذه هي الميزانية التي تتنافس عليها المقاطع.
كل ما هنا TypeScript، كما منذ الفصل 14، وهذا الفصل هو الموضع الذي تستحق فيه القاعدة نفسها: الإدخال إلى النظام هو طوابير وتخزين، والبحث نداء شبكي، وتجميع prompt مع الاستشهادات عمل خادم. القياس هو الكود نفسه مع لوحة نتائج حوله، عن قصد — فالمسترجِع الذي تُحرزه بنسخة تنفيذ ثانية يعطيك رقماً عن برنامج لن تشحنه، وعتبة cosine أدناه لا تصبح قابلة للتصديق إلا لأنك تراها تُمسَح عبر chunker الذي سيعمل في الإنتاج.
المتن، وما الذي يُعد إجابة صحيحة
رابط إلى القسم: المتن، وما الذي يُعد إجابة صحيحةكل ما يلي يُقاس مقابل متن واحد: الفصول الثلاثة عشر الأولى من هذه الدورة — 13 مستنداً، 359,067 حرفاً، 127 قسماً، بعد إزالة الواجهة التمهيدية والببليوغرافيات. إنه متن تقني حقيقي، فيه نثر وجداول وصيغ وكتل كود، وهو بالضبط نوع الشيء الذي يحمّله الناس في قاعدة معرفة ثم يشتكون منه.
الحقيقة المرجعية هي 32 سؤالاً، يقترن كل واحد منها بـ إبرة: جملة قصيرة حرفية من المتن تجيب عنه. تظهر كل إبرة مرة واحدة بالضبط في 359,067 حرفاً، ولا تكون أي منها عنوان قسم — وهذا الفحص مهم، لأن chunker ينسخ العناوين إلى كل chunk كان سيحرز لنفسه بخلاف ذلك. يُطرح كل سؤال مرتين، مرة بإنجليزية الدورة ومرة كما تصوغه تذكرة دعم: 64 استعلاماً فوق 32 حقيقة مرجعية.
يكون الاسترجاع صحيحاً عندما يحتوي chunk مُعاد على الإبرة كاملة. هذا هو التعريف الوحيد الذي يطابق ما يحتاجه المولّد: نصف جملة في prompt ليس إجابة، بل خطر.
embedding model هو all-MiniLM-L6-v2 — 384 بُعداً، مع mean pooling وتطبيع، وهو النموذج المدرَّب تباينياً الذي قاسه الفصل 8. تستغرق فهرسة المتن 20.8 ثانية على CPU، و22 ms لكل chunk؛ ويستغرق embedding لاستعلام واحد 13 ms.
Chunking، مقاس بست طرق
رابط إلى القسم: Chunking، مقاس بست طرقست استراتيجيات من ثلاثة مكوّنات مستقلة. الأعمى يقطع كل 512 حرفاً من دون النظر إلى النص. الحدود لا تقطع أبداً داخل فقرة، وتعود إلى حدّ الجملة فقط عندما تتجاوز فقرة واحدة الميزانية. الرأس يسبق كل chunk بعنوان مستنده ومسار القسم. Overlap ينسخ آخر 64 حرفاً من chunk السابق إلى التالي.
| الاستراتيجية | chunks | إجابات مدمّرة | R@1 | R@4 | R@8 | R@20 | MRR |
|---|---|---|---|---|---|---|---|
| A أعمى 512 | 708 | 4 / 32 | 0.125 | 0.297 | 0.422 | 0.594 | 0.241 |
| B أعمى + overlap | 809 | 0 | 0.172 | 0.391 | 0.453 | 0.625 | 0.286 |
| C حدود | 940 | 0 | 0.156 | 0.422 | 0.531 | 0.672 | 0.293 |
| D حدود + overlap | 940 | 0 | 0.156 | 0.359 | 0.516 | 0.656 | 0.277 |
| E حدود + رأس | 940 | 0 | 0.094 | 0.422 | 0.578 | 0.828 | 0.280 |
| F حدود + رأس + overlap | 940 | 0 | 0.156 | 0.391 | 0.562 | 0.766 | 0.298 |
مع 64 استعلاماً، تكون فترة Wilson عند 95 % على R@20 هي [0.471, 0.705] للاستراتيجية A و[0.718, 0.901] للاستراتيجية E — لا تتداخلان، لكن معظم الأعمدة الأخرى تتداخل، ولا يستطيع جدول غير مزدوج أن يفصل بينها. تجيب كل استراتيجية عن نفس الاستعلامات، لذا فالاختبار الصادق مزدوج: احسب انتصارات وخسائر كل استراتيجية مقابل أخرى، وشغّل اختبار إشارة على الأزواج المتخالفة. ثلاث نتائج تصمد.
Blind chunking يدمّر أربعاً من اثنتين وثلاثين إجابة بالكامل. لا يرتّبها ترتيباً سيئاً — بل يدمّرها. تمتد الإبرة عبر حدّ 512 حرفاً، لذلك لا يحتوي أي chunk في الفهرس عليها، وسقف الاسترجاع لتلك الاستعلامات هو صفر. لا يستعيدها أي reranker، ولا تساعد أي عتبة، ولا يساعد نموذج أكبر. لا يمكنك استرجاع نص غير موجود كقطعة واحدة في أي مكان من فهرسك. هذا هو الفشل الأقل تبليغاً في RAG، لأنه يبدو تماماً كمسترجِع سيئ.
Overlap يصلح ذلك ولا يصلح شيئاً آخر. كل استراتيجية مع overlap تخسر صفراً من الإجابات، وهذا ما وُجد overlap من أجله. لكنه لا يحسّن الترتيب: B مقابل A عند R@8 هي +8/−6، وp = 0.79؛ وعند R@20 هي +9/−7، وp = 0.80. والأسوأ أن إضافة overlap فوق الرأس تؤذي فعلياً — F مقابل E هي +2/−6 عند R@20 — والسبب ميكانيكي. متجه chunk هو متوسط عبر tokens الخاصة به، لذلك تسحب 64 حرفاً من chunk السابق ذلك المتوسط نحو موضوع الجار. overlap تأمين ضد انقسام الإجابة، يُدفع ثمنه من الدقة.
الرأس السياقي هو ما يشتري الاسترجاع. E مقابل A هي +18/−3 عند R@20، p = 0.0015. ويقول الاجتثاث إن الحدود ليست السبب: E مقابل C — القطوع نفسها، والرأس هو الفرق الوحيد — هي +12/−2، p = 0.0129. إلحاق "Classification, Cross-Entropy, and How Not to Fool Yourself > How many test examples do I need?" بفقرة يخبر embedding model عمّا تدور حوله الفقرة، وهو ما لا تقوله الفقرة نفسها غالباً. إنه محلّل ضمائر للمستندات.
وهذا يعطي chunker شكله، وقاعدة واحدة يسهل الخطأ فيها:
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;
}نصّان، لا نص واحد. text هو ما يُعمل له embedding، مع الرأس كله. content هو كلمات هذا chunk وحدها، وهو ما يُقتبس للمستخدم. اقتبس text وسيعرض الاستشهاد رأساً غير موجود في المستند عند تلك النقطة — ومع overlap، ذيلاً مكرراً يعود إلى المقطع السابق. عندئذ يعرض نصاً ليس في المكان الذي يقول إنه فيه، وهذا أسوأ من ألا يعرض شيئاً.
الرأس ليس مجانياً. عبر 940 chunks يكلّف 24,213 من أصل 114,275 embedded tokens في الفهرس: 21.2 % مما تدفعه للـ embedding هو رأس كتبته بنفسك. كما يدفع chunks نحو نافذة المُرمّز. يقبل all-MiniLM-L6-v2 عدد 256 word-pieces؛ لدى الاستراتيجية E عدد 17 chunks فوق ذلك الحد ولدى F عدد 28، وكل واحد منها يُقتطع بصمت من دون أي تحذير من أي شيء. حجم chunk الفعلي ليس الرقم في إعدادك — بل الأصغر بينه وبين نافذة المُرمّز.
عشرون سطراً من BM25 يتجاوزها الجميع
رابط إلى القسم: عشرون سطراً من BM25 يتجاوزها الجميعللاسترجاع الكثيف نقطة ضعف منهجية واحدة وليست خفية: إنه يطابق المعنى، لذلك لا يكترث بالسلسلة الدقيقة التي كتبتها. رقم قطعة، رمز خطأ، اختصار، اسم عائلة — لا يحمل أي منها معنى مفيداً للـ embedding، وأقرب جار لرمز خطأ هو كل رمز خطأ آخر في متنك.
الإجابة الكلاسيكية أقدم من كل هذا وتتطلب عشرين سطراً. BM25 يحرز مستنداً بحسب عدد مرات ظهور مصطلحات الاستعلام فيه، فيخمّد كل مصطلح مع ارتفاع تكراره ويعاقب المستندات الطويلة التي تجمع المطابقات بطولها وحده.1 يساهم المصطلح بـ
حيث هو عدد مرات المصطلح في المستند، و طوله، و متوسط الطول، و و الثابتان التقليديان — يحدد سرعة توقف التكرار عن المساعدة، و يحدد شدة عقوبة الطول.
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، يحرز ذلك استعلاماً في 1.14 ms بلا أي فهرس على الإطلاق سوى خريطتي hash. وليس قطعة متحفية:
| المسترجِع | R@1 | R@4 | R@8 | MRR | التكلفة لكل استعلام |
|---|---|---|---|---|---|
| كثيف (cosine) | 0.094 | 0.422 | 0.578 | 0.280 | 13 ms للـ embedding + 0.3 ms للمسح |
| معجمي (BM25) | 0.219 | 0.375 | 0.469 | 0.313 | 1.14 ms |
| هجين (RRF) | 0.203 | 0.484 | 0.609 | 0.346 | كلاهما |
| هجين + cross-encoder | 0.312 | 0.578 | 0.703 | 0.447 | + 569 ms |
يزيد BM25 دقة top-1 للمسترجِع الكثيف على هذا المتن بأكثر من الضعف، ويخسر أمامه بشدة عند الرتبة 8. يفشلان في استعلامات مختلفة، وهذا هو كامل الحجة لتشغيلهما معاً.
دمجهما هو الموضع الوحيد الذي تكون فيه المقاربة البديهية خاطئة. مسافات cosine ودرجات BM25 ليست على المقياس نفسه، وليست محدودة بالطريقة نفسها، وتطبيعها لكل استعلام يجعل الوزن يعتمد على مدى جودة أفضل نتيجة صادفت الظهور. Reciprocal rank fusion يرمي الدرجات ويبقي الرتب فقط: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);
}وهنا تهم القراءة الصادقة للجدول أكثر من الجدول. الهجين يتغلب على BM25 عند R@4 بـ +10/−3، وp = 0.09. ويتغلب على الكثيف بـ +10/−6، وp = 0.45. على هذا المتن، ومع 64 استعلاماً، لا يمكن تمييز الاسترجاع الهجين عن الاسترجاع الكثيف. إنه أفضل في التقديرات النقطية وكل أعمدة الاسترجاع، لكن الدليل لا يبلغ الدلالة. يكاد كل منشور عن البحث الهجين على الإنترنت ينشر جدولاً مثل أعلاه بلا فترة؛ وهذا ما تقوله الفترة.
Bi-encoder وcross-encoder، وأين يوجد التحسن فعلاً
رابط إلى القسم: Bi-encoder وcross-encoder، وأين يوجد التحسن فعلاًكل ما سبق هو bi-encoder: يمر الاستعلام عبر النموذج وحده، وكل chunk مر عبره وحده قبل أشهر، ولا يلتقيان إلا كضرب نقطي. هذا ما يجعل الفهرس ممكناً — embedding مرة واحدة، وإعادة استخدام إلى الأبد — وهو أيضاً السقف. لا ينظر النموذج أبداً إلى الاستعلام وchunk معاً.
cross-encoder يفعل ذلك تحديداً: يأخذ الزوج كمدخل واحد ويعيد درجة صلة. لا يمكن حساب شيء مسبقاً، لذلك لا يستطيع ترتيب فهرس — لكنه يستطيع reranking لقائمة قصيرة. reranking لأفضل 25 من الهجين باستخدام ms-marco-MiniLM-L-6-v2 ينقل R@1 من 0.094 (كثيف) إلى 0.312 وMRR من 0.280 إلى 0.447: أكبر تحسن منفرد في هذا الفصل، والوحيد الذي يمس رأس القائمة لا ذيلها.
يكلف 569 ms لكل استعلام على CPU، مقابل 1.14 ms لـ BM25 و0.3 ms لمسح المتجهات. نحو ألفي ضعف تكلفة الاسترجاع، لخمسة وعشرين مستنداً. هذه هي مقايضة bi-encoder/cross-encoder كلها في رقم واحد، ولهذا تكون البنية دائماً بالشكل نفسه: مسترجِع رخيص باسترجاع واسع، ثم مُحرز مكلف على قائمة قصيرة تستطيع تحملها. يقع ColBERT بين الاثنين، إذ يحسب مسبقاً متجهات لكل token ويجري تفاعلاً متأخراً أرخص من cross-encoder وأدق من الضرب النقطي.3
L2 وcosine وعتبة لم تستحقها بعد
رابط إلى القسم: L2 وcosine وعتبة لم تستحقها بعدتبلّغ قواعد بيانات المتجهات عن مسافات، ونوع المسافة خيار إعداد. على المتجهات المطبَّعة يكون الاختيار شكلياً، وتستحق الهوية إجراءها مرة واحدة لأن كل ما بعدها يعتمد على كون المتجهات وحدية فعلاً. من أجل :
لذلك تكون مسافة cosine مساوية تماماً لـ . هذا هو الضرب النقطي والمعيار من الفصل 1 بعد صرفهما. فُحصت على متجهي chunks حقيقيين من الفهرس أعلاه، ثم على 40,000 زوج:
||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-07مطابقة حتى ضجيج الفاصلة العائمة — وفقط لأن المتجهات مطبَّعة. تخطَّ التطبيع تصبح الهوية خاطئة، ولا تعني عتبتك شيئاً، وتصبح المسافة التي يبلّغ عنها مستند معتمدة على طول نصه.
والآن الرقم الذي لا يشتقه أحد. المسترجِع يعيد شيئاً دائماً: يرتّب الفهرس كله ويسلمك رأس القائمة، سواء كانت الإجابة موجودة في المتن أم لا. العتبة هي الجزء الوحيد من النظام الذي يمكنه أن يقول لا — ولضبطها تحتاج إلى استعلامات ينبغي ألا يعود لها شيء. هذه ثلاثون: واحد وعشرون عن أشياء لا يغطيها هذا المتن فعلاً — streaming، وحدود المعدل، وprompt caching، ومخططات JSON، وحلقات agent، وقواعد بيانات المتجهات، وprompt injection، وتوليد الصور — وتسعة عن الباييلا وجوازات السفر وسياسات الاسترداد. مقابل الفهرس نفسه:
| مسافة cosine لأفضل نتيجة | |
|---|---|
| استعلامات داخل النطاق، كلها 64 | متوسط 0.445، مدى 0.270 – 0.721 |
| داخل النطاق، أفضل نتيجة صحيحة فعلاً | متوسط 0.370 |
| داخل النطاق، أفضل نتيجة خاطئة | متوسط 0.452 |
| خارج النطاق، كلها 30 | متوسط 0.699، مدى 0.497 – 0.867 |
تتفاصل التوزيعات، وتتداخل. أسوأ استعلام داخل النطاق أبعد عن إجابته (0.721) من أفضل استعلام خارج النطاق عن فقرة غير ذات صلة (0.497)، لذلك لا توجد عتبة تصيب الاثنين. عند مسحها فوق البوابة الحقيقية — احتفظ بأربعة chunks كحد أقصى، وفقط تلك الواقعة تحت الحد:
| العتبة | داخل النطاق أُجيب عنها | منها كانت الإجابة موجودة | خارج النطاق أُجيب عنها |
|---|---|---|---|
| 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 |
| بلا عتبة | 64 / 64 | 27 | 30 / 30 |
اقرأ العمود الأخير كـ خداع. بلا عتبة ينتج المساعد إجابة واثقة جيدة الاستشهاد عن "كيف أجدد جواز سفري الإسباني" من متن عن backpropagation، ثلاثين مرة من ثلاثين. عند 0.675 يفعل ذلك عشر مرات من ثلاثين. عند 0.525 يفعل ذلك مرتين، ويتخلى عن اثني عشر سؤالاً كان يستطيع الإجابة عنها.
تلك المقايضة قرار منتج، والطرف الصحيح منها يعتمد على تكلفة الإجابة الخاطئة لديك. ما لا يقبل التفاوض هو وجود العمود الأخير أصلاً. إذا لم تقس مسترجِعك قط مقابل أسئلة ينبغي أن يرفضها، فلا تملك عتبة — بل تملك رقماً.
اثنتان من الخدع العشر عند 0.675 تُظهران طريقتي الفشل.
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."الأولى إصابة قريبة: يشرح المتن KV cache بالتفصيل، والاستعلام عن prompt cache، والكلمات هي الكلمات نفسها، و0.497 أقرب من معظم الاسترجاعات الصحيحة داخل النطاق في التجربة كلها. لا يعرف embedding أن مخبأين بالاسم نفسه آلتان مختلفتان. الثانية مطابقة حرفية بلا إجابة: يحتوي المتن على العبارة الدقيقة "what is the capital of France"، مستخدمة كمثال لسؤال لا يحتاج إلى استدلال. المسترجِع محق؛ الإجابة ليست هناك. أي نظام يقرأ "وجدت شيئاً مشابهاً" على أنها "وجدت الإجابة" سيؤكد باريس بناءً على ذلك الدليل — أو، أسوأ، لن يفعل.
لماذا لا يكتب النموذج الاستشهاد
رابط إلى القسم: لماذا لا يكتب النموذج الاستشهادلا يملك النموذج قدرة منفصلة للحقائق. إنتاج جملة صحيحة وإنتاج جملة معقولة هما العملية نفسها — تنبؤ الفصل 8 بالـ token التالي — ولا شيء في تلك العملية يوسم أيهما أي. التحليل في 2025 الذي أعاد تأطير هذا يجادل بأن خط أنابيب التدريب والتقييم يكافئ التخمين فعلياً: benchmarks تُحرز بدقة ثنائية ولا تمنح أي فضل للامتناع، لذلك يتفوق نموذج يجيب دائماً على نموذج مطابق يقول "لا أعرف" عندما لا يعرف، ويُحسَّن ما بعد التدريب وفقاً لذلك.6 Hallucination وفق هذه القراءة ليس عيباً غامضاً. إنه ما تحصل عليه عندما تصحح امتحان اختيار من متعدد بلا عقوبة على الإجابة الخاطئة.
راقب شكله. عندما طُلب منه ثماني أوراق عن contrastive sentence embeddings، مع معرّفات، أنتج Qwen2.5-0.5B-Instruct ثمانية أسطر بتنسيق مثالي. كل المعرّفات الثمانية سليمة الشكل. كل الثمانية تؤدي إلى أوراق حقيقية على arXiv. صفر من الثمانية هو الورقة المزعومة.
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هذا نموذج صغير والمعدل خاص به؛ نموذج frontier يخترع أقل بكثير. الآلية تعمم، وهي سبب القاعدة التالية. مدقق يتحقق من "هل هذا المعرّف موجود" يمرر الثمانية كلها، ومستخدم ينقر واحداً يهبط على صفحة حقيقية من أرشيف حقيقي بلا طريقة لمعرفة أن الربط مُخترَع. الفشل ليس في المعرّف أو التنسيق. إنه في الارتباط — تحديداً الشيء الذي ينتجه نموذج اللغة بالمعقولية.
إذن: النموذج يكتب [1] و[2]، ولا يكتب الرابط أبداً. تشير الأرقام إلى مقاطع استرجعها الخادم، والخادم — الذي يعرف بالضبط أي مستند وأي إزاحات أتى منها كل رقم — يرفق المستند والتسمية وURL بعد ذلك. لا يوجد ما يخترعه النموذج لأنه لا يُطلب منه أبداً الشيء الوحيد الذي كان سيخترعه.
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 };
}شغّله على السؤال الافتتاحي، وتصبح chunks الأربعة prompt من 591 tokens وجدولاً لا يراه النموذج أبداً:
[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.605المحدد هو الجزء الذي يتجاوزه الناس ثم لا يستطيعون إضافته لاحقاً. #char=25873,26272 نطاق في النص القانوني للمستند؛ وبالنسبة إلى PDF فالمكافئ هو #page=12، وللصوت أو الفيديو #t=132.4,158.9، ولجدول بيانات ورقة ونطاق A1. هذان ليسا اختراعين — #page= هو PDF Open Parameters و#t= هو W3C Media Fragments، وتدعمهما المتصفحات أصلاً في عناصر الفيديو والصوت. الاستشهاد بلا محدد هو اسم مستند، واسم المستند ليس استشهاداً؛ إنه اقتراح بأن يذهب المستخدم ويبحث.
وعندما لا يمر شيء العتبة، لا يصل خط الأنابيب إلى النموذج أصلاً:
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، لأنه مقارنة بين رقمين لا طلب إلى نظام احتمالي.
قيّم المسترجِع منفصلاً عن المولّد
رابط إلى القسم: قيّم المسترجِع منفصلاً عن المولّدكل قياس في هذا الفصل يحرز المسترجِع ولا يطلب من نموذج، ولو مرة، أن يكتب إجابة. هذا مقصود، وهو الجزء الذي تتخطاه معظم الفرق.
لنظام RAG نمطان من الفشل يبدوان متطابقين من الخارج. المسترجِع لم يجد المقطع؛ أو وجده وتجاهله المولّد، أو ناقضه، أو مزجه بشيء كان يعتقده مسبقاً. إذا أحرزت الإجابة النهائية فقط فلا يمكن تمييز الاثنين، فتضبط prompts مقابل مشكلة تعيش في chunker لديك. Recall@k وMRR وعدد الإجابات المدمّرة لا تحتاج أي نداء توليد على الإطلاق، وهي رخيصة بما يكفي لتشغيلها عند كل deploy، وهي harness الفصل 15 بدالة إحراز مختلفة — الطلب نفسه، والمهلة نفسها، والتوازي نفسه، والعدّ نفسه، فوق مجموعة أسئلة ثابتة بدلاً من محادثة حية.
بلّغ عنها مع فترات. حساب الفصل 4 ينطبق كما هو: عند 64 استعلاماً، يحمل استرجاع 0.5 فترة Wilson عند 95 % بنحو ±0.12، لذلك لا تخبرك استراتيجية تتقدم بأربع نقاط على أخرى بأي شيء. استخدم الاختبار المزدوج كلما أجابت الاستراتيجيتان عن الأسئلة نفسها، وهذا ما يحدث دائماً هنا — فهو ما حوّل "E تبدو أفضل من A" إلى p = 0.0015.
والصدق الأخير: RAG يقلل hallucination ولا يزيله. وضع المقطع الصحيح في prompt لا يلزم النموذج باستخدامه، والأدبيات قالت ذلك منذ الورقة الأصلية.7 شيئان يجعلان الأمر أسوأ في الإنتاج. السياقات الطويلة تتدهور — يجد النموذج المعلومات في بداية prompt طويل ونهايته بموثوقية أكبر من وسطه، لذلك يمكن لعشرين chunks بدلاً من أربعة أن تخفض الدقة وترفع الفاتورة، وهو أثر قيس في الفصل 24. ويمكن للاسترجاع أن يكون صحيحاً ويبقى غير كافٍ، كما أظهر المخزنان أعلاه. SelfCheckGPT يوسم الادعاءات التي لا تصمد عند إعادة العينة؛8 وSelf-RAG يدرب النموذج على إصدار tokens خاصة به للاسترجاع والنقد؛9 وTruthfulQA جعل نمط الفشل مقروءاً من الأساس.10 لا يغلق أي منها الفجوة، والنظام الذي يقدم النص المسترجع كبرهان خلط بين مُسنَد إلى مصدر وصحيح.
نصف النظام الذي يعمل قبل أي استعلام
رابط إلى القسم: نصف النظام الذي يعمل قبل أي استعلامالمسترجِع هو الجزء المرئي من خط أنابيب تحدث كل إخفاقاته في وقت أسبق، في الظلام. تتكرر ثلاثة منها.
الاستخراج هو حيث يموت المحتوى. PDF ليس نصاً؛ إنه تعليمات رسم. تتداخل التخطيطات ذات العمودين، وتتحول الجداول إلى حساء كلمات، وتتكرر رؤوس الصفحات في كل chunk، والصفحة الممسوحة لا تملك أي نص على الإطلاق حتى يعطيها OCR بعضه، مع ثقة. كل ما قيس أعلاه افترض أن المستخرج قام بعمله؛ في الإنتاج غالباً لا يفعل، وتظهر الأعراض كاسترجاع سيئ على بعد ثلاث طبقات.
يُختم الفهرس بالنموذج الذي بناه. Embeddings من نموذجين غير قابلة للمقارنة — ليست "أقل دقة"، بل غير قابلة للمقارنة، لأنها نقاط في فضاءات مختلفة. غيّر embedding model يصبح كل متجه في المخزن قمامة حتى يُعاد بناؤه. لذلك يُكتب اسم النموذج، وعدد الأبعاد، وإصدار خط الأنابيب، وإصدار المستخرج بجانب كل مستند وقت الفهرسة. من دونها، في يوم الترقية، لا تستطيع معرفة أي المستندات قديمة وأيها حديثة، ويعيد فهرس نصف مهاجر هراءً واثقاً بلا خطأ في أي مكان.
لا ينبغي أن يكسر مستند معطوب المجلد، ويجب أن تعد العدادات ما حدث. مستند يفشل استخراجه ينتهي في حالة failed مع سببه، مرئياً وقابلاً لإعادة المحاولة، بينما تبقى التسعة والتسعون الأخرى قابلة للبحث؛ ويكتب الخادم عدد chunks المفهرسة عندما ينتهي، لا يعلنه العميل عند الرفع. مجلد يبلّغ عن 400 مقطع ويحمل 40 كذبة لا تظهر إلا كسؤال لا يمكن الإجابة عنه.
إلى أين يذهب هذا بعد ذلك
رابط إلى القسم: إلى أين يذهب هذا بعد ذلكالنظام في هذا الفصل يجيب عن أسئلة كُتبت إجاباتها. يسترجعها، ويرتبها، ويرفض عندما لا يستطيع، ويستشهد بالمكان الذي نظر فيه. هذا معظم ما يريده الناس من مساعد فوق مستنداتهم الخاصة، وهو محدود بطريقة محددة: لا يمكن للاسترجاع إلا أن يعيد ما كتبه أحدهم.
وهذا يترك النصف الآخر. بعض ما تريد من نموذج أن يفعله ليس حقيقة في مستند على الإطلاق — تنسيق عليه الحفاظ عليه، نبرة، تصنيف بأربعمئة تسمية، طريقة قرار تعيش في عشرة آلاف مثال سابق ولا توجد في أي فقرة. لا يستطيع الاسترجاع إيصال تلك الأشياء، لأنه لا يوجد شيء يُسترجع؛ وprompt أطول لا يفعل إلا دفع فاتورة الفصل 16 مقابل وصف skill بدلاً من skill.
الفصل 20 هو ذلك القرار — fine-tune، أو استرجع، أو استخدم prompt — ونتيجته أن القرار اقتصادي قبل أن يكون تقنياً: تُسعّر الثلاثة من البداية إلى النهاية على السؤال نفسه، ونقطة التقاطع هي عدد token. السؤال الذي يفتتحه هو السؤال الذي لا يستطيع هذا الفصل الإجابة عنه. ليس أين كُتبت الإجابة، بل ماذا تفعل عندما لم تُكتب أصلاً.
المصادر والمنهج
رابط إلى القسم: المصادر والمنهجكل ما قيس في هذا الفصل استخدم متناً واحداً وأداة واحدة، وكلاهما قابل لإعادة الإنتاج. المتن هو الفصول 1 إلى 13 من هذه الدورة كما كانت في 7 سبتمبر 2026 — 13 مستنداً، 359,067 حرفاً، 127 قسماً، مع إزالة الواجهة التمهيدية والببليوغرافيات. تستمر تلك الفصول في التحرير، لذلك فإن تطبيق القاعدة نفسها اليوم يعدّ بضعة آلاف من الأحرف أكثر: عدد الأقسام لم يتغير وكذلك كل استنتاج أدناه، لكن إجمالي الأحرف لقطة وموسوم على هذا الأساس. الحقيقة المرجعية هي 32 سؤالاً، كل منها مقترن بجملة حرفية تظهر مرة واحدة بالضبط في المتن ولا تكون أبداً عنوان قسم، وتُطرح بصياغتين للحصول على 64 استعلاماً. Retrieval embeddings هي sentence-transformers/all-MiniLM-L6-v2 (384 بُعداً، mean-pooled، L2-normalised، نافذة 256-token)؛ وreranking هو cross-encoder/ms-marco-MiniLM-L-6-v2 فوق أفضل 25؛ ومثال التوليد هو Qwen/Qwen2.5-0.5B-Instruct مع greedy decoding. كل الأزمنة CPU بخيط واحد. لم يُستدعَ أي API مدفوع لإنتاج هذا الفصل، ولهذا أيضاً كل كمون هنا محلي وموسوم كذلك.
chunker المعروض في TypeScript هو chunker الذي قيس: قورنت أداة Python التي تنفذ القاعدة نفسها وts/chunk.ts، chunkاً مقابل chunk، على المتن كله، وتتفقان على كل الـ 940 chunks، نصوصاً وإزاحات على السواء. الفترات هي Wilson عند 95 %؛ والمقارنات المزدوجة اختبارات إشارة دقيقة ثنائية الطرف على الأزواج المتخالفة.
حُلّت كل المعرّفات الأربعة عشر المستشهد بها أعلاه عبر arXiv API وفُحصت عنواناً بعنوان في 7 سبتمبر 2026 — وهذا، بالنظر إلى الثمانية التي لم تكن كذلك، بدا أقل ما يستطيع هذا الفصل تحديداً فعله.
المراجع
رابط إلى القسم: المراجع-
Robertson, S. and Zaragoza, H. The Probabilistic Relevance Framework: BM25 and Beyond. Foundations and Trends in Information Retrieval 3(4), pp. 333–389 (2009). مصدر دالة التشبع والثابتين المستخدمين أعلاه، والمكان الذي تقرأ فيه لماذا يوجد أصلاً. ↩
-
Cormack, G. V., Clarke, C. L. A. and Büttcher, S. Reciprocal Rank Fusion Outperforms Condorcet and Individual Rank Learning Methods. SIGIR 2009. إن لهم، وفكرة الطريقة أنها لا تحتاج أي معايرة بين مقاييس الدرجات التي تدمجها. ↩
-
Khattab, O. and Zaharia, M. ColBERT: Efficient and Effective Passage Search via Contextualized Late Interaction over BERT. arXiv:2004.12832 (2020). المنطقة الوسطى بين الضرب النقطي وcross-encoder. Reimers, N. and Gurevych, I., Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks, arXiv:1908.10084 (2019)، هو bi-encoder الذي بُني عليه فهرس هذا الفصل وقيس في الفصل 8. ↩
-
Malkov, Yu. A. and Yashunin, D. A. Efficient and Robust Approximate Nearest Neighbor Search using Hierarchical Navigable Small World Graphs. arXiv:1603.09320 (2016). فهرس الرسم البياني وراء معظم قواعد بيانات المتجهات المباعة حالياً. ↩
-
Johnson, J., Douze, M. and Jégou, H. Billion-scale Similarity Search with GPUs. arXiv:1702.08734 (2017). FAISS، والتنفيذ المرجعي لـ IVF المقاس في الصندوق أعلاه. ↩
-
Kalai, A. T., Nachum, O., Vempala, S. S. and Zhang, E. Why Language Models Hallucinate. arXiv:2509.04664 (2025). الحجة أن hallucination تنتج عن تصحيح بالدقة الثنائية لا يكافئ الامتناع أبداً، ولذلك فهي مشكلة تقييم قبل أن تكون مشكلة نمذجة. ↩
-
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). الورقة التي سمّت النمط، وهي ما ينبغي قراءته لمعرفة ما يصلحه وما لا يصلحه. Guu et al., REALM: Retrieval-Augmented Language Model Pre-Training, arXiv:2002.08909 (2020)، هو العمل المعاصر الذي يدرّب المسترجِع مع النموذج بدلاً من إلصاقه به؛ وKarpukhin et al., Dense Passage Retrieval for Open-Domain Question Answering, arXiv:2004.04906 (2020)، هو مصدر المسترجِع الكثيف ذي المُرمّزين المستخدم في هذا الفصل كله؛ وIzacard and Grave, Leveraging Passage Retrieval with Generative Models for Open Domain Question Answering, arXiv:2007.01282 (2020)، هو ترتيب fusion-in-decoder لإطعام مقاطع كثيرة إلى مولّد واحد. Gao et al., Retrieval-Augmented Generation for Large Language Models: A Survey, arXiv:2312.10997 (2023)، هو خريطة كل ما جاء بعد ذلك، بما في ذلك HyDE (Gao et al., Precise Zero-Shot Dense Retrieval without Relevance Labels, arXiv:2212.10496, 2022)، الذي يعمل embedding لإجابة افتراضية بدلاً من السؤال. ↩
-
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). كشف بإعادة العينة، بلا وصول إلى داخل النموذج وبلا قاعدة معرفة خارجية. ↩
-
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). تدريب النموذج على أن يقرر متى يسترجع، بدلاً من الاسترجاع في كل دور. ↩
-
Lin, S., Hilton, J. and Evans, O. TruthfulQA: Measuring How Models Mimic Human Falsehoods. arXiv:2109.07958 (2021). benchmark مبني من أسئلة تختلف فيها الإجابة المعقولة عن الإجابة الصحيحة، وهذا هو كامل الصعوبة في جملة واحدة. ↩