Özelleştirilmiş RAG çözümlerinden uygulamalı örnekler

Gerçek dünya örnekleri, özelleştirilmiş getirmeyle zenginleştirilmiş üretim sistemlerinin karmaşık kurumsal bilgi sorunlarını nasıl çözdüğünü gösteriyor.

RAG bugünlerde bazen kötü anılıyor. Bunun nedeni, insanların artık onu tamamen basit sanması (başlamak kolay, ölçeklemek o kadar değil) ya da yerini “ajan tabanlı sistemlere” bıraktığını düşünmesi (oysa çoğu durumda yüzeyi biraz kazıyınca bunlar hızla RAG'e benzemeye başlıyor…).

Bu blog yazısında, sık karşılaştığımız şu zorlukları nasıl ele aldığımızı bir iki uygulamalı örnekle gösteriyoruz:

  • Metin ve sayısal verileri birlikte işleme ve bunun basit RAG'i neden bozduğu: Anahtar kelimeler çakışır, sayılar ise anlamsal bir anlam taşımaz.

  • Önce özet yaklaşımıyla embedding tasarlamak neden yararlıdır: Her parça için kısa ve açıklayıcı bir özet oluşturun, ardından embedding ve sorgulama işlemlerini bu özet üzerinde yapın.

  • Bağlamsal özetler nasıl oluşturulur: Benzer yapıdaki istatistiklerin ayırt edilebilmesi için ana belge bağlamını ekleyin.

  • Ne zaman kod ve Pydantic modellerine başvurulmalı: İçeriğin aynen korunması gerektiğinde güvenilirlik için özel kodu ve/veya Pydantic modelini LLM çağrılarıyla birleştirin.

Özel RAG çözümleri geliştirme

Temel bilgiler

RAG sistemleri, destek botlarından şirket içi bilgi asistanlarına kadar pek çok çözümün temelini oluşturur.

Arka planda genellikle şunları yaparsınız:

  1. Kaynak belgelerinizi parçalara ayırırsınız

  2. Her parçayı bir vektör uzayına gömersiniz

  3. Sorgu sırasında en iyi K parçayı getirirsiniz

  4. Bu parçaları temel alan bir yanıt üretirsiniz

LangChain, LlamaIndex ve OpenAI Filestore gibi popüler araç setleri bu adımları neredeyse zahmetsiz hâle getirir. Ancak gerçek dünyadaki veri hatlarında yalnızca yoğun metinden oluşmayan verilerle karşılaşırsınız ve temel RAG bunlarla başa çıkmakta zorlanabilir. İzleyen bölümlerde veriyle ilgili zorlukları somut örneklerle gösterecek ve karmaşıklık arttıkça çözümü adım adım geliştireceğiz.

İşler zorlaştığında

  1. Verileriniz yalnızca metinden oluşmadığında (aslında oldukça sık görülür)

Oyun bağlamındaki şu veri parçasını ele alalım:

JSON

{ "Attack": { "Range": { "default": 5, "with_Draconic_Ascension": 5.5, }, "Speed": { "default": "2 seconds", "with_Draconic_Ascension": "2.2 seconds", }, "Damage": { "default": 10, "with_Draconic_Ascension": 12, } }} 

Embedding'ler, kelimeler arasında anlamsal anlam ve dil bilgisi üzerinden öğrenilen ilişkiler sayesinde çalışır. Yukarıdaki veriler metin ve sayıların bir karışımıdır; bu bağlamın dışında sayılar ile kelimeler arasında herhangi bir ilişki yoktur. Dolayısıyla bu veri parçasının, biraz açıklayıcı birkaç kelime ile bunları izleyen rastgele sayıların birleşiminden ibaret olduğunu söyleyebiliriz.

Elimizde yalnızca bu tür veriler olsaydı bu aslında sorun yaratmazdı; mevcut birkaç açıklayıcı kelimenin embedding'leri üzerinden ilgili parçayı yine getirebilirdik (ya da doğrudan text-to-sql kullanabilirdik). Peki bu parça, aynı kelimelerin de geçtiği çok sayıda yoğun metin parçasının arasında gömülü olsaydı ne olurdu? Örneğin:

JSON

{"Draconic_Ascension": { "description": "Transform into dragon form. Attack range and speed are increased by 10%, damage is boosted by 20%.", "details": { "activation_conditions": "Can only be activated when HP is below 50%or Fury meter is full.", "visual_effects": "Wings unfurl, scales shimmer with embers, voice lines change to echoing growls.", "lore": "An ancient bloodline awakens. The bearer of the mark channels the soul of the last Flamewing Wyrm, becoming a living storm of fire and fury." } }}

Şimdi “What is the attack range with Draconic Ascension?” sorgusuyla ilgili parçayı getirmek istediğimizi düşünün. İstediğimiz parçayı büyük olasılıkla getiremeyiz; çünkü aynı anahtar kelimeleri içeren diğer parçaların yarattığı gürültünün içinde kaybolmuştur.

Temel sorun, aynı konu hakkında farklı türde bilgiler içerseler bile bu veri parçalarını birbirinden yeterince iyi ayıramamamızdır. Peki bunu bir şekilde zenginleştirip geliştirebilir miyiz? Elbette yapabiliriz:smile:

  1. Verilerinizi özetleyerek zenginleştirin; evet, yanlış okumadınız

Parçanın embedding'ini doğrudan oluşturmak yerine, önce verinin ne hakkında olduğunu açıklayan bir özet üretebilir; ardından embedding ve getirme işlemlerini bu özet üzerinden yapabiliriz. Üretim aşamasında ise özetle bağlantılı özgün verileri kullanmaya devam ederiz.

Yukarıda gösterilen iki parça örneği için şöyle özetler oluştururduk:

  1. Menzil, hız ve hasara ilişkin saldırı istatistikleri (varsayılan ve Draconic Ascension etkinken).

  2. Etkinleştirme koşulları, görsel efektleri ve hikâyesi dâhil olmak üzere Draconic Ascension'ın açıklaması ve ayrıntıları.

Ardından sorguyu da özetle “uyumlu” hâle getirecek şekilde zenginleştiririz. Örneğin “What is the attack range with Draconic Ascension?” sorgusunu “What is the statistics of attack range with Draconic Ascension?” sorgusuna dönüştürürdük.Bu, özellikle getirme sorgusunun teknik alanlar dışındaki kullanıcılar tarafından ~~“serbest tarzda”~~ normal insan diliyle yazıldığı durumlarda önemlidir. Sonuçta RAG'in kesinlik ve duyarlılığı en üst düzeye çıkarmak için nasıl çalıştığını bilmek onların ne uzmanlık alanıdır ne de önceliğidir.

İşler zorlaştığında ne olduğunu gösteren diyagram.

  1. Konuyu bağlamından koparmayın (genel olarak hayatta da geçerli)

Bir sonraki senaryoda ise aşağıdaki gibi birbirine benzeyen çok sayıda veri parçasıyla uğraşıyoruz:

Plain Text

# Chunk one{ "Attack": { "Range": { "default": 9, }, ... }}# Chunk two{ "Attack": { "Range": { "default": 6, }, ... }}# Chunk three{ "Attack": { "Range": { "default": 7, }, ... }}

Aynı yaklaşımı sürdürdüğümüzü ve “what is character X’s attack range?” diye sorduğumuzu düşünün. Yeni oluşturduğumuz bu özetler de birbirine çok benzeyeceği için şansa dayalı bir tahmin oyunu oynamış olurduk. Peki bunları nasıl ayırt edebiliriz?

Basit yanıt: Bağlam sağlayarak. Veri parçasına ana belgesine yönelik bir referans ekleyebiliriz; örneğin bu durumda {”character”: “X”}. Böylece Y ve Z karakterleri için aynı veriler mevcut olsa bile X karakterine ait doğru verileri isabetli biçimde getirebiliriz.

Ancak daha iyi ve genellenebilir bir yaklaşım, parçanın bağlamsal bir özetini oluşturmaktır. Başka bir deyişle, yalnızca veri parçasının özetini oluşturmak yerine hem ana belgeyi hem de parçayı modele ileterek genel bir bağlamsal özet oluşturabiliriz. Bu özette parçanın ana belgedeki yerini de açıklarız. Örneğin:

  1. Bu parça, X karakterine ait … hakkında ayrıntılı istatistikler sunar. Bu parça, X'in saldırı hızındaki gücünü göstererek belgenin bütününü tamamlar…

  2. Bu parça, Y karakterine ait … hakkında ayrıntılı istatistikler sunar. Bu parça, Y'nin özel yeteneğiyle güçlenen istatistiklerini göstererek belgenin bütününü tamamlar…

  3. Bu parça, Z karakterine ait … hakkında ayrıntılı istatistikler sunar. Bu parça, Z'nin takım maçlarında tank rolüne çok uygun istatistiklerini göstererek belgenin bütününü tamamlar…

Kısmen Anthropic'ten esinlenen bu yöntem, yukarıdaki örnek için gereğinden fazla kapsamlı görünebilir. Ancak “bağlamından koparıldığında” yanlış yorumlanabilecek parçalar için son derece etkilidir. Üstelik tüm parçalarda çalışan birleşik bir yaklaşım sunarak düzenli bir mühendislik veri hattının korunmasını sağlar.

İşler zorlaştığında ne olduğunu gösteren diyagram.

  1. ~~Kontrol manyağı~~ titiz olmanız gerektiğinde

Verileri genellikle bütün hâlinde alır ve bir RAG sistemi için parçalara böleriz. Bu örnekte biraz farklı bir durumu ele alıyoruz: Veriler parçalara ayrılmış, ancak kötü biçimde. Aslında yeniden bir araya getirilmesi gereken mantıksal bir parçanın rastgele bölümlerinden oluşuyorlar. Mantıksal parça; bir belgenin alt bölümü veya kendi içinde tutarlı bir paragraf gibi, doğal olarak bir arada bulunması gereken içerik bölümüdür.

İşler zorlaştığında ne olduğunu gösteren diyagram.

Bu verilerle ilk denememizde her şeyi tek bir LLM çağrısına verip uygun gördüğü şekilde gruplandırmasını ve gruplanmış içeriği döndürmesini istedik. LLM'in bu konuda oldukça iyi olması gerekir, değil mi? Hem evet hem hayır.

Başka birçok durumda da gördük ki özellikle bağlam uzun olduğunda LLM'ler, içeriğin tamamını ve birebir aynısını istediğinizde tembel davranma eğiliminde oluyor ve güvenilir sonuç vermiyor. Bu da son derece anlaşılır. Ancak özgün içeriği kelimesi kelimesine, eksiksiz almamız gerektiğinden bu durum söz konusu kullanım senaryosu için kabul edilemezdi. Özetleme veya herhangi bir bölümü atlama söz konusu olamazdı. Hiçbir ayrıntıyı kaçırmamalıyız.

İşin “evet” kısmıysa bozuk parçaların anlam ve yapılarını çözmekte harika bir iş çıkarmasıydı. Tabii içeriği birebir aktarmayı reddetmediği sürece. Hay aksi:/

Peki bir LLM'in güçlü yönlerinden yararlanırken güvenilir olmadığı noktalardan nasıl kaçınabiliriz? Eski dostumuz koda başvurduk (özel Python fonksiyonu diye okuyun). Bir de “daha basiti olamaz” denecek bir Pydantic modeline. Çözüm şöyle:

  • Mevcut mantıksal parçayı koruyarak bölümleri sırayla işleyin

  • Her bölümde LLM'e şunu sorun: Bu bölüm mevcut mantıksal parçaya ait mi? (Pydantic modeline uygun olarak) evet veya hayır diye yanıtla.

  • Yanıt evetse bölümü parçaya ekleyin. Hayırsa tamamlanmış olan mevcut mantıksal parçayı olduğu gibi çıkarın ve bu bölümle yeni bir parça başlatın.

İşler zorlaştığında ne olduğunu gösteren diyagram.

Elbette burada tüm içeriği tek geçişte işlemekten biraz daha fazla token kullanıyoruz. Ancak içeriği birebir korumanın en önemli öncelik olduğu bu kullanım senaryosunda, küçük ek maliyete kesinlikle değdi.

Bu çok basit bir çözüm olsa da önemli bir ilkeye dayanıyor: Titizlik gerektiğinde, olasılıksal yapıları nedeniyle yalnızca LLM'lere güvenmemeliyiz.

Özel kod ve fonksiyonlar ile Pydantic modelleri, LLM'lerin yeteneklerinden yararlanmaya devam ederken öngörülebilir ve güvenilir sonuçlar elde etmek için kullanılabilir.

Sonuç

Üretken yapay zekâ çözümü geliştirmek, yapay zekâ kadar mühendislik açısından da zorlu bir süreçtir. Bu örneklerin, kendinize özgü zorlukların üstesinden gelmeniz için size ilham verdiğini umuyoruz. Mühendislik öncelikli üretken yapay zekâ çözümleri hakkında daha fazla bilgi edinmek için yönlendirici tabanlı ajan sistemi tasarımı konulu blog yazımıza göz atın.

Yazar

Cynthia Yu