Artikel ini adalah bagian ketiga dari serial tentang pengembangan fitur seCall — alat CLI untuk mengumpulkan dan mencari sesi percakapan agen AI. Dibuat sebagai bagian dari proyek Vibe Coding, jadi saya tidak tahu dan tidak ingin tahu detail kodenya. Tidak apa-apa jika tidak tahu. Kalau tahu malah jadi repot. Kalau ingin tahu, tanya ke pembuat(?)nya. Tetapi saya menulis ini karena ingin menguraikan teknologi yang digunakan dalam fitur yang diterapkan setelah trial and error, dan alasan penerapannya.
Situasi Hingga Saat Ini
Saya membuat dua jenis pencarian.
BM25 (Pencarian Teks): Jika mengetik "model", akan menemukan percakapan yang berisi "model". Akurat, tetapi tidak dapat menemukan "desain" dengan "arsitektur".
Vektor (Pencarian Semantik): Mencari "arsitektur" juga menemukan percakapan terkait "desain". Cerdas, tetapi terkadang lebih buruk dari BM25 dalam pencocokan kata kunci yang tepat.
Saat menggunakannya, hal seperti ini terjadi. Saat mencari "indeksing BM25" — BM25 dengan tepat menunjukkan percakapan tempat kata itu muncul, sedangkan vektor secara luas membawa percakapan konteks serupa seperti "optimasi pencarian" dan "struktur indeks". Sebaliknya, saat mencari sesuatu yang samar-samar seperti "mengapa pencariannya lambat", BM25 hampir tidak bisa menangkap, tetapi vektor menemukan percakapan terkait dengan baik.
Kedua-duanya memiliki kelebihan dan kekurangan, jadi kesimpulan yang wajar adalah satu: Mengapa tidak menggunakan keduanya?
Apa itu Pencarian Hibrida
Artinya, menjalankan pencarian teks dan pencarian vektor secara bersamaan dan menggabungkan hasilnya. Konsepnya sederhana, tetapi masalahnya adalah bagaimana cara menggabungkannya.
BM25 memberikan skor dalam kisaran seperti 0-25, sedangkan vektor memberikan kesamaan kosinus antara 0-1. Skalanya sama sekali berbeda. Apakah 15 poin di BM25 itu tinggi, atau 0,7 di vektor itu tinggi? Perbandingan langsung tidak mungkin dilakukan.
Anda bisa menormalkan skor dan menggabungkannya, tetapi bagaimana cara menetapkan standar normalisasi menjadi masalah lagi. Karena distribusi hasil berubah setiap saat.
RRF — Menggabungkan Peringkat Bukan Skor
Dari sini muncul algoritma **RRF (Reciprocal Rank Fusion)**. Namanya terdengar besar, tetapi ideanya sangat sederhana. (Dan ini bukan buatan saya. Percayalah) Hal yang menarik adalah algoritma itu sendiri tidak terduga sederhana, tetapi menjadi standar industri seiring dengan berkembangnya ML/AI — algoritma malang(?).
Abaikan skor, hanya lihat peringkat (ranking) di setiap pencarian.
Jika ada dokumen yang berada di peringkat 3 di BM25 dan peringkat 7 di vektor — kami memberikan skor komposit hanya berdasarkan fakta bahwa itu peringkat 3 dan fakta bahwa itu peringkat 7. Tidak peduli apa skor aslinya.
Formulanya juga sederhana:
Skor RRF = 1/(k + peringkat BM25) + 1/(k + peringkat vektor)
k adalah konstanta, dan seCall menggunakan 60. Peran k adalah — jika nilai besar, menjadi kurang sensitif terhadap perbedaan peringkat (mencerminkan secara merata), dan jika kecil, mengkonsentrasikan ke peringkat atas. Katanya 60 adalah nilai default yang sering digunakan dalam praktik, jadi saya menggunakannya begitu saja(ini adalah getaran!). Jujur saja, saya tidak memiliki volume data sebanyak itu untuk menyesuaikan angka ini. (Mohon).
Sebagai contoh:
Dokumen | Peringkat BM25 | Peringkat Vektor | Skor RRF |
|---|
Percakapan A | Peringkat 1 | Peringkat 5 | 1/61 + 1/65 = 0.032 |
Percakapan B | Peringkat 10 | Peringkat 2 | 1/70 + 1/62 = 0.030 |
Percakapan C | Peringkat 3 | Peringkat 3 | 1/63 + 1/63 = 0.032 |
Percakapan A tinggi karena peringkat 1 di BM25, dan Percakapan C tinggi karena peringkat 3 di kedua sisinya. Bahkan jika hanya tampil bagus di satu sisi, atau tampil layak di kedua sisinya, Anda bisa naik ke atas. Hasil yang sepenuhnya hilang dari satu pencarian juga bertahan jika peringkat atas di sisi lain.
Ini adalah daya tarik RRF — menggabungkan dengan rapi hanya berdasarkan peringkat tanpa khawatir mencocokkan skala skor.(Sekali lagi, ini bukan buatan saya. Percayalah)
Optimasi Kinerja: Jangan Cari Semuanya
Ada satu bagian lain yang diperhatikan dalam pencarian hibrida. Pencarian vektor pada dasarnya berjalan terhadap semua vektor. Jika ada ribuan percakapan dan puluhan ribu chunk, melewati semuanya setiap kali adalah pemborosan.
seCall menjalankannya dalam urutan ini:
Jalankan BM25 terlebih dahulu — karena cepat. Ambil kandidat dengan murah hati (3 kali jumlah permintaan!,getaran!)
Jalankan pencarian vektor hanya dalam rentang ID sesi yang ditemukan BM25
Gabungkan kedua hasil dengan RRF
BM25 bertindak sebagai semacam filter. Karena vektor hanya berjalan dalam "rentang yang setidaknya agak relevan secara tekstual", perbandingan yang tidak perlu berkurang drastis. Jika BM25 tidak memiliki hasil sama sekali, maka beralih ke pencarian vektor penuh.
Berkat struktur ini, untuk 600 percakapan, pencarian hibrida secara subjektif bahkan lebih cepat dari pencarian vektor saja.(Serius)
Apakah Benar-Benar Ada Perbedaan?
Jujur saja, untuk ratusan percakapan, BM25 saja sering sudah cukup. Pencarian hibrida bersinar dalam kasus seperti ini:
Pencarian yang samar-samar seperti "apa itu tadi" — BM25 bingung karena tidak ada kata kunci tetapi vektor menangkapnya dengan konteks
Pencarian pesan kesalahan yang tepat — vektor membawa semua kesalahan serupa tetapi BM25 dengan tepat menunjukkan pesan itu
Pencarian campuran Korea-Inggris — saat mencari "설계", percakapan "architecture" juga muncul bersama
Hasil yang terlewatkan oleh satu sisi saja ditangkap dengan menggabungkan. Mungkin bukan perbedaan dramatis, tetapi sekali Anda merasakannya, Anda tidak ingin kembali.
Jadi Setelah Menggabungkan, Apakah Benar-Benar Menjadi Lebih Baik?
Jujur saja, saya tidak menjalankan benchmark akademis.(Saya tidak memiliki kecerdasan atau kemampuan untuk melakukannya 😇) Untuk mengukur metrik seperti precision dan recall, Anda memerlukan dataset jawaban ground truth, tetapi tidak ada jawaban yang benar dalam percakapan pribadi. Tetap saja, ada proses verifikasi, jadi saya hanya menjalankan istilah pencarian yang sama dalam tiga mode berbeda dan membandingkannya.
Untuk tujuan debugging, saya membuat tiga mode pencarian: --lex-only (hanya BM25), --vec-only (hanya vektor), dan default (hibrida). Karena skor BM25 dan skor vektor dicetak terpisah dalam hasil pencarian, Anda dapat melihat dengan mata yang mana yang berkontribusi.(Ini bukan yang awalnya saya buat, tetapi...)
Ada beberapa pola:
Pencarian kata kunci yang tepat seperti "indeksing BM25" BM25: dengan tepat menunjukkan 5 percakapan tempat kata itu muncul ✓ Vektor: membawa 15 percakapan lebar dengan percakapan sekitar seperti "optimasi pencarian" dan "struktur indeks". Terkait tetapi bukan yang diinginkan juga bercampur ✗ Hibrida: 5 yang tepat yang ditemukan BM25 berada di atas, percakapan terkait yang ditemukan vektor di bawah. Menjadi akurat sambil memperluas jangkauan ✓
Pencarian samar-samar seperti "mengapa itu tidak bekerja itu" BM25: token seperti "mengapa" dan "tidak bekerja" terlalu umum, jadi percakapan yang tidak relevan naik ✗ Vektor: menangkap percakapan dengan konteks kesalahan/kegagalan dengan baik ✓ Hibrida: hasil vektor mendominasi, tetapi jika ada yang tertangkap BM25, itu naik ✓
Apakah "desain" dalam pencarian juga menampilkan "architecture" BM25: tentu saja tidak ✗ Vektor: ya ✓ Hibrida: ya. Percakapan tempat "desain" digunakan dengan tepat lebih dulu, diikuti percakapan "architecture" ✓
Kesimpulannya — tidak ada kasus di mana hibrida lebih buruk dari satu sisi saja. Bahkan dalam kasus terburuk sekalipun, hasil dari pihak yang lebih baik naik. RRF "penjumlahan peringkat" tampaknya memainkan peran jaringan pengaman ini dengan baik. Ini adalah struktur di mana satu pihak berkompromi bahkan jika satu pihak melakukan kesalahan. Tentu saja, ini adalah perbandingan intuitif dari 600 percakapan, jadi jauh dari verifikasi akademis. Namun untuk alat pribadi, "apakah apa yang saya inginkan keluar dengan baik saat saya menggunakannya" adalah praktis satu-satunya metrik, dan menurut standar itu, metode hibrida saat ini adalah yang terbaik yang saya tahu(mohon).
Ringkasan
Teknologi | Peran | Alasan Pemilihan |
|---|
Pencarian Hibrida | Eksekusi BM25 + vektor bersamaan | Menangkap ketepatan teks + kesamaan semantik keduanya |
RRF (k=60) | Algoritma penggabungan hasil | Abaikan skala skor, jumlahkan hanya peringkat — implementasi sederhana |
Pipeline BM25-first | Batasi jangkauan pencarian vektor | Kurangi perbandingan vektor yang tidak perlu, optimalkan kecepatan |
Melihat kembali, membuat FTS5 + analisis morfologi di #1, pencarian vektor di #2, dan menggabungkannya dengan RRF di #3 — bukan karena ada rencana, tetapi karena memikirkan bagaimana cara menutupi kekurangan masing-masing, struktur ini muncul secara alami.(Tidak ada teman dan terus berbicara hanya dengan AI, jadi cara berbicara juga menjadi alami seperti ini.🫢) Beginilah adanya Vibe Coding. Bukan sesuatu yang berjalan sesuai rencana, tetapi struktur yang masuk akal muncul saat Anda terus menggali. Namun verifikasi selalu diperlukan. (Dalam proses ini alur kerja adalah ide - implementasi - verifikasi - perkuatan - verifikasi - perkuatan - makan - verifikasi - perkuatan - aaargh!... lingkaran infinity neraka)
Di edisi berikutnya, saya akan mengakhiri seri(?) ini dengan cerita tentang mengatur percakapan yang dikumpulkan dan dicari ini ke dalam Obsidian Vault sebagai wiki markdown. Pencarian bagus, tetapi ada kesenangan tersendiri dalam mengulang dengan melihat percakapan atau dokumen dalam bentuk wiki selama waktu luang.
Tulisan ini ditulis oleh no(tidak)-vibe. Anda dapat membaca dengan aman 😁 Mencoba-coba di lahan kosong bukan pengembangan agentic. Kolaborasi monster paling kacau — piggyback tiga orang manusia dengan domain knowledge dari pengalaman beragam yang terakumulasi, dan pekerjaan presisi yang tidak lelah dari kecerdasan buatan dengan framework yang kokoh.