İçeriğe geç
ceaksan

Hybrid Search: FTS5 + Vector + RRF ile Akıllı Arama Mimarisi

Keyword search tek başına yetersiz. Vector search de. İkisini Reciprocal Rank Fusion ile birleştirdiğinizde ne oluyor? dnomia-knowledge implementasyonu üzerinden hybrid search mimarisi.

26 Şub 2026 5 dk okuma
TL;DR

Keyword search (BM25) kelimeleri bulur ama anlamı kaçırır. Vector search (embedding) anlamı yakalar ama kısaltmaları kaçırır. İkisini Reciprocal Rank Fusion (RRF) ile birleştirdiğinizde, her iki yöntemin de güçlü olduğu sonuçlar üste çıkar. dnomia-knowledge bu hybrid yaklaşımı SQLite FTS5 + sqlite-vec + RRF ile tek dosyada uyguluyor. 340 karakterlik odaklı bir chunk, 1.5KB çok konulu dokümandan 2.3 kat daha yüksek similarity skoru alıyor.

“GTM datalayer debug” diye arıyorsun. Keyword search “GTM” ve “datalayer” kelimelerini içeren dokümanları buluyor. Ama “etkinlik verilerinin izlenmesinde sorun giderme” başlıklı, tam senin aradığın dokümanı bulamıyor, çünkü o kelimeler geçmiyor.

Vector search ile arıyorsun. Embedding modeli “GTM datalayer debug” ile “etkinlik verilerinin izlenmesinde sorun giderme” arasındaki anlamsal benzerliği yakalıyor. Ama bu sefer “GTM” kısaltmasını içeren konfigürasyon dosyasını kaçırıyor, çünkü embedding 3 harflik kısaltmayı anlamsal olarak eşleştiremiyor.

İkisini birlikte çalıştırdığında, ikisinin de güçlü olduğu sonuçlar üste çıkıyor. Bu, hybrid search.

Keyword Search: BM25 ile Kelime Eşleştirme

BM25 (Best Matching 25) bir metin arama algoritması. Temel mantığı basit ama etkili:

  1. Kelime sıklığı (TF): Aranan kelime dokümanda ne kadar sık geçiyorsa, skor o kadar yükselir
  2. Doküman uzunluğu normalizasyonu: Uzun dokümanlar sırf uzun oldukları için yüksek skor almasın diye düzeltme yapılır
  3. Ters doküman sıklığı (IDF): Kelime koleksiyondaki her dokümanda geçiyorsa (örneğin “ve”, “bir”), değeri düşer. Nadir kelimeler daha değerli

SQLite’ın built-in FTS5 (Full-Text Search 5) modülü BM25 scoring kullanır. Ayrı bir sunucu gerektirmez, veritabanı dosyasının içinde çalışır.

Ne zaman güçlü?

  • Kısaltmalar: “GTM”, “sGTM”, “CAPI”, “RRF”
  • Değişken/fonksiyon adları: handleSubmit, datalayer.push
  • Hata kodları: “ERR_CONNECTION_REFUSED”, “404”
  • Exact phrase: “consent mode v2 advanced”

Ne zaman zayıf?

  • Eş anlamlılar: “performans” ararken “hız” veya “speed” dönemez
  • Çapraz dil: “sepet terk” ararken “cart abandonment” bulamaz
  • Kavramsal sorgu: “kullanıcı neden sayfadan ayrılıyor?” gibi soyut sorularda

Vector Search: Embedding ile Anlam Eşleştirme

Embedding, metni yüksek boyutlu bir vektöre (sayı dizisi) dönüştürür. “Sepet terk oranı” ve “cart abandonment rate” farklı kelimeler ama embedding uzayında birbirine yakın noktalarda bulunur.

Benzerlik ölçümü cosine similarity ile yapılır: iki vektör arasındaki açının kosinüsü. 1.0 = birebir aynı yön, 0.0 = ilişkisiz, -1.0 = zıt.

dnomia-knowledge intfloat/multilingual-e5-base modeli kullanıyor (768 boyut, çok dilli). sqlite-vec eklentisi SQLite’a KNN (K-Nearest Neighbors) vektör araması ekliyor.

Ne zaman güçlü?

  • Anlamsal sorgu: “tracking neden çalışmıyor?” ararken debug rehberini bulur
  • Çapraz dil: Türkçe soru, İngilizce doküman (veya tersi)
  • Kavramsal benzerlik: “veri kaybı” ararken “data loss prevention” döner

Ne zaman zayıf?

  • Kısaltmalar: “GTM” için embedding çok genel, spesifik eşleşme zayıf
  • Yeni/nadir terimler: Modelin eğitim verisinde olmayan kelimeler
  • Exact match: “consent_mode_v2” gibi teknik terimlerde keyword daha isabetli

Hybrid Search: İkisini Birleştirmek

İki arama motoru farklı güçlere sahip. Hybrid search ikisini birlikte çalıştırıp sonuçları birleştirir.

Reciprocal Rank Fusion (RRF)

RRF, iki farklı sıralı listeyi tek bir sıralı listeye birleştirmenin yöntemi. Formül:

RRF_score(d) = Σ 1 / (k + rank_i(d))

k sabiti genellikle 60 (orijinal paper’daki değer). rank_i(d) dokümanın i. listesindeki sırası.

Somut örnek:

BM25 sonuçları:     1. dosya-A, 2. dosya-C, 3. dosya-B
Vector sonuçları:   1. dosya-B, 2. dosya-A, 3. dosya-D

RRF birleştirme (k=60):
  dosya-A: 1/(60+1) + 1/(60+2) = 0.0164 + 0.0161 = 0.0325
  dosya-B: 1/(60+3) + 1/(60+1) = 0.0159 + 0.0164 = 0.0323
  dosya-C: 1/(60+2) + 0         = 0.0161
  dosya-D: 0         + 1/(60+3) = 0.0159

Final sıralama: dosya-A, dosya-B, dosya-C, dosya-D

İki arama motorunun “oy kullanması” gibi düşün. İkisi de yüksek sıralıyorsa, doküman kesinlikle ilgili. Sadece birinde yüksekse, yine listeye girer ama daha aşağıda.

Neden basit skor ortalaması değil?

BM25 skoru ve cosine similarity farklı ölçeklerde. BM25 0-25 arası, cosine 0-1 arası. Doğrudan ortalama almak büyük ölçeği (BM25) dominant kılar. RRF sıralama bazlı çalıştığı için ölçek farkından etkilenmez.

Chunk Boyutu: Küçük ve Odaklı Kazanır

Hybrid search’ün etkinliği doğrudan chunk kalitesine bağlı. Burada kritik bir bulgu var:

340 karakterlik odaklı bir kural, hedef sorgusu için 0.57 similarity skoru aldı. Aynı bilgiyi içeren 1.5KB çok konulu bir doküman, aynı sorgu için 0.25 skor aldı1.

2.3 kat fark. Aynı bilgi, farklı paketleme. Neden?

Embedding modeli tüm chunk’ı tek bir vektöre sıkıştırır. Chunk büyüdükçe, her kavram daha az temsil edilir. 340 karakterlik chunk “consent mode v2 advanced modda cookie yazılmaz ama cookieless ping gönderilir” dediğinde, embedding tam bu anlama odaklanır. 1.5KB’lık doküman aynı cümleyi içerse bile, diğer 20 cümle embedding’i sulandırır.

dnomia-knowledge heading bazlı chunking kullanıyor: ## ve ### başlıklarında bölüyor, minimum 200 karakter birleştirme uyguluyor. Bu, her chunk’ın tek bir konuya odaklı olmasını sağlıyor.

Structured Matching: TF-IDF Bazen Daha İyi

Her şey embedding gerektirmiyor. Yapılandırılmış eşleşmelerde (skill routing, tag matching, exact kategori eşleştirme) TF-IDF, embeddings’ten 250 kat daha verimli olabiliyor2.

Neden? Embedding modeli yüklemek, çalıştırmak ve inference yapmak computational olarak pahalı. “Bu sorgu hangi skill’e gitmeli?” gibi bir routing kararında, anahtar kelime eşleştirmesi (TF-IDF) milisaniyede cevap verirken, embedding modeli yüzlerce milisaniye sürer.

Kural: embedding karmaşık, anlamsal sorular için. Keyword/TF-IDF yapısal, kategorik eşleştirmeler için.

Pratik Uygulama: dnomia-knowledge

dnomia-knowledge bu hybrid yaklaşımı SQLite üzerinde tek dosyada uyguluyor:

SQLite veritabanı (tek .db dosyası)
  ├── FTS5 tablosu (BM25 keyword search)
  ├── sqlite-vec tablosu (vector KNN search)
  └── chunk tablosu (metadata, dosya yolu, proje)

Arama akışı:

Sorgu geldi: "GTM consent mode debug"

1. FTS5 arama (BM25): "GTM", "consent", "mode", "debug" kelimelerini ara
   → Sonuç: [chunk-A (skor 12.3), chunk-C (skor 8.1), chunk-E (skor 5.2)]

2. Vector arama (cosine): sorguyu embed et, en yakın chunk'ları bul
   → Sonuç: [chunk-B (skor 0.82), chunk-A (skor 0.74), chunk-D (skor 0.68)]

3. RRF birleştirme (k=60): iki listeyi sıralama bazlı birleştir
   → Final: [chunk-A (0.0325), chunk-B (0.0323), chunk-C (0.0161), ...]

chunk-A iki listede de üst sıralarda, en ilgili sonuç. chunk-B sadece vector’da güçlü (anlamsal benzerlik), chunk-C sadece keyword’de güçlü (kelime eşleşmesi). İkisi de listeye giriyor ama chunk-A’nın altında.

Performans

  • FTS5 arama: < 1ms (SQLite native, indeksli)
  • Vector arama: < 10ms (sqlite-vec, 768 boyut, birkaç bin chunk)
  • Embedding üretimi: ~50ms/sorgu (multilingual-e5-base, CPU)
  • Toplam: ~60ms/sorgu

Ayrı Elasticsearch + Pinecone kurulumu gerektirmez. Tek SQLite dosyası, tek Python process.

Prefix Kuralı

intfloat/e5 modeli prefix gerektirir:

  • Sorgular: "query: GTM consent mode debug"
  • Dokümanlar: "passage: Bu rehberde consent mode v2 yapılandırması..."

Prefix olmadan similarity skorları düşer. Model bu prefix’lere göre eğitilmiş, sorgu ve doküman vektörlerini farklı uzaylara haritalıyor.

Fallback Stratejisi

Bazen hybrid search bile sonuç döndürmez. dnomia-knowledge üç katmanlı fallback kullanıyor:

  1. Hybrid search (RRF): İlk deneme, en iyi sonuçlar
  2. Prefix fallback: Sonuç yoksa, sorgu terimlerinin başlangıç eşleşmesiyle ara (GTM → “GTM server”, “GTM preview”)
  3. Sadece FTS5: Vector sonuç döndürmezse, keyword sonuçlarını tek başına döndür

Bu, “hiç sonuç yok” durumunu minimize ediyor.

Ne Zaman Hangi Yaklaşım?

SenaryoEn İyi YaklaşımNeden
Kısaltma/kod aramaBM25 (keyword)Exact match kritik
”Bu neden çalışmıyor?”Vector (embedding)Anlamsal benzerlik gerekli
Genel aramaHybrid (RRF)İkisinin güçlü yanlarını birleştir
Skill routingTF-IDFHız ve verimlilik
Çapraz dil aramaVector (embedding)Multilingual model dil bariyerini aşar

Sonuç

Hybrid search, “ya keyword ya vector” ikileminden çıkış yolu. RRF ile birleştirme, iki farklı gücü tek bir sıralamaya dönüştürüyor.

Ama teknoloji seçimi kadar chunk kalitesi de önemli. 340 karakter odaklı bilgi, 1.5KB dağınık dokümandan 2.3 kat daha etkili. Arama motorunu iyileştirmeden önce, arama yapılacak veriyi iyileştirmek genellikle daha büyük etki yaratır.

Chunk kalitesinin bir üst seviyesi ise chunk’ı tamamen ortadan kaldırmak. jCodeMunch, Tree-sitter AST parsing ile kod sembollerini (fonksiyon, sınıf, import) doğrudan indeksliyor ve byte-offset ile O(1) erişim sağlıyor. Chunk-based RAG’da fonksiyonun ortasından bölünme riski varken, symbol-based retrieval tam bir mantıksal birim döndürüyor. FastAPI codebase’inde chunk RAG 330K token + %74 precision verirken, symbol retrieval 480 token + %96 precision veriyor3. Kod aramasında chunk’lamak yerine AST’den symbol çıkarmak, metin aramasında hybrid search’ün yaptığını bir adım ileriye taşıyor.

Kaynak kodu: dnomia-knowledge (MIT lisans, SQLite + FTS5 + sqlite-vec + RRF)

Footnotes

  1. Memory Vault shared brain çalışması. 340 karakter focused rule vs 1.5KB multi-topic document similarity karşılaştırması. Kaynak: dev.to/tars_mistaike
  2. Skill resolver token economics. TF-IDF vs embeddings structured matching benchmark. Kaynak: dev.to/comeonoliver
  3. Gravelle, J. (2026). Bringing The Receipts: 95% AI LLM Token Savings. Dev.to. jCodeMunch: Tree-sitter AST parsing ile symbol-level code retrieval. FastAPI benchmark: chunk RAG 330K token / %74 precision vs symbol retrieval 480 token / %96 precision. 15 task, 3 repo üzerinden ortalama %95 token azalma.
Önemli Noktalar
  • 01 BM25 (keyword) kelimeleri eşleştirir, TF-IDF ağırlıklı. Kısaltmalar, değişken adları, hata kodları için güçlü.
  • 02 Vector search (embedding) anlamsal benzerliği yakalar. 'sepet terk oranı' ararken 'cart abandonment rate' döner.
  • 03 İkisi birlikte çalıştığında birbirinin zayıf noktasını kapatır. RRF ile birleştirme, sonuç listelerini sıralama bazlı puanlıyor.
  • 04 Chunk boyutu kritik: 340 karakter odaklı kural 0.57 similarity, 1.5KB çok konulu doküman 0.25. Küçük ve odaklı her zaman kazanır.
  • 05 Yapılandırılmış eşleşmelerde (skill routing, tag matching) TF-IDF embeddings'ten 250 kat daha verimli olabiliyor.
Sık Sorulan Sorular (FAQ)
+ BM25 nedir?

Best Matching 25, bir metin arama algoritması. Kelimenin dokümandaki sıklığına, doküman uzunluğuna ve koleksiyondaki nadirliğine bakarak skor hesaplar. Google'ın ilk yıllarında temel arama mantığı olarak kullanıldı.

+ Embedding search nedir?

Metni yüksek boyutlu bir vektöre dönüştürüp, vektörler arası mesafe (cosine similarity) ile benzerlik ölçen arama yöntemi. 'Anlam' bazlı çalışır: farklı kelimelerle ifade edilen aynı kavramı bulabilir.

+ RRF (Reciprocal Rank Fusion) nedir?

İki farklı arama sonucunu birleştirme yöntemi. Her sonuç listesinde bir dokümanın sırasına göre puan verir: 1/(k+sıra). k genellikle 60. İki listede de üst sıralarda olan dokümanlar en yüksek puanı alır.

+ Neden sadece vector search kullanmıyoruz?

Vector search anlamı yakalar ama exact match'te zayıf. 'GTM' ararken, embedding 'Google Tag Manager' ile 'tag management' arasında ayrım yapamayabilir. BM25 ise 'GTM' kısaltmasını doğrudan eşleştirir.

+ SQLite ile vector search yapılabilir mi?

Evet. sqlite-vec eklentisi SQLite'a vector arama yeteneği ekler. FTS5 zaten built-in. İkisi aynı veritabanında, ayrı sunucu gerektirmeden çalışır.