الگوی مشترک در حوادث اخیر
وقتی یک عامل کاری انجام میدهد که هرگز برای آن ساخته نشده بود، گزارش پسازمرگ معمولاً مدل را مقصر میداند. اما گزارشهای حادثه دقیقترند. یک عامل نقطهپایانی را فراخوانی میکند که هرگز بخشی از وظیفهاش نبوده، یک تغییر را دوباره تلاش میکند چون پاسخ چیز دیگری برای انجام دادن به آن نداده، یا یک مجموعه را شمارش میکند چون API شمارش را ارزان و مستندنشده ساخته بود. اینها شکست در استدلال نیستند. شکست در رابط هستند.
سه شکل تکرار میشوند. اول، دسترسی: عامل به منبعی دست میزند که از نظر فنی قابل دسترسی بود اما خارج از اختیاراتش. سطح ابزار یک فعل گسترده را نمایان کرده بود و عامل از آن استفاده کرد. دوم، رفتار تلاش مجدد: در برابر رابطهایی بدون معناشناسی نرخ، عاملی که شکست مبهم دریافت میکند دوباره تلاش میکند، اغلب بهشدت، چون هیچ چیز در پاسخ به آن نمیگوید که تلاش مجدد اشتباه یا بیفایده است. سوم، شمارش: یک نقطهپایان فهرست سهلگیرانه بدون قرارداد صفحهبندی و بدون مرز دامنه، برای عامل مانند دعوتی به پیمودن کل مجموعه خوانده میشود.
چرا ابزارهای انسانشکل برای عاملها شکست میخورند
APIهای رو به انسان ابهام را تحمل میکنند چون انسان قصد و خویشتنداری را پیرامون آنها تأمین میکند. کسی که یک نقطهپایان سهلگیرانه را میخواند، از بافت و عرف میفهمد که DELETE /items دعوتی به حذف همه چیز نیست. میداند کدام پارامترها در عمل اختیاریاند، کدام خطاها گذرا هستند، و کدام فعلها فقط به دلایل قدیمی وجود دارند. هیچکدام از این دانش در رابط نیست. در انسان است.
عامل سطح را تحتاللفظی میخواند. هر فعل یک کنش نامزد است. هر پارامتر اختیاری یک نقطه تصمیم است. رفتار مستندنشده برای ماشین یک لبه نرم نیست؛ یک مسیر کشفنشده است. جایی که انسان گوشهای ناهموار میبیند و از آن دور میزند، عامل یک انشعاب میبیند و ممکن است آن را بگیرد.
این همان عدم تقارن اصلی است. رابطهای انسانی بخشندهاند چون انسان خودش گاردریل است. رابطهای عامل نمیتوانند این را فرض بگیرند. خود سطح باید محدودیتهایی را حمل کند که در غیر این صورت انسان از تجربه تأمین میکند. وقتی این کار را نمیکند، عامل بیپروا نیست؛ با آنچه به آن داده شده سازگار است.
گاردریلهایی که پیرامون یک سطح گسترده قرار میگیرند این را حل نمیکنند. برخی فراخوانیها را رهگیری میکنند و برخی را از دست میدهند، و یک لایه سیاست اضافه میکنند که عامل نمیتواند ببیند. عامل هنوز روی سطح اصلی استدلال میکند. اصلاح بعد از تصمیم میرسد، نه قبل از آن.

طراحی یک سطح ابزار رو به عامل
یک سطح ابزار برای عاملها باید محدود، صریح و خودتوصیف باشد. سه ویژگی بیشتر کار را انجام میدهند.
یک فعل بهازای هر قصد، با پارامترهای الزامی
فعلهای گسترده را به فعلهای مخصوص قصد فروبکاهید. بهجای یک update عمومی که فیلدهای دلخواه را میپذیرد، set_delivery_address را با یک آدرس الزامی و یک شناسه سفارش الزامی نمایان کنید. بهجای list، list_open_orders_for_customer را با یک شناسه مشتری الزامی و یک فیلتر وضعیت الزامی نمایان کنید. مجموعه پارامترها یک پیشنهاد نیست؛ خود قصد است، نوشتهشده.
این تغییر میدهد که عامل چه چیزی میتواند بیان کند. یک فعل عمومی با فیلدهای اختیاری قصد را ضمنی میگذارد، و عاملی که فیلدهای اختیاری را پر میکند در حال حدس زدن است. یک فعل محدود با پارامترهای الزامی بهصورت بسته شکست میخورد: اگر عامل نتواند شناسه مشتری را تأمین کند، نمیتواند فراخوانی را اجرا کند، و شکست خوانا است نه خاموش.
محدودیتهای سخت بیانشده در رابط
محدودیتهای نرخ و دامنه به تعریف ابزار تعلق دارند، نه به یک سند سیاست که عامل هرگز نمیخواند. اگر یک ابزار حداکثر میتواند تعداد محدودی بار در هر بازه فراخوانی شود، این را در اسکیما بگویید و آن را در مرز اعمال کنید. اگر یک مجموعه حداکثر میتواند یک صفحه در هر بار خوانده شود، صفحهبندی را اجباری کنید و اندازه صفحه را یک پارامتر الزامی با بازه محدود کنید.
مکانیزم مهمتر از عبارتپردازی است. محدودیت نرخی که در مرز ابزار اعمال میشود یک سیگنال ساختاریافته برمیگرداند که عامل میتواند بر اساس آن عمل کند. محدودیت نرخی که جایی در پاییندست اعمال میشود یک شکست مبهم برمیگرداند که عامل دوباره تلاش میکند. همان محدودیت، در جای متفاوت، رفتار معکوس تولید میکند.

یک خطا باید به عامل بگوید بعداً چه کاری میتواند انجام دهد، نه فقط چه چیزی شکست خورد. یک 403 خالی دعوت به تلاش مجدد و کاوش میکند. یک خطای ساختاریافته که میگوید این کنش برای این principal مجاز نیست، جایگزین مجاز را نام میبرد، و نشان میدهد که آیا شرط دائمی است، به عامل مسیری میدهد که شامل حدس زدن نیست.
{
"error": "scope_denied",
"message": "This principal may not modify billing details.",
"permitted": ["read_billing_summary"],
"retryable": false,
"next": "request_billing_change_approval"
}
فیلد retryable بهتنهایی یک کلاس بزرگ از حلقههای تلاش مجدد تهاجمی را حذف میکند. فهرست permitted گام بعدی عامل را محدود میکند بدون اینکه لازم باشد مرزها را از شکستها استنتاج کند.
این چه هزینهای دارد
هیچ نسخهای از این کار رایگان نیست، و هزینهها واقعیاند.
نگهداری یک سطح دوم اولین هزینه است. اگر سطح عامل یک پوشش نازک روی API انسانی باشد، فعلهای گسترده نشت میکنند و تمرین ظاهری است. یک سطح عامل واقعی یک قرارداد جداگانه با فعلهای خودش، الزامات پارامتر خودش و واژگان خطای خودش است. این کار مداوم است، نه یک بازآرایی یکباره.
نسخهبندی دومین هزینه است. سطح عامل باید مستقل از سطح انسانی نسخهبندی شود، چون این دو به دلایل متفاوت و با نرخهای متفاوت تغییر میکنند. همچنین باید بهعنوان یک قرارداد آزمایش شود: نه فقط اینکه فراخوانیها موفق میشوند، بلکه اینکه سطح آنچه را باید رد کند رد میکند و ردها خوانا هستند. یک مجموعه تست که فقط مسیر خوشبینانه را تمرین میکند، فعلی که بیش از حد گسترده است را نمیگیرد.
رد کردن فعلهای قدیمی سومین هزینه است، و ناخوشایندترین. برخی فعلها از نظر فنی از بافت اجرای عامل قابل دسترسیاند حتی وقتی بخشی از سطح آن نیستند. نمایان کردن آنها چون راحت هستند، مشکل اصلی را دوباره وارد میکند. معامله صادقانه این است که محدود کردن سطح یعنی برخی وظایفی که عامل از نظر فنی میتوانست انجام دهد دیگر برایش در دسترس نیستند، و برخی از آن وظایف مشروعاند. این هزینه در توانایی پرداخت میشود تا پیشبینیپذیری خریداری شود. تیمهایی که آن را نادیده میگیرند معمولاً بعداً الگوی حادثه را دوباره کشف میکنند، با سطحی گستردهتر و گاردریلهای بیشتر پیرامون آن.
جایگزین این است که به افزودن سیاست پیرامون سطحی ادامه دهیم که هرگز برای خوانده شدن تحتاللفظی طراحی نشده بود. آن رویکرد با تعداد چیزهایی که میتوانند اشتباه شوند مقیاس میگیرد. محدود کردن سطح با تعداد چیزهایی که عامل قرار است انجام دهد مقیاس میگیرد.
