بدهی جایی نیست که لینترها نگاه میکنند
نرمافزار متعارف بدهی را در جاهایی انباشته میکند که کامپایلر در نهایت از آنها شکایت خواهد کرد: منطق تکراری، رابطهای کهنه، ناورداهای مستندنشده. سیستمهای عامل بدهی را در جاهایی انباشته میکنند که هیچچیز از آنها شکایت نمیکند. پرامپت یک رشته است. اسکیمای ابزار یک سند JSON است. قرارداد درباره اینکه کدام عامل مالک کدام تصمیم است، در سند طراحیای زندگی میکند که هیچکس بعد از اسپرینت دوم آن را نمیخواند.
این یک مسئله سبکی نیست. یک مسئله ساختاری است. وقتی مصنوعی که رفتار سیستم را تعیین میکند نثری بدون نوع و بدون نسخه باشد، هر تغییر یک تغییر خاموش است. هیچچیز در ساخت شکست نمیخورد. سیستم به کار خود ادامه میدهد. فقط متفاوت اجرا میشود، و این تفاوت هفتهها بعد کشف میشود، وقتی یک عامل پاییندستی شروع به تولید خروجیهایی میکند که محتمل به نظر میرسند اما به شکلی مشخص و بازتولیدپذیر غلط هستند.
قطعات پرامپت تکرارشده در میان عاملها با واگرایی خاموش
یک الگوی رایج: یک دستور مشترک مانند «همیشه سند منبع را ذکر کن» در چهار پرامپت عامل کپی میشود. شش هفته بعد، سهتای آنها ویرایش شدهاند. یکی الان میگوید «وقتی موجود بود سند منبع را ذکر کن.» دیگری میگوید «اگر بازیابی موفق بود سند منبع را ذکر کن.» چهارمی بدون تغییر مانده است. هیچ تستی شکست نمیخورد. عاملهایی که شرطی ذکر میکنند و عاملی که بیقیدوشرط ذکر میکند، ردهای حسابرسی متفاوتی تولید میکنند، و این تفاوت فقط زمانی آشکار میشود که کسی بخواهد خروجیها را در سراسر خط لوله با هم تطبیق دهد.
بدهی خودِ تکرار نیست. تکرار ارزان است. بدهی، نبودِ سازوکاری است که واگرایی را تشخیص دهد. یک قطعه پرامپت مشترک که در زمان ساخت import میشود، واگرایی را به یک diff تبدیل میکند. یک قطعه کپیپیستشده آن را نامرئی میکند.
اسکیماهای ابزاری که دیگر با سرویسی که پوشش میدهند مطابقت ندارند
اسکیمای ابزار یک قرارداد است. وقتی سرویس زیربنایی تغییر میکند — یک فیلد تغییر نام میدهد، یک فیلد nullable اجباری میشود، یک خطا بهصورت 200 با یک فیلد status برگردانده میشود — اسکیما خودش را بهروز نمیکند. عامل همچنان ابزار را با شکل قدیمی فراخوانی میکند. سرویس یا فراخوانی را رد میکند، که در آن صورت عامل دوباره تلاش میکند و توکن میسوزاند، یا آن را میپذیرد و چیزی برمیگرداند که عامل آن را اشتباه تفسیر میکند.
حالت دوم بدتر است. ابزاری که با صدای بلند شکست میخورد، ابزاری است که تعمیر میشود. ابزاری که بیصدا شکست میخورد، خروجی مطمئن و غلط تولید میکند. این معادلِ در سیستمهای عاملِ تابعی است که بهجای پرتاب خطا، null برمیگرداند.
قراردادهای مستندنشده درباره اینکه کدام عامل مالک کدام تصمیم است
سیستمهای چندعاملی مالکیت ضمنی پیدا میکنند. یک عامل تصمیم میگیرد چه زمانی escalate کند. دیگری تصمیم میگیرد چه زمانی دوباره تلاش کند. سومی تصمیم میگیرد چه زمانی خلاصه کند. این تصمیمها بهندرت در یک جای واحد نوشته میشوند. آنها از پرامپتها، کد ارکستراسیون، و ترتیبی که عاملها اضافه شدهاند پدیدار میشوند.

وقتی یک عامل جدید معرفی میشود، هیچکدام از اینها را به ارث نمیبرد. تصمیمهای خودش را میگیرد، گاهی در تعارض با یک عامل موجود. تعارض نه باگ در هیچیک از دو عامل است. یک مشخصهسازی غایب است. سیستم الان دو مالک برای یک تصمیم دارد، و حلوفصل به ترتیب اجرا بستگی دارد.
این همان دسته از بدهی است که دیدنش سختترین است، چون در هیچ مصنوع واحدی نیست. در شکافهای میان مصنوعهاست.
قابلدیدن کردنش
راهحل نوشتن پرامپتهای بهتر نیست. راهحل این است که پرامپتها، اسکیماها و قراردادها را تابع همان انضباطی کنیم که کد دارد.
با پرامپتها مانند مصنوعهای نسخهدار با مالک و تغییرنگار رفتار کنید
یک پرامپت باید در یک مخزن زندگی کند، نه در یک ردیف پایگاه داده که از طریق یک پنل مدیریت ویرایش میشود. باید یک مالک داشته باشد — یک تیم یا یک نقش، نه یک فرد — و یک تغییرنگار که ثبت کند چه چیزی و چرا تغییر کرده است. تغییرنگار مهمتر از شماره نسخه است. یک diff پرامپت بدون دلیل، diffی است که نفر بعدی که آن را نمیفهمد آن را برمیگرداند.
نسخهبندی همچنین بازگردانی را ممکن میکند. وقتی یک تغییر پرامپت کیفیت خروجی را افت میدهد، توانایی بازگشت به یک نسخه شناختهشدهخوب، تفاوت میان یک تعمیر پانزدهدقیقهای و یک بررسی چندروزه است.
تستهای قرارداد که اسکیماهای ابزار را در برابر endpointهای زنده میآزمایند
یک اسکیمای ابزار باید همانطور آزمایش شود که یک کلاینت API آزمایش میشود: با فراخوانی endpoint واقعی و تأیید اینکه پاسخ با اسکیما مطابقت دارد. این یک تست واحد با mock نیست. mockها اسکیما را همانطور که در زمان نوشتن mock فهمیده شده بود رمزگذاری میکنند، که دقیقاً همان فرضی است که باید ابطال شود.
یک تست قرارداد برای یک اسکیمای ابزار سه چیز را تأیید میکند: اینکه endpoint شکل اعلامشده توسط اسکیما را میپذیرد، اینکه پاسخ شامل فیلدهایی است که اسکیما وعده میدهد، و اینکه شرایط خطا به شکلی آشکار میشوند که عامل بتواند بر اساس آن عمل کند. تأیید سوم همان چیزی است که بیشتر از همه نادیده گرفته میشود و همان چیزی است که بیشتر از همه اهمیت دارد، چون عاملی که نمیتواند یک خطای گذرا را از یک خطای دائمی تشخیص دهد، یک خطای دائمی را تا زمانی که بودجهاش تمام شود دوباره تلاش میکند.
یک رجیستری که هر عامل را به ابزارها، دامنههای حافظه و مسیر escalate آن نگاشت میکند
رجیستری تنها منبع حقیقت برای مالکیت است. برای هر عامل ثبت میکند: کدام ابزارها را میتواند فراخوانی کند، کدام دامنههای حافظه را میتواند بخواند و بنویسد، و تحت چه شرایطی escalate میکند. رجیستری مستندات نیست. پیکربندیای است که لایه ارکستراسیون آن را میخواند، بهطوری که یک عامل نتواند ابزاری را که مالک آن نیست فراخوانی کند یا در دامنه حافظهای که در اختیار ندارد بنویسد.

این یک قرارداد ضمنی را به یک قرارداد اعمالشده تبدیل میکند. این بدهبستان واقعی است: یک رجیستری یک لایه غیرمستقیم و یک بار نگهداری اضافه میکند. هر ابزار جدید نیازمند یک ورودی رجیستری است، و هر ورودی رجیستری یک نقطه شکست بالقوه است اگر از کد فاصله بگیرد. جایگزین — رها کردن مالکیت بهصورت ضمنی — تا اولین تصمیم متعارض ارزانتر است، و در آن نقطه هزینه تشخیص با فاصلهای زیاد از هزینه رجیستری فراتر میرود.
قاعده بازآرایی
اگر یک رفتار مهم است، جای آن در کد یا یک اسکیما است، نه در نثری داخل یک پرامپت.
این تمام قاعده است. گفتنش آسان و پیروی از آن سخت است، چون پرامپتها مسیر کمترین مقاومت هستند. افزودن یک جمله به یک پرامپت سریعتر از افزودن یک فیلد به یک اسکیما است. جمله بلافاصله کار میکند. اسکیما نیازمند یک مهاجرت، یک تست و یک بازبینی است.
این قاعده منعی بر نثر نیست. پرامپتها برای لحن، برای چارچوببندی، و برای نوعی از راهنمایی که در برابر صورتبندی مقاومت میکند، به نثر نیاز دارند. قاعده یک مرز است: نثر برای چیزهایی است که میتوانند بدون شکستن سیستم تغییر کنند. اسکیماها و کد برای چیزهایی هستند که نمیتوانند.
یک آزمون مفید: اگر رفتار چیزی است که برایش تست مینوشتید، جای آن در پرامپت نیست. اگر برایش تست نمینوشتید، ممکن است جای آن در پرامپت باشد. تست خط تقسیم است.
این چه هزینهای دارد
بدهبستان صادقانه این است که این انضباط نسخه اول را کند میکند. یک پرامپت نسخهدار با مالک و تغییرنگار، دیرتر از یک رشتهای که در یک داشبورد ویرایش شده عرضه میشود. نوشتن یک تست قرارداد بیشتر از یک mock طول میکشد. راهاندازی یک رجیستری بیشتر از یک قرارداد طول میکشد.
بازگشت در نسخه اول نیست. در نسخه پنجم است. سیستمهای عاملیای که تغییرشان ارزان است، سیستمهایی هستند که تغییر در آنها خوانا است. خوانایی از مصنوعهایی میآید که میتوان آنها را diff کرد، تست کرد و مالکیتشان را داشت. پرامپتهایی که در داشبوردها زندگی میکنند، نوشتنشان ارزان و تغییرشان گران است. پرامپتهایی که در مخزنها زندگی میکنند، نوشتنشان گران و تغییرشان ارزان است. انتخاب این است که کدام هزینه را بپردازیم، و چه زمانی.
برای تیمهایی که سیستمهای عامل را در محیط عملیاتی اجرا میکنند، پرسش مرتبط این نیست که آیا بدهی وجود دارد. وجود دارد. پرسش این است که آیا قابلدیدن است. یک قطعه پرامپت که خاموش واگرا شده، بدهی نامرئی است. یک قطعه پرامپت با نسخه، مالک و تغییرنگار، بدهی قابلدیدن است، که تنها نوعی است که میتوان آن را پرداخت کرد.
خلاصه
کدبیسهای عامل به شکل متفاوتی فرسوده میشوند. کامپایلر پرامپتها، اسکیماهای ابزار یا قراردادهای مالکیت را بررسی نمیکند، بنابراین بدهی بدون هیچ سیگنالی انباشته میشود. قابلدیدن کردنش نیازمند رفتار با پرامپتها بهعنوان مصنوعهای نسخهدار، آزمایش اسکیماهای ابزار در برابر endpointهای زنده، و نگهداری یک رجیستری از قابلیتهای عامل و مسیرهای escalate است. قاعده بازآرایی ساده است: اگر یک رفتار مهم است، جای آن در کد یا یک اسکیما است، نه در نثری داخل یک پرامپت. هزینه از پیش پرداخت میشود؛ بازگشت در هر تغییر بعدی پرداخت میشود.
