جلسه ۴ از ۱۱، بخش ۱: طراحی ایجنت

حلقه‌ی ایجنت، طراحی ابزار و مدیریت کانتکست

متنی ۳۵ دقیقه به‌روز شده در ۱۰ آبان ۱۴۰۵

در این جلسه
  1. حلقه‌ی ReAct
  2. ابزار جستجو: ایجنت کد مربوط را چطور پیدا کند؟
  3. ابزار ویرایش: ایجنت کد را چطور تغییر دهد؟
  4. ابزار اجرا: در عمل یک شل
  5. پنج ابزار پرکاربرد دیگر
  6. اصول طراحی ابزار
  7. از مهندسی پرامپت تا مهندسی هارنس
  8. کانتکست یک منبع محدود است
  9. ایجنت = مدل × هارنس
  10. فراتر از ReAct
  11. پرسش‌هایی برای فکر کردن
  12. منابع

از این جلسه به بعد از جای کاربر بیرون می‌آییم و جای طراح ایجنت می‌نشینیم. سه جزء یک ایجنت کدنویس یک پنجره‌ی کانتکست مشترک دارند و باید با هم طراحی شوند:

  1. مدل + حلقه: ایجنت یعنی یک مدل در یک حلقه
  2. ابزارها و محافظ‌ها: جستجو، خواندن، ویرایش، اجرا، پشت مجوز و sandbox
  3. حالت: آنچه الان در پنجره است و آنچه بین قدم‌ها می‌ماند

حلقه‌ی ReAct#

ReAct (Yao و همکاران، ۲۰۲۲) سه فاز را تکرار می‌کند:

  • فکر (Thought): چه می‌دانم، چه کم دارم، قدم بعد چیست؟
  • عمل (Action): یک فراخوانی ساخت‌یافته که محیط بتواند اجرا کند
  • مشاهده (Observation): محیط واقعیت‌ها را برمی‌گرداند

چرا هر دو لازم‌اند؟

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

حلقه در کد#

کل هسته‌ی یک ایجنت در چند خط جا می‌شود. تابع llm را با فراخوانی هر ارائه‌دهنده‌ای که tool calling دارد جایگزین کنید:

import json

MAX_STEPS = 30

def run_agent(task: str, tools: dict, llm) -> str:
    messages = [
        {"role": "system", "content": "Solve the task using the tools."},
        {"role": "user", "content": task},
    ]
    for step in range(MAX_STEPS):
        reply = llm(messages, tools=[t.schema for t in tools.values()])
        messages.append(reply)
        if not reply.get("tool_calls"):
            return reply["content"]               # پاسخ نهایی

        for call in reply["tool_calls"]:
            tool = tools.get(call["name"])
            try:
                if tool is None:
                    raise ValueError(f"unknown tool {call['name']!r}; available: {list(tools)}")
                args = tool.validate(json.loads(call["arguments"]))   # اعتبارسنجی پیش از اجرا
                result = tool.run(**args)
            except Exception as e:                                    # خطا هم مشاهده است
                result = f"ERROR: {e}"
            messages.append({"role": "tool", "tool_call_id": call["id"],
                             "content": str(result)[:10_000]})        # خروجی محدود

    raise RuntimeError("step budget exhausted")

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

ابزار جستجو: ایجنت کد مربوط را چطور پیدا کند؟#

روش کد را با چه پیدا می‌کند بهترین وقتی هزینه‌ی اصلی
glob و grep نام‌ها و مسیرهای واقعی نام معلوم یا قابل حدس است نوبت و کانتکست برای هر جستجو
ایندکس معنایی معنای تکه‌های embed‌شده واژه‌ها با کد منطبق نیستند ساختن و همگام نگه داشتن ایندکس
سیگنال بیرونی stack trace، تست شکسته، تاریخچه‌ی git گزارشی به کد اشاره می‌کند فقط برای بعضی کارها وجود دارد
language server (LSP) گراف نمادهای کامپایلر ویرایش باید ارجاع‌ها را دنبال کند یک سرور زنده برای هر زبان

قاعده‌ی عملی: با grep شروع کنید. هیچ چیزی برای ساختن یا همگام کردن ندارد و همیشه وضعیت واقعی مخزن را می‌بیند. وقتی نام‌ها جواب ندادند، سراغ ایندکس یا LSP بروید. اگر stack trace یا تست شکسته دارید، دقیق‌ترین سیگنال همان است و اغلب اصلاً جستجو لازم نیست.

چهار مثال از اسلایدها، یکی برای هر روش:

  • grep و glob، «timeout لاگین را درست کن»: grep در ۱۲ فایل ۳۷ نتیجه می‌دهد. glob دامنه را به auth/retry.ts محدود می‌کند. ایجنت فایل را می‌خواند و RETRY_MS را از ۵۰۰ تغییر می‌دهد.
  • ایندکس معنایی، «کجا rate-limit می‌کنیم؟»: با اولین جستجو throttle.ts پیدا می‌شود، با اینکه کلمه‌ی rate-limit در آن نیست. ایندکس معنایی می‌تواند بر اساس embedding تکه‌های کد باشد، یا یک «نقشه‌ی مخزن» از فایل‌ها، کلاس‌ها و امضای توابع (روش Aider).
  • سیگنال بیرونی، «crash در صفحه‌ی پرداخت»: stack trace به cart.ts:88 اشاره می‌کند. فریم‌ها به گرد کردن در applyTax داخل tax.ts می‌رسند. اصلاح و اجرای دوباره‌ی تست، بدون هیچ جستجویی.
  • LSP، «تغییر نام charge() به capture()»: ۴۲ ارجاع در ۷ فایل پیدا می‌شود و همه، از جمله importهای با نام مستعار، تغییر می‌کنند و بعد type-check اجرا می‌شود. grep importهای مستعار و overloadها را فقط می‌تواند حدس بزند.

ابزار ویرایش: ایجنت کد را چطور تغییر دهد؟#

روش چطور مزیت ضعف
بازنویسی کامل فایل مدل کل فایل را دوباره می‌نویسد همیشه بدون خطا اعمال می‌شود خروجی متناسب با حجم فایل؛ کد دست‌نخورده ممکن است عوض شود
unified diff مدل patch می‌دهد فشرده، قابل بازبینی، آشنا برای CI با جابه‌جایی شماره‌ی خط می‌شکند
جستجو و جایگزینی رشته‌ی دقیق قدیم و جدید خروجی متناسب با حجم تغییر لنگر مبهم، کهنه یا با فاصله‌ی متفاوت شکست می‌خورد
ویرایش ساختاری (AST) عمل روی نماد: تغییر نام، افزودن پارامتر نمی‌تواند سینتکس نامعتبر بسازد ابزار جدا برای هر زبان؛ عملیات محدود

بیشتر ایجنت‌های امروز از جستجو و جایگزینی استفاده می‌کنند، با یک قید مهم: رشته‌ی قدیمی باید دقیقاً یک بار در فایل باشد. اگر صفر یا چند بار پیدا شود، ابزار خطا می‌دهد و مدل باید لنگر دقیق‌تری بدهد. هر هارنسی هم ترفندهای خودش را دارد. یکی بعد از چند تلاش سراغ تطبیق فازی می‌رود. دیگری خط‌ها را با هش برچسب می‌زند. سومی ویرایش را فقط وقتی قبول می‌کند که فایل از آخرین خواندن عوض نشده باشد (compare-and-swap).

مثال هر روش در اسلایدها:

روش مثال
بازنویسی کامل افزودن retry به کلاینت API: ۱۲۰ خط خوانده می‌شود، کل فایل با ۴ خط تغییر دوباره نوشته می‌شود و diff گرفته می‌شود
unified diff افزایش حد retry در دو فایل: یک patch شش‌خطی با دو hunk، با git apply
جستجو و جایگزینی grep برای retries = 3 (یک مورد)، جایگزینی با ۵، و خواندن دوباره‌ی همان بلوک
AST افزودن پارامتر timeout با پیش‌فرض ۵ ثانیه به fetchUser() و بازنویسی هر ۱۸ فراخوانی، بعد parse و type-check

روی دو محور هزینه‌ی توکن و استحکام (آیا ویرایش همان جایی می‌نشیند که باید؟)، هیچ روشی بر همه برتری ندارد.

ابزار اجرا: در عمل یک شل#

شل قدرتمندترین و خطرناک‌ترین ابزار است:

  • ترکیب: یک فراخوانی می‌تواند زنجیر، فیلتر، حلقه و تلاش مجدد کند
  • دسترسی: فایل‌سیستم، شبکه، package managerها، هر برنامه‌ی نصب‌شده
  • ماندگاری: فایل‌ها و پروسه‌ها بعد از فراخوانی می‌مانند
  • شعاع انفجار: هر کاری شل بتواند، ایجنت هم می‌تواند

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

پنج ابزار پرکاربرد دیگر#

  • یادداشت و حافظه: یافته‌ها را در فایل بنویس تا برنامه از compaction جان سالم به در ببرد
  • زیرایجنت: جستجو یک فهرست کوتاه برمی‌گرداند، نه ۴۰ فایل خوانده‌شده
  • جستجو و خواندن وب: اول سورس نصب‌شده را بخوان، بعد وب را
  • مرورگر و بازخورد دیداری: از صفحه‌ی رندرشده عکس بگیر و به مدل نشان بده
  • پرسیدن از کاربر: یک سؤال ارزان بهتر از یک patch غلط با اعتمادبه‌نفس است

اصول طراحی ابزار#

  1. ابزار کم و متعامد: ابزارهای هم‌پوشان مدل را مردد می‌کنند.
  2. ابزار هدفمند: search_orders(customer_id) بهتر از run_sql خام است.
  3. خروجی کم‌توکن: کوتاه کنید، صفحه‌بندی کنید، خلاصه کنید. طراحی ابزار همان طراحی کانتکست است.
  4. خطایی که یاد می‌دهد: به جای «invalid argument»، راه اصلاح را بگویید.
  5. idempotent به‌طور پیش‌فرض: تا جای ممکن تکرار امن باشد.

مقایسه‌ی دو پیام خطا:

بد:  Error: invalid argument
خوب: Error: old_str found 3 times in auth.py (lines 12, 40, 88).
     Include more surrounding lines so the match is unique.

آیا فقط شل کافی نیست؟#

نموداری در اسلایدهای درس، برگرفته از آزمایش‌های SWE-agent با GPT-4، نرخ حل مسئله را با ابزارهای مختلف مقایسه می‌کند: بدون ویرایشگر تخصصی ۱۰٫۳٪، فقط شل ۱۱٫۰٪، نمایش کامل فایل ۱۲٫۷٪، تاریخچه‌ی کامل تعامل ۱۵٫۰٪، و ابزارهای اختصاصی SWE-agent ۱۸٫۰٪. ابزار و نمای اختصاصی کمک می‌کنند، هرچند مدل‌های قوی‌تر امروز این فاصله را کمتر کرده‌اند. این را خودتان با ایجنتتان آزمایش کنید.

از مهندسی پرامپت تا مهندسی هارنس#

دوره سؤال اصلی ابزارها
مهندسی پرامپت (۲۰۲۳-۲۰۲۴) به مدل چه بگوییم؟ واژه‌ها، مثال‌ها، قالب خروجی
مهندسی کانتکست (۲۰۲۵-۲۰۲۶) مدل چه ببیند؟ بازیابی، حافظه، تاریخچه، compaction
مهندسی هارنس (۲۰۲۶) ایجنت چطور کار کند؟ حلقه، ابزار، sandbox، تلاش مجدد، توقف
مهندسی هدف (بعدی) موفقیت چیست؟ معیارها، قیدها، مصالحه‌ها

اسلاید یادآوری می‌کند که این اصطلاح‌ها را آورده تا بشناسید، نه اینکه تأییدشان کند.

تکنیک‌های پرامپت در یک نگاه#

تکنیک کی استفاده کنیم
zero-shot خط پایه؛ کار رایج با قالبی که در کلمات جا می‌شود
few-shot قالب خروجی غیرمعمول یا قاعده‌ای که نشان دادنش از گفتنش راحت‌تر است
نقش و شخصیت لحن، قضاوت یا مخاطب مهم است
chain-of-thought کار واقعاً چند مرحله دارد
tree-of-thought چند مسیر معقول هست که ارزیابی‌شان ارزان است
self-consistency یک جواب درست هست و اشتباه گران است؛ چند نمونه و رأی اکثریت
زنجیره‌ی پرامپت کار به مراحل قابل اعتبارسنجی تقسیم می‌شود
meta prompting یک مجموعه‌ی ارزیابی دارید و پرامپت خودش محصول است

مثال‌هایی که اسلایدها برای هر تکنیک آورده‌اند، کمک می‌کند مرز بینشان روشن شود:

تکنیک مثال
zero-shot تیکت پشتیبانی را در یکی از چهار دسته (صورت‌حساب، باگ، درخواست قابلیت، سایر) بگذار و فقط JSON برگردان. «موقع خروجی PDF برنامه بسته می‌شود» ← {"label": "bug"}. وقتی قاعده ضمنی است، سراغ few-shot بروید.
few-shot همان کار با سه نمونه‌ی برچسب‌خورده (صورت‌حساب، قابلیت، و متن بی‌معنا ← سایر)، بعد تیکت بدون برچسب
نقش «مثل یک SRE ارشد، این گزارش پس از حادثه را برای مدیران بازبینی کن. شکاف‌های شواهد را علامت بزن، حدس نزن، حداکثر پنج بند.» یک خروجی نمونه: «خط زمانی تأیید هشدار ساعت ۰۴:۱۲ را ندارد؛ این شکاف شواهد است، نه علت.»
chain-of-thought «هفته‌ای ۳ قابلیت به مدت ۴ هفته، بعد هفته‌ای ۵ قابلیت به مدت ۲ هفته» ← ۳×۴=۱۲، ۵×۲=۱۰، ۱۲+۱۰=۲۲، و پایان با ANSWER: 22
tree-of-thought تأخیر صفحه‌ی پرداخت: گزینه‌ی A (گرم کردن کش؛ اثر ۳، تلاش ۲)، B (حذف سرویس شخص ثالث؛ اثر ۵، تلاش ۴)، C (بارگذاری تنبل؛ اثر ۴، تلاش ۱). اول C، بعد B بررسی می‌شود و A هرس می‌شود.
self-consistency «آیا این فاکتور تکراری است؟» پنج نمونه با دمای ۰٫۷: بله، بله، نه، بله، بله ← بله با اطمینان ۰٫۸. اگر رأی فقط ۳ از ۵ بود، به انسان بسپار.
زنجیره‌ی پرامپت ۱) استخراج فروشنده، مبلغ و تاریخ از ایمیل؛ ۲) اعتبارسنجی و افزودن شناسه‌ی فروشنده؛ ۳) پیش‌نویس پاسخ. اگر مرحله‌ای از اعتبارسنجی شِما رد شد، فقط همان مرحله تکرار می‌شود.
meta prompting پرامپت، ۱۰ مورد آزمون و ۳ موردی که شکست می‌خورند را به مدل بدهید و بخواهید نسخه‌ای بنویسد که آن ۳ را درست کند و ۷ تای دیگر را خراب نکند. نسخه‌ی دوم یک قاعده‌ی امتناع صریح اضافه کرد و از ۸ از ۱۰ به ۱۰ از ۱۰ رسید.

اسلاید انتخاب تکنیک را در سه پله خلاصه می‌کند: ساده شروع کنید (zero-shot، few-shot، نقش)، استدلال اضافه کنید (chain-of-thought، tree-of-thought، self-consistency)، و سیستم را مهندسی کنید (زنجیره‌ی پرامپت، meta prompting). هر تکنیک هم یک «کی سراغ بعدی بروید» دارد: وقتی قاعده ضمنی است از zero-shot به few-shot، وقتی کار قدم دارد به chain-of-thought، وقتی چند مسیر هست به tree-of-thought، وقتی یک جواب درست هست به self-consistency، وقتی مراحل جداشدنی‌اند به زنجیره، و وقتی خود پرامپت را تنظیم می‌کنید به meta prompting. اگر بهبود متوقف شد، انتخابتان را بازبینی کنید.

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

نمونه‌ی اسلاید برای ترتیب بخش‌ها: اول دستور، بعد جداکننده، بعد داده.

### Instruction ###
Translate the text below to Spanish:
Text: "hello!"

خروجی مدل: ¡Hola!

کانتکست یک منبع محدود است#

Karpathy مهندسی کانتکست را این‌طور تعریف کرده: «پر کردن پنجره‌ی کانتکست با دقیقاً اطلاعات درست برای قدم بعدی». سه دلیل که کانتکست بیشتر لزوماً بهتر نیست:

  • هزینه: هر قدم کل پنجره را دوباره می‌فرستد، پس هر نوبت دوباره پرداخت می‌شود.
  • تأخیر: پرامپت بزرگ‌تر یعنی prefill و decode کندتر در هر نوبت.
  • توجه: با پر شدن پنجره یادآوری ضعیف می‌شود.

پژوهش «Lost in the Middle» (Liu و همکاران) نشان داد وقتی جواب بین ۲۰ سند پنهان است، دقت در حالتی که جواب در سند اول است حدود ۷۶٪ است، وقتی در میانه است به حدود ۵۵ تا ۶۱٪ می‌رسد و در انتها کمی بهتر می‌شود. گزارش «Context Rot» از Chroma هم افت دقت با بلندتر شدن ورودی را نشان داد. هم مقدار و هم جای اطلاعات مهم است.

کالبدشکافی پنجره#

یک بودجه‌ی نمونه برای یک نوبت ایجنت:

پرامپت سیستمی        ~2K
تعریف ابزارها        ~6K
کانتکست بازیابی‌شده  ~25K
محیط پروژه          ~8K
تاریخچه‌ی پیام‌ها (کاربر، مدل، نتیجه‌ی ابزارها)   80K و بیشتر، و در حال رشد

چهار راهبرد مدیریت پنجره#

از پیشگیرانه و بی‌اتلاف تا پرهزینه و برگشت‌ناپذیر:

راهبرد ایده نمونه
۱. پذیرش جلوی ورود را بگیر به جای DOM خام ۴۸ هزار توکنی، عصاره‌ی ۹۰۰ توکنی (۹۸٪ کوچک‌تر، همان جواب)
۲. برون‌سپاری قابل بازیابی نگه دار، نه مقیم به جای ۶۰ شِمای ابزار (۴۲ هزار توکن)، یک فهرست و ۳ شِمای لازم (۳۱۰۰ توکن)
۳. تبدیل بخشی از تاریخچه را حذف کن حذف تکراری‌ها و پنهان کردن نتایج قدیمی ابزار: ۹۶ هزار به ۲۴ هزار، بدون خلاصه‌سازی
۴. فشرده‌سازی LLM خلاصه می‌کند؛ آخرین راه خلاصه‌ی غلتان، خلاصه‌ی کامل در حد پنجره، حافظه‌ی خودکار

جزئیات هر راهبرد:

  • پذیرش: بازیابی هدفمند (فیلد بخواه، نه سند)، شکل دادن خروجی ابزار، برش فقط وقتی شکل دادن ممکن نیست، و سپردن داده‌ی خام به زیرایجنت.
  • برون‌سپاری: تعریف ابزار تدریجی یا با جستجو، برگرداندن دستگیره به جای خروجی بزرگ (مسیر فایل)، فهرست کارها روی دیسک، و یادداشت پایدار با یک ایندکس کوچک.
  • تبدیل: نگه داشتن فقط آخرین وضعیت هر شیء، پنهان کردن نتایج ابزار قدیمی‌تر از N قدم، و پنجره‌ی لغزان که ابتدای گفتگو را حفظ می‌کند.
  • فشرده‌سازی: جا برای خروجی بگذارید، پیشوند سیستمی را دست نزنید (وگرنه prefix cache از دست می‌رود)، و بعد از خلاصه‌سازی واقعیت‌های حیاتی را دوباره تزریق کنید.

پنج حالت خرابی کانتکست#

Drew Breunig این پنج حالت را نام‌گذاری کرده است:

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

وقتی خرابی را نام‌گذاری کنید، راه‌حل معمولاً واضح می‌شود.

ایجنت = مدل × هارنس#

توانایی مدل سقف را تعیین می‌کند و هارنس تعیین می‌کند چقدر از آن سقف محقق شود. پژوهشی که در اسلایدها آمده (Lewis، ۲۰۲۶) با همان مدل و فقط تغییر هارنس این نتایج را گرفت:

بنچمارک هارنس پایه هارنس بهبودیافته
SWE-bench Verified ۴۳ از ۱۶۹ ۷۲ از ۱۶۹
SWE-bench Pro ۳۱ از ۳۱۶ ۷۲ از ۳۱۶

تغییرات هارنس ساده بودند: فشرده کردن خروجی‌های قدیمی ابزار، نگه داشتن مشاهده‌های اخیر، تشخیص تکرار و گیر کردن، و دخالت وقتی ایجنت گیر کرده بود. بعضی از چیزهایی که «ضعف مدل» به نظر می‌رسند، در واقع خرابی کانتکست یا حلقه‌ی کنترل‌اند.

فراتر از ReAct#

الگو حلقه کی تکرار می‌شود
ReAct هر قدم: فکر، عمل، مشاهده
Reflexion بعد از شکست: تلاش، ارزیابی، نقد، تلاش دوباره
Plan-and-execute هنگام انحراف: برنامه، اجرای قدم‌ها، برنامه‌ریزی دوباره
جستجوی درختی چند شاخه موازی بررسی می‌شوند و بهترین می‌ماند

پرسش‌هایی برای فکر کردن#

  • در حلقه‌ی کد بالا، خروجی ابزار به ۱۰ هزار کاراکتر بریده شد. چه نوع خطایی ممکن است از این برش پنهان بماند؟ چه طراحی بهتری پیشنهاد می‌کنید؟
  • اگر ایجنت شما ۶۰ ابزار MCP دارد، کدام راهبرد مدیریت کانتکست بیشترین اثر را دارد؟

منابع#