جستجوی بردار محلی برای عامل‌های کدنویسی: از ۵۰۷ مگابایت تا یک حلقه ۷ خطی

تاریخ انتشار: ۱۲ نوامبر ۲۰۲۶

این هفته Modern Web Guidance 1.0 را راه‌اندازی کردیم، مهارتی متن‌باز که به عامل‌های کدنویسی کمک می‌کند به‌جای راه‌حل‌های موقت قدیمی از ویژگی‌های پلاتفرم وب نوین استفاده کنند. این پست نگاهی به موتور جستجوی محلی می‌اندازد که مرتبط‌ترین راهنما را برای تکلیف شما پیدا می‌کند، بدون اینکه توکن‌هایتان را هدر دهد.

اگر به مهندسی بگویید که پایگاه دانشی با ۱٫۷ مگابایت نوشتار Markdown برای عامل کدنویسی او ساخته‌اید، دو سؤال خواهد پرسید:

  1. «این کار چند نشانه به پنجره بافت من اضافه می‌کند؟»
  2. «به چه نوع پایگاه داده برداری متورمی نیاز دارد؟»

درطول شش ماه گذشته، موتور بازیابی محلی‌مان را سه بار بازسازی کردیم. ما از ۵۰۷ مگابایت اتصال‌های C++‎ به ۲۳ مگابایت جاوا اسکریپت خالص بااستفاده از TensorFlow.js رسیدیم، و سپس به یک بسته ۷ مگابایتی رسیدیم که در آن جاسازی پُرسمان یک حلقه for ۷ خطی است، و درعین‌حال دقت را بهبود دادیم.

RAG عامل‌گرا و شفاف‌سازی تدریجی

ریختن ۱٫۷ مگابایت نوشتار Markdown حاوی بیش‌از ۲۰۰ راهنمای HTML/CSS/Web API در AGENTS.md عملی نیست. ما به شفاف‌سازی تدریجی باور داریم، اما می‌دانیم که نمایه Markdown در مقیاس بزرگ دیگر ازنظر توکن کارآمد نخواهد بود.

برای حل این مشکل، از RAG عاملی با پشتیبانی CLI جستجو استفاده می‌کنیم:

  1. در حالت استراحت، نماینده فقط شرح مهارت را می‌بیند (~۲۰۵ کد). شرح از رهنمودهای راه‌انداز مهارت Anthropic پیروی می‌کند تا نماینده مهارت را در تکالیف مرتبط میانای کاربری فراخوانی کند.
  2. در تکالیف مربوط به ظاهر، عامل می‌تواند از CLI برای جستجو استفاده کند: modern-web-guidance search "animate height auto"، دریافت ۱۰ تطابق مرتبط (~۵۳۰ کد).
  3. عامل modern-web-guidance retrieve animate-to-intrinsic-sizes را اجرا می‌کند تا کل راهنما را استخراج کند (حدود ۸۰۰ کد). اکنون به روال‌های مطلوب پیاده‌سازی برای سازگاری، بازگشت، و تکنیک مجهز شده است.

چرا مهارت CLI به‌جای سرور MCP یا جستجوی وب؟

  • ابزارهای MCP یا ژتون‌ها را هدر می‌دهند یا سازوکارهای راه‌انداز غیرقابل‌اعتمادی دارند (به‌دلیل کاوش پویای ابزار). شرح مهارت ما برای اثربخشی بهینه‌سازی شده است و به نماینده امکان می‌دهد فقط درصورت لزوم از راهنمایی استفاده کند.
  • جستجوی وب داخلی به‌جای الگوهای پیاده‌سازی به‌روز و دست‌چین‌شده DO / DO NOT، پاسخ‌های Stack Overflow و اسناد مرجع سال ۲۰۱۵ را نشان می‌دهد. همچنین چند ثانیه در شبکه طول می‌کشد و در محیط‌های امن آفلاین ناموفق است، درحالی‌که CLI محلی در چند میلی‌ثانیه روی CPU اجرا می‌شود.

نسل ۱: نمونه اولیه ۵۰۷ مگابایتی C++‎

اولین نسخه «راهنمای وب نوین» از پشته RAG محلی استفاده می‌کرد: LanceDB،‏ Apache Arrow، و Transformers.js با onnxruntime-native.

این روش در ماشین توسعه نرم‌افزار به‌خوبی کار می‌کرد، اما برای مهارت نماینده چندان مناسب نبود:

  • وقتی مهارتی را به پروژه‌ای اضافه می‌کنید (یا عاملی آن را با npx فراخوانی می‌کند)، انتظار ندارید که برای بارگیری باینری‌های .node وابسته به معماری یا کامپایل C++‎‏ node-gyp که معمولاً در محیط‌های آزمایشی ابری با شکست مواجه می‌شود، منتظر npm install بمانید.
  • فهرست راهنمای node_modules استخراج‌شده ۵۰۷ مگابایت حجم داشت.

‫Gen 2: Golfing TensorFlow.js down to 23 MB

ما می‌خواستیم که هیچ کامپایل بومی‌ای وجود نداشته باشد و اجرای فوری در هر جایی که Node اجرا می‌شود امکان‌پذیر باشد. بنابراین در آوریل، پشته بومی را با موتور CPU جاوا اسکریپت خالص که all-MiniLM-L6-v2 را در @tensorflow/tfjs اجرا می‌کند جایگزین کردیم:

  • ‫onnxruntime-node را در esbuild شبیه‌سازی کردیم و نشان‌ساز Hugging Face را با نشان‌ساز کوچک BERT WordPiece درون‌درختی جایگزین کردیم (۰ عدم‌تطابق شناسه داده‌واحد).
  • پس‌از کاوش در نقشه منبع، بیش‌از ۴۰۰ ماژول @tensorflow/tfjs-backend-cpu را به ۲۶ هسته‌ای که MiniLM واقعاً استفاده می‌کند هرس کرد و موتور JS را از ۶ مگابایت به ۸۳۰ کیلوبایت کاهش داد.
  • LanceDB را برای بردارهای gzipped زمان ساخت و اسکن حاصل ضرب نقطه‌ای <1 ms در حافظه در تمام ۲۰۰ راهنما یا بیشتر حذف کردیم.
  • جاساز را پشت تماس import() قرار دهید تا retrieve و list در کمتر از ده میلی‌ثانیه تمام شوند.

این کار باعث شد بسته npm به ۲۳ مگابایت کاهش یابد و هیچ وابستگی زمان اجرایی نداشته باشد. آن را عرضه کردیم و مأموران حدود ۷۵۰٬۰۰۰ جستجو در آن انجام دادند.

جایی که MiniLM در تولید خراب شد

چند ماه بعد، مسیرهایی را بررسی کردیم که در آن‌ها عامل‌ها مهارت را نصب کرده بودند اما همچنان کد قدیمی می‌نوشتند.

در یک اجرای Codex، توسعه‌دهنده‌ای درخواست پیام‌واره تأیید حذف کرد. We have a whole guide on building accessible confirmation modals with HTML <dialog closedby="any">.

نماینده کار درست را انجام داد و این عبارت را جستجو کرد:

npx modern-web-guidance@latest search "confirm destructive action"

رابط خط فرمان [] را برگرداند. بنابراین نماینده شانه بالا انداخت و window.confirm() را نوشت.

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

  1. all-MiniLM-L6-v2 حدود سال‌های ۲۰۱۸ تا ۲۰۲۰ آموزش دیده است، بنابراین واژگان WordPiece آن به‌معنای واقعی کلمه نمی‌داند که میاناهای برنامه‌سازی کاربردی وب پس‌از سال ۲۰۲۰ وجود دارند. وقتی عاملی fetchpriority، popover، یا startViewTransition را جستجو می‌کند، واحدساز سال ۲۰۱۸ fetchpriority را به چهار زیرواژه بی‌معنی تقسیم می‌کند: fetch · ##pr · ##ior · ##ity.
  2. برای جلوگیری از برگرداندن راهنماهای تصادفی توسط پُرسمان‌های خارج از محدوده، یک PR قبلی حد آستانه شباهت کسینوسی سخت‌گیرانه‌ای0.38 اضافه کرده بود. چون MiniLM نتوانست عبارت ۳ کلمه‌ای "confirm destructive action" را به "dialog" بالاتر از 0.38 متصل کند (امتیاز 0.349، درحالی‌که "popover anchor" امتیاز 0.362 و "autofill address form" امتیاز 0.354 را گرفت)، دروازه مطابقت‌های معتبر را دور انداخت. بدتر اینکه، هرگاه search مقدار [] را برمی‌گرداند، دستورالعمل‌های مهارت به عامل می‌گفتند که به modern-web-guidance list برگردد و همه حدود ۲۰۰ شرح راهنما (حدود ۱۵۰۰ توکن هدررفته) را در بافت بریزد.

نموداری آموزشی که دو محدودیت فنی را برجسته می‌کند. بخش ۱ نشان می‌دهد که چگونه یک واحدساز سال ۲۰۱۸ میاناهای برنامه‌سازی کاربردی وب پس‌از سال ۲۰۲۰ مانند «fetchpriority» را به زیرواژه‌ها تقسیم می‌کند. بخش ۲ نشان می‌دهد که چگونه آستانه شباهت سخت‌گیرانه ۰٫۳۸ تطابق‌های پرسمان معتبر مانند «تأیید اقدام مخرب»، «لنگر پاپ‌اور»، و «فرم تکمیل خودکار نشانی» را مسدود می‌کند.

رفع مشکل بازیابی: دورسنجی واقعی، جستجوی ترکیبی، و برگرداندن ۱۰ نتیجه

معیار اصلی ما ۸۵۰ پُرسمان مصنوعی بود که از خود راهنماها تولید شده بودند، که با عبارت‌بندی راهنما بسیار نزدیک بودند و نمی‌توانستند این خطاها را شناسایی کنند. بنابراین، یک مهار ارزیابی جدید (pnpm benchmark:rag) را حول ۴٬۰۰۰ جستجوی زنده نمونه‌برداری‌شده از دور سنجی CLI انصراف‌داده‌شده (که فقط آرگومان search ۲ تا ۵ کلمه‌ای عامل را ثبت می‌کند، هرگز پیام‌واره‌های کاربر یا کد مخزن را ثبت نمی‌کند، و به DISABLE_TELEMETRY=1 احترام می‌گذارد) به‌علاوه ۱٬۰۲۹ جستجوی استخراج‌شده از ارزیابی‌های شبانه عامل در Claude Code،‏ Codex CLI، و Gemini CLI ساختیم.

بازپخش سابقه git ما دربرابر آن پُرسمان‌های نگه‌داشته‌شده نشان داد که حد آستانه 0.38 پاسخ‌های خالی [] را ۸ برابر افزایش داده است. با وجود این محک، قبل‌از اینکه به مدل جاسازی برسیم، دو بهبود سریع ایجاد کردیم:

  1. جستجوی معنایی + کلمه کلیدی ترکیبی: بردارهای متراکم نام‌های دقیق میانای برنامه‌سازی کاربردی (:user-invalid، showModal، details-name) را محو می‌کنند، درحالی‌که جستجوی کلمه کلیدی وقتی نماینده نام میانای برنامه‌سازی کاربردی را نمی‌داند، پُرسمان‌های هدف را ازدست می‌دهد. ما یک نمایه کلیدواژه BM25 با وابستگی صفر (اگر می‌دانید، می‌دانید) با ریشه‌یابی جمع اضافه کردیم، web-feature-ids هر راهنما را نمایه‌سازی کردیم، و BM25 را با رتبه‌بندی معنایی ترکیب کردیم. اکنون search فقط زمانی [] را برمی‌گرداند که هم شباهت معنایی <0.30 باشد و BM25 هیچ کلیدواژه منطبقی نداشته باشد.
  2. ۱۰ نتیجه فشرده به‌جای ۵ نتیجه پرجزئیات: دستورالعمل‌های مهارت ازقبل به نماینده‌ها گفته بود که similarity و tokenCount را نادیده بگیرند. با حذف کردن این فیلدهای استفاده‌نشده، ۱۰ نتیجه فشرده {id, description} را در همان بودجه دقیقاً ۵۳۰ نشانه‌ای ۵ نتیجه پرجزئیات جا می‌دهیم:
// Before: 5 verbose results (~105 tokens each = ~525 tokens)
{
  "id": "dialog",
  "category": "html",
  "description": "Build accessible modal and non-modal dialogs with native <dialog>, showModal(), and closedby.",
  "featuresUsed": ["dialog", "dialog-closedby"],
  "tokenCount": 780,
  "similarity": 0.49
}
// After: 10 compact results (~53 tokens each = ~530 tokens)
{"id":"dialog","description":"Build accessible modal and non-modal dialogs with native <dialog>, showModal(), and closedby."}

این دو تغییر باهم نرخ موفقیت جستجوی ما را در پُرسمان‌های تله‌متری نگه‌داشته‌شده از ٪۸۰٫۶ به ٪۹۴٫۷ افزایش دادند، که به‌عنوان Hit@K (آیا راهنمای موردانتظار در K نتیجه برتر ظاهر می‌شود یا نه) اندازه‌گیری شد.

نسل ۳: جاسازی‌های ازپیش آماده‌شده و حلقه for ۷ خطی

حتی با نرخ موفقیت 94.7%، دورسنجی ما عدم تطابق آشکاری را نشان داد: ‫۸۶٫۷٪ از جستجوهای نماینده فعال فقط ۲ تا ۵ کلمه طول داشتند (میانه: ۳ کلمه). هر بار که نماینده‌ای عبارت ۳ کلمه‌ای را جستجو می‌کرد، Node درحال راه‌اندازی TensorFlow.js، تجزیه شبکه عصبی ۲۲٫۶ مگابایتی، و سوزاندن ۷۷۰ میلی‌ثانیه تأخیر شروع سرد و ۳۴۶ مگابایت حافظه دسترسی تصادفی اوج بود.

وقتی از او پرسیده شد که آیا روش سبک‌تری برای جاسازی پُرسمان‌های ۳ کلمه‌ای بدون شبکه عصبی وجود دارد، عامل Antigravity ما با مجموعه‌ای از تکنیک‌های ناآشنای تقطیر یادگیری ماشین برگشت: Model2Vec،‏ Tokenlearn، ابعاد ماتریوشکا، Adagrad. (اگر جزئیات فنی را می‌خواهید، در صفحه کیفیت جستجوی ما اطلاعات بیشتری وجود دارد.)

‫RAG محلی استاندارد هم اسناد و هم پُرسمان‌ها را مجبور می‌کند از همان مدل کوچک در CPU کاربر عبور کنند. اما حدود ۲۰۰ راهنمای ما پس‌از انتشار npm تغییر نمی‌کنند، بنابراین دلیلی برای جاسازی آن‌ها با مدل کوچک ۲۰۱۸ وجود ندارد. درعوض، embed-guide-chunks.ts همه بردارهای راهنمای ما را در زمان انتشار در فضای ابری بااستفاده از gemini-embedding-001 پیش‌پخت می‌کند و آن‌ها را به‌عنوان بردارهای Int8 ۲۵۶ بعدی دودویی (guide-chunks.i8، 335 KB فشرده‌شده با gzip) ارسال می‌کند.

چگونه یک CLI آفلاین و بدون کلید API، پُرسمان زنده کاربر را به همان فضای برداری Gemini منتقل می‌کند؟ چون پُرسمان‌های عامل تقریباً همیشه ۲ تا ۵ کلمه هستند، در زمان اجرا به ترانسفورماتور نیاز نداریم. ما فقط به یک جدول جستجوی بردارها برای کلمات شناخته‌شده (table) و یک روش هوشمند برای جمع کردن آن‌ها نیاز داریم. در زمان اجرا، کل خط لوله ۲۳ مگابایتی TensorFlow.js به یک حلقه ۷ خطی for در static-embedder.ts تبدیل می‌شود:

// Each word's weight and Int8 scale are pre-folded into `scales[i]`
for (const [i, f] of enc) {
  const wt = scales[i] * f;
  const offset = i * 256;
  for (let k = 0; k < 256; k++) {
    out[k] += wt * table[offset + k];
  }
}

برای ساختن آن جدول جستجو (train-query-words.ts)، واژگانی با ۲۹٬۱۶۵ کلمه (که در آن اصطلاحات وب مدرن مانند fetchpriority، light-dismiss، popover، و scrollytelling کلمات کامل مستقل هستند) را انتخاب کردیم، آن‌ها را با gemini-embedding-001 جاسازی کردیم، و بردارهای کلمه را به‌صورت آفلاین دربرابر ۳۳٬۰۰۰ پُرسمان وب واقعی تنظیم کردیم تا ترکیب‌های چندکلمه‌ای در جای مناسب قرار گیرند (برای مثال، "light" + "dismiss" به <dialog closedby="any"> اشاره می‌کند، نه به طرح‌های نورپردازی یا رنگ). فشرده‌شده به Int8 (query-words.i8)، کل جدول جستجو 5.3 MB gzipped است.

چرا از آن جدول جستجوی ایستا برای جاسازی راهنماها نیز استفاده نکنیم و از مرحله ساخت ابری صرف‌نظر کنیم؟ ابتدا آن را امتحان کردیم و در پاراگراف‌های کامل ازهم پاشید: جمع کردن ۳ کلمه پُرسمان دقیق باقی می‌ماند، اما میانگین گرفتن از صدها کلمه در یک راهنمای کامل، هر راهنما را به یک مخروط باریک نزدیک به میانگین انگلیسی عمومی تبدیل می‌کند (تا جایی که "best pizza in new york" امتیاز شباهت کسینوسی 0.899 را دربرابر راهنمای dialog ما کسب کرد، که بالاتر از خود پُرسمان "dialog" در 0.844 بود!). جفت کردن جستجوهای پُرسمان ایستا ۳ کلمه‌ای (query-words.i8) دربرابر جاسازی‌های راهنمای Gemini مدل کامل (guide-chunks.i8) به ما دقت در سطح ابری می‌دهد درحالی‌که پُرسمان‌ها همچنان به‌صورت آفلاین در ۳۰ میکروثانیه اجرا می‌شوند.

هدایت بردار واژه تنظیم‌شده در بخش ۱، و جاسازی‌های راهنمای Gemini نامتقارن درمقابل میانگین‌های ایستای ۳۰۰ واژه‌ای فروپاشیده در کره ۲۵۶ بعدی در بخش‌های ۲الف/۲ب.

تابلو پیشتازان در هر سه نسل

درطول نقاط عطف انتشار ما، Hit@K از ٪۷۸٫۲ با جاسازی‌های کل سند به ٪۹۵٫۴ با جاسازکننده ایستا Int8 افزایش یافت (درمقایسه با ٪۹۶٫۹ با gemini-embedding-001 API ابری):

نمودار خطی Hit@K در نقاط عطف در سمت راست، و نمودار حبابی تأخیر جستجوی سرد درمقابل اوج RAM و اندازه بسته در سمت چپ.

در اینجا نحوه مقایسه این سه نسل پس‌از پیاده‌سازی جاساز ایستا آورده شده است:

متریک نسل ۱ (LanceDB + ONNX بومی) نسل ۲ (TF.js + MiniLM-L6-v2) نسل ۳ (Static Int8 × Gemini + BM25) تغییرات خالص (نسل ۲ ← نسل ۳)
مرحله ساخت C++‎ بومی الزامی (node-gyp) خالی هیچ‌کدام نصب بدون دردسر
npm tarball / disk size ‫۵۰۷ مگابایت (از حالت فشرده خارج‌شده) ‫۲۳٫۴ مگابایت (.tgz) ۷٫۱ مگابایت (.tgz) ‫۷۰٪ کاهش اندازه بسته
search.mjs اندازه بسته ~۶.۰ مگابایت ‫۴۲۳٫۶ کیلوبایت ۵٫۷ کیلوبایت ‫−۹۸٫۶٪ بسته JS
تأخیر search Cold CLI ~۳۵ میلی‌ثانیه (هنگام ساخت) ۷۷۰ میلی‌ثانیه ۱۸۵ میلی‌ثانیه ‫۴٫۲ برابر راه‌اندازی سرد سریع‌تر
زمان جاسازی پرسمان گرم ‫~۳۴ میلی‌ثانیه ~۲۲۷ میلی‌ثانیه ‫۳۰ میکروثانیه (0.03 ms) ~۷٬۵۰۰ برابر سریع‌تر
اوج حافظه RSS ~۴۰۰+ مگابایت ‫۳۴۶ مگابایت ۱۴۵ مگابایت ‫۵۸٪- حافظه دسترسی تصادفی
جستجوی دورسنجی Hit@K اندازه‌گیری‌نشده ‫٪۸۰٫۶ (v0.0.195) ← ٪۹۴٫۷ (تنظیم‌شده) ٪۹۵٫۴ ‫‎+۱۴٫۸ واحد درصدی در مقایسه با v0.0.195
ارزیابی شبانه عامل Hit@10 اندازه‌گیری‌نشده ‫٪۹۴٫۱ (v0.0.195) ← ٪۹۹٫۲ (تنظیم‌شده) ٪۹۹٫۴ ‎+۵.۳ درصد در مقایسه با v0.0.195

و اگر آن پُرسمان Codex خراب را از قبل اجرا کنید:

npx modern-web-guidance@latest search "confirm destructive action"

راهنمای dialog درست در بالای فهرست برمی‌گردد.

(می‌توانید نمودارهای تعاملی را در همه نقاط عطف، طول پُرسمان‌ها، و افق‌های K در داشبورد کیفیت جستجو ما کاوش کنید.)

کد ارزان است؛ اندازه‌گیری کار اصلی است

یک عامل می‌تواند در چند دقیقه یک رتبه‌بندی BM25 یا یک اسکریپت تقطیر بردار کلمه بنویسد. تولید پیاده‌سازی‌های نامزد دیگر گلوگاه نیست. دانستن اینکه آیا تغییری واقعاً کمک کرده است (یا بی‌سروصدا ۹٪ از جستجوهای تولید را خراب کرده است، مانند کاری که دروازه 0.38 ما انجام داد) تقریباً کل کار است.

در اینجا نحوه برخورد ما با این سیستم آمده است:

  • هدف نهایی را تعریف کنید و به‌جای هر پیاده‌سازی تکی، روی مهار اندازه‌گیری سرمایه‌گذاری کنید. ما زمان بسیار بیشتری را صرف ساختن pnpm benchmark:rag، گردآوری ۵٬۰۰۰ پُرسمان واقعی، و کالیبره کردن نمره‌دهی خودکارمان درمقابل رده‌بندی‌های انسانی کردیم تا نوشتن هرگونه بازیاب.
  • یک دستورالعمل برای نحوه قضاوت کیفیت توسط شما و نمایندگان خود بسازید و انتظار داشته باشید که پس‌از برخورد با داده‌های دنیای واقعی تکامل یابد. افراد این را «اندازه‌گیری-اول» می‌نامند که فقط نیمی از آن درست است: شما می‌توانید در روز اول بررسی‌های پایه با پاسخ‌های شناخته‌شده بنویسید، اما نمی‌توانید قبل‌از بررسی شکست‌های واقعی، یک معیار خوب در خلأ طراحی کنید (شانکار و همکاران این را انحراف معیار در چه کسی اعتبارسنج‌ها را اعتبارسنجی می‌کند؟ می‌نامند). دیدن عامل‌هایی که بازگشت‌های list ۱۵۰۰ نشانه‌ای را راه‌اندازی می‌کنند، چیزی است که ما را مجبور کرد تا تورم نشانه را در کنار یادآوری جریمه کنیم.
  • با وجود اندازه‌گیری‌های قابل‌اعتماد، با پیاده‌سازی‌های مختلف به‌طور جدی آزمایش کنید و اجازه دهید تابلو امتیازات بحث‌های معماری را حل کند. وقتی یک محک ۵۰۰۰ پرسمان را در چند ثانیه درجه‌بندی می‌کند، دیگر لازم نیست در درخواست‌های ارسال درباره اینکه آیا یک حلقه Int8 ۷ خطی می‌تواند یک ترانسفورماتور ۲۳ مگابایتی را شکست دهد یا نه بحث کنید. پنج رویکرد را در یک بعدازظهر امتحان کنید و رویکرد برنده را نگه دارید.

البته همه کدها (ازجمله جاساز، تقطیرکننده، و مهار محک) متن‌باز هستند.