در طراحی سیستمهای مبتنی بر مدلهای زبانی بزرگ، یکی از خطاهای پرهزینه پرسیدن پرسشهای تکراری از مدل است؛ کاری که توکنها را میسوزاند و خروجیهای شبیه به هم تولید میکند. وقتی میخواهید یک چرخهی پژوهش خودکار (Research Loop) بسازید، دریافت یک پاسخ منفرد کافی نیست. سیستم باید بتواند مجموعهای غنی از ایدهها و فرضیههای متنوع آماده کند تا در صورت شکست فرضیهی اول در آزمایش، بلافاصله گزینهی بعدی در دسترس باشد.
در این درس یاد میگیرید که چطور با ترکیب افزایش پلهای دما (Temperature Ramping) و فیلتر نوآوری مبتنی بر فاصلهی کسینوسی امبدینگها، فرضیههای تکراری را حذف کنید. تمام محاسبات این درس با کتابخانهی استاندارد پایتون پیاده شده است و نیازی به کارت گرافیک مجزا، سرویسهای ابری خارجی یا کتابخانههای سنگین ندارید؛ بنابراین پیادهسازی بهصورت کاملاً محلی و قطعی روی هر سیستمی اجرا میشود.
ایجنت پژوهشی که یک سؤال را دوبار بپرسد، دارد توکن هدر میدهد. ترفند کار این است که هر پیشنویس را مجبور کنیم به ایدهای تازه برسد.
اهداف یادگیری
- هدایت یک نمونهگیر (sampler) با پرامپت اولیه (seed prompt) و تبدیل خروجیهای آن به رکوردهای تایپبندیشدهی فرضیه.
- افزایش پلهای دمای نمونهگیر در هر دور، تا پیشنویس بعدی بیشتر از قبلی فاصله بگیرد.
- فیلتر کردن موارد تقریباً تکراری با یک مدل امبدینگ کوچک و آستانهی فاصلهی کسینوسی.
- رتبهبندی بازماندهها با تابع امتیازبندی که نوآوری، جزئینگری و آزمونپذیری را ترکیب میکند.
- قطعی (deterministic) نگه داشتن تمام مراحل، تا پرامپت اولیهی یکسان همیشه صف یکسانی تولید کند.
چرا اول تولید، بعد فیلتر؟
برنامهریزی که یکبار از یک مدل سؤال میپرسد، به یک فرضیه میرسد. این کار برای یک مثال حلشده خوب است، اما برای یک چرخهی پژوهش ساختار نامناسبی دارد. چرخه به یک صف رتبهبندیشده و عمیق نیاز دارد؛ تا وقتی اولین فرضیه شکست خورد، مجری بدون پرداخت هزینهی یک دور کامل نمونهگیری دیگر، گزینهی بعدی را آماده داشته باشد.
این صف از ترکیب دو ایده ساخته میشود: اولی افزایش پلهای دما است؛ در هر دور گذر از نمونهگیر، دما یک درجه بالا میرود تا پیشنویسهای بعدی ترغیب شوند ایدههای متفاوتتری را جستوجو کنند. دومی فیلتر نوآوری است؛ پس از هر پیشنویس، مولد فاصلهی امبدینگ آن را از تکتک بازماندههای پیشین میسنجد و هر چه را که درون آن خوشه باشد رد میکند.
در این درس یک مدل زبانی شبیهسازیشده (mock) ارائه میدهیم که در برابر پرامپتهای مشخص، توالیهای متنی از پیش تعریفشده را برمیگرداند. این ماک برای اجرای کامل مسیر کفایت میکند: دریافت پرامپت اولیه، اعمال افزایش پلهای دما، پارس کاندیداها، اجرای فیلتر نوآوری و تحویل صف رتبهبندیشده.
ساختار فرضیه
Hypothesis
id : int (monotonic within a run)
text : str (the claim)
variables : list[str] (what changes between conditions)
metric : str (what the runner will measure)
baseline_ref : str | None (which paper or run the comparison cites)
draft_pass : int (which sampler pass produced this)
temperature : float (the sampler setting at draft time)
novelty_score : float (distance from prior survivors, 0..1)
rank_score : float (weighted sum used for ordering)
فیلدهای variables و metric متن آزاد نیستند. پارسر آنها را از دل پاسخ برچسبخورده استخراج میکند. مجری در درس 52 هنگام ساخت کانفیگ آزمایش، این فیلدها را مستقیم میخواند.
فیلد baseline_ref اختیاری است اما توصیهاش میکنیم. ارزیاب در درس 53 برای مقایسه به یک خط مبنا نیاز دارد. اگر فرضیه خط مبنا نداشته باشد، ارزیاب به اجرای قبلی روی همان معیار بازمیگردد.
معماری
چرخه سرراست است. بخش جالب اینجاست که هر بلوک قرارداد سفتوسختی دارد.
افزایش پلهای دما
شروع از t_min، پایان در t_max و اندازهی هر گام برابر با (t_max - t_min) / (n_passes - 1). در هر دور، نمونهگیر با دمای جاری فراخوانی میشود و خروجی n_passes مقدار با فواصل یکنواخت از GeneratorConfig.schedule() به دست میآید. مدل ماک با جابهجایی میان مجموعهی کوچکی از پاسخهای سناریونویسیشده که کلیدشان (prompt, temp_bucket) است، اثر دما را شبیهسازی میکند. این بازهها باز هستند؛ بنابراین تغییر کوچکی در دما بازهی دیگری را انتخاب میکند و پیشنویس متفاوتی میسازد. در محیط عملیاتی (پروداکشن)، نمونهگیر یک مدل واقعی است که temperature=t مستقیماً به آن پاس داده میشود.
برنامهی پیشفرض 6 دور از 0.2 تا 1.2 است. 6 دور برای پر کردن صف کافی است، بدون اینکه هزینهای برای نمونههایی بپردازیم که فیلتر نوآوری در هر حال ردشان میکند. زیر 0.2 مدل همان پرامپت اولیه را طوطیوار تکرار میکند. بالای 1.2 پاسخها معمولاً از موضوع پرت میشوند و در مرحلهی پارس شکست میخورند.
فیلتر نوآوری
پس از پارس هر پیشنویس، مولد متن را امبد میکند و آن را با تکتک فرضیههای پذیرفتهشده میسنجد. این امبدینگ یک کیسه کلمات هششدهی کوچک از توکنهای واژهای است که طولش روی بردار یکه نرمالایز شده است. فاصلهی کسینوسی بین دو بردار یکه برابر با 1 - dot(a, b) است. اگر کمترین فاصلهی یک پیشنویس تا تمام بازماندههای پیشین بالاتر از novelty_threshold باشد، قبول میشود. مقدار پیشفرض 0.25 است.
این امبدینگ هششده اصلاً پیچیده نیست؛ کاملاً قطعی است، هیچ وابستگی خارجی ندارد و برای مهار حالتهای تکراری واضح—یعنی دو پیشنویس که اکثر اسمهایشان مشترک است—کفایت میکند. در پروداکشن میتوانید یک مدل کوچکِ جملهای را جایگزین آن کنید؛ اینترفیس هیچ تغییری نمیکند.
امتیاز رتبه
rank_score = w_novelty * novelty_score
+ w_specificity * specificity_score
+ w_testability * testability_score
سه زیرامتیاز داریم: novelty_score کمترین فاصلهی امبدینگ از بازماندههای پیشین است. specificity_score تعداد متغیرهای عینی فرضیه تقسیم بر تعداد هدف است. testability_score اگر فرضیه هم معیار و هم خط مبنا داشته باشد 1، اگر فقط معیار داشته باشد 0.5 و در غیر این صورت 0 است.
وزنهای پیشفرض 0.4، 0.3 و 0.3 هستند. وزنها درون کانفیگ مولد قرار دارند تا درسهای بعدی بتوانند بدون fork زدن کد، آنها را تغییر دهند.
مدل زبانی ساختگی (Mock)
class MockLLM:
def sample(self, prompt: str, temperature: float, seed: int) -> str:
...
نمونهگیر با داشتن سهگانهی (prompt, temperature, seed) رفتاری کاملاً قطعی دارد. ماک جدولی از پاسخهای سناریونویسیشده نگهمیدارد که کلیدشان (prompt_signature, temperature_bucket) است. اگر جدول برای کلیدی مدخلی نداشته باشد، نمونهگیر یک طرح پشتیبان (fallback) برمیگرداند که پارسر آن را رد میکند. یکی از تستها دقیقاً همین مسیر پشتیبان را امتحان میکند.
مقدار seed درون پاسخ ترکیب میشود؛ بنابراین یک جفت (prompt, temperature) با seedهای مختلف، پیشنویسهای متفاوتی تولید میکند. در تستها seed را ثابت نگه میداریم تا نتایج بازتولیدپذیر باشند. در استقرار واقعی، این seed از ساعت سیستم یا یک شمارنده تأمین میشود.
صف خروجی
خروجی فهرستی از رکوردهای Hypothesis است که بر اساس rank_score بهصورت نزولی مرتب شدهاند. مجری در درس 52 سر صف (head) را برمیدارد، آزمایش را اجرا میکند و ارزیاب در درس 53 حکم نهایی را برمیگرداند. اگر ارزیاب بگوید فرضیه غلط بوده، مجری گزینهی بعدی را از صف برمیدارد.
این صف متناهی است. وقتی خالی شود، هماهنگکننده (orchestrator) میتواند یا پرامپت اولیه را گستردهتر کند و دوباره مولد را به کار بیندازد، یا کار را متوقف کند و گزارش دهد که بودجه تمام شده است.
راهنمای خواندن کد
فایل code/main.py موجودیتهای Hypothesis، MockLLM و HypothesisGenerator و یک دموی قطعی را تعریف میکند. مولد فقط یک متد run(seed_prompt) دارد که صفی مرتبشده تحویل میدهد؛ تعداد دورها بهجای اینکه ورودی متد باشد، مستقیماً از GeneratorConfig.n_passes خوانده میشود. امبدینگ بهشکل یک کیسه کلمات هششده از توکنهاست. فیلتر نوآوری یک تابع مستقل است و امتیاز رتبهبندی هم یک تابع مجزا. هیچچیز به numpy وابسته نیست؛ تمام محاسبات ریاضی امبدینگ با کتابخانهی استاندارد پایتون انجام شده تا درس کاملاً قابلحمل بماند.
فایل code/tests/test_generator.py مسیر مستقیم، مسیر رد موارد تکراری، مسیر خطای پارسر، مرزهای افزایش پلهای دما و ترتیب رتبهبندی را پوشش میدهد.
جایگاه این بخش در تصویر کلی
درس 50 صف را تولید میکند. درس 51 سر صف را برمیدارد و برای رد یا تأیید آن، در ادبیات پژوهش جستوجو میکند. درس 52 همان سر صف را میگیرد و یک آزمایش واقعی روی آن اجرا میکند. درس 53 خروجی هر دو مرحله را میخواند و حکم را صادر میکند. این چهار درس در کنار هم چرخهی پژوهشی بدون دخالت انسان میسازند؛ هرچند انسان میتواند در هر کجای این مرزها وارد عمل شود.
