تکنیک‌های پیشرفته RAG

۱. مقدمه

نمای کلی

بازیابی نسل افزوده (RAG) با پایه‌گذاری پاسخ‌های مدل زبان بزرگ (LLM) بر اساس دانش خارجی، آنها را بهبود می‌بخشد. با این حال، ساخت یک سیستم RAG آماده برای تولید به چیزی بیش از یک جستجوی برداری ساده نیاز دارد. شما باید نحوه دریافت داده‌ها، رتبه‌بندی نتایج مرتبط و نحوه پردازش پرس‌وجوهای کاربر را بهینه کنید.

در این آزمایشگاه جامع، شما یک برنامه RAG قوی با استفاده از Cloud SQL برای PostgreSQL (که با pgvector توسعه داده شده است) و Vertex AI خواهید ساخت. شما با سه تکنیک پیشرفته پیشرفت خواهید کرد:

  1. استراتژی‌های قطعه‌بندی: شما مشاهده خواهید کرد که چگونه روش‌های مختلف تقسیم متن (کاراکتر، بازگشتی، توکن) بر کیفیت بازیابی تأثیر می‌گذارند.
  2. رتبه‌بندی مجدد: شما Vertex AI Reranker را برای اصلاح نتایج جستجو و رفع مشکل «گم شدن در میانه» پیاده‌سازی خواهید کرد.
  3. تبدیل پرس‌وجو: شما از Gemini برای بهینه‌سازی پرس‌وجوهای کاربر از طریق تکنیک‌هایی مانند HyDE (جاسازی‌های فرضی سند) و Step-back Prompting استفاده خواهید کرد.

کاری که انجام خواهید داد

  • با استفاده از pgvector یک نمونه Cloud SQL برای PostgreSQL راه‌اندازی کنید.
  • یک خط لوله دریافت داده بسازید که متن را با استفاده از چندین استراتژی تکه‌تکه کرده و جاسازی‌ها را در Cloud SQL ذخیره کند.
  • جستجوهای معنایی انجام دهید و کیفیت نتایج حاصل از روش‌های مختلف قطعه‌بندی را مقایسه کنید.
  • یک Reranker را برای مرتب‌سازی مجدد اسناد بازیابی شده بر اساس میزان مرتبط بودن، ادغام کنید.
  • پیاده‌سازی تبدیل‌های پرس‌وجو مبتنی بر LLM برای بهبود بازیابی سوالات مبهم یا پیچیده.

آنچه یاد خواهید گرفت

  • نحوه استفاده از LangChain با Vertex AI و Cloud SQL .
  • تأثیر جداکننده‌های متن کاراکتری ، بازگشتی و توکنی .
  • نحوه پیاده‌سازی جستجوی برداری در PostgreSQL
  • نحوه استفاده از ContextualCompressionRetriever برای رتبه‌بندی مجدد
  • نحوه پیاده‌سازی HyDE و راهنمای گام به گام .

۲. راه‌اندازی پروژه

حساب گوگل

اگر از قبل حساب گوگل شخصی ندارید، باید یک حساب گوگل ایجاد کنید .

به جای حساب کاری یا تحصیلی از حساب شخصی استفاده کنید .

ورود به کنسول ابری گوگل

با استفاده از یک حساب کاربری شخصی گوگل، وارد کنسول ابری گوگل شوید.

فعال کردن صورتحساب

استفاده از اعتبار ۵ دلاری گوگل کلود (اختیاری)

برای اجرای این کارگاه، به یک حساب صورتحساب با مقداری اعتبار نیاز دارید. اگر قصد دارید از صورتحساب خودتان استفاده کنید، می‌توانید از این مرحله صرف نظر کنید.

  1. روی این لینک کلیک کنید و با یک حساب گوگل شخصی وارد شوید. چیزی شبیه به این خواهید دید: برای صفحه اعتبارات اینجا کلیک کنید
  2. روی دکمه «برای دسترسی به اعتبارات خود اینجا کلیک کنید» کلیک کنید. این شما را به صفحه‌ای می‌برد که می‌توانید نمایه صورتحساب خود را تنظیم کنید. صفحه پروفایل صورتحساب را تنظیم کنید
  3. روی تأیید کلیک کنید. اکنون به حساب پرداخت آزمایشی پلتفرم گوگل کلود متصل شده‌اید. تصویر نمای کلی صورتحساب

یک حساب پرداخت شخصی تنظیم کنید

اگر صورتحساب را با استفاده از اعتبارهای Google Cloud تنظیم کرده‌اید، می‌توانید از این مرحله صرف نظر کنید.

برای تنظیم یک حساب پرداخت شخصی، به اینجا بروید تا پرداخت را در کنسول ابری فعال کنید .

برخی نکات:

  • تکمیل این آزمایشگاه باید کمتر از ۱ دلار آمریکا از طریق منابع ابری هزینه داشته باشد.
  • شما می‌توانید مراحل انتهای این آزمایش را برای حذف منابع دنبال کنید تا از هزینه‌های بیشتر جلوگیری شود.
  • کاربران جدید واجد شرایط استفاده از دوره آزمایشی رایگان ۳۰۰ دلاری هستند.

ایجاد پروژه (اختیاری)

اگر پروژه فعلی ندارید که بخواهید برای این آزمایشگاه استفاده کنید، اینجا یک پروژه جدید ایجاد کنید .

۳. ویرایشگر Cloud Shell را باز کنید

  1. برای دسترسی مستقیم به ویرایشگر Cloud Shell ، روی این لینک کلیک کنید.
  2. اگر امروز در هر مرحله‌ای از شما خواسته شد که مجوز دهید، برای ادامه روی تأیید کلیک کنید. برای تأیید Cloud Shell کلیک کنید
  3. اگر ترمینال در پایین صفحه نمایش داده نشد، آن را باز کنید:
    • روی مشاهده کلیک کنید
    • روی ترمینال کلیک کنید باز کردن ترمینال جدید در ویرایشگر Cloud Shell
  4. در ترمینال، پروژه خود را با این دستور تنظیم کنید:
    gcloud config set project [PROJECT_ID]
    
    • مثال:
      gcloud config set project lab-project-id-example
      
    • اگر نمی‌توانید شناسه پروژه خود را به خاطر بیاورید، می‌توانید تمام شناسه‌های پروژه خود را با استفاده از دستور زیر فهرست کنید:
      gcloud projects list
      
      شناسه پروژه را در ترمینال ویرایشگر Cloud Shell تنظیم کنید
  5. شما باید این پیام را ببینید:
    Updated property [core/project].
    

۴. فعال کردن APIها

برای ساخت این راهکار، باید چندین API گوگل کلود را برای Vertex AI، Cloud SQL و سرویس Reranking فعال کنید.

  1. در ترمینال، APIها را فعال کنید:
    gcloud services enable \
      aiplatform.googleapis.com \
      sqladmin.googleapis.com \
      cloudresourcemanager.googleapis.com \
      serviceusage.googleapis.com \
      discoveryengine.googleapis.com
    
    
    

معرفی API ها

  • رابط برنامه‌نویسی کاربردی هوش مصنوعی ورتکس ( aiplatform.googleapis.com ): استفاده از Gemini برای تولید و Vertex AI Embeddings را برای برداری‌سازی متن فعال می‌کند.
  • رابط برنامه‌نویسی کاربردی مدیریت SQL ابری ( sqladmin.googleapis.com ): به شما امکان می‌دهد نمونه‌های SQL ابری را به صورت برنامه‌نویسی مدیریت کنید.
  • رابط برنامه‌نویسی کاربردی موتور اکتشاف ( discoveryengine.googleapis.com ): قابلیت‌های Vertex AI Reranker را تقویت می‌کند.
  • API استفاده از سرویس ( serviceusage.googleapis.com ): برای بررسی و مدیریت سهمیه‌های سرویس مورد نیاز است.

۵. یک محیط مجازی ایجاد کنید و وابستگی‌ها را نصب کنید

قبل از شروع هر پروژه پایتون، ایجاد یک محیط مجازی تمرین خوبی است. این کار وابستگی‌های پروژه را ایزوله می‌کند و از تداخل با سایر پروژه‌ها یا بسته‌های پایتون سراسری سیستم جلوگیری می‌کند.

  1. یک پوشه به نام rag-labs ایجاد کنید و به آن بروید. کد زیر را در ترمینال اجرا کنید:
    mkdir rag-labs && cd rag-labs
    
  2. ایجاد و فعال‌سازی یک محیط مجازی:
    uv venv --python 3.12
    source .venv/bin/activate
    
  3. یک فایل requirements.txt با وابستگی‌های لازم ایجاد کنید. کد زیر را در ترمینال اجرا کنید:
    cloudshell edit requirements.txt
    
  4. وابستگی‌های بهینه‌شده‌ی زیر را در requirements.txt قرار دهید. این نسخه‌ها برای جلوگیری از تداخل و افزایش سرعت نصب، پین شده‌اند.
    # 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. وابستگی‌ها را نصب کنید:
    uv pip install -r requirements.txt
    

۶. تنظیم Cloud SQL برای PostgreSQL

در این کار، شما یک نمونه Cloud SQL برای PostgreSQL تهیه خواهید کرد، یک پایگاه داده ایجاد خواهید کرد و آن را برای جستجوی برداری آماده خواهید کرد.

تعریف پیکربندی SQL ابری

  1. یک فایل .env برای ذخیره پیکربندی خود ایجاد کنید. کد زیر را در ترمینال اجرا کنید:
    cloudshell edit .env
    
  2. پیکربندی زیر را در .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. به جای [YOUR_PROJECT_ID] ، شناسه واقعی پروژه گوگل کلود خود را وارد کنید. (مثلاً PROJECT_ID = "google-cloud-labs" )
    اگر شناسه پروژه خود را به خاطر نمی‌آورید، دستور زیر را در ترمینال خود اجرا کنید. این دستور لیستی از تمام پروژه‌های شما و شناسه‌های آنها را به شما نشان می‌دهد.
    gcloud projects list
    
  4. متغیرها را در جلسه پوسته خود بارگذاری کنید:
    source .env
    

ایجاد نمونه و پایگاه داده

  1. یک نمونه Cloud SQL برای PostgreSQL ایجاد کنید. این دستور یک نمونه کوچک مناسب برای این آزمایش ایجاد می‌کند.
    gcloud sql instances create ${SQL_INSTANCE_NAME} \
      --database-version=POSTGRES_15 \
      --tier=db-g1-small \
      --region=${REGION} \
      --project=${PROJECT_ID}
    
  2. پس از آماده شدن نمونه، پایگاه داده را ایجاد کنید:
    gcloud sql databases create ${SQL_DATABASE_NAME} \
      --instance=${SQL_INSTANCE_NAME} \
      --project=${PROJECT_ID}
    
  3. کاربر پایگاه داده را ایجاد کنید:
    gcloud sql users create ${SQL_USER} \
      --instance=${SQL_INSTANCE_NAME} \
      --password=${SQL_PASSWORD} \
      --project=${PROJECT_ID}
    

افزونه pgvector را فعال کنید

افزونه‌ی pgvector به PostgreSQL اجازه می‌دهد تا جاسازی‌های برداری را ذخیره و جستجو کند. شما باید آن را به صراحت در پایگاه داده خود فعال کنید.

  1. یک اسکریپت با نام enable_pgvector.py ایجاد کنید. کد زیر را در ترمینال اجرا کنید:
    cloudshell edit enable_pgvector.py
    
  2. کد زیر را در enable_pgvector.py قرار دهید. این اسکریپت به پایگاه داده شما متصل می‌شود و 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. اسکریپت را اجرا کنید:
    python enable_pgvector.py
    

۷. بخش ۱: استراتژی‌های قطعه‌بندی

اولین قدم در هر خط لوله RAG، تبدیل اسناد به فرمتی است که LLM بتواند آن را درک کند: تکه‌ها .

LLMها محدودیت پنجره زمینه (میزان متنی که می‌توانند همزمان پردازش کنند) دارند. علاوه بر این، بازیابی یک سند ۵۰ صفحه‌ای برای پاسخ به یک سوال خاص، اطلاعات را رقیق می‌کند. ما اسناد را به "قطعه‌های" کوچکتر تقسیم می‌کنیم تا اطلاعات مرتبط را جدا کنیم.

با این حال، نحوه تقسیم متن بسیار مهم است:

  • تقسیم‌کننده‌ی کاراکتر: متن را صرفاً بر اساس تعداد کاراکترها تقسیم می‌کند. این روش سریع اما پرخطر است؛ می‌تواند کلمات یا جملات را به نصف تقسیم کند و معنای معنایی را از بین ببرد.
  • تقسیم‌کننده بازگشتی: تلاش می‌کند ابتدا بر اساس پاراگراف، سپس جمله و در نهایت کلمه تقسیم‌بندی کند. این روش سعی می‌کند واحدهای معنایی را در کنار هم نگه دارد.
  • تقسیم‌کننده توکن: تقسیم‌ها را بر اساس واژگان خود LLM (توکن‌ها) انجام می‌دهد. این تضمین می‌کند که تکه‌ها کاملاً در پنجره‌های زمینه قرار می‌گیرند، اما تولید آنها می‌تواند از نظر محاسباتی گران‌تر باشد.

در این بخش، شما داده‌های یکسانی را با استفاده از هر سه استراتژی برای مقایسه آنها دریافت خواهید کرد.

اسکریپت مصرف را ایجاد کنید

شما از اسکریپتی استفاده خواهید کرد که یک مجموعه داده هری پاتر را دانلود می‌کند، آن را با استفاده از استراتژی‌های Character ، Recursive و Token تقسیم می‌کند و جاسازی‌ها را در سه جدول جداگانه در Cloud SQL بارگذاری می‌کند.

  1. فایل ingest_data.py را ایجاد کنید:
    cloudshell edit ingest_data.py
    
  2. کد اصلاح‌شده‌ی زیر را در ingest_data.py قرار دهید. این نسخه به درستی ساختار JSON مجموعه داده را تجزیه می‌کند.
    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. اسکریپت ingestion را اجرا کنید. این کار پایگاه داده شما را با سه جدول (مجموعه) مختلف پر می‌کند.
    python ingest_data.py
    

مقایسه نتایج قطعه‌بندی

حالا که داده‌ها بارگذاری شده‌اند، بیایید یک کوئری روی هر سه مجموعه اجرا کنیم تا ببینیم استراتژی قطعه‌بندی چگونه بر نتایج تأثیر می‌گذارد.

  1. ایجاد query_chunking.py :
    cloudshell edit query_chunking.py
    
  2. کد زیر را در 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. اسکریپت کوئری را اجرا کنید:
    python query_chunking.py
    

خروجی را مشاهده کنید.

توجه کنید که چگونه تقسیم‌بندی کاراکتری ممکن است جملات را در میانه تفکر قطع کند، در حالی که بازگشتی سعی می‌کند مرزهای پاراگراف را رعایت کند. تقسیم‌بندی توکن تضمین می‌کند که قطعات به طور کامل در پنجره‌های متن LLM قرار می‌گیرند، اما ممکن است ساختار معنایی را نادیده بگیرد.

۸. بخش ۲: رتبه‌بندی مجدد

جستجوی برداری (بازیابی) فوق‌العاده سریع است زیرا به نمایش‌های ریاضی فشرده (جاسازی‌ها) متکی است. این جستجو شبکه‌ی گسترده‌ای را برای اطمینان از بازیابی (یافتن تمام موارد بالقوه مرتبط) ایجاد می‌کند، اما اغلب از دقت پایین رنج می‌برد (رتبه‌بندی آن موارد ممکن است ناقص باشد).

اغلب، اسناد مرتبط در میانه‌ی فهرست نتایج گم می‌شوند. یک LLM که به ۵ نتیجه‌ی برتر توجه می‌کند، ممکن است پاسخ حیاتی را که در جایگاه شماره ۷ قرار دارد، از دست بدهد.

رتبه‌بندی مجدد با اضافه کردن یک مرحله دوم، این مشکل را حل می‌کند.

  1. بازیابی‌کننده: با استفاده از جستجوی برداری سریع، مجموعه‌ای بزرگتر (مثلاً ۲۵ مورد برتر) را واکشی می‌کند.
  2. Reranker: از یک مدل تخصصی (مانند Cross-Encoder) برای بررسی متن کامل پرس‌وجو و جفت اسناد استفاده می‌کند. این روش کندتر اما بسیار دقیق‌تر است. این روش ۲۵ مورد برتر را دوباره امتیازدهی می‌کند و ۳ مورد برتر مطلق را برمی‌گرداند.

در این کار، شما مجموعه recursive ایجاد شده در بخش ۱ را جستجو خواهید کرد، اما این بار از Vertex AI Reranker برای اصلاح نتایج استفاده خواهید کرد.

  1. ایجاد query_reranking.py :
    cloudshell edit query_reranking.py
    
  2. کد زیر را جایگذاری کنید. توجه داشته باشید که چگونه به صراحت مجموعه _recursive را هدف قرار داده و از 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. کوئری تغییر رتبه‌بندی را اجرا کنید:
    python query_reranking.py
    

مشاهده کردن

ممکن است در مقایسه با جستجوی بردار خام، متوجه امتیازهای مرتبط بالاتر یا ترتیب متفاوتی شوید. این تضمین می‌کند که LLM دقیق‌ترین زمینه ممکن را دریافت می‌کند.

۹. بخش ۳: تبدیل پرس‌وجو

اغلب، بزرگترین گلوگاه در RAG، کاربر است. پرس‌وجوهای کاربر اغلب مبهم، ناقص یا با عبارات ضعیف هستند. اگر جاسازی پرس‌وجو از نظر ریاضی با جاسازی سند همسو نباشد، بازیابی با شکست مواجه می‌شود.

تبدیل پرس‌وجو از یک LLM برای بازنویسی یا گسترش پرس‌وجو قبل از رسیدن به پایگاه داده استفاده می‌کند. شما دو تکنیک را پیاده‌سازی خواهید کرد:

  • HyDE (جاسازی‌های فرضی سند): شباهت برداری بین یک سوال و یک پاسخ اغلب کمتر از شباهت بین یک پاسخ و یک پاسخ فرضی است. HyDE از LLM می‌خواهد که یک پاسخ کامل را تصور کند، آن را جاسازی می‌کند و اسنادی را که شبیه به آن توهم هستند، جستجو می‌کند.
  • پرسش گام به گام: اگر کاربر یک سوال جزئی و خاص بپرسد، سیستم ممکن است زمینه وسیع‌تر را از دست بدهد. پرسش گام به گام از LLM می‌خواهد که یک سوال انتزاعی سطح بالاتر ("تاریخچه این خانواده چیست؟") ایجاد کند تا اطلاعات بنیادی را در کنار جزئیات خاص بازیابی کند.
  1. query_transformation.py را ایجاد کنید:
    cloudshell edit query_transformation.py
    
  2. کد زیر را جایگذاری کنید:
    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. اسکریپت تبدیل را اجرا کنید:
    python query_transformation.py
    

خروجی را مشاهده کنید.

توجه کنید که چگونه پرس‌وجوی Step-back ممکن است زمینه وسیع‌تری را در مورد تاریخچه خانواده دورسلی بازیابی کند، در حالی که HyDE بر جزئیات خاص تولید شده در پاسخ فرضی تمرکز می‌کند.

۱۰. بخش ۴: تولید سرتاسری

ما داده‌هایمان را خرد کرده‌ایم، جستجویمان را اصلاح کرده‌ایم و عبارت جستجوی کاربر را اصلاح کرده‌ایم. حالا، بالاخره "G" را در RAG: Generation قرار داده‌ایم.

تا این لحظه، ما فقط اطلاعات را پیدا می‌کردیم . برای ساخت یک دستیار هوش مصنوعی واقعی، باید آن اسناد با کیفیت بالا و رتبه‌بندی مجدد شده را به یک LLM (Gemini) بدهیم تا یک پاسخ به زبان طبیعی را ترکیب کند.

در یک خط تولید، این شامل یک جریان خاص است:

  1. بازیابی: با استفاده از جستجوی برداری سریع، مجموعه‌ای گسترده از کاندیداها (مثلاً 10 مورد برتر) را دریافت کنید.
  2. رتبه‌بندی مجدد: با استفاده از Vertex AI Reranker، بهترین‌ها (مثلاً ۳ مورد برتر) را فیلتر کنید.
  3. ساخت زمینه: محتوای آن سه سند برتر را در یک رشته واحد به هم وصل کنید.
  4. اعلان مبتنی بر زمینه: آن رشته زمینه را در یک الگوی اعلان دقیق قرار دهید که LLM را مجبور می‌کند فقط از آن اطلاعات استفاده کند.

ایجاد اسکریپت تولید

ما برای مرحله تولید gemini-2.5-flash استفاده خواهیم کرد. این مدل برای RAG ایده‌آل است زیرا دارای پنجره زمینه طولانی و تأخیر کم است و به آن اجازه می‌دهد چندین سند بازیابی شده را به سرعت پردازش کند.

  1. end_to_end_rag.py را ایجاد کنید:
cloudshell edit end_to_end_rag.py
  1. کد زیر را جایگذاری کنید. به متغیر template توجه کنید - اینجا جایی است که ما با مقید کردن آن به زمینه ارائه شده، اکیداً به مدل دستور می‌دهیم که از "توهم" (ساختن چیزها) جلوگیری کند.
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. اجرای نهایی برنامه:
python end_to_end_rag.py

درک خروجی

وقتی این اسکریپت را اجرا می‌کنید، تفاوت بین تکه‌های خام بازیابی شده (که در مراحل قبل دیدید) و پاسخ نهایی را مشاهده کنید. LLM به عنوان یک ترکیب‌کننده عمل می‌کند - «تکه‌های» تکه‌تکه شده متن ارائه شده توسط Reranker را می‌خواند و آنها را به یک جمله منسجم و قابل خواندن توسط انسان تبدیل می‌کند.

با زنجیره‌سازی این اجزا، شما از یک «حدس» تصادفی به یک گردش کار قطعی و مبتنی بر پایه حرکت می‌کنید. رتریور تور را می‌اندازد، رِینکر بهترین صید را انتخاب می‌کند و مولد غذا را می‌پزد.

۱۱. نتیجه‌گیری

تبریک! شما با موفقیت یک خط لوله RAG پیشرفته ساختید که بسیار فراتر از جستجوی برداری ساده است.

خلاصه

  • شما Cloud SQL را با pgvector برای ذخیره‌سازی برداری مقیاس‌پذیر پیکربندی کردید.
  • شما استراتژی‌های قطعه‌بندی را مقایسه کردید تا بفهمید آماده‌سازی داده‌ها چگونه بر بازیابی تأثیر می‌گذارد.
  • شما Reranking را با Vertex AI پیاده‌سازی کردید تا دقت نتایج خود را بهبود بخشید.
  • شما از تبدیلات پرس‌وجو (HyDE، Step-back) برای همسو کردن قصد کاربر با داده‌هایتان استفاده کردید.

اطلاعات بیشتر

از نمونه اولیه تا تولید

این آزمایشگاه بخشی از پروژه «هوش مصنوعی آماده تولید با مسیر یادگیری ابری گوگل» است.