Tool Calling و خروجیهای ساختاریافته: قراردادی که پابرجا میماند
۲۴ فراخوانی، بدون JSON خراب، و فقط دو تاریخ قابلاستفاده؛ سپس همان endpoint با توصیفی بهتر، و آنچه schema نمیتواند حل کند.
در این صفحه
به یک مدل یک ابزار جستوجوی پرواز بدهید و از آن بخواهید پروازی از مادرید به برلین پیدا کند. این چیزی است که برمیگردد:
<tool_call>
{"name": "search_flights",
"arguments": {"from": "Madrid", "to": "Berlin", "date": "3rd October 2026"}}
</tool_call>JSON معتبر است. نام ابزار درست است. همه فیلدهای الزامی وجود دارند. و این فراخوانی بیفایده است: هیچ API پروازی "Madrid" را جایی که کد فرودگاه میخواهد، یا "3rd October 2026" را جایی که تاریخ میخواهد، نمیپذیرد.
همین شکاف — از نظر نحو بینقص، از نظر معنا غیرقابلاستفاده — موضوع این فصل است، و اولین چیزی که باید روشن شود این است که این مشکل JSON نیست. در بیستوچهار درخواست با این ابزار، مدل ۲۴ tool call معتبر و صفر JSON خراب تولید کرد. حتی یک بار هم در همان بخشی که همه debug میکنند شکست نخورد.
مدل هیچچیزی را اجرا نمیکند
لینک به بخش: مدل هیچچیزی را اجرا نمیکندقبل از سازوکارها، جملهای که بیشترین سوءتفاهم را رفع میکند: tool call یک درخواست است، نه یک عمل.
مدل یک پیام ساختاریافته منتشر میکند که میگوید میخواهم search_flights با این آرگومانها فراخوانی شود. بعد متوقف میشود. کد شما آن پیام را دریافت میکند، تصمیم میگیرد آن را بپذیرد یا نه، هر چیزی را که باید فراخوانی کند فراخوانی میکند، و نتیجه را بهصورت یک پیام دیگر برمیگرداند. مدل هرگز به پایگاهداده شما دست نزده، هرگز درخواست HTTP نفرستاده، و هرگز credential نداشته است.
همهچیز درباره امنیت agent در فصل ۳۰ از همین تقسیمبندی میآید، و همینطور همهچیز درباره طراحی agent در فصل ۲۳: مدل پیشنهاد میدهد و کد شما تعیینتکلیف میکند، و کد همان جایی است که همه تضمینها در آن زندگی میکنند.
پس یک ابزار، اگر واژگان اضافی را کنار بگذاریم، دو چیز است:
یک schema. یک JSON Schema که یک تابع را توصیف میکند: نامش، کاری که انجام میدهد، و آرگومانهایی که میگیرد، همراه با نوعها و محدودیتهایشان. این همان چیزی است که وارد prompt میشود، و تنها چیزی است که مدل هرگز میبیند.
یک endpoint. تابعی در کد شما که آن آرگومانها را میگیرد و چیزی برمیگرداند. مدل هرگز آن را نمیبیند، هرگز نمیداند با چه زبانی نوشته شده، و نمیتواند یک query پایگاهداده را از یک رشته hardcoded تشخیص دهد.
schemaها را همراه درخواست میفرستید
لینک به بخش: schemaها را همراه درخواست میفرستیدتعریفهای ابزار داخل prompt میروند، در قالبی serialise شده که مدل با آن آموزش دیده است. آنها در تکتک فراخوانیها token مصرف میکنند — واقعیتی که کمی بعد در همین فصل با یک عدد برمیگردد.
مدل بهجای متن با یک call پاسخ میدهد
لینک به بخش: مدل بهجای متن با یک call پاسخ میدهدبهجای نثر، پاسخ شامل یک درخواست ساختاریافته است، و API یک finish reason گزارش میکند که همین را میگوید. این reason مهم است: کد شما از همین میفهمد که باید ابزار را اجرا کند، نه اینکه پاسخی را به کاربر نشان دهد.
کد شما آن را اجرا میکند — یا رد میکند
لینک به بخش: کد شما آن را اجرا میکند — یا رد میکنداین همان مرحلهای است که هیچ مدلی در آن وجود ندارد. آرگومانها را در برابر schema اعتبارسنجی کنید، تصمیم بگیرید آیا این caller اجازه انجام این کار را دارد یا نه، و اجرا کنید.
نتیجه را بهصورت یک پیام برمیگردانید
لینک به بخش: نتیجه را بهصورت یک پیام برمیگردانیدنتیجه تبدیل به نوبت دیگری در مکالمه میشود، در نقشی که برای آن رزرو شده است. مدل آن را مثل هر context دیگری میخواند.
مدل پاسخ میدهد، یا ابزار دیگری میخواهد
لینک به بخش: مدل پاسخ میدهد، یا ابزار دیگری میخواهداین همان loop فصل ۲۳ است، و دلیل اینکه یک درخواست واحد میتواند به دوازده رفتوبرگشت تبدیل شود.
هیچکدام از اینها emergent نیست. همانطور که فصل ۱۱ نشان داد، tool calling یک رفتار آموزشدیده است:1 در طول post-training، مدل هزاران مکالمه دیده که دقیقاً به همین شکل ساخته شدهاند. به همین دلیل قالب آن وابسته به مدل است، به همین دلیل reliability بین مدلهای هماندازه اینقدر تفاوت دارد، و به همین دلیل یک مدل میتواند ابزاری را صدا بزند که هرگز ندیده است — شکل آموزش داده شده، ابزار مشخص از prompt شما میآید.
هزینه یک schema بد، با اندازهگیری
لینک به بخش: هزینه یک schema بد، با اندازهگیریاین همان ابزاری است که بیشتر افراد در ابتدا مینویسند. توجه کنید که هیچچیز آن غلط نیست؛ فقط کمجزئیات است:
{
name: "search_flights",
description: "Search for flights.",
parameters: {
type: "object",
properties: {
from: { type: "string", description: "Airport." },
to: { type: "string", description: "Airport." },
date: { type: "string", description: "The date." },
},
required: ["from", "to", "date"],
},
}بیستوچهار درخواست، شش جفت شهر ضربدر چهار روش بیان تاریخ («سوم ماه بعد»، «جمعه آینده»، «۱۵ دسامبر»، «فردا»)، با greedy decoding تا نتایج بازتولید شوند:
| ابزار فراخوانی شد | JSON خراب | تاریخ در ISO | فرودگاهها بهصورت IATA | همهچیز درست | |
|---|---|---|---|---|---|
| schema بالا | 24/24 | 0 | 2/24 | 4/24 | 1/24 |
قبل از سه ستون آخر، دو ستون اول را بخوانید. مدل هر بار ابزار درست را فراخوانی میکند و هر بار JSON خوشفرم تولید میکند. شکست کاملاً در مقادیر است، و مقادیر غیرقابلاستفادهاند: "Madrid" بهجای MAD، "3rd October 2026" بهجای 2026-10-03.
ارزش تأکید دارد، چون تعیین میکند وقتی چیزی میشکند کجا را نگاه کنید. غریزه این است که یک JSON parser با retry اضافه کنید، یا از مدل محکمتر بخواهید JSON معتبر بدهد. هیچکدام به چیزی که اینجا اتفاق افتاده نمیپردازد.
حالا فقط توضیح را تغییر دهید
لینک به بخش: حالا فقط توضیح را تغییر دهیدهمان endpoint. همان کد پشت آن. همان مدل، همان prompts، همان decoding. تنها چیزی که عوض میشود متن داخل schema است:
{
name: "search_flights",
description: "Search scheduled flights between two airports on a given day.",
parameters: {
type: "object",
properties: {
from: {
type: "string",
description: "Departure airport as a three-letter IATA code, e.g. MAD for Madrid. Never a city name.",
pattern: "^[A-Z]{3}$",
},
to: { /* same */ },
date: {
type: "string",
description: "Departure date as an ISO 8601 calendar date, YYYY-MM-DD. Resolve relative dates against today before calling.",
format: "date",
pattern: "^\\d{4}-\\d{2}-\\d{2}$",
},
},
required: ["from", "to", "date"],
},
}| FORMAT تاریخ | VALUE تاریخ | FORMAT فرودگاه | VALUE فرودگاه | |
|---|---|---|---|---|
| schema کمجزئیات | 2/24 | 1/24 | 4/24 | 4/24 |
| schema توصیفشده | 24/24 | 12/24 | 16/24 | 8/24 |
فرمت تاریخ از ۲ مورد از ۲۴ به ۲۴ مورد از ۲۴ میرسد. کامل، فقط با تغییر متن، بدون دستزدن به کد و بدون منطق retry. اگر از این فصل یک عادت عملیاتی بردارید، همین است: وقتی یک ابزار اشتباه فراخوانی میشود، راهحل تقریباً همیشه در توضیح است، و ارزانترین راهحل در کل سیستم هم هست.
حالا ستون دوم را بخوانید، که نیمه مهمتر ماجراست.
schema شکل را محدود میکند. نمیتواند دانش فراهم کند.
لینک به بخش: schema شکل را محدود میکند. نمیتواند دانش فراهم کند.تاریخ ۲۴ بار از ۲۴ بار در فرمت ISO است. در ۱۲ بار از ۲۴ بار روز درست است.
پس حالا نیمی از فراخوانیها تاریخی کاملاً خوشفرمت دارند که تاریخ غلطی است. توضیح به مدل گفت چه شکلی تولید کند، و مدل آن را بینقص تولید کرد — اما تبدیل «جمعه آینده» به 2026-09-11 نیازمند دانستن تاریخ امروز و انجام حساب تقویمی است، و هیچ مقدار توضیحی آن دانش را فراهم نمیکند. برای فرودگاهها هم همین است: فرمت از ۴ به ۱۶ رفت، اما مقدار فقط از ۴ به ۸، چون نوشتن MAD نیازمند دانستن این است که فرودگاه مادرید MAD است.
این تمایز، ایده باربر این فصل است:
یک schema قراردادی درباره فرم است. میتواند خروجی مدل را قابل parse، typed و سازگار کند. نمیتواند آن را درست کند، و هر حالت شکستی که از یک schema خوب جان سالم به در میبرد، شکست دانش است، نه شکست فرمت.
این دو به اصلاحهای متفاوت نیاز دارند، و قاطیکردنشان هفتهها زمان هدر میدهد. شکستهای فرمت در توضیح یا با decoding محدودشده، در پایین، رفع میشوند. شکستهای دانش با گذاشتن دانش در prompt رفع میشوند — تاریخ فعلی در پیام system، lookup فرودگاه بهعنوان ابزار دوم که مدل اول آن را صدا میزند، یا یک enum در schema وقتی مجموعه آنقدر کوچک است که بتوان فهرستش کرد. توجه کنید هر سه چه وجه مشترکی دارند: مسئله را از حافظه مدل بیرون میآورند و وارد input آن میکنند، که کل موضوع فصل ۲۴ است.
خروجیهای ساختاریافته، و اینکه «decoding محدودشده» واقعاً چیست
لینک به بخش: خروجیهای ساختاریافته، و اینکه «decoding محدودشده» واقعاً چیستهمه آنچه بالا آمد هنوز به این متکی است که مدل انتخاب کند شکل درست را تولید کند. تضمین قویتری هم وجود دارد، و بهترین بازده فصل ۱۷ همین است.
به یاد بیاورید تولید چگونه کار میکند: در هر گام، مدل برای هر token در واژگان یک logit تولید میکند، و sampler یکی را انتخاب میکند. Constrained decoding یک گام در میان اضافه میکند. با داشتن یک grammar — مشتقشده از JSON Schema شما — محاسبه میکند کدام tokenها میتوانند بهطور قانونی بعدی باشند، logits همه بقیه را به منفی بینهایت میبرد، و اجازه میدهد sampler از میان آنچه باقی مانده انتخاب کند.
اگر schema بگوید چیز بعدی باید یک { باشد، آنوقت احتمال هر tokenی که { نیست صفر است. نه «بعید»: صفر. مدل نمیتواند JSON نامعتبر emit کند، چون tokenهای نامعتبر قبل از sampling از توزیع حذف شدهاند.
زیرساخت «خروجیهای ساختاریافته»، «JSON mode» و «guided generation» همین است، و دو ویژگی آنها را توضیح میدهد. تضمین برای هر چیزی که grammar بتواند بیان کند کامل است — typeها، فیلدهای الزامی، enumها، nesting — چون بهصورت مکانیکی enforce میشود، نه با درخواست مؤدبانه. و درباره محتوا هیچ نمیگوید: یک grammar میتواند "date" را مجبور کند stringی مطابق الگوی تاریخ باشد، اما نمیتواند مجبورش کند روز درست باشد. این همان دیوار بخش قبلی است، فقط از سمت دیگر به آن رسیدهایم.
دو نکته عملی. رایگان نیست: mask باید در هر گام محاسبه شود، و grammarهای پیچیده latency قابلاندازهگیری دارند. و کاری را که مدل انجام میدهد تغییر میدهد — مدلی که از token ترجیحیاش دور شده ممکن است در حالی که ساختار بینقص تولید میکند، محتوای بدتری تولید کند؛ به همین دلیل «مؤدبانه بپرس و اعتبارسنجی کن» هنوز پیشفرض معقولی برای شکلهای ساده است، و constrained decoding وقتی هزینهاش را توجیه میکند که شکل پیچیده باشد یا مصرفکننده سختگیر.
side effectها، و تنها خاصیتی که مهم است
لینک به بخش: side effectها، و تنها خاصیتی که مهم استفصل ۱۴ یک timeout را اندازه گرفت که بعد از retry، برای یک پاسخ دو generation صورتحساب کرد. با ابزارها همان شکست بدتر میشود، چون ابزار میتواند کاری را انجام دهد.
اگر کد شما charge_card را فراخوانی کند، timeout شود، و retry کند، دو charge دارید. مدل هیچ تصوری ندارد که هیچکدام از اینها اتفاق افتاده؛ یک نتیجه ابزار میبیند. راهحل همان راهحل هر سیستم توزیعشدهای است و مشکل مدل نیست: عملیات را با دادن یک key به call، idempotent کنید، تا اجرای دوم اجرای اول را تشخیص دهد و بهجای انجام دوباره کار، نتیجه آن را برگرداند.
قاعده طراحیای که از این میآید ارزش بیان صریح دارد. readها را از writeها در کاتالوگ ابزار خود جدا کنید. یک read میتواند آزادانه retry شود، parallel اجرا شود، و cache شود. یک write نمیتواند، و باید key، بررسی permission، و — برای هر چیزی که کاربر بخواهد قبل از وقوعش درباره آن بداند — یک مرحله approval داشته باشد که انسان را بین درخواست و عمل قرار میدهد. آن مرحله approval تعارف نیست: یکی از معدود چیزهایی است که بین prompt injection و یک پیامد واقعی میایستد — و همانطور که فصل ۳۰ اندازه میگیرد، ضعیفترینِ آنهاست.
چند ابزار تا قبل از افت کیفیت؟
لینک به بخش: چند ابزار تا قبل از افت کیفیت؟باور رایج میگوید بارگذاری ابزارهای زیاد باعث میشود مدل بد انتخاب کند. بهتر است بهجای تکرار، اندازهگیری کنیم؛ پس: همان بیستوچهار درخواست، با ابزار پرواز بهعلاوه مجموعهای رو به رشد از ابزارهای دیگر — از جمله سه ابزار عمداً قابلاشتباه با آن (برنامه قطارها، مسیرهای کشتی، مسیرهای اتوبوس).
| ابزارهای بارگذاریشده | prompt tokens | search_flights را انتخاب کرد | تاریخ در ISO |
|---|---|---|---|
| 1 | 353 | 24/24 | 24/24 |
| 5 | 730 | 24/24 | 24/24 |
| 10 | 1,193 | 21/24 | 21/24 |
| 20 | 2,119 | 24/24 | 24/24 |
انتخاب افت نکرد. با بیست ابزار، که سهتایشان بهطور معقولی قابلاشتباه بودند، یک مدل نیممیلیاردپارامتری بیستوچهار بار از بیستوچهار بار ابزار درست را انتخاب کرد. افت در ردیف ده، سه call است که نام ابزار دیگری را آوردند، و با رفتن به بیست دوام نمیآورد.
این یک نتیجه منفی است و باید بهعنوان نتیجه منفی گزارش شود: در این task، با این ابزارها، «ابزارهای بیشازحد» مشکل نبود. چیزی که یکنواخت و ششبرابر رشد کرد prompt بود: از ۳۵۳ token به ۲٬۱۱۹، که در هر درخواست مکالمه، تا ابد، چه ابزاری استفاده شود چه نشود، پرداخت میشود.
پس نسخه صادقانه باور رایج درباره هزینه و context است، نه accuracy. بیست ابزار یک مالیات دائمی روی هر پیام است، و فصل ۱۶ قبلاً نشان داد یک prefix دائمی در چهل نوبت با صورتحساب چه میکند. وقتی افراد گزارش میکنند ابزارهای زیاد به کیفیت آسیب میزنند، سازوکار معمولاً این است که تعریفها context مهم را بیرون راندهاند — که مسئله فصل ۲۴ است در لباس فصل ۱۸. ابزارهایی که واقعاً نزدیک به هم و تکراریاند هم مشکل واقعی هستند، و راهحل آنها ابزار کمتر نیست، بلکه توضیحها و namespaceهای بهتر است: آنها را با سیستم prefix کنید (crm.search_customer، billing.search_customer) تا دو کاتالوگ ادغامشده از دو تیم با هم collide نکنند، و مدل چیزی برای تمایز داشته باشد.
سه نوع ابزار، و آن یکی که بخش بعدی را باز میکند
لینک به بخش: سه نوع ابزار، و آن یکی که بخش بعدی را باز میکندمرتبکردن ابزارها بر اساس کاری که با جهان میکنند مفید است، چون مهندسی هر کدام فرق دارد.
ابزارهای داده میخوانند: search، fetch، query. قابل retry، قابل parallelise، قابل cache. شکستشان این است که چیز مفیدی برنمیگردانند، و ریسک اصلیشان این است که متن غیرقابلاعتماد را وارد context میکنند — که کل سطح حمله فصل ۳۰ است.
ابزارهای عمل مینویسند: send، create، charge، delete. بدون key قابل retry نیستند، بهطور امن قابل parallelise نیستند، و دلیل وجود جریانهای approval هستند.
ابزارهای orchestration مدلهای دیگر را صدا میزنند. ابزاری که implementation آن یک agent دیگر است، با prompt خودش، ابزارهای خودش و loop خودش — و برای مدل فراخواننده دقیقاً مثل دو نوع دیگر به نظر میرسد، چون فقط یک schema و یک endpoint میبیند.
آن نوع سوم کنجکاوی نیست. سازوکار پشت نیمه agent-as-a-tool در فصل ۲۵ است — topology دیگر، handoff، مکالمه را واگذار میکند و هرگز آن را پس نمیگیرد — و دقیقاً چون interface این فصل آنقدر باریک است که یک agent کامل پشت آن جا میشود، کار میکند.
بعد به کجا میرویم
لینک به بخش: بعد به کجا میرویمحالا مدلی دارید که میتواند چیزهایی را درخواست کند، و قراردادی که این درخواستکردن را قابل parse میکند. چیزی که ندارید این است که فراتر از آنچه در prompt جا میشود، چیزی برای درخواستکردن دربارهاش داشته باشد.
رایجترین ابزار در production، با فاصله زیاد، جستوجو روی مجموعهای از متن است که مدل هرگز در training ندیده: مستندات شما، ticketهای شما، قراردادهای شما. این شبیه مسئلهای حلشده به نظر میرسد — embed کن، نزدیکترین همسایهها را پیدا کن، paste کن — و بخشهایی که حل نشدهاند همانهاییاند که تعیین میکنند آیا پاسخ قابلاعتماد است یا نه: متن قبل از embedding چگونه بریده میشود، چه آستانه شباهتی آنقدر پایین است که یعنی نمیدانم، و چطور citation به یک claim وصل میشود تا خواننده بتواند بررسیاش کند.
فصل ۱۹ درباره retrieval است، و فصلی است که در آن پاسخ غلط از یک کنجکاوی خارج میشود و به liability تبدیل میشود.
منابع و روش
لینک به بخش: منابع و روشاندازهگیریهای این فصل از Qwen/Qwen2.5-0.5B-Instruct با greedy decoding میآیند، روی ۲۴ درخواست تولیدشده که شش جفت شهر را با چهار عبارتبندی تاریخ ترکیب میکنند، و از chat template خود مدل برای تعریف ابزار استفاده میکنند. دقیقاً بازتولید میشوند، و مربوط به یک مدل کوچکاند: تفکیک format/value را بهعنوان نمایش سازوکار بخوانید، نه benchmark کاری که مدلهای امروز انجام میدهند. یک frontier model «جمعه آینده» را بسیار بیشتر درست resolve میکند — و همچنان schema نمیتواند آن را مجبور به این کار کند، و همین بخش است که تعمیم پیدا میکند.
واژگان JSON Schema که بالا استفاده شد (type، properties، required، pattern، format، enum) در draft مربوط به JSON Schema مشخص شده که documentation ارائهدهنده شما نام میبرد؛ subset مفید کوچک است و بین ارائهدهندهها یکسان، و تفاوتهایی که وجود دارند — اینکه کدام keywordها با constrained decoding enforce میشوند نه اینکه صرفاً به مدل پاس داده شوند — ارزش دارد در راهنمای structured-output ارائهدهنده خوانده شوند، نه اینکه فرض شوند.
برای constrained decoding بهعنوان یک تکنیک، کتابخانههای سبک guidance و پروژه outlines ساخت grammar-to-logit-mask را طوری مستند میکنند که مستقیماً روی sampler فصل ۱۷ map میشود. و برای خود رفتوبرگشت، روشنترین specification یک tutorial نیست، بلکه یک protocol است: فصل ۲۶ آن را خطبهخط میخواند.
ارجاعات
لینک به بخش: ارجاعات-
Ouyang, L. et al. Training language models to follow instructions with human feedback. arXiv:2203.02155 (2022). مقالهای که recipe مربوط به post-training را استاندارد کرد؛ شکل یک tool call همانجا، از demonstrationها، دقیقاً مثل شکل یک پاسخ، یاد گرفته میشود. ↩