Gemini ve LangChain Kullanarak RAG ile Chatbot Geliştirme
Geleneksel sohbet botları yalnızca eğitildikleri verilerle mi çalışmalı? Yapay zeka çağında bundan daha fazlasını beklemiyor muyuz? İşte burada Retrieval-Augmented Generation (RAG) devreye giriyor.
RAG, büyük dil modellerinin (LLM) en büyük eksikliklerinden biri olan doğruluk ve güncellik sorunlarını çözen devrim niteliğinde bir tekniktir. Geleneksel modeller, yalnızca önceden eğitildikleri bilgilerle yanıt üretebilirken, RAG tabanlı bir sistem belirli dokümanlardan, veritabanlarından veya web sayfalarından veri alarak daha isabetli ve güncel cevaplar sunar.
Bu makalede, Google Gemini ile LangChain ve yüksek performanslı vektör veritabanı ChromaDB kullanarak, özel bilgileri içeren bir akıllı chatbot geliştirmeyi öğrenmeye çalışacağız.
Peki Gemini nedir ? Hangi Modeli Kullanmalıyım?
Gemini (eski adıyla Bard), Google AI tarafından geliştirilen ve 21 Mart 2023'te kullanıma sunulan bir yapay zeka sohbet robotudur. 200’den fazla ülkede, 40’tan fazla dilde hizmet veren Gemini, deneysel olarak ücretsiz kullanabiliyor. Google’ın LaMDA dil ailesini temel alan bu model, büyük ölçekli dil işleme yetenekleri sunarak kullanıcıların farklı ihtiyaçlarını karşılayabiliyor. Ben de bu uygulamayı yaparken tanıştım kendisi ile. :)
Ama hangi modeli kullanmam konusunda biraz kararsız kaldım. Bu çalışma özelinde ben bir PDF özelinden soru-cevap yapabileceğim bir chatbot geliştirip basitçe RAG uygulamasını Gemini API’si ile göstermek istiyorum.
Yaptığım araştırmalara istinaden;
- Gemini 2.0 Flash, daha yüksek kullanım limitleri sunar: 15 RPMt (dakikada 15 istek), 1M TPM (aylık 1 milyon token) ve 1,500 RPD (günlük 1,500 istek). Bu özellikleriyle, sık ve yoğun API kullanımı gerektiren senaryolar için daha uygun gözüküyor. Ancak bağlam penceresi hakkında resmi bir bilgiye ulaşamadım, bu nedenle uzun metin işleme kapasitesi net olmadığı için burada soru işaretlerim oldu.
- Gemini 1.5 Pro, daha düşük kullanım limitlerine sahip olsa da 1 milyon tokenlik bağlam penceresi sunarak uzun dokümanları analiz etmek, büyük veri setleriyle çalışmak veya karmaşık içerikleri işlemek isteyenler için daha uygun bir seçenek gibi görünüyor. Ancak, yalnızca 2 RPM(Dakikada 2 istek), 32K TPM(Dakikada 32.000 token) ve 50 RPD(Günlük 50 istek) ile sınırlı olduğundan, sık API çağrıları yapanlar için yetersiz kalabiliyormuş.
Her iki modele de baktığımda Google Arama ile entegre olmadığını görmekteyim, yani güncel bilgileri gerçek zamanlı olarak çekemiyorlar, sadece sadece eğitim verileriyle yanıt üretebilmektedirler.
Yani sonuç olarak;
- Hızlı ve sık API çağrıları yapmak istersem, geniş kullanım limiti nedeniyle Gemini 2.0 Flash daha iyi bir seçim gibi görünüyor.
- Uzun belgeleri analiz etmek, büyük veri setleriyle çalışmak veya bağlamı daha geniş tutmak istiyorsanız, Gemini 1.5 Pro daha uygun gibi gözüküyor.
Başlangıçta Gemini 1.5 Pro ile başlamayı düşünüyorum. Sonrasında sorun aşarsam Gemini 2.0 Flash’ı deneyebilirim. :)
Geliştirme Ortamını Hazırlama
Bu projeyi başlatmak için öncelikle geliştirme ortamımızı kurmamız gerekmektedir. Sanal ortam oluşturmanın temel amacı, bağımsız ve kontrollü bir çalışma alanı sağlamaktır. Her proje farklı kütüphaneler ve bağımlılıklar kullanabilir. Sanal ortam sayesinde projeye özgü bağımlılıklar yönetilebilir ve farklı projelerde aynı kütüphanenin farklı sürümlerini sorunsuz kullanabilirsiniz.
Örneğin : Bir projede LangChain V0.1, başka bir prjede LangChain v0.2 kullanıyorsanız, sanal ortam sayesinde sürüm çatışmalarını önleyebilirsiniz.
Sanal ortam kurmadığınız takdirde, yüklediğiniz her paket tüm sistem genelinde kurulur ve zamanla bağımlılık çakışmaları yaşanabilir. Sanal ortamla bu sorun ortadan kaldırılarak proje izole edilebilir.
Ayrıca bizim de proje özelinde yapacağımız bir örnekte olduğu gibi; bir projeyi farklı makinede çalıştırmak gerekirse sanal ortam ve requirements.txt gibi dosyalar sayesinde projenin aynısını kurabilmek ve çalıştırmak çok daha kolay olur.
- Anaconda, veri bilimi, makine öğrenimi ve yapay zeka projeleri için sıkça kullanılan, Python ve R tabanlı bir dağıtım paketidir. İçinde çok sayıda kütüphane, bağımlılık yöneticisi (Conda) ve sanal ortam desteği bulunur, bu sayede projeleri izole edip yönetmek kolaylaşır. Normalde Python 3.12 ile çalışma gerçekleştirecektim ama ChromaDb’nin paketini yüklerken o kadar fazla hata aldım ki Python sürümünü birazcık düşürmem gerekti. O yüzden Anaconda Prompt’u açtım ve Python 3.10 sürümüne sahip bir sanal ortam oluşturdum:
conda create -n rag_env python=3.10 conda activate rag_env
- Ayrıca conda env list komutu ile birlikte Anaconda veya Miniconda kullanarak oluşturduğunuz tüm sanal ortamları listeleyebilirsiniz.
conda env list
# conda environments:
#
base * C:\Users\Kullanıcı\anaconda3
my_project_env C:\Users\Kullanıcı\anaconda3\envs\my_project_env
another_env C:\Users\Kullanıcı\anaconda3\envs\another_env
base
→ Anaconda’nın varsayılan ortamıdır ve*
işareti, şu an aktif olan ortamı gösterir.my_project_env
,another_env
gibi satırlar, oluşturduğunuz sanal ortamları ve konumlarını gösterir.
conda activate my_project_env
- Eğer belirli bir ortamı aktifleştirmek isterseniz conda env list komutunu kullanabilirsiniz.
- Sanal ortamı oluşturduktan sonra gerekli kütüphaneleri içeren
requirements.txt
dosyasını kullanarak bağımlılıkları yükleyebilirsiniz:
pip install -r requirements.txt
- Misalen bende aşağıdaki paketler vardı. Bunları neden ve ne için kullandığımızdan zaten bahsedeceğim;
langchain
langchain_community
langchain-google-genai
python-dotenv
streamlit
langchain_experimental
sentence-transformers
langchain_chroma
langchainhub
pypdf
rapidocr-onnxruntime
- Conda tarafında sanal ortamı aktif hale getirdikten sonra paketleri listeleyebilmek için aşağıdaki komutu kullanabilirsiniz. Bu komut yalnızca aktif olan sanal ortam içindeki paketleri gösterir.
conda list
- Ortamı aktif hale getirmeden spesifik bir sanal ortamdaki paketleri listelemek isterseniz de aşağıdaki komutu kullanabilirsiniz.
conda list -n my_project_env
Bu işlem, LangChain, ChromaDB, Google’ın Gemini Pro modeli ve diğer bağımlılıkları yükleyecektir.
Streamlit Üzerinde Çalışma Yapmadan Önce Süreçleri Jupyter Notebook’tan İnceleme
Jupyter Notebook üzerinden çalışma yaparak, Streamlit uygulamasına geçmeden önce süreçleri daha ayrıntılı bir şekilde inceleyebiliriz. Öncelikle, uygun bir sanal makine seçildikten sonra gerekli tüm paketlerin yüklenmesi sağlanmalıdır. Ardından, verilerin işlenmesi, analiz edilmesi ve görselleştirilmesi gibi temel adımları Jupyter Notebook ortamında test edebiliriz. Bu sayede, kodun doğruluğunu ve performansını değerlendirme imkânı bulur, olası hata ve iyileştirme alanlarını belirleyerek Streamlit entegrasyonu öncesinde daha sağlam bir temel oluşturabiliriz.
Aman Dikkat! Python’un sanal ortam üzerinde seçili olduğuna dikkat edin, yoksa yüklediğiniz paketler özelinde çalıştırdığınız kodlar çalışmayacaktır.
Aşağıdak kod, Python’un çalıştırıldığı yürütülebilir dosyanın (executable) yolunu gösterir.
import sys
print(sys.executable)
C:\Users\kullanici_adi\AppData\Local\miniconda3\envs\env_langchain\python.exe
Bu çıktı, Python’un Miniconda içinde oluşturduğunuz “env_langchain” adlı sanal ortamda çalıştığını gösteriyor. Yani, Python bu sanal ortamın içindeki python.exe
dosyasından çalıştırılıyor.
Miniconda ve Anaconda, her ikisi de Conda paket ve ortam yönetim sistemini kullanır, ancak aralarında bazı temel farklar vardır.Miniconda, Conda’nın hafif bir sürümüdür ve sadece temel bileşenleri içerir. İhtiyacınıza göre kütüphaneleri kendiniz yüklersiniz.Anaconda, hazır yüklü paketlerle gelir ve özellikle veri bilimi alanında çalışanlar için hızlı bir başlangıç sağlar.
Eğer sisteminizde hafif ve özelleştirilebilir bir ortam istiyorsanız, Miniconda iyi bir seçimdir. Ancak hazır bir ekosistem ile başlamak istiyorsanız, Anaconda daha uygun olabilir. Ben buradaki örnekte MiniConda’yı daha uygun gördüm, benim için yeterliydi :)
RAG Kavramlarını Anlama
Büyük Dil Modelleri (LLM’ler), yalnızca eğitim verileriyle sınırlıdır. Örneğin, eğer bir model 2021 yılına kadar eğitildiyse, 2022 veya sonrasında ortaya çıkan yeni gelişmeleri bilemez ve bu nedenle güncel yanıtlar veremez.
Retrieval-Augmented Generation (RAG) yöntemi sayesinde, model bir soruya yanıt vermeden önce ilgili belgeleri tarar, gerekli bilgileri çeker ve yanıtını güncellenmiş verilerle oluşturur. Böylece model, yalnızca eğitim verilerine değil, harici kaynaklara da erişerek daha doğru ve güncel bilgiler sunabilir.
Bu projede ne yapacağız?
Bu projede, chatbot’un “Attention Is All You Need” araştırma makalesini temel alarak soruları yanıtlamasını sağlayacağız.
Nasıl Çalışacak?
- Öncelikle PDF formatındaki “Attention Is All You Need” makalesini yükleyeceğiz.
- Chatbot, bu makalenin içeriğini işleyecek ve anlayacak.
- Kullanıcı bir soru sorduğunda, chatbot ilgili bölümleri makaleden çekecek ve yanıtını güncellenmiş bilgilerle oluşturacak.
“Attention Is All You Need” makalesini seçtim çünkü günümüz büyük dil modellerinin (LLM) temelini oluşturan Transformer mimarisini tanıtan çığır açıcı bir çalışmadır. RAG tabanlı bir chatbot geliştirirken, modelin hangi bilgilere odaklanması gerektiğini belirleyen “attention” mekanizmasını anlamak kritik bir avantaj sağlar. Geleneksel RNN ve LSTM tabanlı yaklaşımların aksine, self-attention sayesinde uzun bağlamları etkili bir şekilde işleyerek en alakalı bilgileri seçebilme yeteneği sunar. Bu yüzden, chatbot’un dokümanlardan doğru bilgileri almasını ve en verimli şekilde yanıt üretmesini sağlamak için bu makaleyi temel almak mantıklı bir seçimdir.
from langchain_community.document_loaders import PyPDFLoader
loader = PyPDFLoader("attentionisallyouneed.pdf")
data = loader.load() # entire PDF is loaded as a single Document
#data
- LangChain’in
PyPDFLoader
sınıfını içe aktarıyoruz. PyPDFLoader
, PDF dosyalarını okuyup LangChain için doküman (Document) nesnesi haline getirmeye yarayan bir yükleyicidir.loader
nesnesi artık bu PDF dosyasını okumaya hazır hale geliyor.loader.load()
fonksiyonu çağrıldığında, PDF içeriği okunur ve tek bir Document nesnesi olarak yüklenir.- Tüm PDF tek bir belge olarak saklanır, yani her sayfa ayrı bir nesne halinde değil, tek bir büyük metin belgesi olarak işlenir.
len(data)
#15
PyPDFLoader
her PDF sayfasını ayrı birDocument
nesnesi olarak yüklemiştir.- Yani,
data
değişkeni 15 elemandan oluşan bir liste olup, her eleman bir PDF sayfasına karşılık gelmektedir.len(data) == 15
çıktısı, PDF’nin 15 sayfa içerdiğini gösterir.
Veriyi Parçalara Ayırma (Chunking)
from langchain.text_splitter import RecursiveCharacterTextSplitter
# split data
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000)
docs = text_splitter.split_documents(data)
print("Total number of documents: ",len(docs))
Tek bir büyük metin dosyası yerine, belgeyi daha küçük bölümlere ayırarak chatbot’un doğru bilgiyi daha iyi bulmasını sağlayabiliriz.
from langchain.text_splitter import RecursiveCharacterTextSplitter
LangChain’in RecursiveCharacterTextSplitter
modülünü içe aktarıyoruz.
- Bu modül, büyük metinleri küçük parçalara bölmek için kullanılır.
- Özellikle uzun dokümanlarla çalışan RAG sistemlerinde gereklidir, çünkü dil modelleri çok büyük girdileri tek seferde işleyemez.
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000)
Metni küçük parçalara ayırırken (chunking), ideal parça boyutunu belirlemek önemlidir. 1000 karakter, genellikle doğru dengeyi sağlamak için seçilen bir değerdir. 1000 karakterlik chunk boyutu, LLM’lerin bağlam sınırlarını aşmadan metni verimli işlemesini sağlarken, çok küçük chunk’lar gibi bağlam kaybına neden olmaz ve çok büyük chunk’lar gibi arama süreçlerini yavaşlatmaz. Bu boyut, anlam bütünlüğünü koruyarak doğru bilgiyi hızlı ve etkili şekilde geri getirmek için optimum dengeyi sağlar.
- Genel olarak birçok RAG tabanlı projede, chunk_size 512–2000 arasında seçilir.
- Deneysel olarak, 1000 karakter hem anlam bütünlüğünü koruyup hem de arama hızını artırdığı için sıkça tercih edilir.
- Eğer chunk_size=5000 veya daha büyük olsaydı, her parça çok fazla bilgi içerirdi ve modelin bağlam penceresi çabuk dolardı.
❌ Kötü Seçenek — chunk_size=200
:
"Transformers are a type of deep learning model that has revolutionized natural language processing. The key innovation in Transformers is..."
(Bu parçadan sonra model bağlamı kaybedebilir.)
✅ Daha İyi Seçenek — chunk_size=1000
:
"Transformers are a type of deep learning model that has revolutionized natural language processing. The key innovation in Transformers is the self-attention mechanism, which allows the model to focus on relevant parts of the input sequence dynamically..."
(Bu parça daha anlamlıdır ve bağlamı daha iyi korur.)
RecursiveCharacterTextSplitter
nesnesini oluşturuyoruz.
chunk_size=1000
,her parçanın maksimum 1000 karakter uzunluğunda olacağını belirtiyor.- Recursive karakter bazlı kesme işlemi, bölme için uygun yerler (örneğin, cümle sonları, boşluklar) arayarak metni en anlamlı şekilde böler.
- Eğer tam 1000 karakterlik bir bölme yapamazsa, anlamlı bir bölme noktası bulana kadar biraz daha küçük veya büyük kesebilir.
docs = text_splitter.split_documents(data)
split_documents(data)
fonksiyonu çağrılıyor vedata
listesindeki tümDocument
nesneleri parçalanıyor.data
içinde 15 sayfa vardı.- Her sayfa, RecursiveCharacterTextSplitter ile 1000 karakterlik parçalara ayrıldı.
- Yeni parçalar
docs
adlı listeye kaydedildi.
print("Total number of documents: ", len(docs))
Total number of documents: 52
Kaç tane küçük belge (chunk) oluşturulduğunu ekrana yazdırır.
- Başlangıçta 15 sayfalık bir PDF vardı (len(data) = 15).
- Her sayfa RecursiveCharacterTextSplitter kullanılarak 1000 karakterlik parçalara bölündü.
- Sonuç olarak, 15 sayfalık belge toplamda 52 parçaya (chunk) bölündü.
- Yani, bazı sayfalar birden fazla parçaya ayrıldı çünkü sayfa başına 1000 karakter sınırını aşan metinler içeriyordu.
Eğer bir sayfanın karakter sayısı 1000'den fazlaysa, bu sayfa birden fazla parçaya bölünecektir. Eğer bir sayfa zaten 1000 karakterden kısa ise, olduğu gibi korunur. Sonuç olarak, 15 sayfa => daha fazla parçaya bölünerek
docs
içinde saklanır.
Bu aşama, büyük metinleri küçük parçalara bölerek modelin daha verimli ve anlamlı çalışmasını sağlar.
- RAG sistemlerinde model, ilgili bilgileri almak için küçük belgelere erişir, böylece arama işlemleri hızlanır ve daha doğru sonuçlar elde edilir.
- Dil modelleri (LLM’ler) genellikle sınırlı bağlam pencerelerine sahiptir, bu yüzden büyük metinler küçük parçalara bölünerek işlenmelidir.
- Chunk size (parça boyutu) çok küçük olursa, bağlam kaybolabilir; çok büyük olursa, model fazla bilgi yüküyle karşılaşabilir. 1000 karakter, genellikle dengeli bir seçimdir.
docs[7]
Document(metadata={'producer': 'pdfTeX-1.40.25', 'creator': 'LaTeX with hyperref', 'creationdate': '2024-04-10T21:11:43+00:00', 'author': '', 'keywords': '', 'moddate': '2024-04-10T21:11:43+00:00', 'ptex.fullbanner': 'This is pdfTeX, Version 3.141592653-2.6-1.40.25 (TeX Live 2023) kpathsea version 6.3.5', 'subject': '', 'title': '', 'trapped': '/False', 'source': 'attentionisallyouneed.pdf', 'total_pages': 15, 'page': 1, 'page_label': '2'}, page_content='in the distance between positions, linearly for ConvS2S and logarithmically for ByteNet. This makes\nit more difficult to learn dependencies between distant positions [ 12]. In the Transformer this is\nreduced to a constant number of operations, albeit at the cost of reduced effective resolution due\nto averaging attention-weighted positions, an effect we counteract with Multi-Head Attention as\ndescribed in section 3.2.\nSelf-attention, sometimes called intra-attention is an attention mechanism relating different positions\nof a single sequence in order to compute a representation of the sequence. Self-attention has been\nused successfully in a variety of tasks including reading comprehension, abstractive summarization,\ntextual entailment and learning task-independent sentence representations [4, 27, 28, 22].\nEnd-to-end memory networks are based on a recurrent attention mechanism instead of sequence-')
docs[7]
nesnesinin içeriğini gösteren bir LangChain Document
nesnesidir. İçinde iki temel bilgi bulunur:
metadata
(üstbilgi): Belgeye dair ek bilgiler içerir.page_content
(sayfa içeriği): Asıl metni tutar.
metadata
(Üstbilgi) → Belge Hakkında Teknik Bilgiler
'producer': 'pdfTeX-1.40.25'
→ PDF'nin üretildiği yazılım (TeX tabanlı oluşturulmuş).'creator': 'LaTeX with hyperref'
→ PDF'nin LaTeX kullanılarak oluşturulduğunu gösterir.'creationdate': '2024-04-10T21:11:43+00:00'
→ PDF’nin oluşturulma tarihi.'moddate': '2024-04-10T21:11:43+00:00'
→ Son değişiklik tarihi.'source': 'attentionisallyouneed.pdf'
→ PDF'nin adı.'total_pages': 15
→ PDF'nin toplam sayfa sayısı.'page': 1
→ Bu parçanın hangi sayfadan geldiğini gösterir (sayfa 1).'page_label': '2'
→ PDF içindeki görünen sayfa numarası (PDF'de ilk sayfa kapak olabilir, bu yüzden "2" olarak görünüyor).
page_content
(Sayfa İçeriği) → PDF’nin Metin İçeriği
Bu kısım, PDF’den çıkarılan gerçek metni içerir.
Örneğin, docs[7], PDF’nin 1. sayfasına ait bir bölümü içeriyor ve şu metni barındırıyor:
“Self-attention, sometimes called intra-attention, is an attention mechanism relating different positions of a single sequence in order to compute a representation of the sequence…”
Bu, “Attention Is All You Need” makalesinin self-attention mekanizmasını tanımlayan bölümü gibi görünüyor.
Embedding (Gömülü Temsiller) Oluşturma ve ChromaDB’ye Kaydetme
from langchain_chroma import Chroma
from langchain_google_genai import GoogleGenerativeAIEmbeddings
from dotenv import load_dotenv
load_dotenv()
#Get an API key:
# Head to https://ai.google.dev/gemini-api/docs/api-key to generate a Google AI API key. Paste in .env file
# Embedding models: https://python.langchain.com/v0.1/docs/integrations/text_embedding/
embeddings = GoogleGenerativeAIEmbeddings(model="models/embedding-001")
vector = embeddings.embed_query("hello, world!")
vector[:5]
#vector
Dil modelleri metinleri doğrudan anlayamaz. Bunun yerine, her kelimeyi matematiksel bir vektöre dönüştüren embedding (gömülü temsil) işlemi yapılmalıdır.
- Gerekli Kütüphaneleri İçeri Aktarma
from langchain_chroma import Chroma
from langchain_google_genai import GoogleGenerativeAIEmbeddings
Chroma
, vektör veritabanı olarak kullanılan bir bellek yönetim aracıdır.GoogleGenerativeAIEmbeddings
, Google'ın Generative AI servisinden embedding (vektörleştirme) almak için kullanılır.
API Anahtarını Yüklemek
from dotenv import load_dotenv
load_dotenv()
.env
dosyasını yükler ve içindeki ortam değişkenlerini (örneğin API anahtarlarını) sisteme ekler..env
dosyasında Google AI API anahtarının saklandığını varsayabiliriz.- Bu, güvenlik açısından iyi bir uygulamadır, çünkü API anahtarlarını doğrudan koda yazmak yerine
.env
dosyasında saklamak daha güvenlidir.
API anahtarını nasıl alabilirsiniz?
- Kodun yorum satırına göre, Google AI API anahtarı almak için şu adrese gidilmesi gerekiyor: https://ai.google.dev/gemini-api/docs/api-key. Daha önce de bahsettiğimiz üzere burada Gemini 1.5 Pro’yu seçimledim.
Google AI Embedding Modelini Kullanma
embeddings = GoogleGenerativeAIEmbeddings(model="models/embedding-001")
- Google AI’nin embedding modelini başlatıyoruz.
Google Gemini API, üç farklı embedding modeli sunuyor:
- gemini-embedding-exp-03–07
- text-embedding-004
- embedding-001
"models/embedding-001"
, Google'ın embedding modeli olarak kullanılıyor."embedding-001"
, genel amaçlı metin yerleştirme (embedding) için en temel model olabilir.gemini-embedding-exp-03-07
modelinin isminde "exp" (experimental) geçiyor, yani henüz tam oturmamış veya test aşamasında olabilir diye seçimlemedim.text-embedding-004
, OpenAI’nintext-embedding-ada-002
modeline benzer gibi geldiği için farklı bir embedding seçimlemek istediğimden ve ilk sürüm olduğundan stabil,test edilmiş bir model olduğundan “embedding-001”’i seçtim.- Bu model, verilen metni sayısal vektörlere dönüştürerek daha sonra arama, karşılaştırma veya makine öğrenimi modellerinde kullanılmasını sağlıyor.
Bir Metni Vektöre Dönüştürme
vector = embeddings.embed_query("hello, world!")
"hello, world!"
ifadesini embedding modeline veriyoruz.
embed_query()
fonksiyonu, bu metni yüksek boyutlu bir vektöre çevirir.
Bu vektör, metnin anlamını sayısal olarak temsil eden bir dizi sayıdır.
İlk 5 Vektör Değerini Yazdırma
vector[:5]
- Vektörün yalnızca ilk 5 elemanını görüntüleriz.
- Bir embedding vektörü genellikle 256, 512 veya 768 boyutlarında olabilir.
[0.05168594419956207,
-0.030764883384108543,
-0.03062233328819275,
-0.02802734263241291,
0.01813093200325966]
- 0.0516 → Metnin belirli bir semantik özelliğini temsil ediyor.
- -0.0307 → Metnin bağlamına bağlı olarak başka bir özelliği gösteriyor.
- Bu vektör, aynı anlama gelen kelimelerin benzer sayısal değerlere sahip olmasını sağlıyor.
Neden Önemli?
- Benzer anlamlara sahip metinler, uzayda birbirine yakın vektörlere dönüşür.
- Bu vektörler, benzerlik karşılaştırmaları ve arama motorları için kullanılır.
- Örneğin, “hello, world!” ve “hi there!” gibi benzer ifadeler benzer vektörlere sahip olur.
ChromaDB Kayıt Aşaması
vectorstore = Chroma.from_documents(documents=docs, embedding=embeddings)
- Chroma, metinleri vektör formatında saklamak ve aramak için kullanılan bir vektör veritabanıdır.
from_documents(...)
, verilen belgeleri alıp, önce embedding vektörlerine çevirir, sonra Chroma içine kaydeder.
Yani bu satır, chatbot’un yanıt verebilmesi için bir bilgi havuzu oluşturuyor.
documents=docs
→ Hangi Belgeler Kullanılıyor?
docs
, önceki adımlarda PDF’den bölünerek oluşturulan küçük metin parçalarıdır.- Bu belgeler, embedding modeliyle vektörlere dönüştürülerek Chroma içinde saklanıyor.
- Böylece, bir soru sorulduğunda ilgili bölümler bulunabilir.
embedding=GoogleGenerativeAIEmbeddings(model="models/embedding-001")
→ Embedding Modeli Seçiliyor
- Bu satır, Google’ın embedding modelini kullanarak her belgeyi vektör formatına çeviriyor.
"models/embedding-001"
, metinleri sayısal bir vektöre dönüştürmek için kullanılan modeldir.
Özetle:
docs
içindeki her belge vektörleştirilir.- Bu vektörler, Chroma veri tabanına kaydedilir.
- Bir arama yapıldığında, ilgili belgelerin embedding vektörleri kullanılarak en alakalı metinler bulunur.
Chroma vektör veritabanından en alakalı belgeleri bulma
retriever = vectorstore.as_retriever(search_type="similarity", search_kwargs={"k": 10})
- Bu satır, Chroma vektör veritabanından benzer belgeleri getiren bir retriever (bilgi getirme mekanizması) oluşturuyor.
- Chroma, içeride metinleri vektör olarak saklıyor.
- Retriever, gelen sorgunun embedding vektörünü oluşturup en benzer belgeleri bulmayı sağlar.
as_retriever()
, Chroma içindeki belgeler üzerinde benzerlik bazlı aramalar yapabilen bir arama katmanı oluşturur.- Burada “similarity” (benzerlik) araması kullanılıyor.Bu, sorgunun embedding vektörünü alıp, Chroma’daki en yakın vektörleri bulması anlamına gelir.
- Örneğin, “What is Encoder?” sorusu için, “Encoder” kavramının geçtiği veya ona en yakın anlamda bulunan belgeler getirilecektir.
search_kwargs={"k": 10}
→ Kaç Sonuç Döndürüleceğini Belirtiyor.k=10
, en alakalı 10 belgeyi (chunk) döndürmesini sağlar.Büyük bir belgeyi tararken, genellikle en alakalı birkaç bölümü getirmek istenir, böylece model fazla veriyle boğulmaz.Eğerk=3
olsaydı, yalnızca en alakalı 3 belge getirilirdi.
retrieved_docs = retriever.invoke("What is Encoder?")
retriever.invoke("What is Encoder?")
→ Sorguyu Çalıştırınca Ne Olur?
- Bu satır, “What is Encoder?” (Encoder nedir?) sorusunu retriever’a yönlendirir.
- Retriever, bu sorunun embedding vektörünü çıkarır ve Chroma içindeki en benzer 10 belgeyi getirir.
- Sonuç olarak, bu belgeler
retrieved_docs
değişkeninde saklanır.
Bu, chatbot’un sorulan soruya uygun olarak vektör veritabanından en iyi belgeleri çekmesini sağlar.
print(retrieved_docs[5].page_content)
retrieved_docs
, "What is Encoder?"
sorgusuna en benzer 10 belgeyi içeren bir listedir.
retrieved_docs[5]
, bu listedeki 6. belgeyi (index 5) temsil eder..page_content
, bu belgenin içerdiği metni gösterir.print(...)
, bu içeriği ekrana yazdırır.
itself. To facilitate these residual connections, all sub-layers in the model, as well as the embedding
layers, produce outputs of dimension dmodel = 512.
Decoder: The decoder is also composed of a stack of N = 6identical layers. In addition to the two
sub-layers in each encoder layer, the decoder inserts a third sub-layer, which performs multi-head
attention over the output of the encoder stack. Similar to the encoder, we employ residual connections
around each of the sub-layers, followed by layer normalization. We also modify the self-attention
sub-layer in the decoder stack to prevent positions from attending to subsequent positions. This
masking, combined with fact that the output embeddings are offset by one position, ensures that the
predictions for position i can depend only on the known outputs at positions less than i.
3.2 Attention
An attention function can be described as mapping a query and a set of key-value pairs to an output,
Bu çıktı, “Encoder” kavramını anlatan en uygun belgelerden biri olduğu için Chroma tarafından getirilmiştir.
Google Gemini API için Özel Chat Model İle Çalışma : Gemini 1.5 Pro
from langchain_google_genai import ChatGoogleGenerativeAI
- LangChain’in Google Gemini API’si için özel chat modelini içe aktarıyoruz.
- ChatGoogleGenerativeAI, Google’ın Gemini modellerini kullanarak doğal dil işleme (NLP) görevlerini gerçekleştirmeye olanak tanır.
llm = ChatGoogleGenerativeAI(model="gemini-1.5-pro", temperature=0.3, max_tokens=500)
model="gemini-1.5-pro"
- Google’ın en gelişmiş dil modellerinden biri olan Gemini 1.5 Pro’yu kullanıyoruz.
- Daha uzun bağlam penceresi, gelişmiş dil anlayışı ve güçlü metin üretme yeteneklerine sahip.
temperature=0.3
→ Yanıtın Rastgeleliği (Çeşitlilik Ayarı)
temperature
, modelin yanıtlarındaki rastgeleliği ve yaratıcılığı belirler.- Düşük değerler (0.1–0.4): Daha kesin ve tutarlı cevaplar verir. Model daha tahmin edilebilir hale gelir.
- Orta değerler (0.5–0.7): Hem mantıklı hem de yaratıcı cevaplar verir.
- Yüksek değerler (0.8–1.0): Daha rastgele ve yaratıcı, ancak bazen tutarsız yanıtlar üretebilir.
Bu kodda temperature=0.3
olduğu için:
- Model, daha az rastgele ve daha mantıklı cevaplar üretecektir.
- Özellikle teknik veya doğru bilgi gerektiren durumlarda düşük sıcaklık iyidir.
max_tokens=500
→ Yanıtın Maksimum Uzunluğu
max_tokens
, modelin ürettiği yanıtın maksimum uzunluğunu belirler.- “Token”, kelime parçacıkları anlamına gelir. Bir kelime genellikle 1 ila 3 token arasında olabilir.
"Hello, how are you?"
(5 kelime) yaklaşık 7-8 token olabilir.- 500 token, yaklaşık 350 kelimelik bir yanıt anlamına gelir.
Bu kodda max_tokens=500
olduğu için:
- Model, yanıtını 500 token ile sınırlandıracaktır.
- Çok uzun veya gereksiz bilgiler içeren yanıtların önüne geçer.
- Yanıtları optimize ederek API kullanım maliyetini düşürebilir.
from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_core.prompts import ChatPromptTemplate
create_retrieval_chain
→ Belge arama (retrieval) işlemi için bir zincir (chain) oluşturur.create_stuff_documents_chain
→ Bulunan belgeleri birleştirip (stuffing) modelin anlamlı bir yanıt üretmesini sağlar.ChatPromptTemplate
→ Chatbot’a verilen sistem ve kullanıcı mesajlarını tanımlamak için kullanılır.
Sistem Prompt’unu Tanımlama (Kurallar ve Rol)
system_prompt = (
"You are an assistant for question-answering tasks. "
"Use the following pieces of retrieved context to answer "
"the question. If you don't know the answer, say that you "
"don't know. Use three sentences maximum and keep the "
"answer concise."
"\n\n"
"{context}"
)
Bu sistem promptu, chatbot’un nasıl davranacağını ve yanıtlarını nasıl oluşturması gerektiğini belirliyor:
🔹 “You are an assistant for question-answering tasks.”
👉 Chatbot’un bir soru-cevap (Q&A) asistanı olduğunu söylüyor.
🔹 “Use the following pieces of retrieved context to answer the question.”
👉 Chatbot’un sadece verilen bağlam (retrieved context) içinde kalarak yanıt vermesini sağlıyor.
🔹 “If you don’t know the answer, say that you don’t know.”
👉 Chatbot’un yanlış bilgi üretmesini önlemek için, eğer bağlamda bilgi yoksa “Bilmiyorum” demesini söylüyor.
🔹 “Use three sentences maximum and keep the answer concise.”
👉 **Chatbot’un cevabını en fazla 3 cümleyle kısa ve öz tutmasını sağlıyor.
🔹 {context}
👉 Bu, retriever tarafından getirilen ilgili bilgilerin buraya yerleştirileceği yerdir.
Chat Prompt’unu Tanımlama (Sistem + Kullanıcı Mesajları)
prompt = ChatPromptTemplate.from_messages(
[
("system", system_prompt),
("human", "{input}"),
]
)
("system", system_prompt)
→ Modelin temel kurallarını belirleyen sistem mesajıdır. (Yukarıda tanımlanansystem_prompt
kullanılıyor.)("human", "{input}")
→ Kullanıcının sorusunu temsil eder.
Sistem mesajı: Modelin nasıl davranacağını açıklar.
Kullanıcı mesajı: Modelin yanıtlaması gereken soruyu içerir.
from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_core.prompts import ChatPromptTemplate
system_prompt = (
"You are an assistant for question-answering tasks. "
"Use the following pieces of retrieved context to answer "
"the question. If you don't know the answer, say that you "
"don't know. Use three sentences maximum and keep the "
"answer concise."
"\n\n"
"{context}"
)
prompt = ChatPromptTemplate.from_messages(
[
("system", system_prompt),
("human", "{input}"),
]
)
Soru-Cevap Zinciri (LLM ve Prompt ile Yanıt Üretme)
question_answer_chain = create_stuff_documents_chain(llm, prompt)
Bu satır, büyük dil modeline (LLM) verilen bağlamı kullanarak yanıt üretmesini sağlayan bir “zincir” oluşturur.
create_stuff_documents_chain(llm, prompt)
,
- LLM’yi (
llm
) alır. - Chat Prompt’unu (
prompt
) alır. - Retriever tarafından getirilen bağlam içindeki bilgileri “stuffing” yöntemiyle LLM’ye ekler.
- Bu şekilde, model bağlamdaki bilgileri doğrudan kullanarak yanıt üretir.
RAG Zincirini Oluşturma (Retriever + LLM Entegrasyonu)
rag_chain = create_retrieval_chain(retriever, question_answer_chain)
Bu satır, “Retriever” (bilgi getirme) ile “LLM” (yanıt üretme) işlemlerini birleştiren tam bir RAG zinciri oluşturur.
retriever
, vektör veritabanındaki ilgili belgeleri bulur.question_answer_chain
, bulunan belgeleri LLM’ye vererek nihai cevabı oluşturur.- Bu işlem, RAG sisteminin temel mekanizmasını oluşturur.
- Retriever, en uygun belgeleri getirir.
- LLM, bu belgelerden faydalanarak doğru bir yanıt oluşturur.
- Bu iki adım birleştirilerek doğru bilgiyi çeken ve işleyen tam bir RAG zinciri oluşturulur.
Kullanıcı Sorgusunu Çalıştırma (RAG Zincirini Kullanarak Cevap Üretme)
response = rag_chain.invoke({"input": "what is encoder?"})
Bu satır, “what is encoder?” (Encoder nedir?) sorusunu RAG sistemine ileterek çalıştırır.
- “what is encoder?” sorgusu retriever’a gider.
- Retriever, ilgili belgeleri bulup
question_answer_chain
içine aktarır. - LLM, bu belgeleri kullanarak bir yanıt üretir.
- Yanıt,
response
değişkeninde saklanır.
Kullanıcı bir soru sorar.
Sistem, en uygun bilgileri içeren belgeleri vektör veritabanından alır.
LLM, bu belgeleri işleyerek en iyi yanıtı oluşturur
Nihai Yanıtı Yazdırma
print(response["answer"])
Bu satır, RAG zincirinin ürettiği cevabı ekrana yazdırır.
The encoder maps an input sequence of symbol representations (x1, ..., xn) to a sequence of continuous representations z = (z1, ..., zn). It is composed of a stack of 6 identical layers, each with two sub-layers: a multi-head self-attention mechanism and a position-wise fully connected feed-forward network. Residual connections and layer normalization are employed around each sub-layer.
Retriever → Uygun bilgileri bulur.
LLM → Bilgileri kullanarak yanıt üretir.
Yanıt ekrana yazdırılır. ✅
Streamlit Üzerinde Testi Nasıl Yapacağız
Jupyter Notebook’ta test edilen LangChain ve Gemini tabanlı RAG Chatbot’unu Streamlit ile çalıştırmayı ve web arayüzünde göstermeyi adım adım anlatalım.
1️⃣ Streamlit’i içe aktar:
import streamlit as st
2️⃣ Uygulama başlığını ayarla:
st.title("RAG Application with Gemini Pro 1.5")
3️⃣ Kullanıcıdan giriş al:
query = st.chat_input("Ask me a question:")
4️⃣ Yanıtı ekrana yazdır:
st.write(response["answer"])
5️⃣ Streamlit uygulamasını başlat:
streamlit run app.py
Bugünlük anlatacaklarım bu kadar, bir sonraki yazıda görüşmek üzere.
Sevgiler,
Kardel