پیاده‌سازی

ابزارها و function calling — چطور ایجنت به دنیای بیرون وصل می‌شود

مدل ابزار را اجرا نمی‌کند، فقط انتخابش می‌کند. راهنمای طراحی tool schema، فراخوانی موازی، بازگرداندن خطا و اینکه چرا طراحی ابزار از prompt مهم‌تر است.

· ۹ دقیقه مطالعه

مدل چیزی را اجرا نمی‌کند — فقط درخواست می‌دهد

رایج‌ترین سوءتفاهم درباره‌ی function calling این است که فکر کنیم مدل کد را اجرا می‌کند. اجرا نمی‌کند. مدل فقط یک خروجی ساخت‌یافته تولید می‌کند به این معنا: «تابع get_order را با ورودی {"order_id": "A-482"} صدا بزن». اجرای واقعی در کد شماست. نتیجه را هم شما به‌عنوان یک پیام جدید به مدل برمی‌گردانید.

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

آناتومی یک tool schema

هر ابزار با یک توصیف ساخت‌یافته به مدل معرفی می‌شود: نام، توضیح، و schema ورودی‌ها (معمولاً JSON Schema).

{
  "name": "search_orders",
  "description": "جستجوی سفارش‌های یک مشتری در یک بازه‌ی زمانی. فقط سفارش‌های ثبت‌شده را برمی‌گرداند؛ سبد خرید باز را شامل نمی‌شود.",
  "input_schema": {
    "type": "object",
    "properties": {
      "customer_id": {
        "type": "string",
        "description": "شناسه‌ی داخلی مشتری، مثل c-1042"
      },
      "status": {
        "type": "string",
        "enum": ["paid", "unpaid", "cancelled"]
      },
      "since": {
        "type": "string",
        "description": "تاریخ شروع به فرمت YYYY-MM-DD"
      }
    },
    "required": ["customer_id"]
  }
}

این تعریف در هر درخواست به مدل فرستاده می‌شود و دقیقاً مثل بخشی از prompt عمل می‌کند — با یک تفاوت مهم: مدل‌ها روی این ساختار آموزش دیده‌اند و به آن وزن بیشتری می‌دهند تا به یک جمله در متن دستور.

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

  • توضیح، جای مستندسازی است نه برچسب. «جستجوی سفارش» بد است. جمله‌ای که بگوید چه چیزی را شامل نمی‌شود و خروجی‌اش چه شکلی است، خوب است.
  • enum بهتر از توضیح متنی است. هر محدودیتی که بتوانید در schema بیان کنید، یک دسته خطای ورودی را حذف می‌کند.
  • required را کم بگیرید و پیش‌فرض‌ها را شفاف کنید. ورودی اجباری‌ای که مدل نمی‌تواند بداند، آن را به حدس زدن وادار می‌کند.

مدل چطور انتخاب می‌کند

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

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

اکثر APIها اجازه می‌دهند این رفتار را کنترل کنید: حالت خودکار (مدل تصمیم می‌گیرد)، اجباری (حتماً ابزاری صدا بزند)، یا اجبار به یک ابزار خاص. برای گام اول یک workflow نیمه‌ثابت، اجبار به ابزار مشخص معمولاً از امید بستن به انتخاب درست مدل بهتر است.

فراخوانی موازی

مدل‌های امروزی می‌توانند در یک پاسخ چند فراخوانی مستقل تولید کنند. اگر کاربر بپرسد «وضعیت سفارش‌های A-482 و A-491 چیست؟»، مدل می‌تواند هر دو را هم‌زمان درخواست کند. شما هر دو را موازی اجرا می‌کنید و هر دو نتیجه را با شناسه‌ی متناظرشان برمی‌گردانید.

چند نکته‌ی اجرایی:

  • هر نتیجه باید با tool_call_id مربوط به خودش برگردد، وگرنه مدل نمی‌داند کدام جواب به کدام درخواست است.
  • اگر یکی از فراخوانی‌ها خطا داد، باز هم باید برای همه‌ی فراخوانی‌ها نتیجه بفرستید — برای آن یکی، متن خطا. رها کردن یک فراخوانی بدون پاسخ معمولاً به خطای ساختاری در درخواست بعدی منجر می‌شود.
  • موازی‌سازی فقط برای عمل‌های مستقل امن است. اگر ابزارها روی یک منبع مشترک می‌نویسند، ترتیب اهمیت دارد و باید در کد خودتان آن را مهار کنید.

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

خطاها را به مدل برگردانید

وسوسه‌ی طبیعی این است که وقتی ابزار خطا می‌دهد، در کد خودتان try/except بگذارید و رشته‌ی خالی برگردانید. این کار مدل را کور می‌کند. مدل نمی‌فهمد چه شد و معمولاً یا همان اشتباه را تکرار می‌کند یا نتیجه‌ی خالی را به‌عنوان «چیزی پیدا نشد» تفسیر می‌کند — که یک خطای نادرست به کاربر تحویل می‌دهد.

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

پاسخ ضعیفپاسخ خوب
Errorتاریخ نامعتبر است. فرمت درست YYYY-MM-DD است.
[]هیچ سفارشی برای c-1042 در این بازه یافت نشد. بازه یا شناسه را بررسی کنید.
500سرویس موقتاً در دسترس نیست. تلاش دوباره پس از چند ثانیه ممکن است موفق شود.

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

چرا طراحی ابزار مهم‌تر از جمله‌بندی prompt است

وقتی کیفیت ایجنت پایین است، اولین واکنش معمولاً بازنویسی system prompt است. در بیشتر موارد سود واقعی جای دیگری است:

  • ابزار درست‌دانه‌بندی‌شده بسازید. ده ابزار ریز که مدل باید پشت‌سر هم زنجیر کند، هم گام‌های بیشتری می‌خواهد هم فرصت خطای بیشتری می‌سازد. یک ابزار که کار کامل و معناداری انجام می‌دهد بهتر است — به شرط اینکه به یک ابزار همه‌کاره با پانزده پارامتر تبدیل نشود.
  • خروجی را برای مصرف مدل طراحی کنید، نه برای مصرف UI. برگرداندن ۵۰۰ رکورد کامل JSON، context را پر می‌کند و در هر گام بعدی دوباره هزینه دارد. فیلدهای لازم و یک شمارنده‌ی کل، معمولاً کافی است.
  • مرزهای مجاز را در ابزار بگذارید، نه در prompt. جمله‌ی «فقط داده‌ی همین کاربر را بخوان» در system prompt یک درخواست است؛ فیلتر کردن بر اساس شناسه‌ی کاربر در خود پیاده‌سازی ابزار، یک تضمین. برای عمل‌های پرخطر، جداسازی اجرا لایه‌ی بعدی است.
  • تعداد ابزارها را کنترل کنید. هرچه فهرست ابزارها بلندتر شود، انتخاب سخت‌تر و ورودی هر درخواست سنگین‌تر می‌شود.

اگر ابزارهایتان را استاندارد و قابل استفاده‌ی مجدد می‌خواهید، MCP دقیقاً همین لایه را قالب‌بندی می‌کند.

چند قاعده‌ی عملی

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

جمع‌بندی

function calling پلی است بین تصمیم مدل و اجرای واقعی: مدل انتخاب می‌کند، کد شما اجرا می‌کند، نتیجه برمی‌گردد. کیفیت این پل تقریباً به‌طور کامل به کیفیت تعریف ابزارها بستگی دارد — نام، توضیح، schema ورودی و شکل خروجی. یک ساعت وقت گذاشتن روی توصیف ابزارها معمولاً بیشتر از یک روز بازنویسی prompt نتیجه می‌دهد.