تصور کنید تنها با ۲۰ خط کد بتوانید یک مدل چت ساده را به دستیاری تبدیل کنید که واقعاً کارهای شما را انجام میدهد. طبق آموزش فنی منتشر شده توسط buttercup.sh در ۱۵ سپتامبر ۲۰۲۶، جادوی عاملها (Agents) در واقع یک حلقه چهارمرحلهای تخت است که در آن مدل هرگز کد را اجرا نمیکند، بلکه فقط از برنامه شما میخواهد این کار را انجام دهد.
بسیاری از توسعهدهندگان با این تصور وارد میشوند که به یک ماژول برنامهریزی پیچیده یا سیستم حافظه پیشرفته نیاز دارند. در واقعیت، یک عامل صرفاً مدلی است که فهرستی از توابع مجاز برای درخواست را دریافت کرده است. وقتی مدل به ابزاری نیاز دارد، نوشتن متن عادی را متوقف کرده و یک بلوک ساختاریافته JSON حاوی نام ابزار و آرگومانها صادر میکند. سپس برنامه شما آن تابع معمولی را اجرا کرده و نتیجه را به گفتگو بازمیگرداند. درک این ساختار فنی، پیشنیاز انتخاب درست مدل است؛ چرا که بسته به سطح خودمختاری مورد نیاز، دستهبندیهای متفاوتی از عاملهای هوش مصنوعی وجود دارد که هر کدام موازنه متفاوتی میان ریسک و کارایی ایجاد میکنند.
حلقه چهارمرحلهای عامل
فرآیند عاملمحور یک مسیر دایرهای سختگیرانه را دنبال میکند. در این مسیر، مرحله سوم تنها جایی است که اتفاق واقعی رخ میدهد و توسط کد شما، نه مدل، اجرا میشود:
۱. درخواست: شما تاریخچه گفتگو (وضعیت) و فهرستی از ابزارهای موجود را به مدل میفرستید.
۲. پاسخ مدل: مدل یا متن ساده میفرستد یا یک بلوک tool_use. در صورت ارسال بلوک ابزار، مدل نوشتن متن را متوقف میکند.
۳. اجرا: کد شما تابع واقعی — مانند یک درخواست شبکه (fetch)، عملیات دیسک یا پرسوجوی پایگاهداده — را اجرا میکند.
۴. نتیجه: برنامه شما یک tool_result را به مدل بازمیگرداند. این نتیجه باید همان tool_use_id درخواست را داشته باشد تا قرارداد پروتکل برقرار بماند.
این حلقه تا زمانی ادامه مییابد که مدل پاسخی نهایی در قالب متن ساده ارائه دهد. دقیقاً همین شرط — یعنی پاسخ دادن با متن ساده بهجای فراخوانی ابزار — به معنای «پایان کار عامل» است. همانطور که در تحلیلهای پیشین ما درباره امنیت مدلهای بازمتن اشاره کردیم، مدیریت وضعیت در این سیستمها حیاتی است. در اینجا، آرایه گفتگو تنها حافظه موجود است و هیچ ماژول برنامهریزی یا حافظه خارجی وجود ندارد. اگر مدلی ابزار نداشته باشد، یک نویسنده است؛ اما با این حلقه، به یک عامل تبدیل میشود.
تعاریف ابزار و طرحواره JSON
برای اجرای این سازوکار، شما ابزار را یک بار با استفاده از JSON Schema توصیف میکنید. این توصیف شامل نام، یک جمله توضیحی و شکل آرگومانهاست. مدل این توصیف را بهجای مستندات فنی، بهعنوان بخشی از پرامپت (Prompt) — یعنی همان دستورالعمل ورودی که مدل را هدایت میکند — میخواند.
بهعنوان مثال، یک ابزار آبوهوا اینگونه تعریف میشود:
- نام:
get_weather - توضیحات: «آبوهوای فعلی یک شهر. برای هر سوالی درباره دما، باران یا شرایط فعلی از این ابزار استفاده کن.»
- طرحواره ورودی: یک شیء که به رشتهای برای
cityنیاز دارد (مثلاً "Paris").
وقتی مدل تصمیم میگیرد از این ابزار استفاده کند، متنی مطابق با این طرحواره تولید میکند، مثلاً {"city": "Paris"}. چون توصیفات در واقع پرامپت هستند، اگر مدل در استفاده از ابزاری خطا کند، معمولاً دلیلش توصیفی ضعیف است. توسعهدهندگان اغلب زمان بیشتری را صرف ویرایش آن یک جمله توضیحی میکنند تا ویرایش کد حلقه.
جزئیات پیادهسازی
به نقل از مستندات buttercup.sh، پیادهسازی در جاوااسکریپت یا پایتون از سه بخش تشکیل شده است:
- تابع: یک تابع معمولی. در جاوااسکریپت، این میتواند تابعی مثل
getWeatherباشد که مقداری را از یک شیء محلی برمیگرداند (مثلاً پاریس: «۱۸ درجه، باران سبک»، توکیو: «۲۷ درجه، آسمان صاف»). در پایتون، این یک تابع استانداردdef get_weather(city)است. هیچ چیز در مورد خود تابع خاص نیست. - توصیف: پرامپتی که مدل میخواند و شامل
name(نام)،description(توضیحات) وinput_schema(تعریف انواع دادهها و ویژگیهای ضروری) است. در پایتون، نام ویژگیهای طرحواره به نام پارامترهای تابع تبدیل میشود و اجازه میدهد آرگومانها مستقیماً از طریقget_weather(**block.input)باز شده و فراخوانی شوند. - حلقه: یک حلقه
whileکه مدل (مثلاً Claude Opus 5 باmax_tokens: 4096) را فراخوانی کرده، پاسخ را بدون تغییر به آرایه پیامها اضافه میکند و دلیل توقف (stop_reason) را بررسی میکند. اگر دلیل توقفtool_useباشد، کد تابع را اجرا کرده و نتیجه را الحاق میکند.
یک نکته عجیب در این پروتکل این است که نتایج ابزارها با نقش user بازگردانده میشوند. اگرچه این نتایج از سوی انسان نیستند، اما پروتکل پاسخ به درخواست مدل را در این جایگاه قرار میدهد. این الگویی است که حتی پس از صدمین بار مشاهده توسط توسعهدهنده، همچنان عجیب به نظر میرسد.
هزینه نتایج «چاق» ابزارها
از آنجا که مدلهای زبانی بزرگ (LLM) — مثل کتابخانهداری که میلیاردها صفحه را خوانده و حالا با همان لحن کتابها جواب میدهد — بدون وضعیت (Stateless) هستند، هر نوبت در حلقه نیاز به ارسال مجدد کل تاریخچه گفتگو دارد. مدل هیچ حافظهای بین درخواستها ندارد؛ آرایه پیامها همان حافظه است. این موضوع باعث اثر تجمعی در صورتحساب میشود، زیرا توکنهای ورودی با هر نوبت رشد میکنند.
یک گفتگوی پنجمرحلهای که در نهایت به ۴.۲ هزار توکن میرسد، در واقع ۱۰.۳ هزار توکن ورودی هزینه میکند (۰.۳ هزار + ۰.۹ هزار + ۲.۰ هزار + ۲.۹ هزار + ۴.۲ هزار)، زیرا پیشوندهها تکرار میشوند. شما در واقع ۲.۵ برابر اندازه گفتگوی نهایی را پرداخت میکنید.
خطر اصلی زمانی است که یک ابزار نتیجه «چاق» برگرداند، مثلاً یک فایل منبع کامل با ۸ هزار توکن. اگر این نتیجه در نوبت دوم از یک اجرای دهمرحلهای ظاهر شود، در هشت درخواست بعدی مجدداً ارسال میشود.
- محاسبه: ۸ درخواست × ۸ هزار توکن = ۶۴ هزار توکن ورودی اضافی.
- هزینه: با قیمت Claude Opus 5 (۵ دلار به ازای هر میلیون توکن ورودی)، یک فایل اصلاحنشده ۰.۳۲ دلار به هر اجرا اضافه میکند.
- مقیاس: برای توسعهدهندهای که روزی ۱۰۰۰ بار این عامل را اجرا میکند، یک ابزار
read_fileناکارآمد، روزانه ۳۲۰ دلار هزینه دارد.
برای کاهش این هزینهها، buttercup.sh دو راهکار اصلی پیشنهاد کرده است:
۱. بازگرداندن دادههای کمتر: بهجای تخلیه کامل دادهها، بازههای خطی، تعداد یا بیست ردیف اول مطابق با جستجو را برگردانید. نتیجه ابزار را برش دهید، نه پرامپت را؛ زیرا پرامپت یک بار ارسال میشود اما نتیجه در هر نوبت بعدی ارسال میگردد.
۲. کش کردن پیشونده: از cache_control استفاده کنید تا هزینه تکرار پیشوندهها به حدود ۰.۱ برابر نرخ ورودی کاهش یابد (در مقابل یک هزینه اضافی ۱.۲۵ برابری در اولین بار نوشتن). تنها با دو درخواست روی یک پیشونده، توسعهدهنده به سود میرسد.
چهار قانون برای پایداری در محیط عملیاتی
برای جلوگیری از شکست عاملها و غافلگیری در صورتحساب، چهار قانون الزامی تعریف شده است:
- پاسخ به هر فراخوانی: اگر تابعی خطا داد (مثلاً
ENOENT: notes/paris.md)، نتیجه را رها نکنید. آن را بهعنوانtool_resultباis_error: trueو پیام خطا در محتوا بازگردانید. نبودِ نتیجه، نقض پروتکل است و باعث میشود درخواست بعدی با خطای شدید شکست بخورد؛ اما یک خطای بازگردانده شده، صرفاً اطلاعاتی است که مدل میتواند برای اصلاح مسیر، امتحان ابزاری دیگر یا اطلاع دادن به کاربر استفاده کند. - دستهبندی نتایج: مدل ممکن است چندین ابزار را همزمان درخواست کند (مثلاً
read_fileوlist_dirوgrep). همه را اجرا کرده و تمام بلوکهایtool_resultرا در یک پیام کاربر بازگردانید. تقسیم آنها در چندین پیام، مدل را بهطور نامحسوس آموزش میدهد که دیگر درخواستهای موازی نفرستد و عامل را بدون دلیل کند میکند. - الحاق کامل پاسخها: بلوک
reply.contentرا بدون تغییر در تاریخچه قرار دهید. آن را از روی متن بازسازی نکنید. مدلهای فعلی بلوکهایی بهجز متن حمل میکنند و حذف آنها کیفیت نوبت بعدی را کاهش میدهد بدون اینکه خطایی صادر شود. - سقفگذاری حلقه: هرگز از
while (true)در محیط عملیاتی استفاده نکنید. یک حد سخت (مثلاً ۲۰ نوبت) تعیین کنید. مدلی که توصیف ابزار را اشتباه خوانده باشد، با خوشحالی ۴۰ بار آن را فراخوانی میکند؛ یک شمارنده تفاوت بین یک باگ ساده و یک صورتحساب عظیم است. رسیدن به سقف معمولاً نشانه مشکل در توصیف ابزار است.
SDKها در برابر حلقههای دستی
اگرچه نوشتن دستی حلقه برای یادگیری ضروری است، اما buttercup.sh اشاره میکند که SDKهای مدرن اکنون این کار را خودکار کردهاند. SDK پایتون Anthropic با client.beta.messages.tool_runner() و دکوراتور @beta_tool این کار را انجام میدهد و SDK تایپاسکریپت از طریق betaZodTool در client.beta.messages.toolRunner() این قابلیت را فراهم کرده است. این اجراکنندهها حلقه چهارمرحلهای و قوانین پایداری را بهطور خودکار مدیریت کرده و کد را از ۲۰ خط به ۱۰ خط میرسانند.
با این حال، استفاده از یک Runner به معنای از دست دادن کنترل مستقیم روی جریان است. اگر به یک دروازه تایید (Approval Gate) قبل از عملیات نوشتن، یک خط لاگ برای هر فراخوانی، یا مکانیزم تلاش مجدد (Retry) سفارشی با آرگومانهای بازنویسی شده نیاز دارید، باید از قلابهای (Hooks) هر نوبت بهجای یک خط کد ساده در حلقه while استفاده کنید.
این سازوکار در کل صنعت یکسان است. چه از Vercel AI SDK از طریق generateText({ tools }) استفاده کنید، چه مدلهای محلی از طریق Ollama و llama.cpp (که از ابزارهای سبک OpenAI استفاده میکنند)، یا پروتکل زمینه مدل (MCP) — که به سرورهای خارجی اجازه میدهد آرایه ابزارهای شما را پر کنند — «حلقه» چهار ایستگاهی ثابت میماند. اگر بتوانید این چهار ایستگاه را در هر چارچوبی شناسایی کنید، میتوانید آن را عیبیابی کنید.
کاربرد عملی و تست
برای تایید درست کار کردن حلقه، توسعهدهندگان باید ترنسکریپت را برای شکل خاص تعامل بررسی کنند. برای درخواستی مثل «یک فایل در مسیر notes/paris.md با سه خط درباره آبوهوا بساز و سپس آن را بخوان»، ترنسکریپت باید اینگونه باشد:
۱. دستیار: tool_use برای write_file (مثلاً {"path": "notes/paris.md", ...}).
۲. کاربر: tool_result تایید نوشتن (مثلاً "۳ خط، ۸۷ بایت نوشته شد").
۳. دستیار: tool_use برای read_file (مثلاً {"path": "notes/paris.md"}).
۴. کاربر: tool_result حاوی خطوط عیناً نوشته شده.
۵. دستیار: پاسخ نهایی متنی (مثلاً "نوشته شد و بازخوانی شد. محتوای آن این است...").
اگر فراخوانی read_file قبل از رسیدن نتیجه write_file ظاهر شود، حلقه در ایستگاه سوم منتظر نمانده است که یک باگ بحرانی است. تستها همچنین باید شامل «شکستن» عامل باشد: خاموش کردن یک ابزار در تنظیمات برای دیدن اینکه مدل چگونه بداهه عمل میکند، یا غلط نوشتن tool_use_id برای مشاهده خطای پروتکل حاصل. در یک محیط امن مانند سیستم فایل مجازی در localStorage، این تستها تنها کسری از یک سنت هزینه توکن دارند.
این تغییر دیدگاه — از دیدن عاملها بهعنوان «موجودات هوشمند» به دیدن آنها بهعنوان «حلقههای درخواست JSON» — نحوه عیبیابی توسعهدهندگان را تغییر میدهد. شکست عاملها بهندرت ناشی از نقص در «استدلال» است و تقریباً همیشه نتیجه توصیف ضعیف پرامپت ابزار یا عدم تطابق پروتکل در حلقه است.
گام بعدی شما
- توصیفات ابزارهای خود را بازبینی کنید و جملات را برای دقت بیشتر مدل بازنویسی کنید.
- برای هر ابزاری که خروجی حجیم دارد، مکانیزم برش (Trimming) یا بازگرداندن بازههای مشخص را پیاده کنید.
- در محیط توسعه، سقف تعداد نوبتهای حلقه را روی ۵ یا ۱۰ قرار دهید تا از هزینههای پیشبینینشده جلوگیری کنید.
اما داستان سختافزاری این تحول حتی شگفتانگیزتر است — به تحلیل ما درباره تراشههای Blackwell مراجعه کنید.




گفتگو