یک فراخوانی اشتباه از ابزار میتواند حلقهای بینهایت ایجاد کند که تمام بودجه API شما را ببلعد یا دو بار وجه مشتری را بازگرداند. برای توسعهدهندگانی که با Node.js کار میکنند، تفاوت میان یک نمونهٔ نمایشی و یک سامانه آماده برای محیط عملیاتی، در پیادهسازی پنج حفاظ (Guardrails) مشخص است.
بسیاری از توسعهدهندگان با یک حلقه ساده while(true) شروع میکنند که تحت فشار زیاد بهسرعت میشکند. الگوی رایج شامل حلقهای است که مدل را فراخوانی میکند، وجود فراخوانی تابع (Function Calling) را بررسی میکند، آنها را از طریق یک نقشه ابزار (Tool Map) اجرا کرده و نتایج را به تاریخچه پیامها برمیگرداند. همانطور که در پوششهای قبلی ما دربارهی استفاده Meta از مدلهای زبانی بزرگ برای شناسایی محتوای سوءاستفاده از کودکان دیدیم، دقت مدلها حیاتی است، اما در جریانهای کاری عاملمحور (Agentic)، پایداری سامانه مستقیماً با هزینههای مالی و فوری گره خورده است. در این راستا، مدیریت بهینه هزینهها در مقیاس عملیاتی اهمیت ویژهای دارد، مشابه آنچه در راهکار Oxlo.ai برای مدیریت بودجهٔ عملیاتی بررسی کردیم.
در محیط عملیاتی، این حلقههای ساده به چندین شکل شکست میخورند: مدل ممکن است یک ابزار جستوجو را مدام فراخوانی کند چون نتایج هرگز منطق داخلیاش را ارضا نمیکند؛ یا یک API کند در لایههای بالادستی باعث معلق ماندن درخواست کاربر و کل سامانه میشود؛ یا یک ابزار خطایی پرتاب میکند که از حلقه خارج شده و کل اجرا را بدون ثبت لاگهای مفید متوقف میکند. علاوه بر این، یک نوسان ساده در شبکه میتواند باعث اجرای مجدد (Retry) شود که در نهایت منجر به اجرای دو باره ابزار «صدور فاکتور» میگردد.
به نقل از راهنمای فنی منتشر شده در ۷ اکتبر ۲۰۲۶ توسط Geminate Solutions، نخستین خط دفاعی، تعیین یک سقف سختگیرانه برای گامها است. توسعهدهندگان باید بهجای شمارش تکتک فراخوانیهای ابزار، تعداد نوبتهای مدل (Model Turns) را بشمارند. سقف پیشنهادی، طولانیترین وظیفه مشروع بهعلاوه دو یا سه گام اضافی است؛ برای اکثر اجراهایی که در ۳ گام تمام میشوند، سقف ۸ گام بهطور موثری حلقههای runaway را میگیرد بدون آنکه کار واقعی را متوقف کند. این چالشهای پایداری در اجرای همزمان ابزارها، نقطه تمرکز رقابتهای فعلی است، همانطور که در بررسی شکاف ایمنی در عاملهای هوشمند گوگل، OpenAI و Anthropic به آن پرداختیم.
مدیریت زمان و تکرار
علاوه بر سقف گامها، این راهنما دو محدودیت دیگر برای جلوگیری از درخواستهای «زامبی» پیشنهاد میدهد:
- ضربالاجل زمانی (Wall-clock deadlines): یک اجرا ممکن است زیر سقف گامها باشد اما زمان بسیار زیادی بگیرد. توسعهدهندگان باید در ابتدای هر گام، مقدار
Date.now() - ctx.startedAtرا بررسی کنند تا تضمین شود کل زمان اجرا در محدوده مجاز باقی میماند. - تشخیص تکرار: اگر مدل یک ابزار را با آرگومانهای یکسان سه بار فراخوانی کرد، سامانه باید متوقف شده و وضعیت فعلی را برگرداند. این کار با هش کردن ترکیب
call.name + JSON.stringify(call.args)و شمارش دفعات تکرار آن هشها انجام میشود.
وقتی این محدودیتها فعال میشوند، سامانه باید وضعیتی مثل step_limit را برگرداند، نه اینکه خطایی (Error) پرتاب کند. این کار به فراخواننده اجازه میدهد تصمیم بگیرد کار را به انسان بسپارد، یک پیام جایگزین (Fallback) نشان دهد یا وظیفه را برای تلاش مجدد در صف قرار دهد.
مهلتهای زمانی مجزای ابزارها
مهلتهای زمانی کلی (Global Timeouts) اغلب برای APIهای کند خیلی کوتاه و برای جستوجوهای سریع پایگاهداده خیلی طولانی هستند. ابزارها رفتار یکسانی ندارند؛ یک جستوجو در پایگاهداده محلی باید در کمتر از یک ثانیه تمام شود، در حالی که یک API گزارشدهی شخص ثالث ممکن است بهطور منطقی ۱۰ ثانیه زمان ببرد. راهکار، تعریف مهلت زمانی برای هر ابزار است.
برای مثال، یک شیء پیکربندی میتواند این محدودیتها را تعریف کند:
search_orders: مهلت ۳۰۰۰ میلیثانیه با ۲ تلاش مجدد.get_shipping_status: مهلت ۸۰۰۰ میلیثانیه با ۲ تلاش مجدد.create_refund: مهلت ۱۰,۰۰۰ میلیثانیه با ۱ تلاش مجدد.
در Node.js ۱۸ و نسخههای جدیدتر، متد AbortSignal.timeout(ms) سیگنالی فراهم میکند که مستقیماً به fetch یا درایورهای پایگاهداده پاس داده میشود. این کار تضمین میکند درخواست در پسزمینه واقعاً لغو شود، نه اینکه فقط توسط حلقه نادیده گرفته شود در حالی که همچنان سوکتها را اشغال کرده است. پیچاندن یک فراخوانی در Promise.race با یک تایمر ناکافی است زیرا درخواست در پسزمینه به اجرا ادامه میدهد. سیگنال باید تا لایه I/O منتقل شود. علاوه بر این، خودِ فراخوانی مدل نیز به مهلت زمانی نیاز دارد، زیرا پاسخهای کند مدل عامل اصلی معلق ماندن نقاط انتهایی (Endpoints) عاملها است.
تلاشهای مجدد هوشمند و عقبنشینی
همه خطاها یکسان نیستند. تلاش مجدد برای خطای ۴۰۰ (Bad Request) بیفایده است، اما برای خطای ۴۲۹ (Too Many Requests) یا ۵۰۳ (Service Unavailable) اغلب موفقیتآمیز است. بر اساس مستندات Geminate، مکانیسم تلاش مجدد باید فقط روی خطاهای «قابل تکرار» متمرکز شود—بهویژه TimeoutError و کدهای وضعیت ۴۲۹ و ۵۰۰ به بالا.
برای اجرای ایمن، توسعهدهندگان باید از عقبنشینی با لرزش (Jittered Backoff) استفاده کنند. با افزودن یک تأخیر تصادفی (مثلاً baseMs * 2 ** attempt + Math.random() * baseMs)، از ایجاد ترافیک شدید (Thundering Herd) که میتواند سرویس در حال بازیابی را دوباره ساقط کند، جلوگیری میشود.
دو نکته حیاتی در پیادهسازی وجود دارد:
۱. مهلتهای زمانی تازه برای هر تلاش: سیگنال AbortSignal باید داخل تابع تلاش مجدد ایجاد شود، نه بیرون از آن. سیگنالی که قبلاً فعال شده است، باعث میشود هر تلاش مجدد بعدی بلافاصله با شکست مواجه شود.
۲. مالکیت شفاف: توسعهدهندگان باید مشخص کنند کدام لایه مسئول تلاشهای مجدد است. اگر یک SDK در حال حاضر تلاشهای مجدد را مدیریت میکند و توسعهدهنده لایه دیگری روی آن اضافه کند، سه تلاش پیشبینیشده ممکن است بهطور تصادفی به نه تلاش تبدیل شوند و فشار روی سرویس بالادستی را تشدید کنند.
الزام یکتایی (Idempotency)
گرانترین اشتباه در طراحی عامل، نادیده گرفتن یکتایی برای ابزارهایی است که وضعیت را تغییر میدهند. اگر یک API بازگشت وجه پس از پردازش پرداخت اما قبل از ارسال پاسخ دچار Timeout شود، تلاش مجدد باعث میشود وجه برای دومین بار بازگردانده شود. این یک شکست بحرانی برای هر ابزاری است که عملیات نوشتن، ارسال، شارژ یا حذف انجام میدهد.
برای حل این مشکل، هر ابزار تغییردهنده وضعیت باید از یک کلید یکتایی (Idempotency Key) استفاده کند. این کلید باید از ترکیب ID اجرا و ID فراخوانی ابزار ساخته شود (مثلاً ctx.runId + ':' + call.id). منطق برنامه باید قبل از ادامه کار، وجود این کلید را در پایگاهداده بررسی کند؛ اگر رکوردی با آن کلید وجود داشت، سامانه بهجای پردازش مجدد، نتیجه موجود را برگرداند.
اگر API مقصد بهطور بومی از کلیدهای یکتایی پشتیبانی میکند — مانند بسیاری از ارائهدهندگان پرداخت — کلید باید به همان مسیر پاس داده شود. این کار تضمین میکند که حتی اگر نوشتن در پایگاهداده محلی پس از موفقیت API خارجی شکست بخورد، حفاظت همچنان برقرار باشد.
مشاهدهپذیری و مدیریت خطا
در نهایت، خطاهای ابزار باید بهعنوان «داده» به مدل برگردانده شوند، نه بهعنوان «استثنا» (Exception). با برگرداندن پیامی مثل «ابزار شکست خورد. روش دیگری را امتحان کن یا به کاربر اطلاع بده»، هوش مصنوعی زاینده (Generative AI) — شبیه به نویسندهای که وقتی قلمش خشک میشود، سراغ مداد میرود — میتواند شکست را ببیند و استراتژی متفاوتی را امتحان کند.
برای توسعهدهنده، هر گام باید دقیقاً یک خط لاگ ساختاریافته با ابزاری مثل pino تولید کند. یک Wrapper مناسب باید رویداد tool_start (شامل runId، step، tool و attempt) و رویداد tool_end (شامل مدت زمان ms و وضعیت ok) را ثبت کند.
برای حفظ امنیت و انطباق، دادههای خاصی نباید در لاگها قرار گیرند:
- پیامهای خام کاربر
- نتایج کامل ابزارها
- اعتبارنامهها یا اسرار (Secrets)
بهجای آن، توسعهدهندگان باید هش آرگومانها یا چند فیلد ایمن و پیشتعریفشده را ثبت کنند. این رویکرد ساختاریافته به تیمها اجازه میدهد به سوالات حیاتی محیط عملیاتی پاسخ دهند: چرا یک اجرا ۴۰ ثانیه طول کشید؟ کدام ابزار دچار Timeout میشود؟ هر چند وقت یکبار اجراها به سقف گامها میرسند؟
پیادهسازی این حفاظها فرآیند توسعه را تغییر میدهد. بهجای صرف هفتهها وقت برای تنظیم پرامپت جهت جلوگیری از حلقهها، توسعهدهندگان ابتدا زیرساخت را ایمن میکنند. این کار محیطی امن ایجاد میکند که در آن میتوان بدون ریسکِ تبدیل شدن یک نوبت بدِ مدل به یک روز بد برای تیم مهندسی، روی پرامپتها و ابزارها آزمایش کرد.
برای اطمینان از اینکه سامانه واقعاً آماده محیط عملیاتی است، توسعهدهندگان باید یک چکلیست پیش از عرضه را اجرا کنند:
- سقف گام و ضربالاجل زمانی برای کل اجرا
- تشخیص تکرار برای فراخوانیهای یکسان ابزار
- مهلت زمانی برای هر ابزار، با انتقال سیگنال به لایه I/O
- مهلت زمانی برای خودِ فراخوانی مدل
- تلاش مجدد فقط برای Timeout، خطای ۴۲۹ و ۵xx با عقبنشینی لرزشی
- یک مالک شفاف برای تلاشهای مجدد (یا SDK یا کد شما، نه هر دو)
- کلید یکتایی برای هر ابزاری که مینویسد، ارسال میکند، شارژ میکند یا حذف میکند
- بازگرداندن خطاهای ابزار به مدل بهعنوان داده، نه پرتاب استثنا
- یک خط لاگ ساختاریافته در هر گام (شامل ID اجرا، گام، ابزار، تلاش، مدت زمان و نتیجه)
- یک وضعیت نهایی شفاف: done، step_limit، deadline یا error
گام بعدی شما
- بررسی کنید آیا تمام ابزارهای تغییردهنده وضعیت (Write/Charge/Delete) در سامانه شما دارای کلید یکتایی هستند یا خیر.
- متد
AbortSignal.timeoutرا جایگزینPromise.raceبرای مدیریت مهلتهای زمانی I/O کنید. - یک سقف گام (Step Cap) برای تمام اجراهای عاملهای خود تعریف کنید تا از هزینههای پیشبینینشده API جلوگیری شود.
اما داستان سختافزاری این تحول حتی شگفتانگیزتر است — به تحلیل ما دربارهی تراشههای Blackwell مراجعه کنید.




گفتگو