Most RAG demos look brilliant on a laptop. Feed a script a twenty-page PDF, ask a question, watch it cite the right paragraph. But shipping that same pipeline into production is where the romance ends. Legal documents get sliced in half at the sentence level. Thick API references drown the important signal in boilerplate noise. Latency balloons. Users wait, twitch, and leave. We hit that wall hard. So we tore the retrieval layer down to the studs and rebuilt it as a measured, tunable system. The result was a pipeline that hits 95% recall without turning the user experience into a slideshow.

Why Demo RAG Dies in Production

The standard stack is surprisingly uniform across hobby projects and early-stage products: fixed token chunks, off-the-shelf embeddings, and a single vector search call. That simplicity is seductive, and it works when your corpus is clean, small, and syntactically predictable. Production data is none of those things. A fixed chunk of 512 tokens will happily cut through the middle of an indemnification clause in a SaaS contract. Suddenly your retrieval layer is feeding the language model half of a legal obligation and asking it to answer a liability question. The model hallucinates because the context is broken.

Large technical documents compound the issue. API documentation is full of function signatures, tables, and code blocks. A fixed window might capture the middle of a TypeScript interface but miss the function name above it and the usage example below it. The embedding vector ends up representing syntax fragments and inline noise instead of the actual capability the user is asking about. Garbage in, hallucination out.

Chunk by Structure, Not by Token Count

The first change we made was to stop thinking about chunks as bags of tokens. Chunks are semantic units. The right strategy depends entirely on what you are indexing.

For legal documents, we moved to recursive chunking that respects the document hierarchy. It treats sections, subsections, and clauses as boundaries. A clause stays intact because a clause is a unit of meaning. If you slice through it, the legal logic leaks out.

For API docs, structure-aware chunking treats functions, classes, and endpoints as atomic. One chunk might contain a function signature, its arguments, and its docstring. It does not spill arbitrarily into the next utility function just because a token counter clicked over. This keeps the embedding focused on a discrete capability.

Support tickets are messier. They are conversational, threaded, and nonlinear. Fixed chunks will grab a status update from an engineer and a customer complaint from the same thread and pretend they form a coherent unit. We switched to semantic chunking, splitting when the topic or speaker shifts rather than when the token budget runs out.

Internal wikis are often the sloppiest data in an organization. Formatting is inconsistent, headers are missing, and sections run together. For these, we use LLM-based chunking. A small model reads ahead and identifies logical boundaries before we ever generate an embedding. It costs more upfront than a character split, but the retrieval quality pays for itself immediately.

Hybrid Retrieval: Combine Signals

Vector search is powerful but it has blind spots. Paste in an exact error code like ERR_CONNECTION_REFUSED_0x800 and similarity search can return a troubleshooting guide for an unrelated module because the embedding space clustered them close together. Exact matches matter, and vector search alone can smooth them away.

Keyword search with BM25 solves the exact-match problem beautifully. But it chokes on conceptual distance. If a user asks about "performance degradation under heavy load," BM25 will miss a diagnostic note that describes "slow throughput during traffic spikes" because there is not enough keyword overlap.

We stopped choosing sides and started running both in parallel. Vector and keyword searches each return their own ranked lists. We merge them with Reciprocal Rank Fusion. RRF is simple and brutal in its effectiveness. It scores each document based on where it sits in each list. Documents that sit near the top in both systems get a massive boost. Documents that only one engine loves still earn a place in the final candidate set.

Füzyon işleminden sonra, en iyi adayları bir cross-encoder reranker üzerinden geçiriyoruz. Bu bedava değil; yaklaşık 50 milisaniyelik bir hesaplama yükü getiriyor. Ayrıca recall oranını %15 artırıyor. Cross-encoder, sorgunun tamamını ve her bir aday parçayı (chunk) birlikte değerlendirerek, bir bi-encoder embedding'in asla sunamayacağı kadar nüanslı bir alaka düzeyi skoru üretir. O fazladan 50 milisaniye aslında çok kârlı bir yatırım. LLM'e çöp bir bağlam penceresi (context window) göndermenizi ve kafası karışmış veya halüsinasyon görmüş bir yanıt beklerken iki saniye harcamanızı engeller.

Aramadan Önce Sorguyu Düzeltin

Kullanıcılar, arama mühendisleri gibi sorgular yazmazlar. "Uygulama bozuldu" yazarlar. Şifreli log parçaları yapıştırırlar. Belirsiz, muğlak sorular sorarlar. Eğer bu ham dizeleri doğrudan indekse gönderirseniz, karşılığında çöp alırsınız.

Her sorguyu, geri çağırma motoruna (retrieval engine) değmeden önce dönüştürüyoruz.

İlk olarak, sorgu genişletme (query expansion). Sistem, tek bir kısa sorudan birden fazla arama terimi üretir. Bir kullanıcı "Zaman aşımı hatasını nasıl düzeltirim?" diye sorar. Motor bunu; bağlantı zaman aşımları, okuma zaman aşımları, ağ geçidi (gateway) zaman aşımları ve yeniden deneme mantığını (retry logic) kapsayacak şekilde genişletir. Sadece bu yaklaşım bile recall oranımızı %78'den %96'ya çıkardı.

İkinci olarak, sorgu ayrıştırma (query decomposition). Karmaşık sorular daha küçük alt sorulara bölünür. "90 günü geçmiş kurumsal müşteriler için iade politikası nedir ve aylık planlardan farkı nedir?" gibi bir sorgu, tek bir hantal embedding araması yerine iki odaklanmış aramaya dönüşür. Her bir alt soru indekse bağımsız olarak gider. Sonuçlar, sürecin ilerleyen aşamalarında tekrar birleştirilir. Bu, geri çağırmayı dar ve hassas tutarak, tek bir embedding'in aynı anda bir düzine kavramla eşleşmeye çalışmasıyla oluşan seyreltmeyi (dilution) önler.

Pipeline'ınızı Bayesian Arama ile Optimize Edin

Eğer hala parça boyutu (chunk size), örtüşme oranları (overlap ratios) ve geri çağırma ağırlıklarını (retrieval weights) elle ayarlıyorsanız, performanstan ödün veriyorsunuz demektir. Biz tahmin yürütmeyi bıraktık.

Parça boyutu, örtüşme yüzdesi, vektör-ve-BM25 ağırlıkları ve reranker eşikleri gibi tüm değerlerin değişken olduğu bir arama alanı tanımladık. Ardından Bayesian optimizasyonu uyguladık. Yüzlerce rastgele konfigürasyon arasında grid search yapmak yerine, Bayesian arama neyin işe yaradığına dair olasılıksal bir model oluşturur. Bir konfigürasyon önerir, recall ve gecikmeyi (latency) gözlemler, çıkarımlarını günceller ve bir sonrakini önerir. Zamanla, bir insanın manuel olarak asla bulamayacağı dengeler üzerinde yakınsar.

Hiç denemeyeceğimiz kombinasyonlar buldu. Daha yoğun örtüşmeye sahip daha küçük parçalar. Yoğun vektör aramasına (dense vector search) biraz daha düşük ağırlık ve daha agresif bir reranker eşiği. Bu bariz olmayan ödünleşimler (tradeoffs) bize hem daha yüksek recall hem de daha düşük gecikme sağladı.

Bu, tek seferlik bir kurulum görevi değildir. Hiperparametre optimizasyonunu her ay yeniden çalıştırıyoruz. Veri kümeniz (corpus) zamanla değişir. Kullanıcı davranışı farklılaşır. Pipeline'ınız yerinde sayıp paslanmak yerine uyum sağlamalıdır.

Sonuçlar

Bu yeniden yapılandırmanın ham çıktısı tartışmaya kapalıdır.

Onuncu sıradaki recall %78'den %95'e çıktı. Doğru yanıt bilgi tabanımızda mevcut olduğunda, yirmi seferden on dokuzunda onu öne çıkarıyoruz. 95. yüzdelik dilimdeki gecikme 850 milisaniyeden 320 milisaniyeye düştü. Sohbet, ağırkanlı değil, anlık hissettiriyor.

Daha iyi geri çağırma, dil modeline daha iyi bir temellendirme (grounding) sağladı. Halüsinasyon oranı %12'den %3'e düştü. Model önünde doğru bağlam olduğunda, gerçekleri uydurmayı bırakıyor. Sorgu başına maliyet %38 düştü. Daha hızlı ve keskin geri çağırma; alakasız bağlamlar, yeniden deneme döngüleri ve gereksiz yere uzatılmış ama faydasız istemler (prompts) için daha az token harcanması anlamına geliyor.

Altyapı Gibi İnşa Edin

Prototipten üretime geçiyorsanız, geri çağırmayı bir konfigürasyon yerine altyapı kodu (infrastructure code) olarak ele alın.