اگر امروز یک خط در پرامپت عامل خود تغییر میدهید تا باگی را رفع کنید، احتمالاً سه باگ جدید ایجاد کردهاید که تا زمان شکایت کاربر متوجه آنها نخواهید شد. این همان کابوس «تست حسی» است؛ جایی که توسعهدهنده با خواندن چند پاسخ تصادفی تصور میکند مدل درست کار میکند، اما در مقیاس واقعی، سیستم فرو میپاشد. یک توسعهدهنده ممکن است خطای مسیریابی را اصلاح کند، اما سه روز بعد متوجه شود که دستیار اکنون با خوشرویی رمز عبور وایفایی را اختراع میکند که پیش از این از بیان آن خودداری میکرد.
به نقل از بی تورکیان (B Torkian)، قهرمان توسعهدهنده انویدیا در دانشگاه USC، ارزیابی دستی مدلهای زبانی بزرگ (LLM) — که شامل خواندن پاراگرافها و دقت کردن به جزئیات است — هرگز نمیتواند فراتر از چند پرسش ابتدایی مقیاسپذیر شود. همانطور که در تحلیلهای قبلی ما دربارهی امنیت مدلهای بازمتن اشاره کردیم، نبودِ یک لایهٔ نظارتی سختگیرانه، منجر به رفتارهای پیشبینیناپذیر میشود. بخش یازدهم از این سری به همین مسئله مرکزی میپردازد: گذار از «حسها» به یک مجموعه تست (Test Suite) که هر نوع نرمافزار دیگری در دنیای برنامهنویسی از آن بهره میبرد. در این راستا، اهمیت استفاده از دادههای واقعی بیش از حد است، چرا که ۲۰ گفتگوی واقعی میتوانند بسیار مؤثرتر از هزاران ارزیابی مصنوعی باشند تا نقاط ضعف واقعی سیستم آشکار شود.
برای حل این مشکل، تورکیان یک مجموعه ارزیابی آماده برای تولید (Production-ready) برای عاملهای NVIDIA Nemotron طراحی کرده است. این سیستم «حس» را با یک مجموعه داده مرجع یا «مجموعه طلایی» (Golden Set) از موارد تست جایگزین میکند که خروجی آنها یک نتیجه باینری (قرمز یا سبز) است. در واقع، این رویکرد عامل هوش مصنوعی را بهجای یک چتبات جعبهسیاه، به عنوان یک نرمافزار قابل وارد کردن (Importable Software) در نظر میگیرد. این متدولوژی بر پایه دو گام معماری قبلی بنا شده است: بخش نهم که به عامل یک قرارداد ساختاریافته (یک دیکشنری تایید شده شامل وضعیت، پاسخ، دستهبندی، آیتمها، موارد گمشده و منابع) داد، و بخش دهم که ردپاهایی (Traces) برای ثبت ابزارهای اجرا شده فراهم کرد. بخش یازدهم هر دوی اینها را به یک «دروازه کیفیت» (Quality Gate) تبدیل میکند.
قانون طلایی: اولویت تصمیم بر واژگان
بزرگترین شکست در اکثر ارزیابیهای هوش مصنوعی، رویکرد سادهلوحانه در تایید پاسخهاست؛ یعنی ادعای اینکه پاسخ باید دقیقاً با یک رشته متنی خاص مطابقت داشته باشد (مثلاً: assert answer == "The USC AI Club meets every Thursday at 5 PM..."). چون مدلهای زبانی غیرقطعی (Non-deterministic) هستند، ممکن است یک پاسخ درست را با کلمات متفاوتی بازنویسی کنند و باعث شوند تست شکست بخورد، حتی اگر منطق پاسخ کاملاً درست باشد. این اتفاق منجر به این میشود که توسعهدهندگان یا تست را حذف کنند — و بدین ترتیب شبکه ایمنی خود را از دست بدهند — یا یاد بگیرند که هشدارهای قرمز را نادیده بگیرند، که این امر منجر به ایجاد یک اعتماد کاذب به سیستم میشود. این چالشها نشاندهنده شکاف عمیقی است که در یافتن باگهای پیچیده از طریق استدلال مدلها وجود دارد و نیاز به روشهای تایید رسمیتر را برجسته میکند.
چارچوب تورکیان این مشکل را با تایید «تصمیمات» حل میکند. عامل بهگونهای طراحی شده است که یک قرارداد ساختاریافته برگرداند. تستها بررسی میکنند که آیا مقدار status برابر با answered است یا category روی campus_event تنظیم شده است، بهجای اینکه روی کلمات دقیق پاسخ تمرکز کنند. این شمارشها (Enums) و ساختارها تنها زمانی تغییر میکنند که یک پسرفت (Regression) واقعی در عملکرد رخ داده باشد. تنها بررسی متنی مجاز، جستوجوی یک زیررشته (Substring) منعطف و بدون حساسیت به حروف بزرگ و کوچک روی یک حقیقت شناخته شده است (مانند «پنجشنبه» یا «۲۰۴») یا استفاده از یک «حفاظ نشت» (Leak Guard) برای اطمینان از اینکه عامل جملاتی مانند «رمز عبور این است» را بیان نکند.
ساخت و منطق مجموعه تست
گام اول شامل برخورد با عامل به عنوان یک نرمافزار قابل وارد کردن است. در اسکریپت part11_evals.py، عامل دیگر یک اسکریپت مستقل نیست، بلکه کتابخانهای است که از طریق دستور from part10_traces import ChatSession, validate_answer, STATUSES, CATEGORIES, WEEKDAYS, LOCAL_TZ وارد میشود. این کار اجازه میدهد عامل به عنوان یک ماژول تست شود، هرچند در نسخه نوتبوک Colab، کلاس ChatSession به صورت داخلی (Inline) نگه داشته شده تا برای اجرای یک-کلیکی، نیازی به کلون کردن گیت یا اتصال به درایو نباشد.
گام دوم، تعریف هر مورد تست (Test Case) به عنوان یک دیکشنری داده ساده است. هر مورد، مجموعهای از نوبتهای ورودی را با یک بلاک «انتظار» (Expect) از تاییدات جفت میکند. در تعریف دادهها از هیچ تابع قابل فراخوانی (Callable) یا پیچیدگی برنامهنویسی استفاده نشده است.
ساختار مورد تست و منطق آن:
- نوبتهای ورودی (Input Turns): لیستی از رشتهها. یک ورودی واحد نشاندهنده یک پرسش ساده است و چندین ورودی نشاندهنده یک جلسه گفتگوی چندمرحلهای است.
- بلاک انتظار (Expect Block): دیکشنریای از تاییدات. برای تست باشگاه AI دانشگاه، این موارد ممکن است شامل موارد زیر باشد:
status: "answered"category: "campus_event"min_items: 1missing_empty: Truesources_nonempty: Trueanswer_contains_any: ["thursday"]tools_called: ["search_campus_info"]
تابع check_case(final, trace_steps, expect) برای هر کلید در بلاک انتظار، یک بررسیکننده اجرا میکند و تمام شکستها را بدون متوقف کردن سریع (Short-circuiting) جمعآوری میکند. اگر کلید ناشناختهای در بلاک انتظار باشد، برای جلوگیری از عبور بیصدای غلطهای تایپی، خطا صادر میکند. نکته حیاتی این است که ابتدا تابع validate_answer اجرا میشود؛ اگر عامل یک دیکشنری بدشکل برگرداند، بلافاصله شکست میخورد (با استفاده از کد بخش نهم). اگر عامل کرش کند یا خروجی غیردیکشنری برگرداند، این مورد به عنوان شکست ثبت میشود، نه به صورت یک Traceback که کل مجموعه تست را متوقف کند.
یک مرز تعمدی در این طراحی وجود دارد: بلاک expect فقط در برابر آخرین نوبت (turns[-1]) بررسی میشود. این امر تضمین میکند که در موارد چندمرحلهای، گفتگو به درستی به پایان برسد؛ این موضوع برای موارد مربوط به حافظه ضروری است، جایی که آخرین نوبت باید ضمایری مانند «آن» را حل کند. اگر توسعهدهنده نیاز داشته باشد رفتار میانی گفتگو را تثبیت کند، باید سناریو را به موارد مجزای هر-نوبت تقسیم کند.
مدیریت دادههای پویا
گام سوم به مشکل «ساعت» میپردازد. سختکد کردن مقداری مانند «۵ روز» برای سوالی مثل «چند روز تا پنجشنبه مانده است؟» باعث میشود مجموعه تست فردا اشتباه باشد. برای رفع این مشکل، سیستم مقادیر مورد انتظار را در زمان اجرا (Runtime) با استفاده از همان منطق ابزار محاسبه میکند:
def days_until(weekday): today = datetime.now(ZoneInfo(LOCAL_TZ)); return (WEEKDAYS.index(weekday) - today.weekday()) % 7
از آنجایی که build_cases() در زمان اجرا و نه در زمان Import اجرا میشود، انتظارات وابسته به تاریخ منقضی نمیشوند. مجموعه تست بهجای متن پاسخ، روی فیلد عددی days_until در ساختار items (از طریق item_field_equals) تاییدیه میگیرد. این کار از «تله واژگان» جلوگیری میکند؛ جایی که بررسی زیررشته برای عدد «۲» ممکن است در عبارت «۱۲ روز» تایید شود اما در عبارت «امروز» شکست بخورد. این بررسی ساختاری است که ارزیابی را صادقانه نگه میدارد.
مدیریت عدم قطعیت (Non-Determinism)
بسیاری از توسعهدهندگان سعی میکنند با تنظیم دمای مدل روی صفر، قطعیت ایجاد کنند. با این حال، تورکیان اشاره میکند که نقاط انتهایی میزبانی شده (Hosted Endpoints) همچنان ممکن است بهدلیل عدم تلازم ممیز شناور (Floating-point non-associativity) تحت دستهبندیهای پویا (Dynamic Batching) و زمانبندی هسته، تفاوتهایی نشان دهند. او توصیه میکند دمای تولید واقعی (مثلاً ۰.۲) حفظ شود و استواری مجموعه تست از طریق تاییدات ساختاری تامین شود. اگر توسعهدهنده مجموعهای سختگیرانهتر را به واقعگرایی ترجیح دهد، میتواند دما را به صفر برساند، اما در این صورت دیگر تنظیماتی را تست نمیکند که کاربران واقعاً با آن مواجه میشوند.
برای مدیریت «لرزشهای تصادفی» (Stochastic Flakes)، اجراکننده یک سیستم حکم سه-سطحی را پیاده میکند:
- PASS: مورد در اولین تلاش پاس شد.
- FLAKY: مورد یک بار شکست خورد اما در یک تلاش مجدد پاس شد. این مورد به عنوان پاس شمرده میشود اما با صدای بلند چاپ میشود، زیرا یک مورد لرزان معمولاً نشاندهنده باگی در پرامپت است که نیاز به بررسی دارد، نه تاییدیه ای که نیاز به تسهیل داشته باشد.
- FAIL: مورد دو بار شکست خورد که نشاندهنده یک پسرفت سخت (Hard Regression) است.
این استراتژی «یکبار اجرا بهعلاوه تلاش مجدد»، اکثر پایداریهای اجرای N مرتبه را فراهم میکند در حالی که هزینهها را به حداقل میرساند، زیرا فراخوانیهای Nemotron میتواند بین ۱۵ تا ۱۳۰ ثانیه زمان ببرد. تابع run_case با حذف EVAL_TRACE قبل از هر تلاش و مقداردهی اولیه یک ChatSession تازه، از نشت حافظه بین موارد تست جلوگیری میکند.
تست پسرفت در دنیای واقعی
«مجموعه طلایی» از پنج مورد خاص تشکیل شده است:
۱. باشگاه AI: یک مورد استاندارد «پاسخ داده شده».
۲. ساعات آزمایشگاه GPU: یک مورد «پاسخ داده شده» در دستهبندی متفاوت برای اثبات اینکه تاکسونومی بهدرستی تفکیک میکند.
۳. امتناع از وایفای: تستی برای سوالاتی که دادهها نمیتوانند به آنها پاسخ دهند (تست امتناع تنها روی سوالی کار میکند که دادهای برای آن وجود ندارد).
۴. مقایسه وابسته به ساعت: تستی برای منطق sooner_comparison.
۵. حافظه چندمرحلهای: موردی که در آن کلمه «آن» باید از طریق تاریخچه گفتگو حل شود.
مورد وایفای به عنوان یک «پسرفت» علامتگذاری شده است زیرا پیش از این در طول مهاجرت به Nemotron دچار شکست شده بود. مدل استدلالی بودجه خروجی خود را صرف «فکر کردن» کرد و پیش از تولید JSON به سقف توکنها رسید (finish_reason: length) و محتوای خالی برگرداند. به همین دلیل است که در این سری، Nemotron برای وظایف مسیریابی با دستور /no_think اجرا میشود. مجموعه ارزیابی اکنون هم حالت «امتناع» و هم حالت «شکست محتوای خالی» را تثبیت میکند، در حالی که یک حفاظ نشت، هرگونه تغییر در پرامپت که منجر به اختراع رمز عبور شود را شکار میکند.
مقیاسپذیری برای محیط تولید
اگرچه این اجراکننده دستساز برای سهولت در Colab بدون وابستگی (Zero Dependencies) است، اما بهگونهای طراحی شده که مستقیماً با استفاده از @pytest.mark.parametrize و pytest-rerunfailures به pytest منتقل شود. نسخه دستساز عمدتاً برای جلوگیری از خروج از شل در Colab و برای نشان دادن این موضوع استفاده شده است که یک ارزیابی صرفاً عبارت است از: اجرای عامل + تایید روی دیکشنری + شمارش.
برای کسانی که فراتر از یک اجراکننده دستساز میروند، تورکیان موارد زیر را پیشنهاد میکند:
- promptfoo: برای تاییدات قطعی مانند Regex و JSON-schema بدون نیاز به یک داور LLM کامل. این یک گام بعدی توصیه شده برای زمانی است که اجراکننده دستساز دیگر پاسخگو نباشد.
- DeepEval / Ragas: برای معیارهای «مدل بهمثابه داور» (LLM-as-judge) مانند وفاداری (Faithfulness) و مرتبط بودن (Relevancy)، هرچند اینها یک مدل غیرقطعی دوم و هزینه اضافی را وارد میکنند.
- NVIDIA NeMo Evaluator: مسیر نهایی برای ارتقا به تولید. این کتابخانه متنباز و سرویس مدیریت شده به توسعهدهندگان اجازه میدهد هزاران مورد و بنچمارک را روی نقاط انتهایی NIM با مدلهای داور اجرا کنند و نتایج را در طول زمان ردیابی نمایند. این ابزار فعلی، یک نسخه مینیاتوری و کاربردی از آن خط لوله است.
این متدولوژی، عامل را از یک دموی «امروز کار میکند» به سیستمی تبدیل میکند که میتوان با اطمینان روی آن تکرار (Iterate) کرد. اسکریپت در صورت شکست، با کد غیرصفر خارج میشود و اجازه میدهد در خط لولههای CI/CD یا بررسیهای PR ادغام شود. با توجه به تأخیر ۱۵ تا ۱۳۰ ثانیهای هر فراخوانی، بهتر است به عنوان یک دروازه شبانه (Nightly Gate) در نظر گرفته شود تا یک قلاب پیش از کامیت (Pre-commit hook) محلی که توسعهدهندگان ممکن است با --no-verify از آن عبور کنند. با تبدیل عامل به یک کتابخانه با قراردادی تعریف شده، توسعهدهندگان میتوانند تضمین کنند که افزودن قابلیتهای قابلیت اطمینان — مانند مدیریت Time-outهای NIM، محدودیتهای نرخ (Rate-limits) یا شکست ابزارها در میان حلقه — عملکردهای موجود را خراب نمیکند.
گام بعدی شما
- بهجای تکیه بر خواندن پاسخها، خروجی عامل خود را به فرمت JSON تبدیل کنید و روی کلیدهای وضعیت (Status) تست بنویسید.
- یک «مجموعه طلایی» از ۱۰ مورد بحرانی (از جمله مواردی که مدل باید پاسخ ندهد) ایجاد کنید و آن را در هر تغییر پرامپت اجرا کنید.
- اگر از مدلهای استدلالی استفاده میکنید، حتماً تستهایی برای بررسی سقف توکنها در خروجیهای ساختاریافته طراحی کنید.
اما داستان سختافزاری این تحول حتی شگفتانگیزتر است — به تحلیل ما دربارهی تراشههای Blackwell مراجعه کنید.




گفتگو