فاز 11 · درس 5

مهندسی بافتار: مدیریت پنجره، بودجه‌بندی توکن، حافظه و بازیابی

نوع
ساختنی
زمان
حدود 90 دقیقه
زبان‌ها
Python
طرح مفهومی از قفسه‌بندی و چیدمان منظم جعبه‌های داده در ورودی یک ساختار محاسباتی با برجسته‌سازی بخش‌های ابتدا و انتها
فهرست مطالب
  1. اهداف یادگیری
  2. صورت مسئله
  3. مفاهیم پایه
  4. پنجره‌ی بافتار منبعی کمیاب است
  5. گم شدن در میانه (Lost-in-the-Middle)
  6. اجزای تشکیل‌دهنده‌ی بافتار
  7. استراتژی‌های فشرده‌سازی بافتار
  8. سیستم‌های حافظه
  9. سرهم‌بندی پویای بافتار
  10. پیاده‌سازی گام‌به‌گام
  11. گام 1: شمارنده‌ی توکن
  12. گام 2: مدیریت بودجه‌ی بافتار
  13. گام 3: بازآرایی برای رفع «گم شدن در میانه»
  14. گام 4: فشرده‌ساز تاریخچه‌ی گفت‌وگو
  15. گام 5: گزینش پویای ابزارها
  16. گام 6: پایپ‌لاین کامل سرهم‌بندی بافتار
  17. کاربرد در عمل
  18. بافتار مدیریت‌شده توسط ابزار (Harness-Managed Context)
  19. بارگذاری پویای بافتار
  20. حافظه‌ی بلندمدت دستیار
  21. RAG به‌مثابه مهندسی بافتار
  22. تحویل پروژه
  23. تمرین‌ها
  24. اصطلاحات کلیدی
  25. برای مطالعه‌ی بیشتر

بسیاری از توسعه‌دهندگان تصور می‌کنند با افزایش ظرفیت پنجره‌ی مدل‌های جدید به صدها هزار توکن، دغدغه‌ی مدیریت ورودی‌ها از بین رفته است. با این حال، افزایش طول ورودی بدون مدیریت مهندسی‌شده منجر به افت دقت، افزایش تصاعدی تاخیر (Latency) و هزینه‌های غیرضروری می‌شود. مهندسی پرامپت فقط نوشتن متن ورودی است؛ اما مهندسی بافتار (Context Engineering) هنر مدیریت کل ورودی مدل شامل تاریخچه، تعاریف ابزارها و اسناد بازیابی‌شده است.

در استفاده از مدل‌های زبانی، محدودیت در پرداخت ارزی و هزینه‌ی توکن‌های ورودی باعث می‌شود بهینه‌سازی دقیق بودجه‌ی توکن به اولویتی اساسی در معماری سیستم‌ها تبدیل شود. هر توکن اضافه نه تنها هزینه را بالا می‌برد، بلکه به دلیل پدیده‌ی «گم شدن در میانه» (Lost-in-the-Middle)، تمرکز اتنشن مدل را روی داده‌های حیاتی کاهش می‌دهد.

در این درس می‌آموزید چگونه با بودجه‌بندی دقیق، فشرده‌سازی تاریخچه، هرس کردن ابزارهای غیرضروری و چیدمان بهینه‌ی اطلاعات، بیشترین کارایی و دقت را از پنجره‌ی بافتار مدل‌های زبانی به دست آورید.

مهندسی پرامپت (Prompt engineering) فقط یک زیرمجموعه است؛ اصل ماجرا «مهندسی بافتار» (Context engineering) است. پرامپت صرفاً رشته‌متنی است که تایپ می‌کنید، اما بافتار شامل همه‌ی چیزهایی است که وارد پنجره‌ی مدل می‌شوند: دستورالعمل‌های سیستمی، اسناد بازیابی‌شده، تعاریف ابزارها، تاریخچه‌ی گفت‌وگو، نمونه‌های چندشات (few-shot) و خودِ پرامپت. بهترین مهندسان هوش مصنوعی در سال 2026، مهندسان بافتار هستند. آن‌ها تصمیم می‌گیرند چه چیزی وارد پنجره شود، چه چیزی بیرون بماند و با چه ترتیبی چیده شود.

مرتبط: فاز 11 · 15 (کش کردن پرامپت یا Prompt Caching) — چیدمان سازگار با کش امتدادی از مهندسی بافتار است. فاز 5 · 28 (ارزیابی بافتار طولانی) برای نحوه‌ی سنجش پدیده‌ی گم شدن در میانه (Lost in the Middle) با NIAH و RULER.

اهداف یادگیری

  • محاسبه‌ی سهمیه‌ی توکن‌ها (token budgets) میان تمام اجزای پنجره‌ی بافتار (پرامپت سیستم، ابزارها، تاریخچه، اسناد بازیابی‌شده و حاشیه‌ی امن تولید پاسخ)
  • پیاده‌سازی استراتژی‌های مدیریت پنجره‌ی بافتار: هرس (کوتاه‌سازی)، خلاصه‌سازی و پنجره‌ی لغزان برای تاریخچه‌ی گفت‌وگو
  • اولویت‌بندی و مرتب‌سازی اجزای بافتار برای بیشینه‌سازی اتنشن مدل روی مرتبط‌ترین اطلاعات
  • ساخت یک سرهم‌کننده‌ی بافتار (context assembler) که توکن‌ها را بر اساس نوع پرس‌وجو و فضای آزاد پنجره به‌شکل پویا تخصیص می‌دهد

صورت مسئله

مدل Claude Opus 4.7 پنجره‌ی بافتاری با ظرفیت 200K توکن دارد (1M در نسخه‌ی بتا). این عدد برای GPT-5 برابر 400K، برای Gemini 3 Pro برابر 2M و طبق ادعاها برای Llama 4 برابر 10M است. این ارقام غول‌آسا به نظر می‌رسند، تا وقتی که بخواهید پرشان کنید.

این تفکیک واقعی هزینه‌ی توکن‌ها در یک دستیار کدنویسی را ببینید: پرامپت سیستم: 500 توکن. تعاریف ابزار برای 50 ابزار: 8,000 توکن. مستندات بازیابی‌شده: 4,000 توکن. تاریخچه‌ی گفت‌وگو (10 نوبت گفت‌وگو): 6,000 توکن. پرسش فعلی کاربر: 200 توکن. سهمیه‌ی تولید پاسخ (حداکثر خروجی): 4,000 توکن. مجموع: 22,700 توکن. این مقدار فقط 18% از یک پنجره‌ی 128K است.

اما اتنشن با طولانی‌تر شدن بافتار، رفتار خطی ندارد. در ترنسفورمرهای استاندارد، مدلی با 128K توکن بافتار، هزینه‌ی اتنشن درجه‌دو (O(n2)O(n^2)) می‌پردازد (هرچند بیشتر مدل‌های پروداکشن از نسخه‌های بهینه‌شده‌ی اتنشن استفاده می‌کنند). از آن مهم‌تر، افت دقت بازیابی است. آزمون «سوزن در انبار کاه» (Needle in a Haystack) نشان می‌دهد که مدل‌ها در پیدا کردن اطلاعات میانه‌ی بافتارهای طولانی به مشکل می‌خورند. پژوهش لیو و همکاران (2023) اثبات کرد که LLMها اطلاعات ابتدا و انتهای بافتارهای طولانی را با دقت نزدیک به 100% بازیابی می‌کنند، اما این دقت برای اطلاعات واقع در میانه (بخش‌های 40 تا 70 درصدی بافتار) بین 10 تا 20 درصد افت می‌کند. اثر «گم شدن در میانه» (lost-in-the-middle) در مدل‌های مختلف شدت متفاوتی دارد، اما دامن‌گیر همه‌ی معماری‌های فعلی است.

درس کاربردی این است: در دسترس بودن 200K توکن به معنای کارآمد بودن استفاده از 200K توکن نیست. بافتاری 10K توکنی که بادقت گلچین شده باشد، اغلب عملکرد بهتری از بافتاری 100K توکنی دارد که بی‌حساب‌وکتاب پر شده است. مهندسی بافتار یعنی هنر به حداکثر رساندن نسبت سیگنال به نویز در داخل پنجره‌ی بافتار.

هر توکن اضافه‌ای که درون پنجره می‌گذارید، جای توکن دیگری را می‌گیرد که می‌توانست داده‌های مرتبط‌تری را حمل کند. هر تعریف ابزار غیرضروری، هر نوبت گفت‌وگوی بی‌استفاده و قدیمی، و هر تکه متن بازیابی‌شده‌ای که پاسخ پرسش نیست، دست‌به‌دست هم می‌دهند تا عملکرد مدل در انجام تسک کمی ضعیف‌تر شود.

مفاهیم پایه

پنجره‌ی بافتار منبعی کمیاب است

پنجره‌ی بافتار را مثل RAM ببینید، نه دیسک. سریع است و مستقیماً در دسترس قرار دارد، اما محدود است. نمی‌توانید همه‌چیز را در آن جا دهید؛ باید دست به انتخاب بزنید.

پنجره بافت (128K توکن)پرامپت سیستم~500 توکنتعاریف ابزار~2K-8K توکنبافت بازیابی‌شده~2K-10K توکنتاریخچه گفتگو~2K-20K توکننمونه‌های Few-shot~1K-3K توکنپرسش کاربر~100-500 توکنبودجه تولید~2K-8K توکن

اجزای مختلف مدام بر سر فضا رقابت می‌کنند. افزودن تعاریف ابزار بیشتر یعنی فضای کمتر برای تاریخچه‌ی گفت‌وگو. آوردن بافتار بازیابی‌شده‌ی بیشتر یعنی جا تنگ شدن برای نمونه‌های چندشات. مهندسی بافتار هنر تخصیص این بودجه برای رساندن عملکرد مدل در تسک به بالاترین حد ممکن است.

گم شدن در میانه (Lost-in-the-Middle)

این مهم‌ترین یافته‌ی تجربی در مهندسی بافتار است. مدل‌ها اطلاعات ابتدا و انتهای بافتار را خیلی بهتر پردازش می‌کنند؛ اطلاعات واقع در میانه نمرات اتنشن کمتری می‌گیرند و احتمال نادیده گرفته شدنشان بسیار بیشتر است.

لیو و همکاران (2023) این موضوع را به‌صورت سیستماتیک سنجیدند. آن‌ها یک سند مرتبط را میان 20 سند نامربوط در موقعیت‌های مختلف کاشتند و دقت پاسخ را اندازه گرفتند. وقتی سند مرتبط در جایگاه اول یا آخر بود، دقت 85 تا 90 درصد بود. وقتی همان سند در وسط قرار می‌گرفت (مثلاً جایگاه 10 از 20)، دقت تا 60 الی 70 درصد سقوط می‌کرد.

این نتیجه پیامدهای مهندسی مستقیمی دارد:

  • مهم‌ترین داده‌ها را در ابتدا بگذارید (پرامپت سیستم، دستورالعمل‌های حیاتی)؛
  • پرس‌وجوی فعلی و مرتبط‌ترین بافتار را در انتها بگذارید (سوگیری تازگی یا recency bias به شما کمک می‌کند)؛
  • میانه‌ی بافتار را کم‌اهمیت‌ترین ناحیه در نظر بگیرید؛
  • اگر ناچارید اطلاعاتی را در میانه مطرح کنید، نکته‌ی کلیدی‌اش را در انتها تکرار کنید.
توزیع اتنشن در سراسر بافتموقعیت 0-20%اتنشن بالا(پرامپت سیستم)موقعیت 20-40%متوسطموقعیت 40-70%اتنشن پایین(گم‌شده در میانه)موقعیت 70-90%متوسطموقعیت 90-100%اتنشن بالا(پرسش فعلی)

اجزای تشکیل‌دهنده‌ی بافتار

پرامپت سیستم (System prompt): شخصیت، محدودیت‌ها و قوانین رفتاری را مشخص می‌کند. همیشه در ابتدا می‌آید و در تمام نوبت‌های گفت‌وگو ثابت می‌ماند. ابزاری مثل Claude Code حدود 6,000 توکن را فقط برای پرامپت سیستم، شامل تعاریف ابزار و دستورالعمل‌های رفتاری، مصرف می‌کند. این بخش را تا جای ممکن فشرده نگه دارید؛ چون تک‌تک واژه‌های آن در تمام فراخوانی‌های API تکرار می‌شوند.

تعاریف ابزارها (Tool definitions): هر ابزار بسته به نام، توضیحات و اسکیمای پارامترها، 50 تا 200 توکن مصرف می‌کند. وجود 50 ابزار با میانگین 150 توکن، یعنی 7,500 توکن پیش از آغاز گفت‌وگو پر شده است. گزینش پویای ابزار (Dynamic tool selection) — یعنی فقط آوردن ابزارهای مرتبط با پرس‌وجوی فعلی — می‌تواند این حجم را 60 تا 80 درصد کاهش دهد.

بافتار بازیابی‌شده (Retrieved context): اسناد پایگاه‌داده‌ی برداری، نتایج جست‌وجو و محتوای فایل‌ها. کیفیت بازیابی مستقیماً کیفیت پاسخ را تعیین می‌کند. بازیابی بد حتی از بازیابی نکردن هم بدتر است؛ چون پنجره را با نویز پر می‌کند و مدل را گمراه می‌سازد.

تاریخچه‌ی گفت‌وگو (Conversation history): تمام پیام‌های قبلی کاربر و پاسخ‌های دستیار. این بخش با طولانی‌تر شدن مکالمه به‌صورت خطی رشد می‌کند. گفت‌وگویی 50 نوبتی با میانگین 200 توکن در هر نوبت، 10,000 توکن تاریخچه تولید می‌کند که بخش عمده‌اش ربطی به پرس‌وجوی فعلی ندارد.

نمونه‌های چندشات (Few-shot examples): جفت‌های ورودی/خروجی که رفتار مطلوب را نشان می‌دهند. معمولاً دو تا سه نمونه‌ی حساب‌شده اثر بهتری از هزاران توکن دستورالعمل دارند، اما فضا را اشغال می‌کنند.

سهمیه‌ی تولید پاسخ (Generation budget): توکن‌هایی که برای پاسخ مدل کنار گذاشته می‌شوند. اگر پنجره را کامل پر کنید، مدل جایی برای پاسخ‌گویی نخواهد داشت. حداقل 2,000 تا 4,000 توکن برای تولید پاسخ رزرو کنید.

استراتژی‌های فشرده‌سازی بافتار

خلاصه‌سازی تاریخچه (History summarization): به‌جای نگه‌داشتن کلمه‌به‌کلمه‌ی همه‌ی نوبت‌های قبلی، گفت‌وگو را در فواصل مشخص خلاصه کنید. متنی 100 توکنی مثل «درباره‌ی X بحث کردیم، روی Y به توافق رسیدیم و کاربر Z را می‌خواهد» جایگزین 10 نوبت بافتار 2,000 توکنی می‌شود. وقتی تاریخچه از یک آستانه (مثلاً 5,000 توکن) گذشت، خلاصه‌سازی را اجرا کنید.

فیلتر کردن بر اساس ارتباط (Relevance filtering): به هر سند بازیابی‌شده بر اساس ارتباطش با پرس‌وجوی فعلی امتیاز بدهید و اسناد زیر آستانه را کنار بگذارید. اگر 10 قطعه آورده‌اید و فقط 3 تای آن‌ها مرتبط هستند، 7 تای دیگر را حذف کنید. داشتن 3 قطعه‌ی فوق‌العاده بهتر از 10 قطعه‌ی معمولی است.

هرس کردن ابزارها (Tool pruning): نیت (Intent) پرس‌وجوی کاربر را دسته‌بندی کنید و فقط ابزارهای مرتبط با آن نیت را بیاورید. یک پرسش برنامه‌نویسی نیازی به ابزارهای تقویم ندارد و یک سوال زمان‌بندی نیازی به ابزارهای سیستم فایل ندارد. همین کار حجم توکن تعاریف ابزار را از 8,000 به 1,000 توکن می‌رساند.

خلاصه‌سازی بازگشتی (Recursive summarization): برای اسناد بسیار طولانی، خلاصه‌سازی را مرحله‌به‌مرحله انجام دهید. ابتدا هر بخش را جداگانه خلاصه کنید، سپس خلاصه‌ی آن خلاصه‌ها را بسازید. به این ترتیب یک سند 50 صفحه‌ای تبدیل به گزیده‌ای 500 توکنی می‌شود که نکات کلیدی را در خود دارد.

سیستم‌های حافظه

مهندسی بافتار سه افق زمانی را پوشش می‌دهد:

حافظه‌ی کوتاه‌مدت (Short-term memory): گفت‌وگوی فعلی که مستقیماً در پنجره‌ی بافتار ذخیره می‌شود. با هر نوبت رشد می‌کند و با خلاصه‌سازی و هرس (کوتاه‌سازی) مدیریت می‌شود.

حافظه‌ی بلندمدت (Long-term memory): فکت‌ها و ترجیحاتی که در گفت‌وگوهای مختلف ماندگارند؛ چیزهایی مثل «کاربر TypeScript را ترجیح می‌دهد» یا «پروژه از PostgreSQL استفاده می‌کند». این داده‌ها در پایگاه‌داده ذخیره می‌شوند و در ابتدای نشست بازیابی می‌گردند. Claude Code این موارد را در فایل‌های CLAUDE.md ذخیره می‌کند و ChatGPT آن را در قابلیت حافظه‌ی خود دارد.

حافظه‌ی رویدادی (Episodic memory): تعاملات شاخص گذشته که ممکن است مرتبط باشند؛ مانند «سه‌شنبه‌ی پیش خطای مشابهی را در ماژول احراز هویت دیباگ کردیم». این موارد به‌شکل امبدینگ ذخیره شده و زمانی که گفت‌وگوی فعلی با رخدادهای پیشین مطابقت داشته باشد، فراخوانی می‌شوند.

معماری حافظهحافظه کوتاه‌مدت(گفتگوی فعلی)مستقیم در پنجره بافتحافظه بلندمدت(حقایق، ترجیحات)پایگاه داده - بازیابی در شروع نشستحافظه رویدادی(تعاملات گذشته)امبدینگ‌ها - بازیابی بر اساس شباهتپرسش فعلیپنجره بافت

سرهم‌بندی پویای بافتار

نکته‌ی کلیدی: هر نوع پرس‌وجویی بافتار خاص خودش را می‌خواهد. پرامپت سیستم ایستا + ابزارهای ثابت + تاریخچه‌ی ایستا اتلاف منابع است. بهترین سیستم‌ها بافتار را به ازای هر پرس‌وجو به‌شکل پویا سرهم می‌کنند:

  1. دسته‌بندی نیت پرس‌وجو؛
  2. انتخاب ابزارهای مرتبط (نه تمام ابزارها)؛
  3. بازیابی اسناد مرتبط (نه یک مجموعه‌ی ثابت)؛
  4. گنجاندن نوبت‌های مرتبط تاریخچه (نه تمام تاریخچه)؛
  5. افزودن نمونه‌های چندشات هماهنگ با نوع تسک؛
  6. مرتب‌سازی همه‌چیز بر اساس اولویت: موارد حیاتی در ابتدا، نکات مهم در انتها و موارد اختیاری در میانه.

تفاوت یک اپلیکیشن هوش مصنوعی خوب با یک اپلیکیشن عالی در همین است. مدل در هر دو یکسان است؛ این بافتار است که برگ برنده به حساب می‌آید.

گم شدن در میانهموقعیت فکت را بکشید
LOST IN THE MIDDLEWhen a single relevant fact is buried in a long context, the model recalls it well if it sits near the start or the end, and far worse if it lands in the middle. Accuracy is U-shaped over position. This is why context engineering puts the most important material at the top or bottom of the prompt and never relies on a long unstructured dump.ابتدای کانتکستانتهای کانتکست
وقتی یک فکت مرتبط در یک بافت طولانی پنهان است، اگر نزدیک به ابتدا یا انتها قرار گیرد، مدل آن را به خوبی بازخوانی می‌کند و اگر در میانه بیفتد، عملکرد بسیار بدتر می‌شود. دقت بر حسب موقعیت، U-شکل است. به همین دلیل مهندسی کانتکست مهم‌ترین بخش‌ها را در بالا یا پایین پرامپت قرار می‌دهد و هرگز به یک متن طولانی ساختارنیافته متکی نمی‌شود.

پیاده‌سازی گام‌به‌گام

گام 1: شمارنده‌ی توکن

چیزی را که نتوانید اندازه بگیرید، نمی‌توانید بودجه‌بندی کنید. با یک شمارنده‌ی ساده‌ی توکن شروع می‌کنیم (این یک تخمین بر اساس جدا کردن با فاصله‌هاست، چون شمارش دقیق به توکنایزر بستگی دارد).

import json
import numpy as np
from collections import OrderedDict

def count_tokens(text):
    if not text:
        return 0
    return int(len(text.split()) * 1.3)

def count_tokens_json(obj):
    return count_tokens(json.dumps(obj))

گام 2: مدیریت بودجه‌ی بافتار

انتزاع اصلی اینجاست. مدیر بودجه وظیفه دارد میزان مصرف توکن هر بخش را رصد کند و سقف‌ها را به اجرا بگذارد.

class ContextBudget:
    def __init__(self, max_tokens=128000, generation_reserve=4000):
        self.max_tokens = max_tokens
        self.generation_reserve = generation_reserve
        self.available = max_tokens - generation_reserve
        self.allocations = OrderedDict()

    def allocate(self, component, content, max_tokens=None):
        tokens = count_tokens(content)
        if max_tokens and tokens > max_tokens:
            words = content.split()
            target_words = int(max_tokens / 1.3)
            content = " ".join(words[:target_words])
            tokens = count_tokens(content)

        used = sum(self.allocations.values())
        if used + tokens > self.available:
            allowed = self.available - used
            if allowed <= 0:
                return None, 0
            words = content.split()
            target_words = int(allowed / 1.3)
            content = " ".join(words[:target_words])
            tokens = count_tokens(content)

        self.allocations[component] = tokens
        return content, tokens

    def remaining(self):
        used = sum(self.allocations.values())
        return self.available - used

    def utilization(self):
        used = sum(self.allocations.values())
        return used / self.max_tokens

    def report(self):
        total_used = sum(self.allocations.values())
        lines = []
        lines.append(f"Context Budget Report ({self.max_tokens:,} token window)")
        lines.append("-" * 50)
        for component, tokens in self.allocations.items():
            pct = tokens / self.max_tokens * 100
            bar = "#" * int(pct / 2)
            lines.append(f"  {component:<25} {tokens:>6} tokens ({pct:>5.1f}%) {bar}")
        lines.append("-" * 50)
        lines.append(f"  {'Used':<25} {total_used:>6} tokens ({total_used/self.max_tokens*100:.1f}%)")
        lines.append(f"  {'Generation reserve':<25} {self.generation_reserve:>6} tokens")
        lines.append(f"  {'Remaining':<25} {self.remaining():>6} tokens")
        return "\n".join(lines)

گام 3: بازآرایی برای رفع «گم شدن در میانه»

پیاده‌سازی استراتژی بازآرایی: مهم‌ترین موارد اول و آخر قرار می‌گیرند و کم‌اهمیت‌ترین‌ها به میانه فرستاده می‌شوند.

def reorder_lost_in_middle(items, scores):
    paired = sorted(zip(scores, items), reverse=True)
    sorted_items = [item for _, item in paired]

    if len(sorted_items) <= 2:
        return sorted_items

    first_half = sorted_items[::2]
    second_half = sorted_items[1::2]
    second_half.reverse()

    return first_half + second_half

def score_relevance(query, documents):
    query_words = set(query.lower().split())
    scores = []
    for doc in documents:
        doc_words = set(doc.lower().split())
        if not query_words:
            scores.append(0.0)
            continue
        overlap = len(query_words & doc_words) / len(query_words)
        scores.append(round(overlap, 3))
    return scores

گام 4: فشرده‌ساز تاریخچه‌ی گفت‌وگو

خلاصه‌سازی نوبت‌های قدیمی برای آزاد کردن بودجه‌ی توکن‌ها.

class ConversationManager:
    def __init__(self, max_history_tokens=5000):
        self.turns = []
        self.summaries = []
        self.max_history_tokens = max_history_tokens

    def add_turn(self, role, content):
        self.turns.append({"role": role, "content": content})
        self._compress_if_needed()

    def _compress_if_needed(self):
        total = sum(count_tokens(t["content"]) for t in self.turns)
        if total <= self.max_history_tokens:
            return

        while total > self.max_history_tokens and len(self.turns) > 4:
            old_turns = self.turns[:2]
            summary = self._summarize_turns(old_turns)
            self.summaries.append(summary)
            self.turns = self.turns[2:]
            total = sum(count_tokens(t["content"]) for t in self.turns)

    def _summarize_turns(self, turns):
        parts = []
        for t in turns:
            content = t["content"]
            if len(content) > 100:
                content = content[:100] + "..."
            parts.append(f"{t['role']}: {content}")
        return "Previous: " + " | ".join(parts)

    def get_context(self):
        parts = []
        if self.summaries:
            parts.append("[Conversation Summary]")
            for s in self.summaries:
                parts.append(s)
        parts.append("[Recent Conversation]")
        for t in self.turns:
            parts.append(f"{t['role']}: {t['content']}")
        return "\n".join(parts)

    def token_count(self):
        return count_tokens(self.get_context())

گام 5: گزینش پویای ابزارها

فقط ابزارهای مرتبط با پرس‌وجوی کنونی را اضافه کنید. نیت را دسته‌بندی کرده و سپس فیلتر کنید.

TOOL_REGISTRY = {
    "read_file": {
        "description": "Read contents of a file",
        "tokens": 120,
        "categories": ["code", "files"],
    },
    "write_file": {
        "description": "Write content to a file",
        "tokens": 150,
        "categories": ["code", "files"],
    },
    "search_code": {
        "description": "Search for patterns in codebase",
        "tokens": 130,
        "categories": ["code"],
    },
    "run_command": {
        "description": "Execute a shell command",
        "tokens": 140,
        "categories": ["code", "system"],
    },
    "create_calendar_event": {
        "description": "Create a new calendar event",
        "tokens": 180,
        "categories": ["calendar"],
    },
    "list_emails": {
        "description": "List recent emails",
        "tokens": 160,
        "categories": ["email"],
    },
    "send_email": {
        "description": "Send an email message",
        "tokens": 200,
        "categories": ["email"],
    },
    "web_search": {
        "description": "Search the web for information",
        "tokens": 140,
        "categories": ["research"],
    },
    "query_database": {
        "description": "Run a SQL query on the database",
        "tokens": 170,
        "categories": ["code", "data"],
    },
    "generate_chart": {
        "description": "Generate a chart from data",
        "tokens": 190,
        "categories": ["data", "visualization"],
    },
}

def classify_intent(query):
    query_lower = query.lower()

    intent_keywords = {
        "code": ["code", "function", "bug", "error", "file", "implement", "refactor", "debug", "test"],
        "calendar": ["meeting", "schedule", "calendar", "appointment", "event"],
        "email": ["email", "mail", "send", "inbox", "message"],
        "research": ["search", "find", "what is", "how does", "explain", "look up"],
        "data": ["data", "query", "database", "chart", "graph", "analytics", "sql"],
    }

    scores = {}
    for intent, keywords in intent_keywords.items():
        score = sum(1 for kw in keywords if kw in query_lower)
        if score > 0:
            scores[intent] = score

    if not scores:
        return ["code"]

    max_score = max(scores.values())
    return [intent for intent, score in scores.items() if score >= max_score * 0.5]

def select_tools(query, token_budget=2000):
    intents = classify_intent(query)
    relevant = {}
    total_tokens = 0

    for name, tool in TOOL_REGISTRY.items():
        if any(cat in intents for cat in tool["categories"]):
            if total_tokens + tool["tokens"] <= token_budget:
                relevant[name] = tool
                total_tokens += tool["tokens"]

    return relevant, total_tokens

گام 6: پایپ‌لاین کامل سرهم‌بندی بافتار

همه‌چیز را به هم متصل می‌کنیم. با دریافت پرس‌وجو، بافتار بهینه به‌شکل پویا سرهم می‌شود.

class ContextEngine:
    def __init__(self, max_tokens=128000, generation_reserve=4000):
        self.budget = ContextBudget(max_tokens, generation_reserve)
        self.conversation = ConversationManager(max_history_tokens=5000)
        self.system_prompt = (
            "You are a helpful AI assistant. You have access to tools for "
            "code editing, file management, web search, and data analysis. "
            "Use the appropriate tools for each task. Be concise and accurate."
        )
        self.knowledge_base = [
            "Python 3.12 introduced type parameter syntax for generic classes using bracket notation.",
            "The project uses PostgreSQL 16 with pgvector for embedding storage.",
            "Authentication is handled by Supabase Auth with JWT tokens.",
            "The frontend is built with Next.js 15 using the App Router.",
            "API rate limits are set to 100 requests per minute per user.",
            "The deployment pipeline uses GitHub Actions with Docker multi-stage builds.",
            "Test coverage must be above 80% for all new modules.",
            "The codebase follows the repository pattern for data access.",
        ]

    def assemble(self, query):
        self.budget = ContextBudget(self.budget.max_tokens, self.budget.generation_reserve)

        system_content, _ = self.budget.allocate("system_prompt", self.system_prompt, max_tokens=1000)

        tools, tool_tokens = select_tools(query, token_budget=2000)
        tool_text = json.dumps(list(tools.keys()))
        tool_content, _ = self.budget.allocate("tools", tool_text, max_tokens=2000)

        relevance = score_relevance(query, self.knowledge_base)
        threshold = 0.1
        relevant_docs = [
            doc for doc, score in zip(self.knowledge_base, relevance)
            if score >= threshold
        ]

        if relevant_docs:
            doc_scores = [s for s in relevance if s >= threshold]
            reordered = reorder_lost_in_middle(relevant_docs, doc_scores)
            doc_text = "\n".join(reordered)
            doc_content, _ = self.budget.allocate("retrieved_context", doc_text, max_tokens=3000)

        history_text = self.conversation.get_context()
        if history_text.strip():
            history_content, _ = self.budget.allocate("conversation_history", history_text, max_tokens=5000)

        query_content, _ = self.budget.allocate("user_query", query, max_tokens=500)

        return self.budget

    def chat(self, query):
        self.conversation.add_turn("user", query)
        budget = self.assemble(query)
        response = f"[Response to: {query[:50]}...]"
        self.conversation.add_turn("assistant", response)
        return budget


def run_demo():
    print("=" * 60)
    print("  Context Engineering Pipeline Demo")
    print("=" * 60)

    engine = ContextEngine(max_tokens=128000, generation_reserve=4000)

    print("\n--- Query 1: Code task ---")
    budget = engine.chat("Fix the bug in the authentication module where JWT tokens expire too early")
    print(budget.report())

    print("\n--- Query 2: Research task ---")
    budget = engine.chat("What is the best approach for implementing vector search in PostgreSQL?")
    print(budget.report())

    print("\n--- Query 3: After conversation history builds up ---")
    for i in range(8):
        engine.conversation.add_turn("user", f"Follow-up question number {i+1} about the implementation details of the system")
        engine.conversation.add_turn("assistant", f"Here is the response to follow-up {i+1} with technical details about the architecture")

    budget = engine.chat("Now implement the changes we discussed")
    print(budget.report())

    print("\n--- Tool Selection Examples ---")
    test_queries = [
        "Fix the bug in auth.py",
        "Schedule a meeting with the team for Tuesday",
        "Show me the database query performance stats",
        "Search for best practices on error handling",
    ]

    for q in test_queries:
        tools, tokens = select_tools(q)
        intents = classify_intent(q)
        print(f"\n  Query: {q}")
        print(f"  Intents: {intents}")
        print(f"  Tools: {list(tools.keys())} ({tokens} tokens)")

    print("\n--- Lost-in-the-Middle Reordering ---")
    docs = ["Doc A (most relevant)", "Doc B (somewhat relevant)", "Doc C (least relevant)",
            "Doc D (relevant)", "Doc E (moderately relevant)"]
    scores = [0.95, 0.60, 0.20, 0.80, 0.50]
    reordered = reorder_lost_in_middle(docs, scores)
    print(f"  Original order: {docs}")
    print(f"  Scores:         {scores}")
    print(f"  Reordered:      {reordered}")
    print(f"  (Most relevant at start and end, least relevant in middle)")

کاربرد در عمل

بافتار مدیریت‌شده توسط ابزار (Harness-Managed Context)

ابزار Claude Code با رویکردی چندلایه بافتار را مدیریت می‌کند. پرامپت سیستم حامل قوانین رفتاری و تعاریف ابزارهاست (حدود 6K توکن). وقتی فایلی را باز می‌کنید، محتوای آن به بافتار تزریق می‌شود. وقتی جست‌وجو می‌کنید، نتایج به آن اضافه می‌شوند. نوبت‌های قدیمی گفت‌وگو فشرده و خلاصه می‌شوند. فایل CLAUDE.md هم حافظه‌ی بلندمدتی را فراهم می‌کند که در تمام نشست‌ها حفظ می‌شود.

تصمیم مهندسی کلیدی: Claude Code کل کدبیس شما را یک‌جا درون بافتار نمی‌ریزد، بلکه فایل‌های مرتبط را بر حسب نیاز بازیابی می‌کند. این یعنی مهندسی بافتار در عمل.

بارگذاری پویای بافتار

ادیتور Cursor کل کدبیس شما را به‌صورت امبدینگ ایندکس می‌کند. وقتی پرسشی می‌نویسید، مرتبط‌ترین فایل‌ها و بلوک‌های کد را با استفاده از شباهت برداری بیرون می‌کشد. فقط همان بخش‌ها وارد پنجره‌ی بافتار می‌شوند. یک کدبیس 500K خطی به 5 تا 10 بلوک کد مرتبط فشرده می‌شود.

الگوی ثابت همین است: همه‌چیز را امبد کنید، هنگام نیاز بازیابی کنید و فقط داده‌های واقعاً مهم را بیاورید.

حافظه‌ی بلندمدت دستیار

سرویس ChatGPT اولویت‌ها و فکت‌های کاربر را به‌عنوان حافظه‌ی بلندمدت ذخیره می‌کند. در شروع هر گفت‌وگو، خاطرات مرتبط واکشی شده و در پرامپت سیستم قرار می‌گیرند. جمله‌ی «کاربر Python را ترجیح می‌دهد» تنها 5 توکن فضا می‌گیرد، اما از هدررفت صدها توکن برای ارائه‌ی دستورالعمل‌های تکراری در مکالمات بعدی جلوگیری می‌کند.

RAG به‌مثابه مهندسی بافتار

تولید مبتنی بر بازیابی (RAG یا Retrieval-Augmented Generation) صورت رسمی مهندسی بافتار است. به‌جای گنجاندن تمام دانش در وزن‌های مدل (آموزش) یا پرامپت سیستم (بافتار ایستا)، اسناد مرتبط را در زمان دریافت پرس‌وجو بازیابی کرده و به پنجره‌ی بافتار تزریق می‌کنید. کل پایپ‌لاین RAG — قطعه‌بندی متن (chunking)، امبدینگ، بازیابی و بازرتبه‌بندی (reranking) — فقط با یک هدف وجود دارد: قرار دادن اطلاعات درست در پنجره‌ی بافتار.

تحویل پروژه

این درس فایل outputs/prompt-context-optimizer.md را تولید می‌کند؛ پرامپتی با قابلیت استفاده‌ی مجدد که استراتژی سرهم‌بندی بافتار شما را ارزیابی کرده و پیشنهادهای بهینه‌سازی می‌دهد. پرامپت سیستم، تعداد ابزارها، میانگین طول تاریخچه و استراتژی بازیابی خود را به آن بدهید تا اتلاف توکن‌ها را شناسایی کند و راه‌کارهای بهبود را بگوید.

همچنین فایل outputs/skill-context-engineering.md آماده می‌شود؛ چارچوبی برای تصمیم‌گیری در طراحی پایپ‌لاین‌های سرهم‌بندی بافتار، متناسب با نوع تسک، اندازه‌ی پنجره‌ی بافتار و سقف تاخیر (latency budget).

تمرین‌ها

  1. قابلیت «شناسایی اتلاف توکن» را به کلاس ContextBudget اضافه کنید. این قابلیت باید بخش‌هایی را که بیشتر از 30% بودجه را مصرف می‌کنند نشانه‌گذاری کند و راهکارهای فشرده‌سازی ویژه‌ی هر بخش را پیشنهاد دهد (خلاصه‌سازی تاریخچه، هرس ابزارها، بازرتبه‌بندی اسناد).

  2. حذف موارد تکراری بر پایه‌ی معنا (semantic deduplication) را برای بافتار بازیابی‌شده پیاده کنید. اگر دو سند بازیابی‌شده بیش از 80% تشابه داشتند (با اشتراک کلمات یا شباهت کسینوسی امبدینگ‌ها)، فقط سندی را که امتیاز بالاتری دارد نگه دارید. اندازه‌گیری کنید که این کار چقدر از بودجه‌ی توکن را برمی‌گرداند.

  3. ابزاری برای «بازپخش بافتار» (context replay) بسازید. رونوشت یک گفت‌وگو را دریافت کنید، آن را دوباره از دل ContextEngine بگذرانید و تغییرات تخصیص بودجه را نوبت به نوبت مصورسازی کنید. مصرف توکن هر بخش در طول زمان را رسم کنید و نوبتی را که فشرده‌سازی بافتار از آنجا شروع می‌شود مشخص نمایید.

  4. یک انتخاب‌کننده‌ی ابزار اولویت‌محور بسازید. به‌جای گنجاندن/حذف دودویی (صفر و یکی)، به هر ابزار بر اساس پرس‌وجوی فعلی امتیاز ارتباط اختصاص دهید. ابزارها را به ترتیب نزولی امتیاز وارد کنید تا جایی که بودجه‌ی ابزارها پر شود. عملکرد تسک را در حالت‌های گنجاندن 5، 10، 20 و 50 ابزار مقایسه کنید.

  5. فشرده‌ساز بافتار چنداستراتژی بسازید. سه استراتژی فشرده‌سازی (هرس کردن، خلاصه‌سازی و استخراج جملات کلیدی) را پیاده کنید و آن‌ها را روی مجموعه‌ای از 20 سند محک بزنید. تعادل بین نسبت فشرده‌سازی و حفظ اطلاعات را ارزیابی کنید (آیا نسخه‌ی فشرده‌شده هنوز حاوی پاسخ پرس‌وجو هست؟).

اصطلاحات کلیدی

اصطلاح در گفتگوهای عامیانه معنای دقیق فنی
پنجره‌ی بافتار (Context window) «مدل تا چه حد می‌تواند بخواند» حداکثر تعداد توکن‌هایی (ورودی + خروجی) که مدل می‌تواند در یک گذر رو به جلو (forward pass) پردازش کند — معادل 400K برای GPT-5، ظرفیت 200K (نسخه‌ی بتای 1M) برای Claude Opus 4.7 و 2M برای Gemini 3 Pro
مهندسی بافتار (Context engineering) «مهندسی پرامپت پیشرفته» دانش تصمیم‌گیری درباره‌ی اینکه چه چیزی وارد پنجره‌ی بافتار شود، با چه ترتیبی بیاید و چه اولویتی داشته باشد — شامل بازیابی، فشرده‌سازی، انتخاب ابزار و مدیریت حافظه
گم شدن در میانه (Lost-in-the-middle) «مدل نکات وسط متن را فراموش می‌کند» یافته‌ای تجربی حاکی از اینکه LLMها به ابتدا و انتهای بافتار اتنشن بهتری دارند و دقت بازیابی اطلاعات در میانه‌ی متن، 10 تا 20 درصد افت می‌کند
سهمیه‌ی توکن (Token budget) «چقدر توکن برایمان مانده» تخصیص صریح ظرفیت پنجره‌ی بافتار میان اجزای مختلف (پرامپت سیستم، ابزارها، تاریخچه، بازیابی، تولید پاسخ) با تعیین سقف برای هر جزء
بافتار پویا (Dynamic context) «بارگذاری چیزها در لحظه» سرهم‌بندی متفاوت پنجره‌ی بافتار برای هر پرس‌وجو، بر اساس دسته‌بندی نیت، انتخاب ابزارهای مرتبط و نتایج بازیابی
خلاصه‌سازی تاریخچه (History summarization) «فشرده کردن مکالمه» جایگزین کردن نوبت‌های کلمه‌به‌کلمه‌ی قدیمی گفت‌وگو با خلاصه‌ای مختصر برای کاهش مصرف توکن و حفظ داده‌های کلیدی
هرس کردن ابزارها (Tool pruning) «فقط نگه داشتن ابزارهای مرتبط» دسته‌بندی نیت پرس‌وجو و فقط وارد کردن تعاریف ابزار مرتبط، که هزینه‌ی توکن ابزارها را 60 تا 80 درصد کم می‌کند
حافظه‌ی بلندمدت (Long-term memory) «یادآوری در طول نشست‌های مختلف» فکت‌ها و اولویت‌های ذخیره‌شده در پایگاه‌داده که در آغاز هر نشست بازیابی می‌شوند — مانند CLAUDE.md، حافظه‌ی ChatGPT و سیستم‌های مشابه
حافظه‌ی رویدادی (Episodic memory) «به یاد آوردن اتفاقات خاص گذشته» تعاملات پیشین ذخیره‌شده در قالب امبدینگ که هنگام شباهت پرس‌وجوی کنونی با گفت‌وگوهای گذشته فراخوانی می‌شوند
سهمیه‌ی تولید پاسخ (Generation budget) «فضا برای نوشتن پاسخ» توکن‌های رزروشده برای پاسخ خروجی مدل — اگر بافتار تمام فضا را ببلعد، مدل مجالی برای نوشتن پاسخ نخواهد داشت

برای مطالعه‌ی بیشتر

سؤالات متداول

پدیده‌ی گم شدن در میانه (Lost-in-the-Middle) چیست؟

یافته‌ای تجربی است که نشان می‌دهد مدل‌های زبانی اطلاعات ابتدا و انتهای بافتار را با دقت بالا (85 تا 90 درصد) بازیابی می‌کنند، اما دقت بازیابی برای اطلاعاتی که در میانه‌ی بافتار طولانی (40 تا 70 درصد میانی) قرار دارند، بین 10 تا 20 درصد افت می‌کند.

چرا مهندسی بافتار مهم‌تر از مهندسی پرامپت دانسته می‌شود؟

پرامپت فقط متنی است که کاربر می‌نویسد، در حالی که بافتار شامل تمام ورودی‌های پنجره اعم از پرامپت سیستم، تعاریف ابزارها، اسناد بازیابی‌شده (RAG)، تاریخچه‌ی پیام‌ها و نمونه‌های چندشات است که مستقیماً بر کیفیت و هزینه‌ی پاسخ تاثیر می‌گذارد.

هرس کردن ابزارها (Tool Pruning) چه تاثیری در مصرف توکن دارد؟

با دسته‌بندی نیت کاربر و ارسال فقط ابزارهایی که با پرس‌وجوی جاری همخوانی دارند، می‌توان حجم توکن مربوط به تعریف ابزارها را بین 60 تا 80 درصد کاهش داد.

ما رو توی گوگل بیشتر ببین

با افزودن نکست ادمین به «منابع ترجیحی» گوگل، درس‌ها و مطالب ما را در بخش اخبار برتر جستجو بیشتر می‌بینید.