Hybrid Search dan Reranking: RAG Retrieval yang Lebih Baik

Foto oleh xmodulo on flickr
Hybrid search menjalankan retriever leksikal, biasanya BM25, dan retriever vector dense terhadap document store yang sama, lalu menggabungkan kedua daftar peringkat menjadi satu. Pendekatan ini ada karena vector search saja melewatkan kecocokan eksak seperti kode produk dan akronim jarang, sementara BM25 saja melewatkan query yang diparafrasekan atau bersifat konseptual, sehingga menggabungkan keduanya mengembalikan kekuatan masing-masing.
Skor BM25 dan cosine similarity vector berada pada skala dan distribusi yang sama sekali berbeda, sehingga merata-ratakannya secara langsung cenderung memihak retriever mana pun yang menghasilkan angka lebih besar. Reciprocal rank fusion hanya melihat posisi peringkat setiap dokumen pada setiap daftar, sehingga sepenuhnya menghindari kebutuhan normalisasi skor dan tidak memerlukan penyetelan.
Laporan produksi dan studi benchmark umumnya menunjukkan kenaikan NDCG pada 10 sebesar lima hingga lima belas poin setelah menambahkan cross-encoder reranker, dengan kenaikan lebih besar pada dataset yang sulit secara leksikal. Pola umumnya adalah mengambil lima puluh hingga seratus kandidat dengan hybrid search, lalu melakukan reranking hingga tersisa delapan sampai lima belas passage sebelum generation.
Bisa, dan biasanya hasilnya akan lebih baik daripada vector search saja, tetapi hybrid search tetap memeringkat dokumen menggunakan sinyal yang relatif dangkal seperti kecocokan kata dan jarak embedding. Reranker menambahkan skor relevansi gabungan query-passage yang menangkap kasus yang terlewat oleh hybrid search, dengan konsekuensi latensi tambahan pada kandidat yang sudah dipersempit.
Buat kumpulan query kecil yang telah dilabeli, dengan passage sumber yang benar ditandai untuk masing-masing query, lalu ukur recall pada k dan NDCG sebelum mengubah prompt generation. Jika passage yang benar tidak pernah muncul dalam k hasil teratas, seberapa pun banyaknya prompt engineering tidak akan memperbaiki jawaban yang salah, karena retriever memang tidak pernah menampilkan konteks yang tepat.

Foto oleh xmodulo on flickr
Kebanyakan sistem RAG hanya mengandalkan satu retriever, yaitu vector database yang mengubah query menjadi embedding lalu mencari chunk terdekat berdasarkan cosine similarity. Pendekatan ini bekerja baik untuk pertanyaan konseptual yang diparafrasekan, tetapi langsung gagal begitu pengguna mengetik kode produk yang eksak, pesan error, atau akronim jarang yang tidak pernah muncul berdampingan dengan teks serupa saat model embedding dilatih.
Tulisan ini membahas dua perubahan yang paling konsisten meningkatkan kualitas retrieval pada RAG produksi, yaitu menggabungkan pencarian leksikal dengan vector search menggunakan reciprocal rank fusion, dan menambahkan tahap reranking cross-encoder sebelum konteks final sampai ke language model.
Dense embedding dilatih untuk menangkap makna, dan justru karena itulah ia sering melewatkan kecocokan eksak. Model vector bisa menempatkan string nomor part tertentu berdekatan dengan nomor part lain yang tidak relevan, karena model belajar bahwa identifier pada umumnya mirip satu sama lain, bukan bahwa identifier ini unik.
Pencarian leksikal dalam bentuk BM25 justru menangani kasus-kasus ini dengan baik karena ia menilai dokumen berdasarkan kecocokan kata secara literal, dibobotkan oleh seberapa jarang setiap kata muncul di seluruh koleksi. Menjalankan kedua retriever secara berdampingan lalu menggabungkan hasilnya mengembalikan kekuatan masing-masing tanpa mewarisi kelemahan keduanya.
Jalankan BM25 dan vector search sebagai dua pemanggilan retrieval yang benar-benar independen terhadap document store yang sama. Jangan mencoba merata-ratakan skor mentahnya secara langsung. Kedua skor berada pada skala yang berbeda, dan rata-rata naif secara diam-diam akan memihak retriever mana pun yang menghasilkan angka lebih besar.
Reciprocal rank fusion, atau RRF, adalah jawaban standar untuk masalah ketidaksesuaian skala skor. Alih-alih menggabungkan skor relevansi mentah, RRF hanya melihat posisi peringkat setiap dokumen pada setiap daftar tempat dokumen itu muncul. Dokumen yang berada di posisi pertama pada daftar BM25 dan posisi ketiga pada daftar vector akan mendapat skor gabungan yang dibangun murni dari kedua posisi peringkat tersebut.
Rumusnya menjumlahkan satu dibagi konstanta ditambah peringkat, untuk setiap daftar tempat dokumen tersebut muncul. Konstanta ini, biasanya diset enam puluh, meredam pengaruh dokumen yang hanya sekadar masuk ke dalam daftar. Dokumen yang muncul di posisi atas pada kedua daftar akan naik ke puncak peringkat gabungan, dan itulah perilaku yang diinginkan dari sebuah retriever yang memasok konteks ke LLM.
| Sinyal | Kekuatan | Kelemahan |
|---|---|---|
| BM25 (leksikal) | Kata eksak, kode, akronim jarang | Tidak memahami makna atau parafrase |
| Vector search (dense) | Parafrase, sinonim, query konseptual | Kehilangan presisi kecocokan eksak pada identifier |
| Fusi RRF | Menangkap kedua sinyal tanpa penyetelan skor | Masih memerlukan reranker untuk presisi akhir |
Implementasinya cukup singkat untuk ditulis manual, meski vector database atau search engine yang Anda pakai sudah menyediakan query hybrid bawaan. Jalankan kedua retriever terhadap query yang sama, kumpulkan daftar dokumen berperingkat masing-masing, lalu gabungkan dengan rumus RRF di bawah ini.
function reciprocalRankFusion(bm25Ranked, vectorRanked, k = 60) {
const scores = new Map();
const addRanks = (rankedList) => {
rankedList.forEach((doc, index) => {
const rank = index + 1;
const contribution = 1 / (k + rank);
scores.set(doc.id, (scores.get(doc.id) || 0) + contribution);
});
};
addRanks(bm25Ranked);
addRanks(vectorRanked);
return Array.from(scores.entries())
.sort((a, b) => b[1] - a[1])
.map(([id, score]) => ({ id, score }));
}Perhatikan bahwa fungsi fusi ini tidak pernah melihat skor BM25 mentah atau cosine similarity mentah, hanya posisi dalam daftar. Itulah inti dari pendekatan ini, fusi berbasis peringkat menghindari kebutuhan untuk menormalkan dua sistem penilaian yang tidak sebanding ke skala yang sama.
RRF mengasumsikan kedua daftar input berukuran cukup besar, biasanya lima puluh hingga seratus hasil teratas per retriever. Jika Anda hanya menggabungkan lima teratas dari masing-masing daftar, kecocokan yang setara atau hampir setara akan mendominasi hasil dan fusi hanya memberi sedikit nilai tambah dibanding retriever tunggal.
Fusi memberi kandidat yang lebih baik, tetapi tetap memeringkat dokumen menggunakan dua sinyal yang relatif dangkal, yaitu kecocokan kata dan jarak vektor. Cross-encoder reranker menutup kesenjangan itu dengan mengenkode query dan setiap passage kandidat secara bersamaan melalui satu proses transformer, menghasilkan satu skor relevansi per pasangan, bukan membandingkan dua representasi yang dihitung secara terpisah.
Karena reranker memperhatikan query dan passage secara bersamaan, ia dapat menangkap hubungan yang tidak terlihat oleh BM25 maupun skor vector similarity, misalnya passage yang menyebut semua kata kunci yang tepat namun sebenarnya menjawab pertanyaan yang sama sekali berbeda. Presisi tersebut datang dengan biaya latensi yang nyata, itulah sebabnya reranking selalu dijalankan pada kandidat yang sudah dipersempit, bukan pada seluruh korpus.
Cross-encoder reranker open source seperti BGE-reranker dan Jina Reranker dapat berjalan dengan nyaman pada satu GPU dan menjadi pilihan default yang wajar jika Anda ingin menghindari biaya API per query. Opsi terhosting seperti Cohere Rerank menukar biaya kecil dengan tidak perlu mengelola infrastruktur sendiri.
Tim sering langsung mengubah-ubah prompt saat jawaban RAG salah, tanpa memeriksa lebih dulu apakah retriever pernah menampilkan passage yang benar. Memisahkan evaluasi retrieval dari evaluasi generation adalah kebiasaan debugging paling berguna dalam pekerjaan RAG.
Jika recall pada k rendah, seberapa pun banyaknya reranking atau prompt engineering tidak akan memperbaiki masalah, karena passage yang benar memang tidak pernah ada dalam kumpulan kandidat sejak awal. Jika recall tinggi namun NDCG rendah, itulah situasi yang persis diselesaikan oleh reranker.
Pipeline hybrid search dan reranking yang praktis kira-kira seperti ini, jalankan BM25 dan vector search secara paralel terhadap chunk store yang sama, gabungkan kedua daftar peringkat dengan reciprocal rank fusion, ambil kandidat teratas dari daftar gabungan, lakukan reranking dengan cross-encoder, dan hanya serahkan segelintir passage terakhir ke model generation.
Setiap tahap mempersempit kumpulan kandidat sambil meningkatkan presisi, sehingga proses cross-encoder yang mahal tetap cepat dan context window yang diserahkan ke LLM tetap kecil serta relevan, bukan dipenuhi kecocokan yang hanya marginal. Rancangan berlapis ini juga memudahkan proses debugging secara terpisah, karena regresi kualitas jawaban akhir biasanya bisa ditelusuri kembali ke satu tahap tertentu, bukan ke keseluruhan sistem.
Instrumentasikan recall pada k dan NDCG sebagai bagian dari pipeline CI setiap kali model embedding, strategi chunking, atau reranker berubah. Regresi kualitas retrieval bersifat diam-diam sampai pengguna menyadari ada jawaban yang salah, jadi perlakukan metrik retrieval sama seriusnya dengan test coverage.