Gelişmiş RAG Teknikleri

1. Giriş

Genel Bakış

Veriyle Artırılmış Üretim (RAG), Büyük Dil Modeli (LLM) yanıtlarını harici bilgilerle temellendirerek geliştirir. Ancak üretim için hazır bir RAG sistemi oluşturmak için basit bir vektör aramasından daha fazlası gerekir. Verilerin nasıl alındığını, alakalı sonuçların nasıl sıralandığını ve kullanıcı sorgularının nasıl işlendiğini optimize etmeniz gerekir.

Bu kapsamlı laboratuvarda, PostgreSQL İçin Cloud SQL (pgvector ile genişletilmiş) ve Vertex AI kullanarak güçlü bir RAG uygulaması oluşturacaksınız. Üç ileri düzey teknik öğreneceksiniz:

  1. Parçalama Stratejileri: Metni bölmek için kullanılan farklı yöntemlerin (Karakter, Özyinelemeli, Token) alma kalitesini nasıl etkilediğini gözlemleyeceksiniz.
  2. Yeniden sıralama: Arama sonuçlarını hassaslaştırmak ve "ortada kaybolma" sorununu gidermek için Vertex AI Reranker'ı uygulayacaksınız.
  3. Sorgu Dönüştürme: HyDE (Varsayımsal Doküman Yerleştirmeleri) ve Geri Adım İstemleri gibi tekniklerle kullanıcı sorgularını optimize etmek için Gemini'ı kullanacaksınız.

Yapacaklarınız

  • pgvector ile PostgreSQL için Cloud SQL örneği oluşturun.
  • Metni birden fazla strateji kullanarak parçalara ayıran ve yerleştirmeleri Cloud SQL'de depolayan bir veri alımı işlem hattı oluşturun.
  • Semantik aramalar yapın ve farklı parçalama yöntemlerinden elde edilen sonuçların kalitesini karşılaştırın.
  • Alınan dokümanları alaka düzeyine göre yeniden sıralamak için bir yeniden sıralayıcı entegre edin.
  • Belirsiz veya karmaşık sorular için almayı iyileştirmek amacıyla LLM destekli sorgu dönüşümlerini uygulayın.

Neler öğreneceksiniz?

  • LangChain'i Vertex AI ve Cloud SQL ile kullanma
  • Karakter, Özyinelemeli ve Token metin ayırıcılarının etkisi.
  • PostgreSQL'de Vector Search'ü uygulama
  • Yeniden sıralama için ContextualCompressionRetriever nasıl kullanılır?
  • HyDE ve Step-back Prompting'i nasıl uygulayacağınızı öğrenin.

2. Proje ayarlama

Google Hesabı

Kişisel Google Hesabınız yoksa Google Hesabı oluşturmanız gerekir.

İş veya okul hesabı yerine kişisel hesap kullanın.

Google Cloud Console'da oturum açma

Kişisel bir Google Hesabı kullanarak Google Cloud Console'da oturum açın.

Faturalandırmayı Etkinleştir

5 ABD doları değerindeki Google Cloud kredilerini kullanma (isteğe bağlı)

Bu atölyeyi düzenlemek için bir miktar kredisi olan bir faturalandırma hesabına ihtiyacınız vardır. Kendi faturalandırmanızı kullanmayı planlıyorsanız bu adımı atlayabilirsiniz.

  1. Bu bağlantıyı tıklayın ve kişisel bir Google Hesabı ile oturum açın. Şuna benzer bir şey görürsünüz: Kredi sayfasını görüntülemek için burayı tıklayın
  2. KREDİLERİNİZE ERİŞMEK İÇİN BURAYI TIKLAYIN düğmesini tıklayın. Bu işlem sizi faturalandırma profilinizi oluşturacağınız sayfaya yönlendirir. Faturalandırma profili sayfasını ayarlama
  3. Onayla'yı tıklayın. Artık Google Cloud Platform deneme sürümü faturalandırma hesabına bağlısınız. Faturalandırmaya genel bakış sayfasının ekran görüntüsü

Kişisel faturalandırma hesabı oluşturma

Faturalandırmayı Google Cloud kredilerini kullanarak ayarladıysanız bu adımı atlayabilirsiniz.

Kişisel faturalandırma hesabı oluşturmak için Cloud Console'da faturalandırmayı etkinleştirmek üzere buraya gidin.

Bazı notlar:

  • Bu laboratuvarı tamamlamak için 1 ABD dolarından daha az tutarda bulut kaynağı kullanmanız gerekir.
  • Daha fazla ödeme alınmaması için bu laboratuvarın sonundaki adımları uygulayarak kaynakları silebilirsiniz.
  • Yeni kullanıcılar 300 ABD doları değerindeki ücretsiz denemeden yararlanabilir.

Proje oluşturma (isteğe bağlı)

Bu laboratuvar için kullanmak istediğiniz mevcut bir projeniz yoksa buradan yeni bir proje oluşturun.

3. Cloud Shell Düzenleyici'yi açma

  1. Doğrudan Cloud Shell Düzenleyici'ye gitmek için bu bağlantıyı tıklayın.
  2. Bugün herhangi bir noktada yetkilendirmeniz istenirse devam etmek için Yetkilendir'i tıklayın.Cloud Shell'e yetki vermek için tıklayın.
  3. Terminal ekranın alt kısmında görünmüyorsa açın:
    • Görünüm'ü tıklayın.
    • Terminal'i tıklayın.Cloud Shell Düzenleyici'de yeni terminal açma
  4. Terminalde şu komutla projenizi ayarlayın:
    gcloud config set project [PROJECT_ID]
    
    • Örnek:
      gcloud config set project lab-project-id-example
      
    • Proje kimliğinizi hatırlamıyorsanız tüm proje kimliklerinizi şu komutla listeleyebilirsiniz:
      gcloud projects list
      
      Cloud Shell Düzenleyici terminalinde proje kimliğini ayarlama
  5. Şu mesajı görmeniz gerekir:
    Updated property [core/project].
    

4. API'leri etkinleştir

Bu çözümü oluşturmak için Vertex AI, Cloud SQL ve yeniden sıralama hizmeti için çeşitli Google Cloud API'lerini etkinleştirmeniz gerekir.

  1. Terminalde API'leri etkinleştirin:
    gcloud services enable \
      aiplatform.googleapis.com \
      sqladmin.googleapis.com \
      cloudresourcemanager.googleapis.com \
      serviceusage.googleapis.com \
      discoveryengine.googleapis.com
    
    
    

API'lerle tanışın

  • Vertex AI API (aiplatform.googleapis.com): Üretim için Gemini'ın, metni vektörleştirme için ise Vertex AI Embeddings'in kullanılmasını sağlar.
  • Cloud SQL Admin API (sqladmin.googleapis.com): Cloud SQL örneklerini programatik olarak yönetmenize olanak tanır.
  • Discovery Engine API (discoveryengine.googleapis.com): Vertex AI Reranker özelliklerini destekler.
  • Service Usage API (serviceusage.googleapis.com): Hizmet kotalarını kontrol etmek ve yönetmek için gereklidir.

5. Sanal ortam oluşturma ve bağımlılıkları yükleme

Herhangi bir Python projesine başlamadan önce sanal ortam oluşturmak iyi bir uygulamadır. Bu, projenin bağımlılıklarını yalıtarak diğer projelerle veya sistemin genel Python paketleriyle çakışmaları önler.

  1. rag-labs adlı bir klasör oluşturun ve bu klasöre geçin. Terminalde aşağıdaki kodu çalıştırın:
    mkdir rag-labs && cd rag-labs
    
  2. Sanal ortam oluşturun ve etkinleştirin:
    uv venv --python 3.12
    source .venv/bin/activate
    
  3. Gerekli bağımlılıkları içeren bir requirements.txt dosyası oluşturun. Terminalde aşağıdaki kodu çalıştırın:
    cloudshell edit requirements.txt
    
  4. Aşağıdaki optimize edilmiş bağımlılıkları requirements.txt içine yapıştırın. Bu sürümler, çakışmaları önlemek ve yüklemeyi hızlandırmak için sabitlenir.
    # Core LangChain & AI
    langchain-community==0.3.31
    langchain-google-vertexai==2.1.2
    langchain-google-community[vertexaisearch]==2.0.10
    
    # Google Cloud
    google-cloud-storage==2.19.0
    google-cloud-aiplatform[langchain]==1.130.0
    
    # Database
    cloud-sql-python-connector[pg8000]==1.19.0
    sqlalchemy==2.0.45
    pgvector==0.4.2
    
    # Utilities
    tiktoken==0.12.0
    python-dotenv==1.2.1
    requests==2.32.5
    
  5. Bağımlılıkları yükleyin:
    uv pip install -r requirements.txt
    

6. PostgreSQL için Cloud SQL'i ayarlama

Bu görevde, PostgreSQL için Cloud SQL örneği sağlayacak, veritabanı oluşturacak ve vektör aramaya hazırlayacaksınız.

Cloud SQL yapılandırmasını tanımlama

  1. Yapılandırmanızı saklamak için bir .env dosyası oluşturun. Terminalde aşağıdaki kodu çalıştırın:
    cloudshell edit .env
    
  2. Aşağıdaki yapılandırmayı .env içine yapıştırın.
    # Project Config
    PROJECT_ID="[YOUR_PROJECT_ID]"
    REGION="us-central1"
    
    # Database Config
    SQL_INSTANCE_NAME="rag-pg-instance-1"
    SQL_DATABASE_NAME="rag_harry_potter_db"
    SQL_USER="rag_user"
    SQL_PASSWORD="StrongPassword123!" 
    
    # RAG Config
    PGVECTOR_COLLECTION_NAME="rag_harry_potter"
    RANKING_LOCATION_ID="global"
    
    # Connection Name (Auto-generated in scripts usually, but useful to have)
    DB_INSTANCE_CONNECTION_NAME="${PROJECT_ID}:${REGION}:${SQL_INSTANCE_NAME}"
    
  3. [YOUR_PROJECT_ID] kısmını, gerçek Google Cloud proje kimliğinizle değiştirin. (ör. PROJECT_ID = "google-cloud-labs")
    Proje kimliğinizi hatırlamıyorsanız terminalinizde aşağıdaki komutu çalıştırın. Tüm projelerinizin ve kimliklerinin listesi gösterilir.
    gcloud projects list
    
  4. Değişkenleri kabuk oturumunuza yükleyin:
    source .env
    

Örneği ve Veritabanını Oluşturma

  1. PostgreSQL için Cloud SQL örneği oluşturun. Bu komut, bu laboratuvar için uygun küçük bir örnek oluşturur.
    gcloud sql instances create ${SQL_INSTANCE_NAME} \
      --database-version=POSTGRES_15 \
      --tier=db-g1-small \
      --region=${REGION} \
      --project=${PROJECT_ID}
    
  2. Örnek hazır olduğunda veritabanını oluşturun:
    gcloud sql databases create ${SQL_DATABASE_NAME} \
      --instance=${SQL_INSTANCE_NAME} \
      --project=${PROJECT_ID}
    
  3. Veritabanı kullanıcısını oluşturun:
    gcloud sql users create ${SQL_USER} \
      --instance=${SQL_INSTANCE_NAME} \
      --password=${SQL_PASSWORD} \
      --project=${PROJECT_ID}
    

pgvector uzantısını etkinleştirme

pgvector uzantısı, PostgreSQL'in vektör yerleştirmelerini depolamasına ve arama yapmasına olanak tanır. Bu özelliği veritabanınızda açıkça etkinleştirmeniz gerekir.

  1. enable_pgvector.py adlı bir komut dosyası oluşturun. Terminalde aşağıdaki kodu çalıştırın:
    cloudshell edit enable_pgvector.py
    
  2. Aşağıdaki kodu enable_pgvector.py dosyasına yapıştırın. Bu komut dosyası, veritabanınıza bağlanır ve CREATE EXTENSION IF NOT EXISTS vector; komutunu çalıştırır.
    import os
    import sqlalchemy
    from google.cloud.sql.connector import Connector, IPTypes
    import logging
    from dotenv import load_dotenv
    
    load_dotenv()
    logging.basicConfig(level=logging.INFO)
    
    # Config
    project_id = os.getenv("PROJECT_ID")
    region = os.getenv("REGION")
    instance_name = os.getenv("SQL_INSTANCE_NAME")
    db_user = os.getenv("SQL_USER")
    db_pass = os.getenv("SQL_PASSWORD")
    db_name = os.getenv("SQL_DATABASE_NAME")
    instance_connection_name = f"{project_id}:{region}:{instance_name}"
    
    def getconn():
        with Connector() as connector:
            conn = connector.connect(
                instance_connection_name,
                "pg8000",
                user=db_user,
                password=db_pass,
                db=db_name,
                ip_type=IPTypes.PUBLIC,
            )
            return conn
    
    def enable_pgvector():
        pool = sqlalchemy.create_engine(
            "postgresql+pg8000://",
            creator=getconn,
        )
        with pool.connect() as db_conn:
            # Check if extension exists
            result = db_conn.execute(sqlalchemy.text("SELECT extname FROM pg_extension WHERE extname = 'vector';")).fetchone()
            if result:
                logging.info("pgvector extension is already enabled.")
            else:
                logging.info("Enabling pgvector extension...")
                db_conn.execute(sqlalchemy.text("CREATE EXTENSION IF NOT EXISTS vector;"))
                db_conn.commit()
                logging.info("pgvector extension enabled successfully.")
    
    if __name__ == "__main__":
        enable_pgvector()
    
  3. Komut dosyasını çalıştırın:
    python enable_pgvector.py
    

7. 1. Bölüm: Parçalara Ayırma Stratejileri

Herhangi bir RAG ardışık düzenindeki ilk adım, dokümanları LLM'nin anlayabileceği bir biçime (parçalar) dönüştürmektir.

Büyük dil modellerinin bağlam penceresi sınırı (aynı anda işleyebilecekleri metin miktarı) vardır. Ayrıca, belirli bir soruyu yanıtlamak için 50 sayfalık bir dokümanı almak bilgileri sulandırır. İlgili bilgileri ayırmak için belgeleri daha küçük "parçalara" böleriz.

Ancak metni nasıl böldüğünüz çok önemlidir:

  • Karakter Bölücü: Kesinlikle karakter sayısına göre böler. Bu yöntem hızlıdır ancak risklidir. Kelimeleri veya cümleleri yarıya bölerek anlamı bozabilir.
  • Özyinelemeli Bölücü: Önce paragrafa, sonra cümleye, ardından kelimeye göre bölmeye çalışır. Anlamsal birimleri bir arada tutmaya çalışır.
  • Token Ayırıcı: LLM'nin kendi kelime dağarcığına (jetonlar) göre ayırır. Bu yöntem, parçaların bağlam pencerelerine mükemmel şekilde uymasını sağlar ancak oluşturulması hesaplama açısından daha maliyetli olabilir.

Bu bölümde, üç stratejiyi de kullanarak aynı verileri alıp karşılaştıracaksınız.

Alım komut dosyasını oluşturma

Harry Potter veri kümesini indiren, Character, Recursive ve Token stratejilerini kullanarak bölen ve yerleştirmeleri Cloud SQL'deki üç ayrı tabloya yükleyen bir komut dosyası kullanacaksınız.

  1. Dosyayı oluşturun ingest_data.py:
    cloudshell edit ingest_data.py
    
  2. Aşağıdaki düzeltilmiş kodu ingest_data.py içine yapıştırın. Bu sürüm, veri kümesinin JSON yapısını doğru şekilde ayrıştırır.
    import os
    import json
    import logging
    import requests
    from typing import List, Dict, Any
    from dotenv import load_dotenv
    
    from google.cloud.sql.connector import Connector, IPTypes
    from langchain_google_vertexai import VertexAIEmbeddings
    from langchain_community.vectorstores import PGVector
    from langchain.text_splitter import CharacterTextSplitter, RecursiveCharacterTextSplitter, TokenTextSplitter
    from langchain.docstore.document import Document
    
    load_dotenv()
    logging.basicConfig(level=logging.INFO)
    
    # Configuration
    PROJECT_ID = os.getenv("PROJECT_ID")
    REGION = os.getenv("REGION")
    DB_USER = os.getenv("SQL_USER")
    DB_PASS = os.getenv("SQL_PASSWORD")
    DB_NAME = os.getenv("SQL_DATABASE_NAME")
    INSTANCE_CONNECTION_NAME = f"{PROJECT_ID}:{REGION}:{os.getenv('SQL_INSTANCE_NAME')}"
    BASE_COLLECTION_NAME = os.getenv("PGVECTOR_COLLECTION_NAME")
    BOOKS_JSON_URL = "https://storage.googleapis.com/github-repo/generative-ai/gemini/reasoning-engine/sample_data/harry_potter_books.json"
    
    CHUNK_SIZE = 500
    CHUNK_OVERLAP = 50
    MAX_DOCS_TO_PROCESS = 10 
    
    # Database Connector
    def getconn():
        with Connector() as connector:
            return connector.connect(
                INSTANCE_CONNECTION_NAME,
                "pg8000",
                user=DB_USER,
                password=DB_PASS,
                db=DB_NAME,
                ip_type=IPTypes.PUBLIC,
            )
    
    def download_data():
        logging.info(f"Downloading data from {BOOKS_JSON_URL}...")
        response = requests.get(BOOKS_JSON_URL)
        return response.json()
    
    def prepare_chunks(json_data, strategy):
        documents = []
    
        # Iterate through the downloaded data
        for entry in json_data[:MAX_DOCS_TO_PROCESS]:
    
            # --- JSON PARSING LOGIC ---
            # The data structure nests content inside 'kwargs' -> 'page_content'
            if "kwargs" in entry and "page_content" in entry["kwargs"]:
                content = entry["kwargs"]["page_content"]
    
                # Extract metadata if available, ensuring it's a dict
                metadata = entry["kwargs"].get("metadata", {})
                if not isinstance(metadata, dict):
                    metadata = {"source": "unknown"}
    
                # Add the strategy to metadata for tracking
                metadata["strategy"] = strategy
            else:
                continue
    
            if not content:
                continue
    
            # Choose the splitter based on the strategy
            if strategy == "character":
                splitter = CharacterTextSplitter(chunk_size=CHUNK_SIZE, chunk_overlap=CHUNK_OVERLAP, separator="\n")
            elif strategy == "token":
                splitter = TokenTextSplitter(chunk_size=CHUNK_SIZE, chunk_overlap=CHUNK_OVERLAP)
            else: # default to recursive
                splitter = RecursiveCharacterTextSplitter(chunk_size=CHUNK_SIZE, chunk_overlap=CHUNK_OVERLAP)
    
            # Split the content into chunks
            chunks = splitter.split_text(content)
    
            # Create Document objects for each chunk
            for chunk in chunks:
                documents.append(Document(page_content=chunk, metadata=metadata))
    
        return documents
    
    def main():
        logging.info("Initializing Embeddings...")
        embeddings = VertexAIEmbeddings(model_name="gemini-embedding-001", project=PROJECT_ID, location=REGION)
    
        data = download_data()
        strategies = ["character", "recursive", "token"]
    
        # Connection string for PGVector (uses the getconn helper)
        pg_conn_str = f"postgresql+pg8000://{DB_USER}:{DB_PASS}@placeholder/{DB_NAME}"
    
        for strategy in strategies:
            collection_name = f"{BASE_COLLECTION_NAME}_{strategy}"
            logging.info(f"--- Processing strategy: {strategy.upper()} ---")
            logging.info(f"Target Collection: {collection_name}")
    
            # Prepare documents with the specific strategy
            docs = prepare_chunks(data, strategy)
    
            if not docs:
                logging.warning(f"No documents generated for strategy {strategy}. Check data source.")
                continue
    
            logging.info(f"Generated {len(docs)} chunks. Uploading to Cloud SQL...")
    
            # Initialize the Vector Store
            store = PGVector(
                collection_name=collection_name,
                embedding_function=embeddings,
                connection_string=pg_conn_str,
                engine_args={"creator": getconn},
                pre_delete_collection=True # Clears old data for this collection before adding new
            )
    
            # Batch add documents
            store.add_documents(docs)
            logging.info(f"Successfully finished {strategy}.\n")
    
    if __name__ == "__main__":
        main()
    
  3. Alım komut dosyasını çalıştırın. Bu işlem, veritabanınızı üç farklı tablo (koleksiyon) ile doldurur.
    python ingest_data.py
    

Parçalara Ayırma Sonuçlarını Karşılaştırma

Veriler yüklendiğine göre, parçalama stratejisinin sonuçları nasıl etkilediğini görmek için üç koleksiyonun tamamında bir sorgu çalıştıralım.

  1. Oluşturma query_chunking.py:
    cloudshell edit query_chunking.py
    
  2. Aşağıdaki kodu query_chunking.py içine yapıştırın:
    import os
    import logging
    from dotenv import load_dotenv
    from google.cloud.sql.connector import Connector, IPTypes
    from langchain_google_vertexai import VertexAIEmbeddings
    from langchain_community.vectorstores import PGVector
    
    load_dotenv()
    logging.basicConfig(level=logging.ERROR) # Only show errors to keep output clean
    
    # Config
    PROJECT_ID = os.getenv("PROJECT_ID")
    REGION = os.getenv("REGION")
    DB_USER = os.getenv("SQL_USER")
    DB_PASS = os.getenv("SQL_PASSWORD")
    DB_NAME = os.getenv("SQL_DATABASE_NAME")
    INSTANCE_CONNECTION_NAME = f"{PROJECT_ID}:{REGION}:{os.getenv('SQL_INSTANCE_NAME')}"
    BASE_COLLECTION_NAME = os.getenv("PGVECTOR_COLLECTION_NAME")
    
    def getconn():
        with Connector() as connector:
            return connector.connect(
                INSTANCE_CONNECTION_NAME,
                "pg8000",
                user=DB_USER,
                password=DB_PASS,
                db=DB_NAME,
                ip_type=IPTypes.PUBLIC,
            )
    
    def main():
        embeddings = VertexAIEmbeddings(model_name="gemini-embedding-001", project=PROJECT_ID, location=REGION)
        pg_conn_str = f"postgresql+pg8000://{DB_USER}:{DB_PASS}@placeholder/{DB_NAME}"
    
        query = "Tell me about the Dursleys and their relationship with Harry Potter"
        print(f"\nQUERY: {query}\n" + "="*50)
    
        strategies = ["character", "recursive", "token"]
    
        for strategy in strategies:
            collection = f"{BASE_COLLECTION_NAME}_{strategy}"
            print(f"\nSTRATEGY: {strategy.upper()}")
    
            store = PGVector(
                collection_name=collection,
                embedding_function=embeddings,
                connection_string=pg_conn_str,
                engine_args={"creator": getconn}
            )
    
            results = store.similarity_search_with_score(query, k=2)
            for i, (doc, score) in enumerate(results):
                print(f"  Result {i+1} (Score: {score:.4f}): {doc.page_content[:150].replace(chr(10), ' ')}...")
    
    if __name__ == "__main__":
        main()
    
  3. Sorgu komut dosyasını çalıştırın:
    python query_chunking.py
    

Çıkışı inceleyin.

Karakter bölme yönteminde cümlelerin düşünce ortasında kesilebileceğini, Özyinelemeli yönteminde ise paragraf sınırlarına saygı gösterilmeye çalışıldığını fark edin. Parça bölme, parçaların LLM bağlam pencerelerine mükemmel şekilde uymasını sağlar ancak anlamsal yapıyı göz ardı edebilir.

8. 2. Bölüm: Yeniden sıralama

Vektör arama (alma), sıkıştırılmış matematiksel temsilleri (yerleştirmeler) kullandığından inanılmaz derecede hızlıdır. Geri çağırma (potansiyel olarak alakalı tüm öğeleri bulma) sağlamak için geniş bir ağ oluşturur ancak genellikle düşük hassasiyetten (bu öğelerin sıralaması mükemmel olmayabilir) etkilenir.

Genellikle alakalı dokümanlar, sonuç listesinin "ortasında kaybolur". İlk 5 sonuca odaklanan bir LLM, 7. sıradaki önemli yanıtı kaçırabilir.

Yeniden sıralama, ikinci bir aşama ekleyerek bu sorunu çözer.

  1. Alıcı: Hızlı vektör araması kullanarak daha büyük bir küme (ör. ilk 25) getirir.
  2. Yeniden sıralayıcı: Sorgu ve belge çiftlerinin tam metnini incelemek için özel bir model (ör. Cross-Encoder) kullanır. Bu yöntem daha yavaştır ancak çok daha doğrudur. En iyi 25'i yeniden puanlandırır ve mutlak olarak en iyi 3'ü döndürür.

Bu görevde, 1. bölümde oluşturulan recursive koleksiyonunda arama yapacaksınız ancak bu kez sonuçları iyileştirmek için Vertex AI Reranker'ı uygulayacaksınız.

  1. Oluşturma query_reranking.py:
    cloudshell edit query_reranking.py
    
  2. Aşağıdaki kodu yapıştırın. Bu örnekte, _recursive koleksiyonunun açıkça hedeflendiğine ve ContextualCompressionRetriever kullanıldığına dikkat edin.
    import os
    import logging
    from dotenv import load_dotenv
    from google.cloud.sql.connector import Connector, IPTypes
    from langchain_google_vertexai import VertexAIEmbeddings
    from langchain_community.vectorstores import PGVector
    
    # Reranking Imports
    from langchain.retrievers import ContextualCompressionRetriever
    from langchain_google_community.vertex_rank import VertexAIRank
    
    load_dotenv()
    logging.basicConfig(level=logging.ERROR)
    
    PROJECT_ID = os.getenv("PROJECT_ID")
    REGION = os.getenv("REGION")
    DB_USER = os.getenv("SQL_USER")
    DB_PASS = os.getenv("SQL_PASSWORD")
    DB_NAME = os.getenv("SQL_DATABASE_NAME")
    INSTANCE_CONNECTION_NAME = f"{PROJECT_ID}:{REGION}:{os.getenv('SQL_INSTANCE_NAME')}"
    
    # IMPORTANT: Target the recursive collection created in ingest_data.py
    COLLECTION_NAME = f"{os.getenv('PGVECTOR_COLLECTION_NAME')}_recursive"
    RANKING_LOCATION = os.getenv("RANKING_LOCATION_ID")
    
    def getconn():
        with Connector() as connector:
            return connector.connect(
                INSTANCE_CONNECTION_NAME,
                "pg8000",
                user=DB_USER,
                password=DB_PASS,
                db=DB_NAME,
                ip_type=IPTypes.PUBLIC,
            )
    
    def main():
        embeddings = VertexAIEmbeddings(model_name="gemini-embedding-001", project=PROJECT_ID, location=REGION)
        pg_conn_str = f"postgresql+pg8000://{DB_USER}:{DB_PASS}@placeholder/{DB_NAME}"
    
        print(f"Connecting to collection: {COLLECTION_NAME}")
        store = PGVector(
            collection_name=COLLECTION_NAME,
            embedding_function=embeddings,
            connection_string=pg_conn_str,
            engine_args={"creator": getconn}
        )
    
        query = "What are the Horcruxes?"
        print(f"QUERY: {query}\n")
    
        # 1. Base Retriever (Vector Search) - Fetch top 10
        base_retriever = store.as_retriever(search_kwargs={"k": 10})
    
        # 2. Reranker - Select top 3 from the 10
        reranker = VertexAIRank(
            project_id=PROJECT_ID,
            location_id=RANKING_LOCATION,
            ranking_config="default_ranking_config",
            title_field="source",
            top_n=3
        )
    
        compression_retriever = ContextualCompressionRetriever(
            base_compressor=reranker,
            base_retriever=base_retriever
        )
    
        # Execute
        try:
            reranked_docs = compression_retriever.invoke(query)
    
            if not reranked_docs:
                print("No documents returned. Check if the collection exists and is populated.")
    
            print(f"--- Top 3 Reranked Results ---")
            for i, doc in enumerate(reranked_docs):
                print(f"Result {i+1} (Score: {doc.metadata.get('relevance_score', 'N/A')}):")
                print(f"  {doc.page_content[:200]}...\n")
        except Exception as e:
            print(f"Error during reranking: {e}")
    
    if __name__ == "__main__":
        main()
    
  3. Yeniden sıralama sorgusunu çalıştırın:
    python query_reranking.py
    

Gözlemleme

Ham vektör aramasına kıyasla daha yüksek alaka düzeyi puanları veya farklı bir sıralama görebilirsiniz. Bu sayede LLM, mümkün olan en hassas bağlamı alır.

9. 3. bölüm: Sorgu Dönüşümü

RAG'deki en büyük darboğaz genellikle kullanıcıdır. Kullanıcı sorguları genellikle belirsiz, eksik veya kötü ifade edilmiş oluyor. Sorgu yerleştirme, belge yerleştirmeyle matematiksel olarak eşleşmezse alma işlemi başarısız olur.

Sorgu Dönüştürme, sorguyu veritabanına ulaşmadan önce yeniden yazmak veya genişletmek için bir LLM kullanır. İki teknik uygulayacaksınız:

  • HyDE (Hypothetical Document Embeddings): Bir soru ile yanıt arasındaki vektör benzerliği, yanıt ile varsayımsal bir yanıt arasındaki benzerlikten genellikle daha düşüktür. HyDE, LLM'den mükemmel bir yanıt uydurmasını ister, bunu yerleştirir ve uydurma yanıta benzeyen belgeleri arar.
  • Geri Adım Atarak İstem: Bir kullanıcı belirli bir ayrıntılı soru sorduğunda sistem, daha geniş bağlamı kaçırabilir. Geri adım istemi, LLM'den temel bilgileri belirli ayrıntılarla birlikte almak için daha üst düzey ve soyut bir soru ("Bu ailenin tarihi nedir?") oluşturmasını ister.
  1. Oluşturma query_transformation.py:
    cloudshell edit query_transformation.py
    
  2. Aşağıdaki kodu yapıştırın:
    import os
    import logging
    from dotenv import load_dotenv
    from google.cloud.sql.connector import Connector, IPTypes
    from langchain_google_vertexai import VertexAIEmbeddings, VertexAI
    from langchain_community.vectorstores import PGVector
    from langchain_core.prompts import PromptTemplate
    
    load_dotenv()
    logging.basicConfig(level=logging.ERROR)
    
    PROJECT_ID = os.getenv("PROJECT_ID")
    REGION = os.getenv("REGION")
    DB_USER = os.getenv("SQL_USER")
    DB_PASS = os.getenv("SQL_PASSWORD")
    DB_NAME = os.getenv("SQL_DATABASE_NAME")
    INSTANCE_CONNECTION_NAME = f"{PROJECT_ID}:{REGION}:{os.getenv('SQL_INSTANCE_NAME')}"
    COLLECTION_NAME = f"{os.getenv('PGVECTOR_COLLECTION_NAME')}_recursive"
    
    def getconn():
        with Connector() as connector:
            return connector.connect(
                INSTANCE_CONNECTION_NAME,
                "pg8000",
                user=DB_USER,
                password=DB_PASS,
                db=DB_NAME,
                ip_type=IPTypes.PUBLIC,
            )
    
    def generate_hyde_doc(query, llm):
        prompt = PromptTemplate(
            input_variables=["question"],
            template="Write a concise, hypothetical answer to the question. Question: {question} Answer:"
        )
        chain = prompt | llm
        return chain.invoke({"question": query})
    
    def generate_step_back(query, llm):
        prompt = PromptTemplate(
            input_variables=["question"],
            template="Write a more general, abstract question that concepts in this question. Original: {question} Step-back:"
        )
        chain = prompt | llm
        return chain.invoke({"question": query})
    
    def main():
        embeddings = VertexAIEmbeddings(model_name="gemini-embedding-001", project=PROJECT_ID, location=REGION)
        llm = VertexAI(model_name="gemini-2.5-flash", project=PROJECT_ID, location=REGION, temperature=0.5)
    
        pg_conn_str = f"postgresql+pg8000://{DB_USER}:{DB_PASS}@placeholder/{DB_NAME}"
        store = PGVector(
            collection_name=COLLECTION_NAME,
            embedding_function=embeddings,
            connection_string=pg_conn_str,
            engine_args={"creator": getconn}
        )
        retriever = store.as_retriever(search_kwargs={"k": 2})
    
        original_query = "Tell me about the Dursleys."
        print(f"ORIGINAL QUERY: {original_query}\n" + "-"*30)
    
        # 1. HyDE
        hyde_doc = generate_hyde_doc(original_query, llm)
        print(f"HyDE Generated Doc: {hyde_doc.strip()[:100]}...")
        hyde_results = retriever.invoke(hyde_doc)
        print(f"HyDE Retrieval: {hyde_results[0].page_content[:100]}...\n")
    
        # 2. Step-back
        step_back_q = generate_step_back(original_query, llm)
        print(f"Step-back Query: {step_back_q.strip()}")
        step_results = retriever.invoke(step_back_q)
        print(f"Step-back Retrieval: {step_results[0].page_content[:100]}...")
    
    if __name__ == "__main__":
        main()
    
  3. Dönüşüm komut dosyasını çalıştırın:
    python query_transformation.py
    

Çıkışı inceleyin.

Step-back sorgusunun Dursley ailesinin geçmişi hakkında daha geniş bir bağlam alabileceğini, HyDE'nin ise varsayımsal yanıtta oluşturulan belirli ayrıntılara odaklandığını fark edin.

10. 4. Bölüm: Uçtan Uca Üretim

Verilerimizi böldük, aramamızı iyileştirdik ve kullanıcının sorgusunu düzenledik. Şimdi nihayet RAG'deki "G"ye, yani Üretim'e geçiyoruz.

Şu ana kadar yalnızca bilgi buluyorduk. Gerçek bir yapay zeka asistanı oluşturmak için bu yüksek kaliteli ve yeniden sıralanmış belgeleri bir LLM'ye (Gemini) aktararak doğal dil yanıtı sentezlememiz gerekir.

Üretim ardışık düzeninde bu işlem belirli bir akışı içerir:

  1. Alma: Geniş bir aday grubu elde edin (ör. En iyi 10) hızlı vektör arama özelliğini kullanarak.
  2. Yeniden sıralama: En iyi sonuçları filtreleyin (ör. İlk 3) Vertex AI Reranker'ı kullanarak.
  3. Bağlam Oluşturma: En iyi 3 dokümanın içeriğini tek bir dizede birleştirin.
  4. Temellendirilmiş istem: Bu bağlam dizesini, LLM'yi yalnızca bu bilgileri kullanmaya zorlayan katı bir istem şablonuna ekleyin.

Üretim komut dosyasını oluşturma

Üretim adımında gemini-2.5-flash kullanılacaktır. Bu model, uzun bağlam penceresi ve düşük gecikme süresi sayesinde RAG için idealdir. Bu özellikler, birden fazla alınan dokümanın hızlı bir şekilde işlenmesini sağlar.

  1. Oluşturma end_to_end_rag.py:
cloudshell edit end_to_end_rag.py
  1. Aşağıdaki kodu yapıştırın. template değişkenine dikkat edin. Bu değişken, modeli sağlanan bağlama bağlayarak "halüsinasyonlardan" (bir şeyler uydurmaktan) kaçınması için kesin bir şekilde yönlendirdiğimiz yerdir.
import os
import logging
from dotenv import load_dotenv
from google.cloud.sql.connector import Connector, IPTypes
from langchain_google_vertexai import VertexAIEmbeddings, VertexAI
from langchain_community.vectorstores import PGVector
from langchain.retrievers import ContextualCompressionRetriever
from langchain_google_community.vertex_rank import VertexAIRank
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain

load_dotenv()
logging.basicConfig(level=logging.ERROR)

PROJECT_ID = os.getenv("PROJECT_ID")
REGION = os.getenv("REGION")
# We use the recursive collection as it generally provides the best context boundaries
COLLECTION_NAME = f"{os.getenv('PGVECTOR_COLLECTION_NAME')}_recursive"

def getconn():
    instance_conn = f"{PROJECT_ID}:{REGION}:{os.getenv('SQL_INSTANCE_NAME')}"
    with Connector() as connector:
        return connector.connect(
            instance_conn, "pg8000",
            user=os.getenv("SQL_USER"), password=os.getenv("SQL_PASSWORD"),
            db=os.getenv("SQL_DATABASE_NAME"), ip_type=IPTypes.PUBLIC
        )

def main():
    print("--- Initializing Production RAG Pipeline ---")

    # 1. Setup Embeddings (Gemini Embedding 001)
    # We use this to vectorize the user's query to match our database.
    embeddings = VertexAIEmbeddings(model_name="gemini-embedding-001", project=PROJECT_ID, location=REGION)

    # 2. Connect to Vector Store
    pg_conn_str = f"postgresql+pg8000://{os.getenv('SQL_USER')}:{os.getenv('SQL_PASSWORD')}@placeholder/{os.getenv('SQL_DATABASE_NAME')}"
    store = PGVector(
        collection_name=COLLECTION_NAME,
        embedding_function=embeddings,
        connection_string=pg_conn_str,
        engine_args={"creator": getconn}
    )

    # 3. Setup The 'Filter Funnel' (Retriever + Reranker)
    # Step A: Fast retrieval of top 10 similar documents
    base_retriever = store.as_retriever(search_kwargs={"k": 10})

    # Step B: Precise reranking to find the top 3 most relevant
    reranker = VertexAIRank(
        project_id=PROJECT_ID,
        location_id="global", 
        ranking_config="default_ranking_config",
        title_field="source",
        top_n=3
    )

    # Combine A and B into a single retrieval object
    compression_retriever = ContextualCompressionRetriever(
        base_compressor=reranker,
        base_retriever=base_retriever
    )

    # 4. Setup LLM (Gemini 2.5 Flash)
    # We use a low temperature (0.1) to reduce creativity and increase factual adherence.
    llm = VertexAI(model_name="gemini-2.5-flash", project=PROJECT_ID, location=REGION, temperature=0.1)

    # --- Execution Loop ---
    user_query = "Who is Harry Potter?"
    print(f"\nUser Query: {user_query}")
    print("Retrieving and Reranking documents...")

    # Retrieve the most relevant documents
    top_docs = compression_retriever.invoke(user_query)

    if not top_docs:
        print("No relevant documents found.")
        return

    # Build the Context String
    # We stitch the documents together, labeling them as Source 1, Source 2, etc.
    context_str = "\n\n".join([f"Source {i+1}: {d.page_content}" for i, d in enumerate(top_docs)])

    print(f"Found {len(top_docs)} relevant context chunks.")

    # 5. The Grounded Prompt
    template = """You are a helpful assistant. Answer the question strictly based on the provided context.
    If the answer is not in the context, say "I don't know."

    Context:
    {context}

    Question:
    {question}

    Answer:
    """

    prompt = PromptTemplate(template=template, input_variables=["context", "question"])

    # Create the chain: Prompt -> LLM
    chain = prompt | llm

    print("Generating Answer via Gemini 2.5 Flash...")
    final_answer = chain.invoke({"context": context_str, "question": user_query})

    print(f"\nFINAL ANSWER:\n{final_answer}")

if __name__ == "__main__":
    main()
  1. Son uygulamayı çalıştırın:
python end_to_end_rag.py

Çıkışı Anlama

Bu komut dosyasını çalıştırdığınızda, alınan ham parçalar (önceki adımlarda gördüğünüz) ile nihai yanıt arasındaki farkı gözlemleyin. LLM, bir sentezleyici gibi davranır. Yeniden sıralayıcı tarafından sağlanan parçalanmış metin "parçalarını" okur ve bunları tutarlı, insanlar tarafından okunabilir bir cümle haline getirir.

Bu bileşenleri zincirleyerek stokastik bir "tahmin"den deterministik ve temellendirilmiş bir iş akışına geçersiniz. Retriever ağı atar, Reranker en iyi yakalamayı seçer ve Generator yemeği pişirir.

11. Sonuç

Tebrikler! Temel vektör aramanın çok ötesine geçen gelişmiş bir RAG işlem hattı oluşturdunuz.

Özet

  • Ölçeklenebilir vektör depolama için Cloud SQL'i pgvector ile yapılandırdınız.
  • Veri hazırlamanın almayı nasıl etkilediğini anlamak için Parçalama Stratejileri'ni karşılaştırdınız.
  • Sonuçlarınızın hassasiyetini artırmak için Vertex AI ile yeniden sıralama özelliğini uyguladınız.
  • Kullanıcı amacını verilerinizle uyumlu hale getirmek için Sorgu Dönüşümleri'ni (HyDE, Step-back) kullandınız.

Daha Fazla Bilgi

Prototipten Üretime

Bu laboratuvar, Google Cloud ile Üretime Hazır Yapay Zeka Öğrenme Rotası'nın bir parçasıdır.

  • Prototip aşamasından üretim aşamasına geçiş yapmak için tüm müfredatı inceleyin.
  • #ProductionReadyAI hashtag'ini kullanarak ilerlemenizi paylaşın.