Kodlama temsilcileri için yerel vektör arama: 507 MB'tan 7 satırlık bir döngüye

Yayınlanma tarihi: 12 Kasım 2026

Bu hafta, kodlama temsilcilerinin eski geçici çözümler yerine modern web platformu özelliklerini kullanmasına yardımcı olan açık kaynaklı bir beceri olan Modern Web Rehberi 1.0'ı kullanıma sunduk. Bu yayında, jetonlarınızı boşa harcamadan göreviniz için en alakalı kılavuzu bulan yerel arama motoru incelenmektedir.

Bir mühendise, kodlama temsilcisi için 1,7 MB Markdown metni içeren bir bilgi bankası oluşturduğunuzu söylerseniz size iki soru soracaktır:

  1. "Bu işlem, bağlam pencereme kaç jeton ekleyecek?"
  2. "Ne tür bir şişirilmiş vektör veritabanı gerekiyor?"

Son altı ayda yerel erişim motorumuzu üç kez yeniden oluşturduk. 507 MB boyutundaki C++ bağlamalarından TensorFlow.js kullanan 23 MB boyutundaki saf JavaScript'e, ardından sorgu yerleştirmenin 7 satırlık bir for döngüsü olduğu 7 MB boyutundaki bir pakete geçtik. Bu süreçte doğruluğu da artırdık.

Temsilci tabanlı RAG ve kademeli açıklama

200'den fazla HTML/CSS/Web API kılavuzu içeren 1,7 MB'lık Markdown metnini AGENTS.md modeline aktarmak pratik değildir. Aşamalı açıklama yöntemini tercih ediyoruz ancak Markdown dizininin büyük ölçekte jeton verimliliğini kaybedeceğini biliyoruz.

Bu sorunu çözmek için arama KSA'sı tarafından desteklenen ajan tabanlı RAG'yi kullanırız:

  1. Bekleme durumunda, temsilci yalnızca becerinin açıklamasını görür (~205 jeton). Açıklama, Anthropic'in beceri tetikleme yönergelerine uygun olduğundan temsilci, beceriyi ilgili ön uç görevlerinde çağırır.
  2. Ön uç görevlerinde temsilci, arama yapmak için CLI'yı kullanabilir: modern-web-guidance search "animate height auto", 10 alakalı eşleşme (~530 jeton) elde eder.
  3. Temsilci, rehberin tamamını (yaklaşık 800 jeton) almak için modern-web-guidance retrieve animate-to-intrinsic-sizes kez çalışır. Artık uyumluluk, yedekler ve teknikle ilgili en iyi uygulama yöntemleriyle donatılmıştır.

Neden MCP sunucusu veya web araması yerine CLI becerisi kullanmalısınız?

  • MCP araçları, jetonları boşa harcar veya güvenilir olmayan tetikleme mekanizmalarına sahiptir (dinamik araç keşfi nedeniyle). Beceri açıklamamız, etkililik açısından optimize edilmiştir ve temsilcinin yalnızca uygun olduğunda rehberlikten yararlanmasını sağlar.
  • Yerleşik web araması, güncel ve özel olarak seçilmiş DO/DO NOT uygulama kalıpları yerine 2015 Stack Overflow yanıtlarını ve referans belgelerini gösterir. Ayrıca ağ üzerinden saniyeler sürer ve çevrimdışı korumalı alanlarda başarısız olur. Yerel CLI ise CPU'da milisaniyeler içinde çalışır.

1. nesil: 507 MB'lık C++ prototipi

Modern Web Rehberi'nin ilk sürümünde yerel bir RAG yığını kullanılıyordu: LanceDB, Apache Arrow ve onnxruntime-native ile Transformers.js.

Geliştirme makinesinde sorunsuz çalışıyordu ancak temsilci becerisi için pek uygun değildi:

  • Bir projeye beceri eklediğinizde (veya bir temsilci bunu npx ile çağırdığında), mimariye bağlı .node ikili dosyalarını indiren bir npm install veya bulut korumalı alanlarında rutin olarak başarısız olan node-gyp C++ derlemesini beklemeniz gerekmez.
  • Ayıklanan node_modules dizini 507 MB boyutundaydı.

2. nesil: TensorFlow.js'yi 23 MB'a düşürme

Yerel derleme olmadan, Node'un çalıştığı her yerde anında yürütme istiyorduk. Bu nedenle Nisan ayında yerel yığını, @tensorflow/tfjs üzerinde all-MiniLM-L6-v2 çalıştıran saf JavaScript CPU motoruyla değiştirdik:

  • esbuild'de onnxruntime-node shimmed out ve Hugging Face tokenizer'ı küçük bir ağaç içi BERT WordPiece tokenizer ile değiştirildi (0 jeton kimliği uyuşmazlığı).
  • Kaynak haritasını inceledikten sonra @tensorflow/tfjs-backend-cpu'nin 400'den fazla modülünü MiniLM'nin kullandığı 26 çekirdeğe budayarak JS motorunu 6 MB'tan 830 KB'a küçülttük.
  • Derleme zamanında gzip ile sıkıştırılmış vektörler ve yaklaşık 200 rehberin tamamında bellek içi <1 ms nokta çarpım taraması için LanceDB'yi bıraktık.
  • Yerleştiriciyi bir import() çağrısının arkasına yerleştirin. Böylece retrieve ve list on milisaniyeden kısa sürede tamamlanır.

Bu sayede npm paketi, çalışma zamanı bağımlılığı olmadan 23 MB'a düşürüldü. Bu özelliği kullanıma sunduk ve temsilciler bu özellik üzerinden yaklaşık 750.000 arama yaptı.

MiniLM, üretimde nerede bozuldu?

Birkaç ay sonra, temsilcilerin beceriyi yüklediği ancak yine de eski kod yazdığı yörüngelere baktık.

Bir Codex çalıştırmasında, bir geliştirici silme onayı istemi istedi. HTML <dialog closedby="any"> ile erişilebilir onay modalleri oluşturma hakkında kapsamlı bir kılavuzumuz var.

Temsilci doğru olanı yaptı ve şu aramayı gerçekleştirdi:

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

KSA, [] döndürdü. Temsilci de omuz silkip window.confirm() yazmış.

Neden boş döndü? Günlükleri ve yerleştirmeleri incelediğimizde iki bilgi alma boşluğu dikkatimizi çekti:

  1. all-MiniLM-L6-v2, 2018-2020 yılları arasında eğitildiği için WordPiece kelime dağarcığı, 2020'den sonraki web API'lerinin varlığından haberdar değildir. Bir temsilci fetchpriority, popover veya startViewTransition için arama yaptığında 2018 token oluşturucu, fetchpriority kelimesini dört anlamsız alt kelimeye ayırır: fetch · ##pr · ##ior · ##ity.
  2. Kapsam dışı sorguların rastgele kılavuzlar döndürmesini önlemek için önceki bir PR'de katı bir 0.38 kosinüs benzerliği kesme noktası eklenmişti. MiniLM, 3 kelimelik "confirm destructive action" ifadesini "dialog" ile 0.38 değerinin üzerinde eşleştiremediği için (0.349 puan alırken "popover anchor", 0.362 ve "autofill address form", 0.354 puan aldı) kapı, geçerli eşleşmeleri attı. Daha da kötüsü, search [] döndürdüğünde beceri talimatlarımız, temsilciye modern-web-guidance list'ye geri dönmesini ve yaklaşık 200 rehber açıklamasının tamamını (yaklaşık 1.500 boşa harcanan jeton) bağlama eklemesini söylüyordu.

İki teknik sınırlamayı vurgulayan eğitici bir diyagram. 1. bölümde, 2018'deki bir token oluşturucunun, 2020'den sonraki web API'lerini (ör. &quot;fetchpriority&quot;) nasıl alt kelimelere ayırdığı gösterilmektedir. Bölüm 2'de, 0,38'lik katı bir benzerlik eşiğinin &quot;confirm destructive action&quot;, &quot;popover anchor&quot; ve &quot;autofill address form&quot; gibi geçerli sorgu eşleşmelerini nasıl engellediği gösterilmektedir.

Bilgi getirme sorununu düzeltme: gerçek telemetri, karma arama ve 10 sonuç döndürme

Orijinal karşılaştırma ölçütümüz, kılavuzların kendisinden oluşturulan 850 yapay sorguydu. Bu sorgular, kılavuzdaki ifadelerle çok yakından eşleştiği için bu hataları yakalayamıyordu. Bu nedenle, kapsam dışında kalmayı seçen CLI telemetrisinden (yalnızca temsilcinin 2-5 kelimelik search bağımsız değişkenini günlüğe kaydeder, kullanıcı istemlerini veya depo kodunu asla kaydetmez ve DISABLE_TELEMETRY=1'e uyar) örneklenen yaklaşık 4.000 canlı arama ve Claude Code, Codex CLI ve Gemini CLI'daki gecelik temsilci değerlendirmelerinden elde edilen 1.029 arama etrafında yeni bir değerlendirme sistemi (pnpm benchmark:rag) oluşturduk.

Git geçmişimizi bu gizli sorgularla karşılaştırdığımızda, 0.38 kesme değerinin boş [] yanıtlarını 8 kat artırdığını gördük. Karşılaştırma ölçütünü belirledikten sonra yerleştirme modeline dokunmadan iki hızlı iyileştirme yaptık:

  1. Karma anahtar kelime + semantik arama: Yoğun vektörler tam API adlarını (:user-invalid, showModal, details-name) bulanıklaştırırken anahtar kelime araması, temsilci API adını bilmediğinde amaç sorgularını kaçırır. Çoğul kök bulma özelliğine sahip, sıfır bağımlılıklı bir BM25 (biliyorsanız biliyorsunuzdur) anahtar kelime dizini ekledik, her kılavuzun web-feature-ids dizine ekledik ve BM25'i anlamsal sıralamayla birleştirdik. Artık search, yalnızca hem semantik benzerlik <0.30 hem de BM25'te sıfır anahtar kelime isabeti olduğunda [] döndürür.
  2. 5 ayrıntılı sonuç yerine 10 kısa sonuç: Beceri talimatlarında, temsilcilere similarity ve tokenCount değerlerini yoksaymaları zaten söylenmişti. Kullanılmayan alanları kaldırarak 10 kompakt {id, description} sonucunu, 5 ayrıntılı sonuçla aynı ~530 jeton bütçesine sığdırabiliriz:
// 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."}

Bu iki değişiklik, ayrı tutulan telemetri sorgularındaki arama isabet oranımızı %80,6'dan %94,7'ye çıkardı. Bu oran, Hit@K (beklenen kılavuzun ilk K sonuçta görünüp görünmediği) olarak ölçüldü.

3. nesil: Önceden hazırlanmış yerleştirmeler ve 7 satırlık for döngüsü

94.7% isabet oranında bile telemetrimiz bariz bir uyumsuzluk gösterdi: Canlı temsilci aramalarının %86, 7'si yalnızca 2 ila 5 kelime uzunluğundaydı (medyan: 3 kelime). Temsilciler 3 kelimelik bir ifadeyi her aradığında Node, TensorFlow.js'yi başlatıyor, 22, 6 MB'lık bir nöral ağı ayrıştırıyor ve 770 ms soğuk başlatma gecikmesi ile 346 MB maksimum RAM kullanıyordu.

Nöral ağ olmadan 3 kelimelik sorguları yerleştirmenin daha basit bir yolu olup olmadığı sorulduğunda Antigravity ajanımız, bilinmeyen bir dizi ML damıtma tekniği önerdi: Model2Vec, Tokenlearn, Matryoshka boyutları, Adagrad. (Teknik ayrıntıları öğrenmek isterseniz arama kalitesi sayfamızda daha fazla bilgi bulabilirsiniz.)

Standart yerel RAG, hem dokümanları hem de sorguları kullanıcının CPU'sundaki aynı küçük model üzerinden işlemeye zorlar. Ancak npm sürümü yayınladıktan sonra yaklaşık 200 kılavuzumuz değişmez. Bu nedenle, bunları küçük bir 2018 modeliyle yerleştirmek için bir neden yoktur. Bunun yerine embed-guide-chunks.ts, tüm kılavuz vektörlerimizi yayınlama sırasında gemini-embedding-001 kullanarak bulutta önceden hazırlar ve bunları ikili 256 boyutlu Int8 vektörleri (guide-chunks.i8, 335 KB gzip ile sıkıştırılmış) olarak gönderir.

Çevrimdışı ve API anahtarı olmayan bir CLI, kullanıcının canlı sorgusunu aynı Gemini vektör uzayına nasıl yansıtır? Temsilci sorguları neredeyse her zaman 2 ila 5 kelimeden oluştuğu için çalışma zamanında dönüştürücüye ihtiyacımız yoktur. Bilinen kelimeler (table) için vektörlerin arama tablosuna ve bunları toplamanın akıllı bir yoluna ihtiyacımız var. Çalışma zamanında, 23 MB'lık TensorFlow.js ardışık düzeninin tamamı static-embedder.ts dilinde 7 satırlık bir for döngüsüne daraltılır:

// 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];
  }
}

Bu arama tablosunu (train-query-words.ts) oluşturmak için 29.165 kelimelik bir sözlük (fetchpriority, light-dismiss, popover ve scrollytelling gibi modern web terimlerinin bağımsız tam kelimeler olduğu) kullandık, bunları gemini-embedding-001 ile yerleştirdik ve kelime vektörlerini 33.000 gerçek web sorgusuna göre çevrimdışı olarak ayarladık. Böylece çok kelimeli kombinasyonlar doğru noktaya ulaştı (örneğin, "light" + "dismiss", aydınlatma veya renk şemalarına değil, <dialog closedby="any">'ye işaret eder). Int8 (query-words.i8) boyutuna sıkıştırılan arama tablosunun tamamı 5.3 MB gzip biçimindedir.

Neden bu statik arama tablosunu kılavuzları yerleştirmek için de kullanmıyor ve bulut derleme adımını atlamıyorsunuz? İlk olarak bunu denedik ancak tam paragraflarda başarısız oldu: 3 sorgu kelimesini toplamak keskin kalıyor ancak tam bir kılavuzdaki yüzlerce kelimenin ortalamasını almak, her kılavuzu genel İngilizce ortalamasına yakın dar bir koniye daraltıyor (öyle ki "best pizza in new york", dialog kılavuzumuza karşı 0.899 kosinüs benzerliği puanı aldı. Bu, 0.844 ile "dialog" sorgusunun kendisinden daha yüksek!). 3 kelimelik statik sorgu aramalarını (query-words.i8) tam model Gemini kılavuz yerleştirmeleriyle (guide-chunks.i8) eşleştirmek, sorgular 30 µs içinde çevrimdışı çalışmaya devam ederken bulut düzeyinde doğruluk sağlıyor.

1. bölümde ayarlanmış kelime vektörü yönlendirmesi ve 2a/2b bölümlerinde 256 boyutlu küre üzerinde daraltılmış 300 kelimelik statik ortalamalara karşı asimetrik Gemini kılavuz yerleştirmeleri.

Üç neslin puan tablosu

Sürüm kilometre taşlarımızda Hit@K, tüm doküman yerleştirmeleriyle %78,2'den statik Int8 yerleştiriciyle %95,4'e yükseldi (bulut gemini-embedding-001 API'si ile %96,9'a kıyasla):

Solda, aşamalara göre Hit@K'nin çizgi grafiği, sağda ise soğuk arama gecikmesi ile maksimum RAM ve paket boyutunun karşılaştırıldığı bir kabarcık grafik gösteriliyor.

Statik yerleştiriciyi kullanıma sunduktan sonra üç neslin karşılaştırması şu şekildedir:

Metrik 1. nesil (LanceDB + yerel ONNX) 2. nesil (TF.js + MiniLM-L6-v2) 3. nesil (Static Int8 × Gemini + BM25) Net Değişim (2. Nesil → 3. Nesil)
Yerel C++ derleme adımı Zorunlu (node-gyp) Yok Yok Yükleme zorluğu yok
npm tarball / disk boyutu 507 MB (paketi açılmış) 23,4 MB (.tgz) 7,1 MB (.tgz) %70 daha küçük paket boyutu
search.mjs paket boyutu ~6,0 MB 423,6 KB 5,7 KB -%98,6 JS paketi
Soğuk CLI search gecikmesi ~35 ms (oluşturulduğunda) 770 ms 185 ms 4,2 kat daha hızlı baştan başlatma
Sıcak sorgu yerleştirme süresi ~34 ms ~227 ms 30 µs (0.03 ms) ~7.500 kat daha hızlı
En yüksek RSS belleği ~400 MB+ 346 MB 145 MB -%58 RAM
Telemetri araması Hit@K Ölçülmedi %80,6 (v0.0.195) → %94,7 (ayarlanmış) %95,4 v0.0.195 ile karşılaştırıldığında +14,8 puan
Gece temsilci değerlendirmesi Hit@10 Ölçülmedi %94,1 (v0.0.195) -> %99,2 (ayarlanmış) %99,4 v0.0.195 ile karşılaştırıldığında +5,3 puan

Daha önce çalıştırdığınız bozuk Codex sorgusunu tekrar çalıştırırsanız:

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

dialog rehberi, listenin en üstüne geri döner.

(Tüm aşamalar, sorgu uzunlukları ve K ufukları genelindeki etkileşimli grafikleri Arama Kalitesi Kontrol Panelimizde inceleyebilirsiniz.)

Kod yazmak kolaydır, asıl iş ölçümdür

Bir temsilci, dakikalar içinde BM25 sıralayıcı veya kelime vektörü damıtma komut dosyası yazabilir. Aday uygulamalar oluşturmak artık darboğaz oluşturmuyor. Bir değişikliğin gerçekten işe yarayıp yaramadığını (veya bizim 0.38 kapımızda olduğu gibi üretim aramalarının %9'unu sessizce bozup bozmadığını) bilmek neredeyse işin tamamını oluşturur.

Bu sistemi şu şekilde tasarladık:

  • Ana hedefi tanımlayın ve tek bir uygulama yerine ölçüm altyapısına yatırım yapın. pnpm benchmark:rag oluşturmak, 5.000 gerçek sorguyu seçmek ve otomatik notlandırmamızı insan derecelendirmelerine göre kalibre etmek için herhangi bir bilgi getirici yazmaktan çok daha fazla zaman harcadık.
  • Sizin ve temsilcilerinizin kaliteyi nasıl değerlendireceğine dair bir puanlama anahtarı oluşturun ve gerçek dünya verileriyle karşılaştığında bu anahtarın gelişmesini bekleyin. Buna "önce ölçüm" denir ancak bu yalnızca kısmen doğrudur: İlk gün bilinen yanıt kontrollerini yazabilirsiniz ancak gerçek hataları incelemeden iyi bir değerlendirme ölçeği tasarlayamazsınız (Shankar ve diğerleri, Who Validates the Validators? adlı makalede buna ölçüt kayması adını vermiştir). Temsilcilerin 1.500 jetonluk list yedekleri tetiklediğini görmek,bizi geri çağırmanın yanı sıra jeton şişkinliğini de cezalandırmaya zorladı.
  • Güvenilir ölçümler yapıldıktan sonra farklı uygulamalarla agresif bir şekilde denemeler yapın ve mimari tartışmaları puan tablosuna bırakın. Bir karşılaştırma ölçütü 5.000 sorguyu saniyeler içinde değerlendirdikten sonra, PR'larda 7 satırlık bir Int8 döngüsünün 23 MB'lık bir dönüştürücüyü geçip geçemeyeceğini tartışmanıza gerek kalmaz. Bir öğleden sonra beş yaklaşım deneyip kazananı seçin.

Kodun tamamı (yerleştirici, damıtıcı ve karşılaştırma donanımı dahil) elbette açık kaynaklıdır.