Kebanyakan demo RAG kelihatan hebat pada komputer riba. Masukkan skrip dengan PDF dua puluh halaman, tanya soalan, dan lihat ia memetik perenggan yang betul. Namun, apabila pipeline yang sama dibawa ke produksi, di situlah realiti sebenar bermula. Dokumen undang-undang terpotong di tengah-tengah pada peringkat ayat. Rujukan API yang tebal menenggelamkan isyarat penting dalam bunyi bising boilerplate. Latensi melambung tinggi. Pengguna menunggu, berasa rimas, dan beredar. Kami menghadapi cabaran tersebut dengan hebat. Jadi, kami merombak lapisan pencarian (retrieval layer) sepenuhnya dan membinanya semula sebagai sistem yang terukur dan boleh dilaraskan. Hasilnya ialah pipeline yang mencapai 95% recall tanpa menjadikan pengalaman pengguna seperti melihat slaid pembentangan.

Mengapa Demo RAG Gagal dalam Produksi

Timbunan (stack) standard adalah sangat seragam merentasi projek hobi dan produk peringkat awal: pecahan token tetap (fixed token chunks), embedding sedia ada, dan satu panggilan carian vektor tunggal. Kesederhanaan itu memikat, dan ia berfungsi apabila korpus anda bersih, kecil, dan boleh diramal dari segi sintaks. Data produksi tidak memenuhi mana-mana kriteria tersebut. Pecahan tetap sebanyak 512 token akan memotong di tengah-tengah klausa indemnifikasi dalam kontrak SaaS. Tiba-tiba, lapisan pencarian anda memberikan separuh daripada obligasi undang-undang kepada model bahasa dan memintanya menjawab soalan liabiliti. Model tersebut mengalami halusinasi kerana konteksnya terputus.

Dokumen teknikal yang besar memburukkan lagi isu ini. Dokumentasi API penuh dengan tandatangan fungsi (function signatures), jadual, dan blok kod. Tetingkap tetap mungkin menangkap bahagian tengah antara muka TypeScript tetapi terlepas nama fungsi di atasnya dan contoh penggunaan di bawahnya. Vektor embedding akhirnya mewakili fragmen sintaks dan bunyi bising dalam baris (inline noise) dan bukannya keupayaan sebenar yang ditanyakan oleh pengguna. Input sampah, output halusinasi.

Pecahan Mengikut Struktur, Bukan Mengikut Bilangan Token

Perubahan pertama yang kami lakukan adalah berhenti menganggap pecahan (chunks) sebagai sekumpulan token. Pecahan adalah unit semantik. Strategi yang betul bergantung sepenuhnya pada apa yang anda indeks.

Untuk dokumen undang-undang, kami beralih kepada pecahan rekursif (recursive chunking) yang menghormati hierarki dokumen. Ia menganggap seksyen, subseksyen, dan klausa sebagai sempadan. Satu klausa kekal utuh kerana klausa adalah unit makna. Jika anda memotongnya, logik undang-undang akan terjejas.

Untuk dokumentasi API, pecahan peka-struktur (structure-aware chunking) menganggap fungsi, kelas, dan titik akhir (endpoints) sebagai atomik. Satu pecahan mungkin mengandungi tandatangan fungsi, argumennya, dan docstring-nya. Ia tidak melimpah secara sewenang-wenangnya ke fungsi utiliti seterusnya hanya kerana pengira token telah bertambah. Ini memastikan embedding kekal fokus pada keupayaan yang khusus.

Tiket sokongan adalah lebih berserabut. Ia bersifat perbualan, berbenang (threaded), dan tidak linear. Pecahan tetap akan mengambil kemas kini status daripada jurutera dan aduan pelanggan daripada benang yang sama dan berpura-pura bahawa ia membentuk satu unit yang koheren. Kami beralih kepada pecahan semantik (semantic chunking), di mana pembahagian berlaku apabila topik atau pembicara berubah, dan bukannya apabila bajet token habis.

Wiki dalaman selalunya merupakan data yang paling tidak teratur dalam sesebuah organisasi. Format tidak konsisten, pengepala (headers) hilang, dan seksyen bercampur aduk. Untuk data sebegini, kami menggunakan pecahan berasaskan LLM. Model kecil akan membaca lebih awal dan mengenal pasti sempadan logik sebelum kami menjana embedding. Ia memerlukan kos lebih tinggi pada mulanya berbanding pembahagian aksara, tetapi kualiti pencarian akan memberikan pulangan nilai dengan segera.

Pencarian Hibrid: Gabungkan Isyarat

Carian vektor adalah berkuasa tetapi ia mempunyai titik buta. Tampal kod ralat yang tepat seperti ERR_CONNECTION_REFUSED_0x800 dan carian keserupaan boleh memulangkan panduan penyelesaian masalah untuk modul yang tidak berkaitan kerana ruang embedding mengelompokkan mereka berdekatan. Padanan tepat adalah penting, dan carian vektor sahaja boleh mengabaikannya.

Carian kata kunci dengan BM25 menyelesaikan masalah padanan tepat dengan sangat baik. Namun, ia sukar menangani jarak konseptual. Jika pengguna bertanya tentang "penurunan prestasi di bawah beban berat," BM25 akan terlepas nota diagnostik yang menerangkan "throughput perlahan semasa lonjakan trafik" kerana tidak terdapat pertindihan kata kunci yang mencukupi.

Kami berhenti memilih pihak dan mula menjalankan kedua-duanya secara selari. Carian vektor dan kata kunci masing-masing memulangkan senarai berperingkat mereka sendiri. Kami menggabungkannya dengan Reciprocal Rank Fusion. RRF adalah ringkas dan sangat berkesan secara drastik. Ia memberi skor kepada setiap dokumen berdasarkan kedudukannya dalam setiap senarai. Dokumen yang berada di kedudukan teratas dalam kedua-dua sistem akan mendapat lonjakan besar. Dokumen yang hanya disukai oleh satu enjin tetap mendapat tempat dalam set calon akhir.

After fusion, we run the top candidates through a cross-encoder reranker. This is not free. It adds roughly 50 milliseconds of compute. It also increases recall by 15%. The cross-encoder evaluates the full query and each candidate chunk together, producing a relevance score far more nuanced than a bi-encoder embedding ever could. That extra 50 milliseconds is a bargain. It prevents you from shipping a garbage context window to the LLM and spending two seconds waiting for a confused or hallucinated answer.

Users do not write queries like search engineers. They type "app broken." They paste cryptic log fragments. They ask vague, ambiguous questions. If you send those raw strings straight to the index, you get garbage back.

We transform every query before it touches the retrieval engine.

First, query expansion. The system generates multiple search terms from a single short question. A user asks, "How do I fix the timeout?" The engine expands that to cover connection timeouts, read timeouts, gateway timeouts, and retry logic. This approach alone moved our recall from 78% to 96%.

Second, query decomposition. Complex questions get broken into smaller sub-questions. A query like "What's the refund policy for enterprise customers past 90 days and how does it differ from monthly plans?" becomes two focused searches rather than one bloated embedding lookup. Each sub-question hits the index independently. The results are stitched back together downstream. This keeps retrieval narrow and precise, which stops the dilution that happens when a single embedding tries to match a dozen concepts at once.

Let Bayesian Search Tune Your Pipeline

If you are still hand-tuning chunk size, overlap ratios, and retrieval weights, you are leaving performance on the table. We stopped guessing.

We defined a search space where chunk size, overlap percentage, vector-versus-BM25 weights, and reranker thresholds are all variables. Then we applied Bayesian optimization. Instead of grid-searching through hundreds of random configurations, Bayesian search builds a probabilistic model of what works. It proposes a configuration, observes the recall and latency, updates its beliefs, and proposes the next one. Over time it converges on balances a human would never stumble into manually.

It found combinations we never would have tried. Smaller chunks with heavier overlap. A slightly lower weight on dense vector search paired with a more aggressive reranker threshold. These non-obvious tradeoffs gave us both higher recall and lower latency.

This is not a one-time setup task. We re-run hyperparameter optimization monthly. Your corpus drifts. User behavior shifts. Your pipeline should adapt instead of rusting in place.

The Payoff

The raw output of that rebuild is hard to argue with.

Recall at position ten went from 78% to 95%. When the correct answer lives in our knowledge base, we surface it nineteen times out of twenty. Latency at the 95th percentile fell from 850 milliseconds to 320 milliseconds. The chat feels instant instead of ponderous.

Better retrieval gave the language model better grounding. Hallucination rate dropped from 12% to 3%. When the model has the right context in front of it, it stops inventing facts. Cost per query fell by 38%. Faster, sharper retrieval means fewer tokens wasted on irrelevant context, retry loops, and verbose but useless prompts.

Build It Like Infrastructure

If you are moving from prototype to production, treat retrieval as infrastructure code rather than a configuration