پرش به محتوای اصلی
پرش به محتوای مقاله

یک حلقهٔ اعتبارسنجی سه‌مرحله‌ای خطاهای پارسر C++ را در ۳ دور اصلاح کرد

·۳۱ مرداد ۱۴۰۵۶ دقیقه مطالعه
راهنما
مطالعه موردی: حلقه کنترل‌شده روی زیرساخت رایگان — ۳ مرحله تا تجزیه‌گر هدر C++ صحیح
مطالعه موردی: حلقه کنترل‌شده روی زیرساخت رایگان — ۳ مرحله تا تجزیه‌گر هدر C++ صحیح
اشتراک‌گذاری
واقعاً چه چیز جدید است؟

جایگزینی کامل مهندسی پرامپت با یک حلقهٔ اعتبارسنجی خودکار (Gated Loop) برای تولید کد C++؛ جایی که زیرساخت تأیید، تکامل کد را هدایت می‌کند نه دستورات متنی.

اگر هنوز برای دریافت کد بدون خطا از مدل‌های زبانی، ساعت‌ها وقت خود را صرف صیقل دادن پرامپت‌ها می‌کنید، احتمالاً در حال جنگیدن با یک نبرد باخته هستید. راهکار واقعی برای رسیدن به کد سطح تولید (Production-ready)، نه در «چه بگوییم»، بلکه در «چگونه بسنجیم» نهفته است. جایگزینی مهندسی پرامپت با یک گیت اعتبارسنجی خودکار اجازه داد تا یک پارسر C++ برای هدرهای Content-Disposition تنها در سه تکرار به سطح عملیاتی برسد.

به نقل از گزارش منتشر شده در dev.to در ۲۲ اوت ۲۰۲۶، یک توسعه‌دهنده توانست با استفاده از یک مدل رایگان از MonkeyCode، یک پارسر برای هدرهای Content-Disposition در زبان C++ بسازد که تنها پس از عبور از یک حلقهٔ سخت‌گیرانه و تکرارپذیر، اجازه ورود به محیط عملیاتی را یافت. این رویکرد دقیقاً در نقطه‌ای وارد می‌شود که برنامه‌نویسان با مشکل «مایل آخر» در کدهای تولید شده توسط هوش مصنوعی مواجه‌اند؛ جایی که منطق کد درست به نظر می‌رسد اما در مواجهه با موارد خاص (Edge Cases) شکست می‌خورد. این استراتژی یادآور استفاده از لایه‌های نگهبان برای جلوگیری از خطاهای پرهزینه در عامل‌های هوش مصنوعی است که بر اهمیت فیلتر کردن خروجی‌های مدل پیش از اجرا تأکید دارد.

این آزمایش بر پایه پوشش‌های قبلی ما است که نشان داد تنها ۷٪ از بذرهای فازینگ (Fuzz Seeds) تولید شده توسط AI در مطالعات موردی C++ توانستند پوشش تست لازم را کسب کنند. در اینجا، تمرکز از خروجی اولیه مدل به زیرساختی منتقل شده است که آن را تأیید می‌کند. تصور کنید با هوش مصنوعی نه به‌عنوان یک برنامه‌نویس، بلکه به‌عنوان نویسنده‌ای برخورد کنید که باید از فیلتر یک ویراستار سخت‌گیر عبور کند. در این جریان کاری، ویراستار یک اسکریپت است که سه مرحله متوالی را اجرا می‌کند: بررسی کامپایل با تبدیل هشدارها به خطا، استفاده از ابزارهای شناسایی خطای حافظه (Memory Sanitizers) مانند ASan و UBSan، و تست تفاضلی در برابر یک مرجع دست‌نویس (Reference Oracle).

معماری حلقهٔ اعتبارسنجی

توسعه‌دهنده برای تابع parse_disposition قراردادی تعریف کرد تا گرامرهای RFC 6266 و RFC 9110 را مدیریت کند. هدف این نبود که مدل از همان ابتدا کدی بی‌نقص بنویسد، بلکه هدف این بود که کد از طریق یک گیت (Gate) تکرارپذیر، صلاحیت خود را ثابت کند؛ جایی که مدل در نقش نویسنده و گیت در نقش ویراستار است. این رویکرد مشابه به‌کارگیری گیت‌های نوع در C++ است که از فروپاشی سیستم در اثر تغییرات ناگهانی مدل‌های زبانی جلوگیری می‌کند.

هدرهای Content-Disposition در ظاهر ساده‌اند؛ یک توکن نوع و سپس پارامترهایی که با نقطه-ویرگول جدا شده‌اند، مانند filename="report.pdf". اما تله در گرامر رشته‌های نقل‌قول‌شده (Quoted-string) است. در اینجا پارسر باید فاصله‌ها، تب‌ها، کاراکترهای فرار (Backslash escapes) و بایت‌های متنی قدیمی (obs-text) در محدوده 0x80 تا 0xFF را مدیریت کند. یک نقل‌قول فرار نباید رشته را ببندد، در حالی که یک رشته باز و بدون پایان باید به‌عنوان خطای نحو (Syntax Error) شناسایی شود.

برای تأیید کار مدل، یک «اوراکل» یا مرجع دست‌نویس در ۱۲۰ خط کد ساخته شد تا به‌عنوان حقیقت مطلق (Ground Truth) عمل کند. منطق این اوراکل به‌طور خاص روی قوانین رشته‌های نقل‌قول‌شده متمرکز بود تا اطمینان حاصل شود که کاراکترهای فرار رها شده، مقدار غلط برمی‌گردانند و تنها کاراکترهای معتبر (شامل محدوده 0x80-0xFF) به خروجی منتقل می‌شوند.

زیرساخت تست به این صورت عمل می‌کرد:

  • تولید مورد (Case Generation): یک اسکریپت پایتون ۵,۰۰۰ ورودی تصادفی بر اساس گرامر تولید می‌کرد. این ورودی‌ها شامل یک توکن نوع، صفر تا سه پارامتر، و مقادیری بودند که یا توکن ساده بودند یا رشته‌های نقل‌قول‌شده با کاراکترهای فرار تصادفی و obs-text.
  • تست تفاضلی (Differential Testing): ورودی‌ها هم‌زمان به پارسر مدل و اوراکل داده می‌شد. هرگونه عدم تطابق در وضعیت موفقیت/شکست یا خروجی نهایی (مقایسه بردار type و params) ثبت می‌شد.
  • کامپایل سخت‌گیرانه: کد با g++ و فلگ‌های -std=c++17 ، -Wall ، -Wextra و -Werror کامپایل می‌شد تا هیچ هشداری نادیده گرفته نشود. همچنین از -fsanitize=address,undefined برای شکار خطاهای حافظه استفاده شد.

سه دور تا رسیدن به صحت

در دور اول، مدل در ۱۲ مورد از ۵,۰۰۰ مورد شکست خورد. اسکریپت مدل وضعیت «پایان ورودی» را برای رشته‌های نقل‌قول‌شده نداشت و نقل‌قول‌های باز (مثلاً filename="abc) را به‌عنوان ورودی معتبر می‌پذیرفت. همچنین نقل‌قول‌های فرار را به‌عنوان پایان رشته می‌دید که باعث رمزگشایی غلط نام فایل می‌شد و نقل‌قول بسته واقعی را به‌عنوان داده‌های زائد (Garbage) در نظر می‌گرفت. ریشه مشکل این بود که مدل به‌جای مدیریت «وضعیت‌ها»، فقط کاراکترها را تطبیق می‌داد.

در دور دوم، مدل سعی کرد مشکل را حل کند اما دچار «بیش‌اصلاح» (Over-correction) شد. در حالی که با افزودن یک بررسی طول، مشکل رشته‌های باز حل شده بود، اما حالا رشته‌های نقل‌قول‌شده خالی (filename="") و بایت‌های معتبر obs-text را رد می‌کرد. این دور ۷ مورد عدم تطابق داشت و ثابت کرد که رفع یک باگ در مرز گرامری، اغلب با افزودن شروط بیش از حد سخت‌گیرانه، باگ جدیدی ایجاد می‌کند.

در دور سوم، مدل سرانجام به صفر عدم تطابق در ۵,۰۰۰ مورد رسید. برای اطمینان از اینکه این نتیجه تصادفی نبوده است، توسعه‌دهنده گیت را با یک بذر (Seed) متفاوت و ۲۰,۰۰۰ مورد تست تکرار کرد و پارسر از تمام آن‌ها عبور کرد. ابزارهای Sanitizer در هر سه دور پاک ماندند و پارسر سرانجام اجازه ورود به محیط عملیاتی را برای این وظیفه خاص یافت.

الگوی شکست مدل‌ها

بر اساس بررسی این تجربه، شکست‌ها تصادفی نبودند و دقیقاً در مرزهای گرامری خوشه‌بندی شده بودند:

  • پایان ورودی: نقل‌قول‌های بسته نشده و کاراکترهای فرار رها شده.
  • انتقال فرار: مدیریت نقل‌قول‌های فرار و بک‌اسلش‌های دوگانه.
  • موارد خالی: پردازش رشته‌های نقل‌قول‌شده تهی.
  • لبه‌های محدوده بایت: مدیریت بایت‌های obs-text (0x80-0xFF).

مدل در «مسیر خوشحال» (Happy Path) عالی عمل می‌کرد اما نسبت به وضعیت‌هایی که یک قانون گرامری در آن به پایان می‌رسد، کور بود. این یک درس مهم برای توسعه‌دهندگان است: هنگام بررسی پارسرهای نوشته شده توسط AI، ابتدا موارد پایان‌دهنده (Terminator) را بررسی کنید، نه مثال‌هایی که در پرامپت داده‌اید.

اقتصاد زیرساخت

استفاده از سرور رایگان MonkeyCode رفتار توسعه‌دهنده را تغییر داد. چون هزینه یک دور شکست تنها چند دقیقه محاسبات بود و نه یک جلسه کاری انسانی، او دیگر وقت خود را صرف صیقل دادن پرامپت‌ها نکرد. در عوض، پرامپت‌ها را به‌عنوان پیش‌نویس‌های خام دید و اجازه داد «گیت» تکامل کد را هدایت کند.

تعدیلات عملیاتی نیز اعمال شد؛ چون اولین کامپایل در هر جلسه کندتر از اجراهای بعدی بود، توسعه‌دهنده تمام موارد تست تفاضلی را در یک پردازش دسته‌بندی کرد تا به‌جای ایجاد یک پردازش برای هر مورد، همه را یک‌جا اجرا کند. همچنین یک مهلت زمانی (Timeout) سخت‌گیرانه تعریف شد تا هر پارسری که باعث توقف سیستم (Hang) شود، به‌جای مصرف کل زمان جلسه، به‌عنوان شکست در آن دور ثبت شود.

این عدم تقارن — جایی که گیت (۱۲۰ خط) بزرگ‌تر از خودِ پارسر نهایی (۸۰ خط) است — یک دارایی قابل استفاده ایجاد می‌کند. این گیت را می‌توان برای هر پارسر دیگری که تیم در آینده نیاز داشته باشد، به کار برد.

محدودیت‌ها و درس‌ها

این روش یک راهکار جادویی نیست. گیت فقط معادل بودن کد با اوراکل را ثابت می‌کند، نه لزوماً انطباق کامل با RFC را. اگر اوراکل غلط باشد، گیت با اطمینان کامل خروجی غلط را تأیید می‌کند. همچنین این حلقه در صورتی که مرجع دست‌نویسی وجود نداشته باشد، گرامر به‌درستی تعریف نشده باشد، یا پارسر برای موارد امنیتی حساس باشد (جایی که ابزارهای coverage-guided fuzzer مانند libFuzzer الزامی است)، توصیه نمی‌شود.

درس‌های کلیدی این تجربه:

  • ابتدا اوراکل را بنویسید: این کار شما را مجبور می‌کند پیش از آنکه مدل پرامپت را ببیند، گرامر را دقیقاً تعریف کنید.
  • برای «قرارداد» پرامپت بنویسید: رابط (Interface) و گیت را تعریف کنید و اجازه دهید مدل پیاده‌سازی را انجام دهد.
  • کل مجموعه داده را دوباره تست کنید: تست کردنِ تنها موارد شکست‌خورده می‌تواند باعث بازگشت باگ‌های قدیمی شود (Regression)، همان‌طور که در دور دوم رخ داد.

این تغییر رویکرد نشان می‌دهد که در وظایف فنی، اوراکل ارزشمندترین دست‌ساخت است. با نوشتن اوراکل در ابتدا، توسعه‌دهنده گرامر را پیش از مواجهه AI با پرامپت، دقیقاً مشخص کرد.

برای شما به‌عنوان کاربر، این یعنی عصر «پرامپت‌نویسی بی‌نقص» جای خود را به «اعتبارسنجی بی‌نقص» می‌دهد. اگر بتوانید موفقیت را از طریق یک هارنس تست تعریف کنید، مدل انتخابی شما اهمیت کمتری نسبت به گیتی دارد که برای فیلتر کردن آن می‌سازید.

گام بعدی شما

  • برای هر تابع حساس که توسط AI می‌نویسید، ابتدا یک اوراکل ساده (حتی در ۱۰ خط) برای تعریف حقیقت مطلق بسازید.
  • به‌جای تغییر کلمات پرامپت، موارد شکست (Failure Cases) را به مدل بدهید و از او بخواهید کد را با توجه به آن‌ها اصلاح کند.
  • از ابزارهای Sanitizer در کامپایلر خود فعال کنید تا خطاهای حافظه که در نگاه اول نامرئی هستند را شکار کنید.

اما داستان سخت‌افزاری این تحول حتی شگفت‌انگیزتر است — به تحلیل ما درباره‌ی تراشه‌های Blackwell مراجعه کنید.

چرا این موضوع مهم است؟

این رویکرد ثابت می‌کند که برای کدهای حساس، اعتماد به خروجی مدل (حتی مدل‌های پیشرفته) یک ریسک مهندسی است. استفاده از تست‌های تفاضلی و اوراکل‌ها، تنها راه رسیدن به سطح اطمینان لازم برای استقرار در محیط‌های عملیاتی است.

تأثیر برای ایران

برنامه‌نویسان ایرانی که به‌دلیل محدودیت‌های API به مدل‌های رایگان یا ضعیف‌تر دسترسی دارند، می‌توانند با ساخت اوراکل‌های محلی، کیفیت خروجی مدل‌های ارزان را به سطح مدل‌های گران‌قیمت برسانند.

·نگاه ما
تحریریه دات‌هوش

ارزش واقعی در این جریان کاری، جابه‌جایی مرکز ثقل از «تولید» به «تأیید» است. وقتی هزینه تکرار (Iteration) به دلیل رایگان بودن مدل یا سرعت بالای اتوماسیون به شدت کاهش می‌یابد، مهندسی پرامپت به یک فعالیت کم‌بازده تبدیل می‌شود. در واقع، اوراکل در اینجا نقش یک «کمپرسور دانش» را ایفا می‌کند که ابهامات گرامری را پیش از رسیدن به مدل، حذف می‌کند.

منابع

این گزارش با خط‌لولهٔ خودکار دات‌هوش از منابع معتبر جهانی تدوین و زیر نظر تحریریه منتشر شده است. روش کار ما

گفتگو

پنج‌شنبه‌های هوش‌محور

بسته‌ی هفتگی دات‌هوش

۵ خبر، ۲ ابزار، ۱ پرامپت در هر شماره. به‌زودی راه‌اندازی می‌شود — هر پنج‌شنبه صبح.

خبر کلیدی
ابزار کاربردی
پرامپت حرفه‌ای
تحلیل پژوهش
به‌زودی
زاویه‌ی ایرانی
به‌زودی
تمرین این هفته
به‌زودی

راهنماهای دات‌هوش

راهنماهای کاربردیِ دات‌هوش برای کار با هوش مصنوعی — از همین‌جا شروع کنید:

دات‌هوش

راهنمای فارسی هوش مصنوعی — با نگاه به ایران

اخبار روزانه، معرفی ابزارها و مدل‌ها، و آموزشِ کار با هوش مصنوعی؛ همیشه با این پرسش که از ایران چه چیزی کار می‌کند و چه چیزی نه.