Bir embedding modeli, metni bir vektöre dönüştürür; böylece benzer anlamlar birbirine yakın düşer. Contextator varsayılan olarak birini CPU’nuzda, local olarak çalıştırır — API key yok, hesap yok, makineden çıkan hiçbir şey yok — ve isterseniz onun yerine OpenAI kullanabilirsiniz.
Varsayılan
| Model | Xenova/multilingual-e5-small |
| Boyut | 384 |
| Diller | Türkçe dahil 100. Soruyu cevabın yazıldığı dilde sorun: diller arası eşleşme ölçülmüş bir model sınırıdır, açılıp kapanan bir ayar değil |
| İndirme | Bir kere ~470 MB (fp32), bir Docker volume’unda önbelleklenir |
| Çalıştığı yer | CPU. GPU kullanılmaz ve gerekmez |
Daha küçük ve daha hızlı seçenekler
EMBEDDING_DTYPE=q8 # aynı model, ~120 MB, quantized
EMBEDDING_MODEL=Xenova/all-MiniLM-L6-v2 # yalnızca İngilizce, ~90 MB, yine 384 boyut
EMBEDDING_DIMENSIONS=384
Herhangi bir transformers.js feature-extraction modeli çalışır. EMBEDDING_DIMENSIONS, modelin
ürettiğiyle eşleşmek zorundadır — eşleşmezse sunucu size doğru sayıyı söyler.
Bunun yerine OpenAI kullanmak
EMBEDDING_PROVIDER=openai
OPENAI_API_KEY=sk-…
OPENAI_EMBEDDING_MODEL=text-embedding-3-small
EMBEDDING_DIMENSIONS=1536
RESET_VECTORS=1 # yalnızca değişiklikten sonraki ilk başlatma için — aşağıya bakın
Uzun parçalarda daha iyi getirme ve çok daha büyük bir girdi penceresi; bedeli, indekslenen her parça ve sorulan her sorgu için bir API çağrısıdır. Dokümantasyon metniniz OpenAI’a gönderilir; bu kabul edilemezse local kalın.
Modeli değiştirmek
Aynı boyut (örn. 384 boyutlu local modeller arasında)
EMBEDDING_MODEL’i değiştirin.- Yeniden başlatın.
- Her projeyi yeniden indeksleyin.
Contextator, her projeyi hangi modelin indekslediğini kaydeder. Sunucunun modeli farklıysa, sonraki
çalışma otomatik olarak tam bir yeniden indeksleme olur ve search_docs, bu gerçekleşene kadar o
projede aramayı reddeder — kendinden emin ama yanlış sonuçlar döndürmek yerine, tam olarak bunu söyleyen
bir mesajla.
Farklı boyut (örn. OpenAI’ın 1536’sına geçiş)
Vektör kolonunun genişliği veritabanı oluşturulduğunda sabitlenir, bu yüzden bu tek seferlik yıkıcı bir adımdır:
- Provider’ı, key’i ve
EMBEDDING_DIMENSIONS’ı ayarlayın. - Bir kere
RESET_VECTORS=1ile başlatın. Her parça ve belge düşürülür, kolon yeniden tipenir (re-typed). RESET_VECTORS’ı kaldırın (ya da0’a geri ayarlayın).- Her projeyi yeniden indeksleyin.
Bayrak olmadan sunucu başlamayı reddeder ve şu talimatı yazdırır:
The database was created with EMBEDDING_DIMENSIONS=384 but the current config says 1536.
Either set EMBEDDING_DIMENSIONS=384, or start once with RESET_VECTORS=1
(drops every indexed chunk; all projects must be re-indexed), or wipe the pgdata volume.
Arama gerçekte nasıl çalışır
Arama hibrittir: anlam ve birebir sözcük aynı anda aranır ve sıralarıyla birleştirilir.
- Sorunuz, belgeleri indeksleyen aynı modelle gömülür.
- PostgreSQL, o projeyle sınırlı olarak iki sıralama üretir — bir HNSW index üzerinde cosine
similarity ile en yakın parçalar ve bir GIN index üzerinde
tsvectoranahtar sözcük eşleşmesi — ve bunları reciprocal rank fusion ile birleştirir; böylece bir ortam değişkeni de en az bir soru kadar kolay bulur sayfasını. - En iyi eşleşmeler; dosya yolu, başlık kırıntısı ve skorlarıyla döndürülür.
Skorlar cosine similarity’dir: yüksek olan daha iyidir ve varsayılan modelde yüksek ve birbirine yakın
dururlar. Sevk edilen yapılandırmada ortalama skor 0.881’dir ve yanlış bir hit, doğru olandan yalnızca
0.02 kadar aşağıdadır. Yani skor bir doğruluk sinyali değildir ve üründe hiçbir
şey bir karar için skor okumaz.
İlgi eşiği — varsayılanı SEARCH_SCORE_FLOOR=0.82 — bambaşka bir şeye karşı ölçüldü:
dokümantasyonunuzla hiç ilgisi olmayan sorulara. Onların en iyi sonucu ancak 0.829’a çıkıyor,
gerçek bir sorunun en düşük en iyi sonucu ise 0.833. Eşiğin iddiasının tamamı bu aralıktır. Eşiğin
yakalamadığı şey, ürününüz gibi görünen ama cevabı hiçbir yerde yazılı olmayan sorudur — onlar tam
olarak gerçek soruların aldığı skorları alır ve hiçbir eşik ikisini ayırmaz. Eşiğin altında
search_docs, bir ajanın alıntılayacağı bir sonucu uzatmak yerine iyi eşleşme yok der.
Skorlar bir proje ve bir model içinde karşılaştırılabilirdir, farklı modeller arasında anlamsızdır —
EMBEDDING_MODEL’i değiştirirseniz eşik npm run eval ile yeniden ölçülmelidir.
SEARCH_SCORE_FLOOR (varsayılanı 0.82) sunucunun eşiğidir. Bir proje, kendi korpusu farklı ölçtüğünde
— düzyazı ağırlıklı korpuslar genelde daha düşük bir eşik ister — sorgu panelinden kendi eşiğini
ayarlayabilir, ya da eşiği kendisi için tamamen kapatabilir. Panel, bir eşiğin, uygulanmadan önce zaten
kaydedilmiş aramalara ne yapacağını gösterir, ve sunucunun SEARCH_SCORE_FLOOR=0’ı yine de her projenin
eşiğini kapatır. Bkz. Yapılandırma.
Birleştirilen sıralamalardır, skorlar değil. Cosine similarity ve PostgreSQL’in ts_rank’i
karşılaştırılabilir nicelikler değildir, bu yüzden füzyon her parçanın her iki tarafta tuttuğu
sıralamayı birleştirir — göründüğü listeler üzerinden Σ 1/(60 + sıra) (reciprocal rank fusion) —
embedding modeli her değiştiğinde yeniden öğrenilmesi gereken ağırlıklı bir toplam yerine. Sonuç:
bir sonucun skoru artık konumunu açıklamaz — 0.86 skorlu biri 0.88 skorlu birinin üstünde
oturabilir. GET /api/projects/:id/search ve pano, her sonucun her iki tarafta tuttuğu sıralamayı
D3 L1 olarak gösterir; bir L’si olup bir D’si olmayan bir sonuç, salt vektör aramanın tek başına
göremeyeceği bir tanımlayıcıdır. Altın kümede, sıralama füzyonu tanımlayıcı biçimli otuz soruyu
recall@5 %76,7’den %83,3’e taşıdı. Doğal dilde sorulan otuz dört soruyu ise hiç kıpırdatmadı;
recall@1 %78,1’den %75,0’a düştü — bu, füzyonun yaptığı takas: yalnızca bir yarı tarafından bulunan
bir parça, ikisi tarafından da makul biçimde bulunan birinin önüne geçemez.
Varsayılan model asimetriktir: bir arama sorgusunun önüne query: , indekslenen bir pasajın önüne
ise passage: konularak eğitilmiştir, bu yüzden sunucu onları otomatik olarak ekler — indekslediğiniz
ya da aradığınız hiçbir şey onlardan hiç bahsetmez, ve önekleri olmayan bir model bir sorguyu ve bir
pasajı hiçbir bedel ödemeden özdeş biçimde kodlar. Retrieval korpusunda önekler bir soruyu sıra 1’de
kazandırdı ve recall@5’te hiçbir şey değiştirmedi; EMBEDDING_PASSAGE_PREFIX=none ile birlikte
EMBEDDING_QUERY_PREFIX=none onları kapatır ve önceki model kimliğini tam olarak geri getirir.
Anahtar sözcük yarısı varsayılan olarak her kaynağı PostgreSQL’in simple yapılandırmasında
indeksler — yazıldığı gibi sözcükler, hiçbir stemming olmadan — ki bu referans materyali için doğru
seçimdir: HALYARD_DISPATCH_TIMEOUT ve HLY-4015, oldukları dizeler olarak indekste hayatta kalır.
Bir kaynak bunun yerine kendi dilini adlandırabilir (kaynak formundaki Language alanı), ki bu onu
stemmed bir yapılandırmaya geçirir — anahtarı hakkında Türkçe sorulan bir soru o zaman
anahtarın diyen bir sayfayı bulur, simple’ın ilişkisiz saydığı iki dize. İkisi bir projede
birbirine karışmadan birlikte yaşar: her kaynak kendi yapılandırmasında aranır ve tek bir sorgu
hepsine ulaşır, her biri füzyona kendi sıralı listesiyle katkıda bulunur. Bir kaynağın dilini
değiştirmek onu yeniden indeksler.
Diller arası arama bilinen bir sınırdır ve düzeltilmeyecek
Soruyu, onu cevaplaması gereken dokümantasyonla aynı dilde yazın. Türkçe sayfalar Türkçe biçimli
ifadeler için yüzeye çıkar. İngilizce sayfalar İngilizce biçimli ifadeler için yüzeye çıkar. O sınırı
geçmek çoğunlukla hiçbir şey döndürmez, ve bu açılabilecek bir ayar değil, embedding modelinin bir
özelliğidir. Her MCP client’ı bir dil modelidir, ve list_topics bir kaynağın dilini adlandırırken
instructions çağıran agent’a search_docs sorgusunu ona uyacak şekilde yazmasını yönlendirir — arama
mekanizmasının kendisinde bir değişiklik değil, çağırana verilen bir yönergedir.
Ölçüm eval/BASELINE.md’dedir
ve fark kıl payı değil: otuz soru korpustan, her biri sorunun ifade edilmediği dildeki bir
sayfa hakkında yazıldı — her yön için on beş. Dördü ilk beşte cevaplanıyor. Otuzdan yirmi yedisi bunun
yerine sıra 1’de ifadenin kendi dilindeki bir sayfayı döndürüyor: model neyin sorulduğunu anlamakta
başarısız olmuyor, ifadenin dilini cevabının üstünde sıralıyor. İki yön de eşit biçimde başarısız
oluyor.
Tanımlayıcılar istisnadır. HALYARD_DISPATCH_TIMEOUT, HLY-4015, X-Halyard-Signature her iki
dilde de aynı dizedir, bu yüzden aramanın anahtar sözcük yarısı onları ne olursa olsun bulur. Kümedeki
bir tanımlayıcı adlandıran dokuz diller-arası sorudan dördü cevaplanıyor; sıradan sorular olarak
ifade edilen yirmi birinden ise hiçbiri cevaplanmıyor.
Bir düzeltme beklemek yerine ne yapılır. Her dili kendi kaynağında tutun ve bir agent’ın aramasını
source, path_prefix ya da version ile kapsamlandırmasına izin verin — her ikisi de iyi cevap veren
iki kaynak, her iki dili de kötü cevaplayan tek bir koleksiyondan iyidir. Bilinen iki çözüm de zaten
ölçülmüştür: hibrit arama (yukarıda) diller-arası tanımlayıcı getirimini kurtardı ve hiçbir doğal dil
sorusunu kurtarmadı; SEARCH_RERANK arkasındaki çok dilli bir cross-encoder rerank’i diller-arası
recall@5’i %13,3’ten %33,3’e taşıdı — yine de bundan önceki gömme modelinin ulaştığı %42,9’un altında —
ve on üç sıradan sorunun sıra-1 cevabına ve bir aramanın gecikmesinin 12 ms’den 1,2 sn’ye çıkmasına mal oldu.
Varsayılan olarak kapalıdır ve kapalı kalmalıdır. Ek bir index olarak ikinci, çeviri üzerine eğitilmiş
bir encoder bunu gerçekten düzeltirdi — kabaca dört katı bir indirme, ikinci bir vektör kolonu, her
kurulumda tam bir yeniden indeksleme — ve bu bedel bir dokümantasyon sunucusu için ödemeye değmez.
Diller arası arama korpusunuz için gerekliyse, bu, bu ürünü beklemek için değil, farklı bir araç seçmek
için bir nedendir.
Air-gapped kurulumlar
Varsayılan bir kurulumun yaptığı tek dışa giden istek, tek seferlik model indirmesidir. Onu da kaldırmak için:
- Models volume’unu (
/app/.cache/models) model dosyalarıyla önceden doldurun — örneğin bağlı bir host’ta bir kere çalıştırıp volume’u kopyalayarak. EMBEDDING_OFFLINE=1ayarlayın.
Sunucu bundan sonra hiçbir zaman indirme denemez ve model eksikse gürültülü biçimde başarısız olur.
Performans notları
- Gömme CPU’ya bağlıdır ve indekslemenin yavaş kısmıdır; aynı anda tek bir projenin indekslenmesinin nedeni budur.
EMBEDDING_BATCH_SIZE(varsayılan 16), her çağrıda kaç parçanın gömüleceğini denetler.- Bir sorgu tam olarak tek bir kısa metni gömer, bu yüzden arama gecikmesine model değil veritabanı hakimdir.
- Model, başlangıçta arka planda yüklenir — pano yüklenirken de çalışır, ama indeksleme ve arama onu bekler.