Teknik RAG Lanjutan

1. Pengantar

Ringkasan

Retrieval Augmented Generation (RAG) meningkatkan respons Model Bahasa Besar (LLM) dengan menghubungkannya ke pengetahuan eksternal. Namun, membangun sistem RAG siap produksi memerlukan lebih dari sekadar penelusuran vektor sederhana. Anda harus mengoptimalkan cara data dimasukkan, cara hasil yang relevan diberi peringkat, dan cara kueri pengguna diproses.

Di lab komprehensif ini, Anda akan membangun aplikasi RAG yang andal menggunakan Cloud SQL untuk PostgreSQL (diperluas dengan pgvector) dan Vertex AI. Anda akan mempelajari tiga teknik lanjutan:

  1. Strategi Chunking: Anda akan mengamati pengaruh berbagai metode pemisahan teks (Karakter, Rekursif, Token) terhadap kualitas pengambilan.
  2. Pemeringkatan ulang: Anda akan menerapkan Vertex AI Reranker untuk menyempurnakan hasil penelusuran dan mengatasi masalah "hilang di tengah".
  3. Transformasi Kueri: Anda akan menggunakan Gemini untuk mengoptimalkan kueri pengguna melalui teknik seperti HyDE (Hypothetical Document Embeddings) dan Step-back Prompting.

Yang akan Anda lakukan

  • Siapkan instance Cloud SQL untuk PostgreSQL dengan pgvector.
  • Bangun pipeline penyerapan data yang membagi teks menggunakan beberapa strategi dan menyimpan sematan di Cloud SQL.
  • Lakukan penelusuran semantik dan bandingkan kualitas hasil dari berbagai metode pengelompokan.
  • Mengintegrasikan Reranker untuk mengurutkan ulang dokumen yang diambil berdasarkan relevansi.
  • Menerapkan transformasi kueri yang didukung LLM untuk meningkatkan pengambilan informasi untuk pertanyaan yang ambigu atau kompleks.

Yang akan Anda pelajari

  • Cara menggunakan LangChain dengan Vertex AI dan Cloud SQL.
  • Dampak pemisah teks Karakter, Rekursif, dan Token.
  • Cara menerapkan Vector Search di PostgreSQL.
  • Cara menggunakan ContextualCompressionRetriever untuk mengubah peringkat.
  • Cara menerapkan HyDE dan Step-back Prompting.

2. Penyiapan project

Akun Google

Jika belum memiliki Akun Google pribadi, Anda harus membuat Akun Google.

Gunakan akun pribadi, bukan akun kantor atau sekolah.

Login ke Konsol Google Cloud

Login ke Konsol Google Cloud menggunakan Akun Google pribadi.

Aktifkan Penagihan

Tukarkan kredit Google Cloud senilai $5 (opsional)

Untuk menjalankan workshop ini, Anda memerlukan Akun Penagihan dengan sejumlah kredit. Jika Anda berencana menggunakan penagihan sendiri, Anda dapat melewati langkah ini.

  1. Klik link ini dan login dengan Akun Google pribadi. Anda akan melihat sesuatu seperti ini: Klik di sini untuk membuka halaman kredit
  2. Klik tombol KLIK DI SINI UNTUK MENGAKSES KREDIT ANDA. Anda akan diarahkan ke halaman untuk menyiapkan profil penagihan Menyiapkan halaman profil penagihan
  3. Klik Konfirmasi. Anda kini terhubung ke Akun Penagihan Uji Coba Google Cloud Platform. Screenshot ringkasan penagihan

Menyiapkan akun penagihan pribadi

Jika menyiapkan penagihan menggunakan kredit Google Cloud, Anda dapat melewati langkah ini.

Untuk menyiapkan akun penagihan pribadi, buka di sini untuk mengaktifkan penagihan di Cloud Console.

Beberapa Catatan:

  • Menyelesaikan lab ini akan dikenai biaya kurang dari $1 USD untuk resource Cloud.
  • Anda dapat mengikuti langkah-langkah di akhir lab ini untuk menghapus resource agar tidak dikenai biaya lebih lanjut.
  • Pengguna baru memenuhi syarat untuk mengikuti Uji Coba Gratis senilai$300 USD.

Membuat project (opsional)

Jika Anda tidak memiliki project saat ini yang ingin digunakan untuk lab ini, buat project baru di sini.

3. Buka Cloud Shell Editor

  1. Klik link ini untuk langsung membuka Cloud Shell Editor
  2. Jika diminta untuk memberikan otorisasi kapan saja hari ini, klik Authorize untuk melanjutkan.Klik untuk memberikan otorisasi pada Cloud Shell
  3. Jika terminal tidak muncul di bagian bawah layar, buka terminal:
    • Klik Lihat
    • Klik TerminalMembuka terminal baru di Cloud Shell Editor
  4. Di terminal, tetapkan project Anda dengan perintah ini:
    gcloud config set project [PROJECT_ID]
    
    • Contoh:
      gcloud config set project lab-project-id-example
      
    • Jika tidak ingat project ID, Anda dapat mencantumkan semua project ID dengan:
      gcloud projects list
      
      Menetapkan project ID di terminal Cloud Shell Editor
  5. Anda akan melihat pesan ini:
    Updated property [core/project].
    

4. Mengaktifkan API

Untuk membangun solusi ini, Anda perlu mengaktifkan beberapa Google Cloud API untuk Vertex AI, Cloud SQL, dan layanan Reranking.

  1. Di terminal, aktifkan API:
    gcloud services enable \
      aiplatform.googleapis.com \
      sqladmin.googleapis.com \
      cloudresourcemanager.googleapis.com \
      serviceusage.googleapis.com \
      discoveryengine.googleapis.com
    
    
    

Memperkenalkan API

  • Vertex AI API (aiplatform.googleapis.com): Memungkinkan penggunaan Gemini untuk pembuatan dan Vertex AI Embeddings untuk memvektorisasi teks.
  • Cloud SQL Admin API (sqladmin.googleapis.com): Memungkinkan Anda mengelola instance Cloud SQL secara terprogram.
  • Discovery Engine API (discoveryengine.googleapis.com): Mendukung kemampuan Pengubah Peringkat Vertex AI.
  • Service Usage API (serviceusage.googleapis.com): Diperlukan untuk memeriksa dan mengelola kuota layanan.

5. Buat lingkungan virtual & instal dependensi

Sebelum memulai project Python, sebaiknya buat lingkungan virtual. Hal ini mengisolasi dependensi project, sehingga mencegah konflik dengan project lain atau paket Python global sistem.

  1. Buat folder bernama rag-labs, lalu pindah ke folder tersebut. Jalankan kode berikut di terminal:
    mkdir rag-labs && cd rag-labs
    
  2. Buat dan aktifkan lingkungan virtual:
    uv venv --python 3.12
    source .venv/bin/activate
    
  3. Buat file requirements.txt dengan dependensi yang diperlukan. Jalankan kode berikut di terminal:
    cloudshell edit requirements.txt
    
  4. Tempel dependensi yang dioptimalkan berikut ke requirements.txt. Versi ini disematkan untuk menghindari konflik dan mempercepat penginstalan.
    # 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. Instal dependensinya:
    uv pip install -r requirements.txt
    

6. Menyiapkan Cloud SQL untuk PostgreSQL

Dalam tugas ini, Anda akan menyediakan instance Cloud SQL untuk PostgreSQL, membuat database, dan menyiapkannya untuk penelusuran vektor.

Tentukan Konfigurasi Cloud SQL

  1. Buat file .env untuk menyimpan konfigurasi Anda. Jalankan kode berikut di terminal:
    cloudshell edit .env
    
  2. Tempel konfigurasi berikut ke .env.
    # 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. Ganti [YOUR_PROJECT_ID] dengan ID Project Google Cloud Anda yang sebenarnya. (misalnya, PROJECT_ID = "google-cloud-labs")
    Jika Anda tidak dapat mengingat project ID, jalankan perintah berikut di terminal. Daftar semua project dan ID-nya akan ditampilkan.
    gcloud projects list
    
  4. Muat variabel ke dalam sesi shell Anda:
    source .env
    

Membuat Instance dan Database

  1. Buat instance Cloud SQL for PostgreSQL. Perintah ini membuat instance kecil yang cocok untuk lab ini.
    gcloud sql instances create ${SQL_INSTANCE_NAME} \
      --database-version=POSTGRES_15 \
      --tier=db-g1-small \
      --region=${REGION} \
      --project=${PROJECT_ID}
    
  2. Setelah instance siap, buat database:
    gcloud sql databases create ${SQL_DATABASE_NAME} \
      --instance=${SQL_INSTANCE_NAME} \
      --project=${PROJECT_ID}
    
  3. Buat pengguna database:
    gcloud sql users create ${SQL_USER} \
      --instance=${SQL_INSTANCE_NAME} \
      --password=${SQL_PASSWORD} \
      --project=${PROJECT_ID}
    

Mengaktifkan ekstensi pgvector

Ekstensi pgvector memungkinkan PostgreSQL menyimpan dan menelusuri embedding vektor. Anda harus mengaktifkannya secara eksplisit di database Anda.

  1. Buat skrip bernama enable_pgvector.py. Jalankan kode berikut di terminal:
    cloudshell edit enable_pgvector.py
    
  2. Tempelkan kode berikut ke enable_pgvector.py. Skrip ini terhubung ke database Anda dan menjalankan CREATE EXTENSION IF NOT EXISTS vector;.
    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. Jalankan skrip:
    python enable_pgvector.py
    

7. Bagian 1: Strategi Chunking

Langkah pertama dalam pipeline RAG adalah mengubah dokumen menjadi format yang dapat dipahami LLM: potongan.

LLM memiliki batas jendela konteks (jumlah teks yang dapat diproses sekaligus). Selain itu, mengambil dokumen 50 halaman untuk menjawab pertanyaan tertentu akan mengurangi kualitas informasi. Kami membagi dokumen menjadi "potongan" yang lebih kecil untuk mengisolasi informasi yang relevan.

Namun, cara Anda memisahkan teks sangatlah penting:

  • Pemecah Karakter: Memecah secara ketat berdasarkan jumlah karakter. Cara ini cepat, tetapi berisiko; dapat memotong kata atau kalimat menjadi dua, sehingga merusak makna semantik.
  • Recursive Splitter: Berupaya memisahkan berdasarkan paragraf terlebih dahulu, lalu kalimat, lalu kata. Fitur ini mencoba menyatukan unit semantik.
  • Pemecah Token: Memisahkan berdasarkan kosakata (token) LLM itu sendiri. Hal ini memastikan potongan cocok dengan sempurna ke dalam jendela konteks, tetapi dapat lebih mahal secara komputasi untuk dihasilkan.

Di bagian ini, Anda akan menyerap data yang sama menggunakan ketiga strategi untuk membandingkannya.

Membuat Skrip Penyerapan

Anda akan menggunakan skrip yang mendownload set data Harry Potter, membaginya menggunakan strategi Karakter, Rekursif, dan Token, serta mengupload sematan ke tiga tabel terpisah di Cloud SQL.

  1. Buat file ingest_data.py:
    cloudshell edit ingest_data.py
    
  2. Tempelkan kode fixed berikut ke dalam ingest_data.py. Versi ini mengurai struktur JSON set data dengan benar.
    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. Jalankan skrip penyerapan. Tindakan ini akan mengisi database Anda dengan tiga tabel (koleksi) yang berbeda.
    python ingest_data.py
    

Membandingkan Hasil Pemecahan

Setelah data dimuat, mari jalankan kueri terhadap ketiga koleksi untuk melihat pengaruh strategi chunking terhadap hasil.

  1. Buat query_chunking.py:
    cloudshell edit query_chunking.py
    
  2. Tempelkan kode berikut ke query_chunking.py:
    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. Jalankan skrip kueri:
    python query_chunking.py
    

Amati outputnya.

Perhatikan bagaimana pemisahan Karakter dapat memotong kalimat di tengah-tengah, sementara Rekursif mencoba mematuhi batas paragraf. Pemisahan token memastikan potongan cocok dengan sempurna ke jendela konteks LLM, tetapi mungkin mengabaikan struktur semantik.

8. Bagian 2: Peringkatan ulang

Penelusuran vektor (pengambilan) sangat cepat karena mengandalkan representasi matematika (embedding) yang dikompresi. Metode ini menjaring luas untuk memastikan Recall (menemukan semua item yang berpotensi relevan), tetapi sering kali menderita Presisi rendah (peringkat item tersebut mungkin tidak sempurna).

Sering kali, dokumen yang relevan "Hilang di Tengah" daftar hasil. LLM yang hanya memperhatikan 5 hasil teratas mungkin melewatkan jawaban penting yang berada di posisi #7.

Peringkatan ulang mengatasi hal ini dengan menambahkan tahap kedua.

  1. Pengambil: Mengambil kumpulan yang lebih besar (misalnya, 25 teratas) menggunakan penelusuran vektor cepat.
  2. Pengubah peringkat: Menggunakan model khusus (seperti Cross-Encoder) untuk memeriksa teks lengkap dari pasangan kueri dan dokumen. Cara ini lebih lambat, tetapi jauh lebih akurat. Fitur ini memberi skor ulang 25 teratas dan menampilkan 3 yang terbaik.

Dalam tugas ini, Anda akan menelusuri koleksi recursive yang dibuat di Bagian 1, tetapi kali ini Anda akan menerapkan Vertex AI Reranker untuk menyaring hasil.

  1. Buat query_reranking.py:
    cloudshell edit query_reranking.py
    
  2. Tempelkan kode berikut. Perhatikan cara penargetan koleksi _recursive secara eksplisit dan penggunaan ContextualCompressionRetriever.
    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. Jalankan kueri peringkat ulang:
    python query_reranking.py
    

Mengamati

Anda mungkin melihat skor relevansi yang lebih tinggi atau pengurutan yang berbeda dibandingkan dengan penelusuran vektor mentah. Hal ini memastikan LLM menerima konteks yang paling tepat.

9. Bagian 3: Transformasi Kueri

Sering kali, hambatan terbesar dalam RAG adalah pengguna. Kueri pengguna sering kali ambigu, tidak lengkap, atau tidak tepat. Jika sematan kueri tidak selaras secara matematis dengan sematan dokumen, pengambilan akan gagal.

Transformasi Kueri menggunakan LLM untuk menulis ulang atau memperluas kueri sebelum kueri tersebut masuk ke database. Anda akan menerapkan dua teknik:

  • HyDE (Hypothetical Document Embeddings): Kemiripan vektor antara pertanyaan dan jawaban sering kali lebih rendah daripada kemiripan antara jawaban dan jawaban hipotetis. HyDE meminta LLM untuk berhalusinasi jawaban yang sempurna, menyematkannya, dan menelusuri dokumen yang terlihat seperti halusinasi tersebut.
  • Perintah Mundur: Jika pengguna mengajukan pertanyaan mendetail yang spesifik, sistem mungkin melewatkan konteks yang lebih luas. Perintah mundur meminta LLM untuk membuat pertanyaan abstrak tingkat yang lebih tinggi ("Apa sejarah keluarga ini?") untuk mengambil informasi dasar bersama dengan detail spesifik.
  1. Buat query_transformation.py:
    cloudshell edit query_transformation.py
    
  2. Tempelkan kode berikut:
    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. Jalankan skrip transformasi:
    python query_transformation.py
    

Amati outputnya.

Perhatikan bagaimana kueri Step-back dapat mengambil konteks yang lebih luas tentang sejarah keluarga Dursley, sementara HyDE berfokus pada detail spesifik yang dihasilkan dalam jawaban hipotetis.

10. Bagian 4: Pembuatan End-to-End

Kami telah membagi data, menyempurnakan penelusuran, dan memoles kueri pengguna. Sekarang, kita akhirnya memasukkan "G" dalam RAG: Generation (Generasi).

Sampai saat ini, kita hanya menemukan informasi. Untuk membangun asisten AI yang sebenarnya, kita perlu memasukkan dokumen berkualitas tinggi yang telah diurutkan ulang tersebut ke dalam LLM (Gemini) untuk menyintesis jawaban dalam bahasa alami.

Dalam pipeline produksi, hal ini melibatkan alur tertentu:

  1. Mengambil: Mendapatkan kumpulan kandidat yang luas (misalnya, 10 Teratas) menggunakan penelusuran vektor cepat.
  2. Peringkat ulang: Memfilter hingga yang terbaik (misalnya, 3 Teratas) menggunakan Vertex AI Reranker.
  3. Konstruksi Konteks: Gabungkan konten 3 dokumen teratas tersebut menjadi satu string.
  4. Grounded Prompting (Perintah Berbasis): Sisipkan string konteks tersebut ke dalam template perintah ketat yang memaksa LLM untuk hanya menggunakan informasi tersebut.

Membuat Skrip Pembuatan

Kita akan menggunakan gemini-2.5-flash untuk langkah pembuatan. Model ini ideal untuk RAG karena memiliki jendela konteks yang panjang dan latensi yang rendah, sehingga dapat memproses beberapa dokumen yang diambil dengan cepat.

  1. Buat end_to_end_rag.py:
cloudshell edit end_to_end_rag.py
  1. Tempelkan kode berikut. Perhatikan variabel template—di sinilah kita secara ketat menginstruksikan model untuk menghindari "halusinasi" (mengarang-ngarang) dengan mengikatnya ke konteks yang diberikan.
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. Jalankan aplikasi akhir:
python end_to_end_rag.py

Memahami Output

Saat Anda menjalankan skrip ini, amati perbedaan antara potongan yang diambil secara mentah (yang Anda lihat di langkah-langkah sebelumnya) dan jawaban akhir. LLM bertindak sebagai alat sintesis—LLM membaca "potongan" teks yang terfragmentasi yang disediakan oleh Reranker dan menyatukannya menjadi kalimat yang koheren dan dapat dibaca manusia.

Dengan merangkai komponen ini, Anda beralih dari "tebakan" stokastik ke alur kerja deterministik yang mendasar. Pengambil menebar jala, Pengurut Ulang memilih hasil tangkapan terbaik, dan Generator memasak makanan.

11. Kesimpulan

Selamat! Anda telah berhasil membuat pipeline RAG tingkat lanjut yang jauh melampaui penelusuran vektor dasar.

Rekap

  • Anda telah mengonfigurasi Cloud SQL dengan pgvector untuk penyimpanan vektor yang dapat diskalakan.
  • Anda membandingkan Strategi Chunking untuk memahami pengaruh penyiapan data terhadap pengambilan.
  • Anda menerapkan Peringkatan Ulang dengan Vertex AI untuk meningkatkan presisi hasil Anda.
  • Anda menggunakan Transformasi Kueri (HyDE, Step-back) untuk menyelaraskan niat pengguna dengan data Anda.

Pelajari Lebih Lanjut

Dari Prototipe hingga Produksi

Lab ini merupakan bagian dari Alur Pembelajaran AI Siap Produksi dengan Google Cloud.

  • Jelajahi kurikulum lengkap untuk menjembatani kesenjangan dari prototipe hingga produksi.
  • Bagikan progres Anda dengan hashtag #ProductionReadyAI.