یک باگ کوچک در اعتبارسنج خروجی میتواند هزاران دلار هزینه اضافی بابت فراخوانیهای بیهوده مدل تحمیل کند. برای حذف این اتلاف منابع، راهنمای فنی منتشر شده در dev.to در ۱۵ اوت ۲۰۲۶، استراتژی تبدیل اعتبارسنجها به توابع خالص (Pure Functions) را تشریح کرده است که کاملاً از مرحله استنتاج مدل جدا هستند.
بسیاری از خطلولههای هوش مصنوعی با یک تناقض در تست مواجهاند: بخشی از سیستم که تست آن سادهترین است — یعنی اعتبارسنج — اغلب کمترین تست را دریافت میکند. در حالی که چالشهای مدلهای زبانی مانند نمونهبرداری (Sampling)، تأخیر، هزینه و تغییر رفتار (Drift) در اعتبارسنج وجود ندارد، توسعهدهندگان معمولاً منطق اعتبارسنجی را با فراخوانی مدل ادغام میکنند. این کار باعث معرفی غیرقطعی بودن (Non-determinism) به چیزی میشود که باید یک بررسی ساده باشد. این وضعیت یک گلوگاه ایجاد میکند که در آن هر اجرای تست به یک درخواست شبکه و پرداخت هزینه توکن نیاز دارد.
همانطور که در تحلیلهای پیشین ما دربارهی بهینهسازی هزینههای استنتاج اشاره کردیم، حذف درخواستهای غیرضروری به شبکه، کلید مقیاسپذیری است. در این معماری، اعتبارسنج باید یک تابع خالص باشد؛ یعنی تابعی که یک خروجی کاندید (یک رشته یا یک شیء تجزیه شده) را میگیرد و یک حکم (Verdict) برمیگرداند. اگر تابعی برای قضاوت درباره یک فیلد، امتیازدهی به یک دستورالعمل (Rubric) یا اصلاح یک خروجی، دوباره از مدل استفاده کند، دیگر یک اعتبارسنج نیست، بلکه یک مرحله استنتاج (Inference) دوم است. این امر نیازمند یک رویکرد تست کاملاً متفاوت است. این رویکرد در واقع تکاملیافتهی بررسیهای ساختاری در برابر تستهای سنتی است که برای جلوگیری از تولید دادههای جایبان توسط هوش مصنوعی به کار میرود.
به نقل از این راهنما، با انتقال تمام منطقهای «مدلمحور» به بیرون از اعتبارسنج و حفظ بررسیهای قطعی در تابعی بدون ورودی/خروجی (I/O)، تستها در چند میلیثانیه اجرا میشوند. این جداسازی، تک تصمیم کلیدی است که خطلوله را قابل تست میکند. پیامد عملی این است که مجموعه تستها به هیچ کلید دسترسی (Credentials)، دسترسی به شبکه، ضبط فیکسچرها (Fixture Recording) یا شبیهسازها (Mocks) نیاز ندارد.
این ساختار اجازه میدهد تستها روی هر کامیت (Commit) و هر درخواست ادغام (Pull Request) در فورکهایی که اسرار (Secrets) در آنها در دسترس نیست، اجرا شوند. از آنجا که این فرآیند بسیار سبک است، میتواند پیش از اجرای مجموعههای تست گرانقیمت اجرا شود تا اطمینان حاصل گردد که یک اعتبارسنج خراب، هرگز باعث شروع یک عملیات هزینهبر فراخوانی مدل نشود. این بهرهوری، ارزش مقدار اندک غیرمستقیم بودن (Indirection) مورد نیاز برای جداسازی منطق را دارد.
پوشش حداکثری با تستهای جدولی
به جای نوشتن تستهای مجزا برای هر قانون، توصیه میشود مجموعه تستها توسط لیستی از موارد نامگذاری شده هدایت شوند. یک جدول باعث میشود پوشش تستها شفاف شود: شما میتوانید نامها را از بالا به پایین بخوانید و ببینید کدام قوانین دارای مورد منفی (Negative Case) هستند و کدامها فقط مورد مثبت دارند. این متدولوژی شباهت زیادی به تست واحدِ قالبهای پرامپت دارد که برای جلوگیری از افت کیفیت خاموش در خروجیهای LLM پیشنهاد شده است.
برای مثال، یک مجموعه تست برای validateInvoice با استفاده از vitest میتواند یک آرایه CASES با سناریوهای زیر تعریف کند:
- فاکتور کامل: یک ورودی معتبر (مثلاً فروشنده: "Acme"، مبلغ: 1250 سنت، ارز: "GBP") که خروجی
valid: trueمیدهد. - مبلغ منفی: ورودی با
total_cents: -1که خروجیvalid: falseبه همراه کد خطایtotal_cents.negativeمیدهد. - مبلغ به صورت رشته: ورودی که در آن
total_cents: "1250"است و خروجیvalid: falseبا کدtotal_cents.typeبرمیگرداند. - ارز ناشناخته: ورودی با
currency: "XYZ"که خروجیvalid: falseبا کدcurrency.enumمیدهد. - نام فروشنده خالی یا فضای خالی: ورودیهای شامل
""یا" "که خروجیvalid: falseبا کدvendor.emptyبرمیگردانند. - مورد چندخطایی: ورودی با هر دو خطای فروشنده خالی و مبلغ منفی، برای اطمینان از اینکه اعتبارسنج هر دو را به طور همزمان شناسایی میکند.
ردیفهای حیاتی در جدول اعتبارسنجی
بر اساس مستندات این راهنما، برخی ردیفهای «حیاتی» وجود دارند که اغلب نادیده گرفته میشوند اما برای استحکام سیستم ضروری هستند:
- تفاوت Absent، Null و Empty: این سه ورودی اغلب مسیرهای کد متفاوتی را فعال میکنند. در رمزگشاییهای محدود به طرحواره (Schema-constrained decoding)، مقادیر Null صریح بیشتر از کلیدهای گمشده ظاهر میشوند، لذا ردیف Null ضروری است.
- رشتههای شامل فضای خالی: مدلها ممکن است زمانی که نمیتوانند مقداری را بیابند، یک فاصله، یک خط تیره یا "N/A" برگردانند. جدول باید صراحتاً تعریف کند که کدام یک از اینها معتبر هستند.
- اعداد در قالب رشته: این مورد بسیار رایج است و اعتبارسنجهایی را که به جای تایپ سختگیرانه، بر اساس Truthiness (درستی منطقی) ساخته شدهاند، به چالش میکشد.
- یونیکد و طول رشته: تستها باید شامل کاراکترهای ترکیبی (Combining Characters)، رشتههای راستبهچپ و رشتههایی باشند که دقیقاً یک کاراکتر بیشتر از حداکثر طول مجاز هستند. اعتبارسنج باید صریحاً مشخص کند که کدپوینتها (Code Points) را میشمارد یا واحدهای UTF-16 را، زیرا تصور داخلی مدل از طول، هیچکدام از این دو نیست.
- تغییر ورودی (Mutation): باید تأیید شود که اعتبارسنج ورودی را Trim نمیکند، تغییر نوع نمیدهد (Coerce) یا مقادیر پیشفرض را پر نمیکند. اعتبارسنجی که ورودیاش را تغییر دهد، در واقع دو تابع در لباس یک نام است و باگهایی ایجاد میکند که یافتن آنها سخت است، زیرا فراخواننده متوجه میشود شیء او تغییر کرده است.
- معتبر اما خالی: ردیفی برای استخراج دادهها که در آن تمام فیلدهای اختیاری Null هستند و فیلدهای اجباری فقط جایبان (Placeholder) هستند. حتی اگر این مورد طرحواره را پاس کند، عملاً بیارزش است. جدول باید صراحتاً تأیید کند که آیا این مورد پذیرفته یا رد میشود تا این تصمیم مستند گردد.
تأکید بر کدهای خطا به جای مقادیر Boolean
در خطلولههای عملیاتی، شکست در اعتبارسنجی به ندرت پایان مسیر است. در عوض، خطاها به پیامهایی تبدیل شده و به مدل بازگردانده میشوند تا تلاشی برای اصلاح (Repair Attempt) صورت گیرد. این امر باعث میشود لیست خطاها به یک «خروجی حیاتی» تبدیل شود.
برای بهینهسازی، اعتبارسنجها نباید با اولین خطا متوقف شوند (Fail-fast)، بلکه باید تمام خطاها را جمعآوری کنند. یک اعتبارسنج Fail-fast باعث ایجاد حلقه اصلاحی میشود که در هر دور فقط یک مشکل را حل میکند. این وضعیت میتواند یک خروجی با دو مشکل را به سه بار فراخوانی مدل تبدیل کند، زیرا هر فراخوانی میتواند مشکل جدید ایجاد کند. توسعهدهندگان باید صراحتاً مورد چندخطایی را تست کنند.
علاوه بر این، توسعهدهندگان باید روی دو ویژگی متمایز تأکید کنند:
۱. کدهای خطای پایدار: این کدها برای شاخهبندی منطق بازگشت (Retry Logic) استفاده میشوند و نباید به طور اتفاقی تغییر کنند.
۲. پیامهای خوانا برای انسان: اینها همان چیزهایی هستند که مدل میبیند. پیام «ورودی نامعتبر» بیفایده است؛ اما پیامی مانند «total_cents باید یک عدد صحیح غیرمنفی در واحدهای کوچک باشد؛ رشته 1250 دریافت شد»، به مدل دقیقاً میگوید چه چیزی را تغییر دهد. هر قانون باید یک تأییدیه داشته باشد که نام فیلد و محدودیت را ذکر کند.
تستهای زاینده برای شناسایی ردهای نادرست
در حالی که تستهای جدولی موارد شناختهشده را میپوشانند، تستهای زاینده (Generative Testing) جنبه «پذیرش» را مدیریت میکنند. با استفاده از ابزارهایی مثل fast-check در جاوااسکریپت یا Hypothesis در پایتون، توسعهدهندگان میتوانند نمونههای دلخواهی بسازند که با طرحواره مطابقت دارند و تأیید کنند که اعتبارسنج هر یک از آنها را میپذیرد.
ردهای نادرست (False Rejections) گرانترین نوع شکست هستند، زیرا خروجیهای درست را برای اصلاح به مدل میفرستند و هر بار هزینه یک دور کامل فراخوانی را تحمیل میکنند. اگر اعتبارسنجی عمداً سختگیرانهتر از طرحواره باشد — به دلیل یک قانون تجاری که طرحواره نمیتواند بیان کند — این موضوع باید به عنوان یک فیلتر روی مولد (Generator) کدگذاری شود. این عملِ نوشتن فیلتر، شکاف بین طرحواره و منطق تجاری را مستند میکند؛ دانشی که معمولاً در هیچ جای دیگر سیستم وجود ندارد. این رویکرد دقیقاً همان فلسفهای است که در جایگزینی بنچمارکهای عمومی با تستهای محلی برای ارزیابی دقیقتر مدلهای کدنویسی مشاهده میکنیم.
اعتبارسنج به مثابه یک قرارداد عمومی
از آنجا که اعتبارسنجها ارزان و خالص هستند، میتوان آنها را در مرزهای مختلف اجرا کرد: در مرز مدل، پیش از ذخیرهسازی در پایگاه داده و در لبه API. این امر کدهای خطا را از جزئیات داخلی به یک قرارداد عمومی بین لایهها تبدیل میکند.
این کدها باید مانند هر قرارداد API دیگر نسخهبندی شوند. رشتههای مربوط به کدها باید از پیامهای کاربر-محور جدا بمانند. در نهایت، توسعهدهندگان باید تستی بنویسند که مجموعه کامل کدهایی را که اعتبارسنج میتواند صادر کند، تأیید کند. این تست در صورتی که قانونی اضافه شود اما پرامپت اصلاح (Repair Prompt) بهروز نشود، شکست میخورد و از تغییراتی جلوگیری میکند که باعث میشود حلقههای بازگشت به طور بیصدا از کار بیفتند.
برای توسعهدهندگانی که به دنبال جداسازی بیشتر سیستمهای خود هستند، گام بعدی بررسی تست کامل خطلوله است، جایی که مدل از طریق ضبط و بازپخش فیکسچرها (Recording and Playback Fixtures) به طور کامل حذف میشود.
گام بعدی شما
- اعتبارسنجهای فعلی خود را از توابع وابسته به API به توابع خالص (Pure Functions) منتقل کنید.
- یک جدول تست (Table Test) برای موارد لبه (Edge Cases) مانند رشتههای یونیکد و مقادیر Null ایجاد کنید.
- سیستم Fail-fast را حذف کرده و اعتبارسنجی را به گونهای تغییر دهید که تمام خطاها را به صورت یکجا برگرداند.
اما داستان سختافزاری این تحول حتی شگفتانگیزتر است — به تحلیل ما دربارهی تراشههای Blackwell مراجعه کنید.




گفتگو