هندسة السياق
درس 2 من 10
2هندسة السياق
الصورة الكبيرةنافذة السياق مساحة محدودة ومدفوعة، وما تضعه فيها يحدّد الإجابة أكثر من النموذج نفسه. هندسة السياق هي فن اختيار ما يدخل وترتيبه وحذف الباقي، لأن النص الأطول لا يعني إجابة أفضل، بل غالبًا أسوأ وأغلى.
ماذا تشاهد في الرسم: الميزانية تمتلئ بالأفضل أولًا: ثلاثة أقسام من سياسة الإجازات، ويبقى 400 رمز فارغة بدل حشوها بنص ضعيف. إرسال كل شيء كان سيكلّف 3,850 رمزًا، أي ثلاثة أضعاف ونصف، ويعطي النموذج مادة أكثر يخلطها بالإجابة.
خارطة هذا الجزء
المفاهيم بالترتيب، اضغط أيًّا منها للقفز إليه:
طريقان إلى نفس الفكرة
النموذج لا يتذكر شيئًا خارج ما تضعه أمامه في الطلب، مثل ضيف لا يعرف إلا ما تحكيه له الآن. لكن لو حكيت له كل شيء، يتوه ويفوته المهم. هندسة السياق أن تختار ماذا تقول وماذا تحذف، وأين تضع أهم معلومة.
نافذة السياق context window محدودة بالرموز، وجودة الإجابة تتراجع مع الحشو والمعلومات المتعارضة، والنماذج تهمل ما في المنتصف أكثر من الأطراف. تختار المقاطع ذات الصلة، وتلخّص التاريخ الطويل، وتضع التعليمات والمصادر في بنية واضحة بعلامات.
قبل أن تبدأ
📞استدعاء نموذج
تعرف أن كل رمز في الطلب مدفوع، وأن رسالة النظام هي مكان التعليمات الثابتة.
📍الموضع
تعرف أن النماذج تتذكّر أول السياق وآخره أفضل من وسطه، وهذا أساس الترتيب في هذا الدرس.
الفكرة الجوهرية
نافذة السياق مساحة محدودة ومدفوعة. في هذا الدرس تختار ما يدخلها بميزانية وعتبة، ثم ترتّبه، ثم تقلّم المحادثة، ثم تجمع كل ذلك في قالب واحد.
1ميزانية وعتبة
نافذة السياق سقف لا هدف. تضع حدًّا للرموز المسترجعة، وعتبة جودة لا يدخل تحتها شيء، ثم تملأ بالأفضل أولًا.
انظر إلى الرسم: ست قطع مسترجعة لسؤال عن الإجازة، بدرجات تشابهها. العتبة 0.5 تُخرج آخر اثنتين قبل أن نحسب أي ميزانية.
chunks = [
("leave policy, section 2", 0.82, 420),
("leave policy, section 5", 0.79, 380),
("holiday calendar 2026", 0.61, 900),
("leave policy, section 1", 0.58, 300),
("HR newsletter, March", 0.41, 1200),
("travel policy", 0.33, 650),
]
budget = 1500 # tokens we allow for retrieved text
picked, used = [], 0
for name, score, tokens in sorted(chunks, key=lambda c: -c[1]):
if score < 0.5:
break # below this, more text adds noise, not answers
if used + tokens <= budget:
picked.append(name)
used += tokens
print("picked:", picked)
print("tokens used:", used, "of", budget)
دخلت ثلاث قطع من سياسة الإجازات. تقويم العطل (0.61) تُرك لأنه لا يتّسع بعد أول قطعتين، والنشرة الإخبارية وسياسة السفر تُركتا لأنهما تحت العتبة. 1100 رمز بدل 3850 لو أرسلنا الكل: أرخص بثلاث مرات ونصف، وأقل تشتيتًا للنموذج.
2ترتيب الساندويتش
النماذج تنتبه لبداية السياق ونهايته أكثر من وسطه. اسم هذه النتيجة البحثية «الضياع في المنتصف»: الدقة تنخفض حين تكون المعلومة المطلوبة في وسط سياق طويل.
انظر إلى الرسم: خمس قطع بدرجاتها. الأقوى في الطرفين، والأضعف في الوسط حيث ينتبه النموذج أقل.
بدل الترتيب التنازلي البسيط، نضع الأفضل أولًا والثاني آخرًا، وهكذا نحو الوسط.
def sandwich(chunks):
"""Best chunk first, second best last, the weakest in the middle."""
ranked = sorted(chunks, key=lambda c: -c[1])
front, back = [], []
for i, c in enumerate(ranked):
(front if i % 2 == 0 else back).append(c)
return front + back[::-1]
chunks = [("A", 0.91), ("B", 0.85), ("C", 0.70), ("D", 0.62), ("E", 0.55)]
print("by score: ", [c[0] for c in sorted(chunks, key=lambda c: -c[1])])
print("sandwiched:", [c[0] for c in sandwich(chunks)])
A في البداية وB في النهاية، والأضعف E في الوسط حيث ينتبه النموذج أقل. هل يحسّن هذا نتائجك فعلًا؟ لا تفترض: شغّل مجموعة الأسئلة الذهبية (الدرس القادم) بالترتيبين وقارن الرقم. أثر هذه الحيلة يختلف بين النماذج، ويقلّ مع النماذج الأحدث.
3تقليم المحادثة
المحادثة الطويلة تأكل الميزانية، لأن كل طلب يحملها كلها. احذف ما لا يحتاجه السؤال الحالي، واحفظ الحقائق المهمة في سطر واحد.
نحتفظ بآخر أربعة أدوار، ونستبدل ما قبلها بسطر حقائق، ونحذف أكثر إن تجاوزنا الميزانية.
history = [("user", "I need leave next week"), ("assistant", "Which days?"),
("user", "Sunday to Tuesday"), ("assistant", "That is 3 days. Annual or sick?"),
("user", "Annual"), ("assistant", "Done, request 1043 submitted."),
("user", "Can I cancel it?")]
def tokens(text):
return len(text.split()) + 3
def fit(history, budget, keep_last=4):
recent = history[-keep_last:]
older = history[:-keep_last]
facts = "Earlier: user asked for annual leave Sunday-Tuesday, request 1043 submitted." if older else ""
msgs = ([("system", facts)] if facts else []) + recent
while sum(tokens(t) for _, t in msgs) > budget and len(msgs) > 2:
msgs.pop(1)
return msgs
for role, text in fit(history, budget=40):
print(f"{role:9} {text}")
سؤال «هل أستطيع إلغاءه؟» يحتاج رقم الطلب 1043، وهو باقٍ في سطر الحقائق وفي آخر رد. الأيام بالتفصيل حُذفت لأنها لا تهمّ هذا السؤال. في نظام حقيقي يكتب سطر الحقائق نموذج صغير رخيص يلخّص الأدوار القديمة، وتختبر جودته على محادثات حقيقية.
4قالب سياق كامل
السياق الجيد له بنية ثابتة في كل طلب. انظر إلى الرسم: رسالة النظام بالدور والقيود وشكل الإجابة، ثم المستندات داخل وسم <documents> ولكل منها معرّف، ثم السؤال في الآخر.
SYSTEM = """أنت مساعد يجيب عن سياسات الموارد البشرية في الشركة.
أجب من المستندات المرفقة فقط، واذكر معرّف المستند بين قوسين مربعين بعد كل معلومة.
إن لم تجد الإجابة في المستندات، فقل ذلك صراحة ولا تخمّن."""
def build_messages(question, chunks):
docs = "\n".join(f'<doc id="{c.id}">{c.text}</doc>' for c in chunks)
return [
{"role": "system", "content": SYSTEM},
{"role": "user", "content": f"<documents>\n{docs}\n</documents>\n\nالسؤال: {question}"},
]
رسالة النظام ثابتة حرفيًّا بين الطلبات، فتستفيد من التخزين المؤقت. والمستندات بوسوم ومعرّفات، والسؤال في الآخر. هذا القالب نقطة بداية، وكل تغيير فيه يُقاس على المجموعة الذهبية.
كيف يعمل، خطوة بخطوة: كيف تبني سياقًا لسؤال واحد
ترتّب القطع المسترجعة بدرجة التشابه، وتُسقط كل ما تحت العتبة مثل 0.5.
تملأ ميزانية الرموز بالأفضل أولًا، وتتخطّى القطعة التي لا تتّسع إلى التي بعدها.
ترتّب ما اخترته بحيث يكون الأقوى في الطرفين والأضعف في الوسط، وكل قطعة بمعرّفها داخل وسم <documents>.
تضع السؤال في آخر رسالة المستخدم، وتقيس كل تغيير في هذا القالب على المجموعة الذهبية.
مثال محلول
تخيّل أن نظامك استرجع ست قطع لسؤال عن الإجازات، بدرجات تشابه مختلفة وأطوال مختلفة. الإغراء أن ترسلها كلها «احتياطًا». بدلًا من ذلك، ضع ميزانية رموز، وعتبة جودة، واملأ بالأفضل أولًا.
جرّب بلا كود
خرافة مقابل حقيقة
| الخرافة | الحقيقة |
|---|---|
| نافذة سياق أكبر تعني أن تملأها. | النافذة حدّ أعلى لا هدف. كل رمز إضافي يكلّف ويبطئ أول رد، والقطع الضعيفة تشتّت النموذج. |
| ترتيب الشطيرة يحسّن النتائج دائمًا. | أثره يختلف بين النماذج ويقلّ في الأحدث منها. شغّل المجموعة الذهبية بالترتيبين وقرّر بالرقم. |
| التعليمات تعمل أينما وضعتها في الطلب. | تعليمة مثل «أجب بالعربية فقط» في وسط عشرين ألف رمز قد تُهمل. مكانها رسالة النظام دائمًا. |
جرّب بنفسك
أين تضع «أجب من المستندات فقط واستشهد بمعرّفاتها»؟
رسالة النظام مكان التعليمات الثابتة: ينتبه لها النموذج أكثر، وتبقى منفصلة عن المحتوى، وتستفيد من التخزين المؤقت لأنها لا تتغيّر.
قطعة بتشابه 0.61 طولها 900 رمز، والميزانية المتبقية 700. ماذا يفعل كود الميزانية؟
هذا ما فعله المثال: تخطّى تقويم العطل وأدخل قطعة أصغر بعده. القصّ ممكن، لكنه قد يقطع المعلومة المطلوبة في المنتصف.
بطاقات مراجعة سريعة
تحقق من فهمك
رفعت عدد القطع المسترجعة من 3 إلى 15 فانخفضت دقة الإجابات. كيف يحدث هذا؟
القطع الإضافية أضعف تشابهًا، وفيها معلومات قريبة لكنها خاطئة للسؤال (سياسة قديمة، قسم آخر)، فيخلطها النموذج بالإجابة. والمعلومة الصحيحة قد صارت في وسط سياق أطول. المزيد من السياق يرفع الاسترجاع ويخفض الدقة أحيانًا، والرقم وحده يحسم.
في مثال تقليم المحادثة، لماذا لم يضرّ حذف الأيام الدقيقة (الأحد إلى الثلاثاء)؟
لأن السؤال الأخير «هل أستطيع إلغاءه؟» يحتاج رقم الطلب 1043 فقط، وقد بقي في سطر الحقائق وفي آخر رد. ما تحذفه يُقرَّر بحسب ما يحتاجه السؤال الحالي.
مصادر هذا الدرس
مرتّبة بالنوع. ابدأ بالتوثيق الرسمي إذا تبي المرجع الدقيق، وبالمقال أو الفيديو إذا تبي الشرح أولًا.
توثيق رسمي2
خلّصت الدرس؟
سجّل الدخول ليُحفظ تقدّمك.