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.
Mengapa vector search saja tidak cukup
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.
- Kode produk, SKU, dan kode error kehilangan sifat eksaknya begitu dikompresi menjadi vektor berukuran tetap.
- Akronim teknis yang jarang dipakai dan nama khusus sering ter-embed berdekatan dengan istilah umum yang tidak relevan.
- Negasi dan batasan numerik, seperti nomor versi tertentu atau jumlah mata uang, cenderung bercampur dalam ruang embedding.
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: menggabungkan dua daftar peringkat
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 |
Mengimplementasikan reciprocal rank fusion
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.
Menambahkan cross-encoder reranker
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.
- Ambil kandidat dalam jumlah besar, umumnya lima puluh hingga seratus chunk, menggunakan hybrid search hasil fusi dari langkah sebelumnya.
- Lewatkan setiap pasangan query-passage melalui model cross-encoder, satu pasangan pada satu waktu.
- Urutkan kandidat berdasarkan skor relevansi dari reranker, bukan berdasarkan skor fusi awal.
- Simpan hanya delapan hingga lima belas passage teratas hasil reranking, lalu serahkan itu ke model generation.
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.
Mengukur kualitas retrieval sebelum menyentuh generation
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.
- Buat kumpulan query kecil yang telah dilabeli, dengan passage sumber yang benar ditandai untuk masing-masing query, lima puluh contoh saja sudah cukup untuk memulai.
- Ukur recall pada k, yaitu proporsi query yang passage benarnya muncul di mana pun dalam k hasil teratas, sebelum dan setelah menambahkan hybrid search dan reranking.
- Lacak normalized discounted cumulative gain, atau NDCG, yang tambahan memberi bobot lebih jika passage yang benar muncul di posisi atas daftar, bukan sekadar muncul di mana saja.
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.
Menyatukan semuanya dalam pipeline produksi
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.

