Retrieval-Augmented Generation (RAG)

RAG خودبهبهبخش: حلقه‌های تکراری برای پرسش‌های چندمرحله‌ای پیچیده

سیستم‌های سنتی تولید افزوده با بازیابی (RAG) اغلب در مواجهه با پرسش‌های چندمرحله‌ای با مشکل مواجه می‌شوند. این پرسش‌ها نیازمند ترکیب اطلاعات از چندین منبع متمایز یا مراحل استدلال هستند. برای مثال، پرسیدن «اولین محصولی که توسط شرکتی که توسط نویسنده پایتون تأسیس شد، عرضه شد چه بود؟» نیازمند شناسایی نویسنده (گیدو ون روسوم)، یافتن شرکتی که او تأسیس کرد (هرچند او در CWI بود، اما پرسش ممکن است به استارتاپ خاصی اشاره داشته باشد که بعداً به آن پیوست یا ترک کرد) و در نهایت بازیابی داده‌های عرضه محصول است. یک مرحله بازیابی به ندرت زمینه کامل مورد نیاز برای پر کردن این شکاف‌ها را فراهم می‌کند.

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

معماری حلقه تکراری

منطق اصلی یک سیستم RAG خودبهبهبخش حول سه مرحله در هر تکرار می‌چرخد:

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

پیاده‌سازی در پایتون

در زیر یک پیاده‌سازی ساده‌شده با استفاده از مفاهیم LangChain برای نشان دادن جریان ارائه شده است. این مثال فرض می‌کند که شما یک `vectorstore` و یک `llm` را مقداردهی کرده‌اید.


from langchain.llms import LLM
from langchain.vectorstores import VectorStore
from langchain.chains import RetrievalQA

class SelfRefiningRAG:
    def __init__(self, llm: LLM, vectorstore: VectorStore, max_iterations: int = 3):
        self.llm = llm
        self.vectorstore = vectorstore
        self.max_iterations = max_iterations

    def retrieve(self, query: str) -> list:
        # Standard vector similarity search
        return self.vectorstore.similarity_search(query, k=3)

    def is_sufficient(self, question: str, context: list) -> bool:
        prompt = f"""
        Question: {question}
        Context: {context}
        
        Does the context contain sufficient information to answer the question?
        Answer with only "Yes" or "No".
        """
        response = self.llm(prompt)
        return response.strip().lower() == "yes"

    def generate_next_query(self, question: str, context: list) -> str:
        prompt = f"""
        Original Question: {question}
        Current Context: {context}
        
        The current context is not sufficient to answer the question.
        Identify the missing information and generate a single search query
        to retrieve that missing information.
        Output only the search query.
        """
        return self.llm(prompt).strip()

    def run(self, question: str) -> str:
        context_docs = []
        
        for i in range(self.max_iterations):
            # 1. Retrieve
            if not context_docs:
                # First iteration uses the original question
                context_docs = self.retrieve(question)
            else:
                # Subsequent iterations use a refined query
                new_query = self.generate_next_query(question, context_docs)
                new_docs = self.retrieve(new_query)
                context_docs.extend(new_docs)
            
            # 2. Evaluate
            if self.is_sufficient(question, [d.page_content for d in context_docs]):
                break

        # 3. Final Generation
        final_context = "\n\n".join([d.page_content for d in context_docs])
        final_prompt = f"""
        Context: {final_context}
        Question: {question}
        Answer:
        """
        return self.llm(final_prompt)

بهینه‌سازی برای کارایی و کیفیت

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

1. حذف تکراری بودن زمینه

بازیابی تکراری اغلب اسناد یکسان را چندین بار فراخوانی می‌کند. یک مرحله حذف تکراری (مثلاً با استفاده از هش شناسه سند یا شباهت محتوا) را قبل از افزودن نتایج جدید به context_docs پیاده‌سازی کنید. این کار از باد کردن غیرضروری پرامپت جلوگیری می‌کند.

2. معیارهای توقف زودهنگام

بررسی is_sufficient پرهزینه است (نیاز به یک فراخوانی LLM دارد). می‌توانید این را با استفاده از یک هوریستیک ارزان‌تر در ابتدا (مثلاً همپوشانی کلیدواژه‌ها) یا با محدود کردن ارزیابی LLM فقط در صورتی که امتیاز اسناد بازیابی‌شده زیر یک آستانه مربوطه خاص باشد، بهینه‌سازی کنید.

3. خروجی ساختاریافته برای پرسش‌ها

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

مثال عملی: استدلال چندمرحله‌ای

پرسش زیر را در نظر بگیرید: «مدیرعامل شرکتی که استارتاپ فناوری تأسیس‌شده توسط نویسنده React را تملک کرد، چه کسی بود؟»

  1. تکرار ۱:
    • بازیابی: جستجو برای «نویسنده React» -> یافتن «جوردن والک».
    • ارزیابی: خیر، ما استارتاپ یا مدیرعامل را نمی‌دانیم.
    • پرسش جدید: «جوردن والک چه استارتاپی را تأسیس کرد؟»
  2. تکرار ۲:
    • بازیابی: یافتن «جوردن والک 8080 Labs را تأسیس کرد».
    • ارزیابی: خیر، ما نمی‌دانیم چه کسی آن را تملک کرد یا مدیرعامل فعلی کیست.
    • پرسش جدید: «چه کسی 8080 Labs را تملک کرد و مدیرعامل آن‌ها کیست؟»
  3. تکرار ۳:
    • بازیابی: یافتن اینکه 8080 Labs توسط فیسبوک (متا) تملک شد. بازیابی مدیرعامل متا (مارک زاکربرگ).
    • ارزیابی: بله، اطلاعات کافی موجود است.
    • پاسخ نهایی: «مارک زاکربرگ مدیرعامل متا است، که 8080 Labs را تملک کرد.»

نتیجه‌گیری

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

Share: