Sebagian besar demo RAG terlihat luar biasa di laptop. Masukkan PDF dua puluh halaman ke dalam skrip, ajukan pertanyaan, dan lihat ia mengutip paragraf yang tepat. Namun, saat membawa pipeline yang sama ke tahap produksi, di situlah "romansa" itu berakhir. Dokumen hukum terpotong di tengah pada tingkat kalimat. Referensi API yang tebal menenggelamkan sinyal penting dalam kebisingan boilerplate. Latensi membengkak. Pengguna menunggu, merasa tidak sabar, lalu pergi. Kami menabrak tembok itu dengan keras. Jadi, kami membongkar lapisan retrieval hingga ke dasarnya dan membangunnya kembali sebagai sistem yang terukur dan dapat disesuaikan (tunable). Hasilnya adalah pipeline yang mencapai recall 95% tanpa mengubah pengalaman pengguna menjadi seperti melihat tayangan slide.
Mengapa Demo RAG Gagal di Tahap Produksi
Stack standar secara mengejutkan seragam di berbagai proyek hobi dan produk tahap awal: chunk token yang tetap, embedding siap pakai, dan satu panggilan pencarian vektor. Kesederhanaan itu menggoda, dan berhasil jika korpus Anda bersih, kecil, dan dapat diprediksi secara sintaksis. Data produksi tidak memiliki karakteristik tersebut. Chunk tetap sebesar 512 token akan dengan mudah memotong bagian tengah klausul ganti rugi dalam kontrak SaaS. Tiba-tiba, lapisan retrieval Anda menyuapi model bahasa dengan setengah dari kewajiban hukum dan memintanya untuk menjawab pertanyaan tentang tanggung jawab hukum. Model mengalami halusinasi karena konteksnya terputus.
Dokumen teknis yang besar memperparah masalah ini. Dokumentasi API penuh dengan tanda tangan fungsi (function signatures), tabel, dan blok kode. Jendela tetap mungkin menangkap bagian tengah dari sebuah interface TypeScript, tetapi melewatkan nama fungsi di atasnya dan contoh penggunaan di bawahnya. Vektor embedding akhirnya mewakili fragmen sintaksis dan kebisingan inline alih-alih kemampuan aktual yang ditanyakan pengguna. Data sampah masuk, halusinasi yang keluar.
Chunking Berdasarkan Struktur, Bukan Jumlah Token
Perubahan pertama yang kami lakukan adalah berhenti menganggap chunk sebagai sekumpulan token. Chunk adalah unit semantik. Strategi yang tepat sepenuhnya bergantung pada apa yang Anda indeks.
Untuk dokumen hukum, kami beralih ke recursive chunking yang menghormati hierarki dokumen. Metode ini memperlakukan bagian, subbagian, dan klausul sebagai batasan. Sebuah klausul tetap utuh karena klausul adalah satu unit makna. Jika Anda memotongnya, logika hukumnya akan hilang.
Untuk dokumentasi API, structure-aware chunking memperlakukan fungsi, kelas, dan endpoint sebagai unit atomik. Satu chunk mungkin berisi tanda tangan fungsi, argumennya, dan docstring-nya. Ia tidak meluber secara sembarangan ke fungsi utilitas berikutnya hanya karena penghitung token telah mencapai batas. Hal ini menjaga embedding tetap fokus pada kemampuan diskrit tertentu.
Tiket dukungan lebih berantakan. Isinya bersifat percakapan, berantai (threaded), dan non-linear. Chunk tetap akan mengambil pembaruan status dari seorang insinyur dan keluhan pelanggan dari utas yang sama, lalu berpura-pura bahwa keduanya membentuk satu unit yang koheren. Kami beralih ke semantic chunking, yang membagi teks saat topik atau pembicara berubah, bukan saat kuota token habis.
Wiki internal sering kali merupakan data yang paling berantakan dalam sebuah organisasi. Formatnya tidak konsisten, tajuk (header) hilang, dan bagian-bagiannya menyatu. Untuk data seperti ini, kami menggunakan LLM-based chunking. Sebuah model kecil membaca lebih dulu dan mengidentifikasi batasan logis sebelum kami menghasilkan embedding. Ini membutuhkan biaya awal yang lebih besar daripada pembagian berdasarkan karakter, tetapi kualitas retrieval-nya akan langsung terbayar.
Hybrid Retrieval: Menggabungkan Sinyal
Pencarian vektor sangat kuat, tetapi memiliki titik buta. Masukkan kode kesalahan yang tepat seperti ERR_CONNECTION_REFUSED_0x800, dan pencarian kemiripan dapat mengembalikan panduan pemecahan masalah untuk modul yang tidak terkait karena ruang embedding mengelompokkan keduanya berdekatan. Kecocokan eksak itu penting, dan pencarian vektor saja dapat mengabaikannya.
Pencarian kata kunci dengan BM25 menyelesaikan masalah kecocokan eksak dengan sangat baik. Namun, ia kesulitan dengan jarak konseptual. Jika pengguna bertanya tentang "penurunan performa di bawah beban berat," BM25 akan melewatkan catatan diagnostik yang mendeskripsikan "throughput lambat selama lonjakan trafik" karena tidak ada tumpang tindih kata kunci yang cukup.
Kami berhenti memilih salah satu dan mulai menjalankan keduanya secara paralel. Pencarian vektor dan kata kunci masing-masing mengembalikan daftar peringkatnya sendiri. Kami menggabungkannya dengan Reciprocal Rank Fusion. RRF itu sederhana namun sangat efektif. Ia memberi skor pada setiap dokumen berdasarkan posisinya di setiap daftar. Dokumen yang berada di posisi atas pada kedua sistem akan mendapatkan dorongan besar. Dokumen yang hanya disukai oleh satu mesin tetap mendapatkan tempat dalam kumpulan kandidat akhir.
Setelah fusi, kami menjalankan kandidat teratas melalui cross-encoder reranker. Ini tidak gratis. Ini menambah sekitar 50 milidetik komputasi. Ini juga meningkatkan recall sebesar 15%. Cross-encoder mengevaluasi seluruh kueri dan setiap chunk kandidat secara bersamaan, menghasilkan skor relevansi yang jauh lebih bernuansa daripada yang bisa dilakukan oleh bi-encoder embedding. Tambahan 50 milidetik itu sangat sepadan. Ini mencegah Anda mengirimkan context window yang sampah ke LLM dan menghabiskan dua detik menunggu jawaban yang membingungkan atau halusinasi.
Perbaiki Kueri Sebelum Anda Melakukan Pencarian
Pengguna tidak menulis kueri seperti insinyur pencarian. Mereka mengetik "app broken." Mereka menempelkan fragmen log yang kriptik. Mereka mengajukan pertanyaan yang samar dan ambigu. Jika Anda mengirimkan string mentah tersebut langsung ke indeks, Anda akan mendapatkan hasil yang sampah.
Kami mentransformasi setiap kueri sebelum menyentuh mesin retrieval.
Pertama, query expansion. Sistem menghasilkan beberapa istilah pencarian dari satu pertanyaan pendek. Seorang pengguna bertanya, "Bagaimana cara memperbaiki timeout?" Mesin tersebut memperluasnya untuk mencakup connection timeouts, read timeouts, gateway timeouts, dan retry logic. Pendekatan ini saja telah meningkatkan recall kami dari 78% menjadi 96%.
Kedua, query decomposition. Pertanyaan kompleks dipecah menjadi sub-pertanyaan yang lebih kecil. Kueri seperti "Apa kebijakan pengembalian dana untuk pelanggan enterprise setelah 90 hari dan bagaimana perbedaannya dengan paket bulanan?" menjadi dua pencarian terfokus daripada satu pencarian embedding yang membengkak. Setiap sub-pertanyaan memicu indeks secara independen. Hasilnya kemudian disatukan kembali di tahap downstream. Hal ini menjaga retrieval tetap sempit dan presisi, yang menghentikan pengenceran (dilution) yang terjadi ketika satu embedding mencoba mencocokkan belasan konsep sekaligus.
Biarkan Bayesian Search Menyetel Pipeline Anda
Jika Anda masih menyetel secara manual ukuran chunk, rasio overlap, dan bobot retrieval, Anda menyia-nyiakan performa. Kami berhenti menebak-nebak.
Kami mendefinisikan ruang pencarian di mana ukuran chunk, persentase overlap, bobot vector-versus-BM25, dan ambang batas reranker semuanya adalah variabel. Kemudian kami menerapkan optimasi Bayesian. Alih-alih melakukan grid-searching melalui ratusan konfigurasi acak, Bayesian search membangun model probabilistik tentang apa yang berhasil. Ia mengusulkan sebuah konfigurasi, mengamati recall dan latensi, memperbarui keyakinannya, dan mengusulkan konfigurasi berikutnya. Seiring waktu, ia konvergen pada keseimbangan yang tidak akan pernah ditemukan manusia secara manual.
Ia menemukan kombinasi yang tidak pernah kami coba. Chunk yang lebih kecil dengan overlap yang lebih besar. Bobot yang sedikit lebih rendah pada dense vector search yang dipasangkan dengan ambang batas reranker yang lebih agresif. Tradeoff yang tidak nyata ini memberi kami recall yang lebih tinggi sekaligus latensi yang lebih rendah.
Ini bukan tugas pengaturan satu kali. Kami menjalankan ulang optimasi hyperparameter setiap bulan. Korpus Anda bergeser. Perilaku pengguna berubah. Pipeline Anda harus beradaptasi, bukan membiarkannya usang begitu saja.
Hasilnya
Hasil mentah dari pembangunan ulang tersebut sulit untuk dibantah.
Recall pada posisi sepuluh naik dari 78% menjadi 95%. Ketika jawaban yang benar ada di dalam basis pengetahuan kami, kami menampilkannya sembilan belas kali dari dua puluh percobaan. Latensi pada persentil ke-95 turun dari 850 milidetik menjadi 320 milidetik. Chat terasa instan, bukan lamban.
Retrieval yang lebih baik memberikan grounding yang lebih baik bagi model bahasa. Tingkat halusinasi turun dari 12% menjadi 3%. Ketika model memiliki konteks yang tepat di hadapannya, ia berhenti mengarang fakta. Biaya per kueri turun sebesar 38%. Retrieval yang lebih cepat dan tajam berarti lebih sedikit token yang terbuang untuk konteks yang tidak relevan, retry loops, dan prompt yang bertele-tele namun tidak berguna.
Bangun Seperti Infrastruktur
Jika Anda berpindah dari prototipe ke produksi, perlakukan retrieval sebagai kode infrastruktur, bukan sekadar konfigurasi
