# Gömme Modelleri

> Varsayılan local model, OpenAI'a geçiş, modeli güvenle değiştirmek, arama sonuçlarının nasıl puanlandığı ve tamamen air-gapped çalıştırma.

- Güncelleme: 2026-09-21
- Kaynak: https://contextator.com/tr/docs/embedding-models/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
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

```bash
EMBEDDING_DTYPE=q8                       # aynı model, ~120 MB, quantized
```

```bash
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

```bash
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)

1. `EMBEDDING_MODEL`'i değiştirin.
2. Yeniden başlatın.
3. 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:

1. Provider'ı, key'i ve `EMBEDDING_DIMENSIONS`'ı ayarlayın.
2. **Bir kere** `RESET_VECTORS=1` ile başlatın. Her parça ve belge düşürülür, kolon yeniden
   tipenir (re-typed).
3. `RESET_VECTORS`'ı kaldırın (ya da `0`'a geri ayarlayın).
4. 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 **hibrit**tir: anlam ve birebir sözcük aynı anda aranır ve sıralarıyla birleştirilir.

1. Sorunuz, belgeleri indeksleyen aynı modelle gömülür.
2. 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 `tsvector` anahtar 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ı.
3. 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
yakala**ma**dığı ş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](/tr/docs/configuration/).

**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`](https://github.com/Contextator/Contextator/blob/main/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:

1. 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.
2. `EMBEDDING_OFFLINE=1` ayarlayı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.
