Skip to content
Günlüğe dön
Yapay Zeka Mimari

Demonun Ötesinde: Production Kalitesinde RAG Sistemleri Kurmak

Bir hafta sonunda yapılan RAG prototipi kolaydır; production ortamında güvenebileceğiniz bir retrieval sistemi ise bambaşka bir mühendislik problemidir ve bu yazı, ikisi arasındaki uçurumu anlatıyor.

Pangaea Engineering 8 dk okuma

Bir RAG demosu bir öğleden sonrada hazır olur. Bir vector store’u pip-install edersiniz, içine birkaç PDF dökersiniz, bir embedding modeli ile bir chat endpoint’ini birbirine bağlarsınız, cevaplarını zaten bildiğiniz üç soru sorarsınız ve üçünü de tam isabetle bilir. Odadaki herkes onaylarcasına başını sallar. Hadi yayına alalım.

Sonra sistem gerçek bir corpus üzerinde gerçek sorular soran gerçek kullanıcılarla karşılaşır ve çatlaklar anında ortaya çıkar: kendi cevabının tam tersini söyleyen bir belgeyi kendinden emin bir şekilde kaynak gösterir, bilgi tabanında apaçık duran bir politikayı bulamaz ve hiç var olmamış bir iade süresi uydurur. Demo aslında tam olarak bir yalan değildi. Sizi pohpohlamak için denk gelmiş, kontrollü bir deneydi. O deney ile altına imzanızı atabileceğiniz bir sistem arasındaki uçurum, asıl mühendisliğin büyük bölümünün yaşadığı yerdir ve neredeyse hiçbiri language model ile ilgili değildir.

Demolar neden yalan söyler

Demolar kötü niyetten değil, doğaları gereği yalan söyler. Sorular, index’i kuran kişi tarafından özenle seçilmiştir, dolayısıyla var olan chunk’lara tertemiz oturur. Corpus o kadar küçüktür ki vasat bir retrieval bile şans eseri doğru pasajı yüzeye çıkarır. Ve kimse zorlayıcı soruları sormaz — corpus’ta cevabı olmayanları, iki belgenin birbiriyle çeliştiği olanları, gerçekten sinirli bir kullanıcının gece 11’de ifade ettiği şekilde ifade edilenleri.

Ölçek, fiziği değiştirir. 50 belgede top-k retrieval bağışlayıcıdır çünkü onu şaşırtacak fazla bir şey yoktur. 50.000’de ise neredeyse-aynı kopyalar, güncelliğini yitirmiş sürümler ve yüzeysel olarak benzer ama yanlış pasajlar her sonuç listesinin tepesini doldurur. Model, ancak ona verdiğiniz şey kadar iyidir ve ona ne verileceği, LLM çağrılmadan çok önce belirlenir.

Model, sistemin kendisi değildir. Sistem, retrieval pipeline’ıdır; model ise sonunda her şeyi güzelce yazıya döken parçadır.

Retrieval kalitesi oyunun tamamıdır

Tek bir şeye yatırım yapacaksanız, buraya yapın. İyi bir retrieval ile beslenen ortalama bir model, çöp context’le beslenen frontier bir modeli her seferinde yener.

Chunking bir varsayılan değil, bir tasarım kararıdır

Gördüğümüz en yaygın hata, chunking’i bir config değeri gibi ele almaktır — chunk_size=512, geç sonrakine. Chunk sınırları, neyin birlikte retrieve edilebileceğini belirler. Bir tabloyu başlığından, ya da bir maddeyi onu kapsamlandıran cümleden ayırırsanız, embedding’leriniz ne kadar iyi olursa olsun doğru cevap fiziksel olarak retrieve edilemez hale gelir. Belge yapısına saygı gösterin: başlıklara göre chunk’layın, liste maddelerini ana gövdeleriyle birlikte tutun ve bir sınırı aşan bir kavramın hayatta kalması için ölçülü bir overlap kullanın. Sonra, retrieve edilen bir parçanın bağlamından koparılarak okunmaması için, generation aşamasında biraz çevre context’i (üst bölüm, komşu chunk’lar) erişilebilir tutun.

Metadata bedava precision’dır

Her chunk yapılandırılmış metadata taşımalıdır — kaynak, bölüm, belge tarihi, sürüm, erişim kapsamı. Bu, sıralamadan önce filtrelemenizi sağlar (yalnızca güncel el kitabı, yalnızca bu kullanıcının görebileceği belgeler) ve daha sonra kaynakları kesin biçimde göstermenizi mümkün kılar. Saf semantik benzerliğin “en yeni” ya da “yetkili” gibi bir kavramı yoktur. Metadata’nın vardır.

Hybrid search, sonra re-rank

Dense vector search anlamı yakalamada harika, kesin token’larda ise kötüdür — ürün SKU’ları, hata kodları, soyadlar, hukuki referanslar. Keyword search (BM25 ya da Postgres full-text) bunun tam tersidir. Production sistemleri her ikisini de çalıştırır ve sonuçları birleştirir; bu kombinasyon güvenilir biçimde her ikisini tek başına yenmekten daha iyi sonuç verir. Sonra birleştirilmiş ilk ~50 adayı alın ve her birini sorguya karşı doğrudan puanlayan bir cross-encoder re-ranker’dan geçirin. İlk aşama recall’ı optimize eder (doğru chunk’ı kaybetme); re-ranker ise precision’ı optimize eder (onu birinci sıraya koy). Bu iki aşamalı yapı — ucuz-ve-geniş, ardından pahalı-ve-dar — her ciddi retrieval pipeline’ının bel kemiğidir.

candidates = vector_search(query, k=40) ∪ keyword_search(query, k=40)
candidates = filter(candidates, metadata)          # scope, recency, perms
ranked     = cross_encoder.rerank(query, candidates)
context    = take(ranked, n=6)                      # fit the budget, with citations
answer     = llm.generate(query, context, "answer only from context; else say you don't know")

Embedding’ler: bilinçli seçin, sonra ölçün

Embedding seçimi, leaderboard peşinde koşma egzersizi değil, gerçek bir trade-off’tur. Daha büyük modeller daha fazla nüans yakalar ve token başına daha fazla maliyet, vektör başına daha fazla depolama ve daha fazla latency getirir. Daha küçük modeller daha ucuzdur ve odaklı bir alan için çoğu zaman fazlasıyla yeterlidir. Boyutluluk (dimensionality), ölçekte index boyutunuzu ve bellek ayak izinizi doğrudan belirler.

“En iyi” modeli seçmekten daha önemli olan iki şey var. Birincisi, alanınız modelin eğitim dağılımıyla örtüşmeyebilir — genel web metni üzerinde tune edilmiş embedding’ler, yoğun hukuki, tıbbi ya da Türkçe corpus’larda vasat kalabilir. İkincisi, embedding modelini değiştirmek her şeyi yeniden embed etmek demektir; bu bir flag değişimi değil, bir migration’dır. O yüzden modeli ve sürümünü metadata’nızda sabitleyin ve kararı başkasının benchmark’ıyla değil, kendi eval set’inizle verin.

Eksik kalan disiplin: değerlendirme

İşte bir hobi projesi ile bir ürün arasındaki çizgi burada. Hobi projesi hislerle değerlendirilir — kurucu birkaç soru sorar ve içine sinen bir his alır. Bir ürün ise her değişiklikte çalıştırabileceğiniz, tekrarlanabilir bir eval set ile değerlendirilir.

Gerekiyorsa elle oluşturun. 50-200 gerçek soru toplayın, retrieve edilmesi gereken chunk’ları ve gerçekten doğru olan cevabı etiketleyin ve zor vakaları da dahil edin: cevabı olmayan sorular, birden fazla belgeye yayılan sorular, neredeyse-aynı-ama-yanlış tuzaklar. Sonra iki ayrı şeyi ölçün, çünkü bağımsız olarak başarısız olurlar:

  • Retrieval kalitesi — etiketlediğiniz chunk’lar üzerinden precision ve recall. Doğru pasaj top-k’ya hiç girdi mi (recall) ve yanında ne kadar gürültü geldi (precision)? “LLM aptal” şikayetlerinin çoğu, kılık değiştirmiş retrieval-recall başarısızlıklarıdır.
  • Cevap sadakati (faithfulness) — üretilen cevap retrieve edilen context tarafından gerçekten destekleniyor mu, yoksa model süsleme mi yaptı? Bu, cevabın “iyi” olup olmamasından ayrı bir konudur. Bir cevap akıcı, faydalı ve tamamen desteksiz olabilir.

Bu test düzeneği olmadan körlemesine uçuyorsunuz. Prompt’u “iyileştirir”, kendinizi daha iyi hisseder ve kullanıcılarınızın dörtte biri için retrieval’da geriye gittiğinizden hiç haberiniz olmaz. Bu düzenekle birlikte ise her chunking ayarı, her re-ranker değişimi, her model yükseltmesi bir his değil, ölçülebilir bir bahis haline gelir.

Grounding, kaynak gösterme ve “bilmiyorum” demenin onuru

Production bir RAG cevabı izlenebilir olmalıdır. Her iddia, retrieve edilen bir chunk’a geri eşlenebilmeli ve UI bu kaynakları görünür kılmalı ki bir kullanıcı — ya da bir denetçi — doğrulayabilsin. Bu bir süsleme değildir; kaynak gösterme, sahip olduğunuz en ucuz halüsinasyon savunmasıdır, çünkü her ifadesini verilen kaynaklara dayandırması istenen bir model, “soruyu yanıtla” denen bir modele kıyasla çok daha az halüsinasyon görür.

Daha zor olan disiplin ise sisteme cevap vermeyi reddetmeyi öğretmektir. Retrieval, bir relevans eşiğinin üzerinde hiçbir şeyle dönmüyorsa, doğru çıktı “Bunu bilgi tabanında bulamadım”dır; modelin parametrik belleğinden derlenmiş kendinden emin bir tahmin değil. Çoğu ekip bunu atlar çünkü demolar buna hiç çarpmaz. Production ise buna sürekli çarpar. Generation’ı retrieval güvenine bağlayan — ve bu eşiğin altında reddeden — bir guardrail, en korkutucu failure mode’unuzu (kendinden emin uydurma) en güvenilir olanına (dürüstçe çekimser kalma) dönüştürür.

Latency, maliyet ve caching gerçeği

Kalite için eklediğiniz her aşama milisaniyelere ve paraya mal olur. Sorguyu embed etmek, çift retrieval, cross-encoder re-ranking ve uzun context’li bir generation çağrısı hızla üst üste birikir ve özellikle re-ranking bedava değildir. Kullanıcılar sizin yerinize yapmadan önce latency’nizi bütçeleyin.

Caching, burada en yüksek kaldıraca sahip optimizasyondur. Query embedding’lerini cache’leyin (aynı sorular sandığınızdan çok daha fazla tekrarlanır), sık sorulan sorular için retrieval sonuçlarını cache’leyin ve güncellik elverdiğinde tam cevapları cache’leyin. Gönderdiğiniz context’i kırpın — iyi sıralanmış altı chunk, yirmi vasat olana karşı maliyette, latency’de ve sadakatte üstün gelir, çünkü daha küçük, daha temiz bir context içinde kaybolmak daha zordur. Maliyet disiplini ile cevap kalitesi, insanların sandığından çok daha sık aynı yönü gösterir.

Gizlilik ve local-first seçeneği

Pek çok ekip için en değerli bilgi — sözleşmeler, hasta kayıtları, iç finansallar, kaynak kod — tam da yasal olarak ya da gönül rahatlığıyla üçüncü taraf bir API’ye gönderemeyecekleri veridir. İşte tam da bu kısıt yüzünden local-first RAG motorları geliştirdik ve bunlar, yukarıdaki ilkelerin tertemiz bir örneğidir.

Bunlardan biri; bir belge dizinini içeri alan, onları embed edip pgvector ile PostgreSQL içinde saklayan ve bir chat endpoint üzerinden grounded cevaplar sunan bir Go uygulamasıdır — embedding ve generation’ın her ikisi de Ollama aracılığıyla yerel modeller üzerinde çalışır, böylece hiçbir veri altyapınızdan dışarı çıkmaz. Mimari tercihler bilinçlidir. Postgres, vektörlerinizin ve metadata’nızın tek bir transactional store içinde yaşaması demektir; dolayısıyla kapsama ve güncelliğe göre filtrelemek, tutarlı tutulması gereken ikinci bir sistem değil, benzerlik aramasının hemen yanındaki bir WHERE cümlesidir. Go’nun concurrency’si, embedding throughput’unun genellikle darboğaz olduğu büyük corpus’larda ingestion’ı hızlı tutar. Ve yerel modeller, gizlilik sorusunu bir politika pazarlığından bir mimari garantiye dönüştürür — bunun karşılığında latency ve GPU bütçesini kiralamak yerine sahiplenmiş olursunuz. Local-first otomatik olarak daha iyi değildir; veriniz dışarı çıkamadığında daha iyidir ve o zaman da yayına alınabilen tek seçenektir.

Bu sizin için ne anlama geliyor

Prototipi geride bıraktıysanız ve production’a doğru bakıyorsanız, yapılacak iş çoğunlukla gösterişsiz ama tümüyle değerlidir. Yayına almadan önce şunları işaretleyebiliyor olmalısınız:

  • Chunking, sabit bir token sayısına değil, belge yapısına saygı gösteriyor
  • Her chunk; filtreleme, güncellik, kapsam ve kaynak gösterme için metadata taşıyor
  • Retrieval, bir re-ranking aşaması içeren hybrid (vector + keyword) yapıda
  • Etiketli bir eval set var, version control’de ve her değişiklikte çalıştırılıyor
  • Retrieval precision/recall ile cevap sadakatini ayrı ayrı ölçüyorsunuz
  • Cevaplar kaynaklarını gösteriyor ve UI bu kaynakları açığa çıkarıyor
  • Sistem, bir retrieval-güven eşiğinin altında “bilmiyorum” diyebiliyor
  • Latency ve sorgu başına maliyet bütçeleniyor, işe yaradığı yerde caching ile
  • Embedding modeli ve sürümü sabitlenmiş; yeniden embed etmek bir migration olarak ele alınıyor
  • Gizlilik duruşu, umutla değil, mimariyle belirleniyor

Bunların hiçbiri için frontier bir modele ya da bir araştırma ekibine ihtiyaç yok. Gereken şey, retrieval’ı ürünün kendisi olarak, değerlendirmeyi pazarlık konusu olmayan bir şart olarak ve “bilmiyorum”u bir özellik olarak ele almaktır. Demo size toplantıyı kazandırır. Bu ise size güveni kazandırır.

Etiketler: #rag #llm #vector-search #ai-engineering

Okumaya devam et

Hadi birlikte geliştirelim.

İster yepyeni bir ürün olsun, ister arkasında ciddi bir ekip gereken bir yazılım — bize anlatın.