نحوه ساخت RAG های کارآمد با Query Routing

نحوه ساخت RAG های کارآمد با Query Routing

نوشته شده توسط: نگین فاتحی
تاریخ انتشار: ۲۰ شهریور ۱۴۰۳
آخرین بروزرسانی: ۲۰ شهریور ۱۴۰۳
زمان مطالعه: 10 دقیقه
۰
(۰)

ساخت RAG با Query Routing برای پیش‌برد هدف‌مند وظایف LLMها است؛ جایی‌که یک دستور واحد نمی‌تواند همه‌چیز را مدیریت کند و یک منبع داده واحد هم قادر به مدیریت همه داده‌ها نیست. مدل‌های زبانی بزرگ که به “LLM” معروف هستند، می‌توانند مسیریابی عمومی (General Routing) را در پایگاه داده انجام دهند. جست‌وجوی معنایی (Semantic Search) هم می‌تواند داده‌های خصوصی را به‌شیوه‌ای بهتر مدیریت کند. پس کدام‌یک را انتخاب کنیم؟

ما در این مقاله، به سوال و دغدغه بسیاری از مهندسان هوش مصنوعی پاسخ می‌دهیم و هرکدام را در قالب مثال‌های عملی توصیف می‌کنیم تا به درک و شفافیت درستی برسیم.

بررسی یک سناریو واقعی در مسیریابی LLM

برای بازیابی اطلاعات از پایگاه داده، گاهی به بیش‌از یک منبع داده نیاز داریم؛ چیزی بیشتر از یک ذخیره بردار، گراف DB یا حتی پایگاه داده SQL Server. همچنین برای انجام وظایف مختلف به دستورهای متعددی هم نیاز داریم.

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

اگر به دلایلی هنوز فکر می‌کنید که این‌ کار خیلی آسان است، در ادامه به مثال توجه کنید.

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

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

این مثال پیچیده است، اما برنامه‌های تولیدکننده ممکن است به چند نگهدارنده برداری نیاز داشته باشند. به‌عنوان مثال، برنامه شما ممکن است یک RAG چندوجهی باشد. شما هم با انواع داده‌ها (متن، تصاویر، صدا) سروکار داشته باشید و از نگهدارنده‌های برداری مختلف استفاده کنید.

این سناریو اهمیت استفاده از چند منبع داده و مسیریابی مختلف را نشان می‌دهد. حال باید به بررسی دو تکنیک اساسی بپردازیم؛ تکنیک‌های متداولی که اغلب برای مسیریابی کوئری‌ها استفاده می‌شوند.

ساخت RAG برای چت‌ بات

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

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

پس دیتابیس خودمان را براساس کدهای زیر بنا می‌کنیم:

 

from langchain_community.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
def create_retriever_from_file(file_name):
   data = TextLoader(file_name).load()
   text_splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=20)
   splits = text_splitter.split_documents(data)
   vectorstore = Chroma.from_documents(splits, embedding=OpenAIEmbeddings())
   return vectorstore.as_retriever()
hr_retriever = create_retriever_from_file("HR_Docs.txt")
accounts_retriever = create_retriever_from_file("Accounts_Docs.txt")

 

توضیح کد: در کد بالا، دو نگهدارنده وکتور، یکی برای HR و دیگری برای Finance ایجاد کردیم. از آن‌جایی‌که به‌صورت مستقیم با ذخیره‌های برداری کار نمی‌کنیم و از آن‌ها فقط به‌عنوان بازیاب‌کننده‌ها بهره می‌بریم، تابع را وادار به انجام یک‌ کار هدف‌مند می‌کنیم: ذخیره برداری را به‌شکل یک شی “retriever” برگرداند. 

ما در این مثال از یک فایل متنی استفاده کردیم؛ اما همین کدها می‌توانند یک پایپ‌لاین داده در برنامه‌های واقعی باشند.

بررسی یک رویکرد غیرمتعارف برای مسیریابی کوئری

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

کد را به‌شکل زیر پیاده می‌کنیم:

 

# Define keywords for HR and Finance queries
HR_KEYWORDS = [
   "benefits",
   "performance",
   "evaluations",
   "leave",
   "policies",
   "human resources",
   "HR",
]
ACCOUNTS_KEYWORDS = [
   "salary",
   "payroll",
   "expense",
   "reimbursements",
   "finance",
   "financial",
   "pay",
]
# Function to route query
def route_query(query: str) -> str:
   # Convert query to lowercase for case-insensitive matching
   query_lower = query.lower()
   # Check if any HR keywords are in the query
   if any(keyword in query_lower for keyword in HR_KEYWORDS):
       return hr_retriever
   # Check if any Finance keywords are in the query
   elif any(keyword in query_lower for keyword in ACCOUNTS_KEYWORDS):
       return finance_retriever
   # If no keywords are matched, return a default response
   else:
       return "Unknown category, please refine your query."
# Example queries
queries = [
   "What are the leave policies?",
   "How do I apply for performance evaluations?",
   "Can I get a breakdown of my salary?",
   "Where do I submit expense reimbursements?",
   "Tell me about the HR benefits available.",
]
# Route each query and retrieve the response
for query in queries:
   retriever = route_query(query)
   response = retriever.invoke(query)[0].page_content
   print(f"Query: {query}" + "\n" + f"Response: {response}" + "\n")

 

توضیح کد: کد بالا کار ما را انجام می‌دهد؛ اما از بسیاری جهات دیگر کم خواهد آورد.

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

به‌همین‌دلیل است که از تکنیک‌های پیشرفته‌تر، مانند مسیریابی مبتنی‌بر LLM (LLM-based Routing) و جست‌جوی مشابهت معنایی (Semantic-similarity Search) استفاده می‌کنیم.

مسیریابی کوئری مبتنی‌ بر LLM

در LLM-based Routing، با جایگزینی جست‌وجوی کلمه کلیدی یا مدل ML با یک LLM، می‌توانیم مزیت بزرگی در رویکرد بالا به‌دست بیاوریم.

به‌طورمعمول، دانش عمومی LLM برای هدایت مستقیم کوئری به Retriever صحیح کافی است. این دانش باید کوئری‌های متفاوت، غلط املایی و ابهام را به‌خوبی مدیریت کند.

دیاگرام زیر، خلاصه‌ای از این فرآیند را نشان می‌دهد:

 

مسیریابی کوئری مبتنی‌بر LLM

 

یکی از بهترین روش‌هایی که در این مثال باید در نظر گرفت، استفاده از یک خروجی ساختاریافته است. خروجی ساختاریافته پاسخ‌های بدون ابهام به ما می‌دهد و به LLMها گزینه‌های پیش‌روی‌شان را آموزش می‌دهد.

پیاده‌سازی این فرآیند با کدهای زیر ممکن می‌شود:

 

from pydantic import BaseModel, Field
from typing import Optional, Literal
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
# Section 1: Setup LLM and Configure Structured Output
class DataSource(BaseModel):
   datasource: Optional[Literal["hr", "accounts"]] = Field(
       title="Organization data source",
       description="Our organization bot has two data sources: HR and accounts",
   )
llm = ChatOpenAI()
structured_routing_llm = llm.with_structured_output(DataSource)
# Section 2: Routing Prompt Template
routing_prompt_template = ChatPromptTemplate.from_template("""
   You are good at routing questions to either accounts or HR departments.
   Which is the best department to answer the following question?
   If you can't determine the best department, respond with "I don't know".
   question: {question}
   department:
""")
routing_chain = routing_prompt_template | structured_routing_llm
# Section 3: Define Retriever Based on the Routed Department
def get_retriever(question):
   datasource = routing_chain.invoke(question).datasource
   hr_prompt_template = ChatPromptTemplate.from_template("""
       You are a human resources professional at Fictional, Inc.
       Respond to the following employee question in the context provided.
       If you can't answer the question with the given context, please say so.
       context: {context}
       question: {question}
   """)
   accounts_prompt_template = ChatPromptTemplate.from_template("""
       You are an accounts professional at Fictional, Inc.
       Respond to the following employee question in the context provided.
       If you can't answer the question with the given context, please say so.
       context: {context}
       question: {question}
   """)
   if datasource == "hr":
       print("HR")
       return hr_retriever, hr_prompt_template
   else:
       print("Accounts")
       return accounts_retriever, accounts_prompt_template
# Section 4: Answer the Question Using the Appropriate Chain
def answer_the_question(question: str) -> str:
   routing_output = routing_chain.invoke(question)
   retriever, prompt_template = get_retriever(routing_output)
   chain = (
       {"question": RunnablePassthrough(), "context": retriever}
       | prompt_template
       | llm
       | StrOutputParser()
   )
   return chain.invoke(question)
# Example usage
answer_the_question("How do I change my salary deposit information?")
>> 'Accounts'
>> 'You can change your salary deposit information by logging into the accounts portal and navigating to the payroll section. From there, you can enter your new bank account information and save the changes. Your salary will then be deposited into the new account each pay period.'

 

توضیح کد: کد بالا دارای چهار بخش و یک مثال استفاده است. بخش اول شی Pydantic را تعریف می‌کند تا ساختار خروجی مورد نیاز را به LLM بگوید. این‌بار خروجی یک آبجکت DataSource خواهد بود نه پاسخ معمولی.

بخش دوم جایی است که روتر را تعریف می‎کنیم. در پرامپت این کد، از مدل می‌خواهیم بگوید «نمی‌دانم» تا سعی نکند به سوالات تصادفی پاسخ دهد.

بخش‌های سه و چهار شی Retriever صحیح را بازیابی می‌کنند، اسناد مربوطه را واکشی کرده و به سوال کاربر درباره داده‌های بازیابی‌شده پاسخ می‌دهند.

معایب مسیریابی کوئری مبتنی‌ بر LLM

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

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

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

مسیریابی معنایی کوئری

Semantic Routing رویکرد بسیار ساده‌ای است؛ چون در آن یک قسمت داریم که هر منبع داده را نشان می‌دهد. با استفاده از یک رویکرد مبتنی‌بر فاصله، ورودی و متن کاربر را مقایسه می‌کنیم و مشابه‌ترین منبع داده را می‌یابیم.

همان‌طور که ممکن است حدس زده باشید، این قسمت باید به‌طور دقیق منبع داده را نشان دهد تا فرآیند با موفقیت به‌پایان برسد. در این حالت، از پرامپت به‌عنوان متنی که اسناد با آن مقایسه می‌شوند، استفاده می‌کنیم.

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

به‌عنوان‌مثال، شما نرم‌افزار اختصاصی به‌نام “MySecret” دارید که به کارمندان‌تان اجازه می‌دهد تا در مورد نگرانی‌های خودش به‌طور امن و خصوصی صحبت کنند. رویکرد مبتنی‌بر LLM هیچ سرنخی از معنای این کار ندارد؛ اما شباهت معنایی می‌تواند همین کار را به‌درستی مسیریابی کند.

دیاگرام زیر، تمام این فرآیند را به‌شکل خلاصه نمایش می‌دهد:

 

مسیریابی معنایی کوئری

 

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

پس اجازه دهید در ادامه، اجرای کامل کدهای این بخش را برای مسیریابی معنایی کوئری همین سناریو ببینیم.

 

# Section 1: Defining the prompts for each data source and embed them.
hr_template = """You're a human resources professional at Fictional, Inc.
Use the context below to answer the question that follows.
If you need more information, ask for it.
If you don't have enough information in the context to answer the question, say so.
context: {context}
question: {query}
Answer:
"""
accounts_template = """You're an accounts manager at Fictional, Inc.
Use the context below to answer the question that follows.
If you need more information, ask for it.
If you don't have enough information in the context to answer the question, say so.
context: {context}
question: {query}
Answer:
{query}"""
prompt_templates = [hr_template, accounts_template]
prompt_embeddings = openai_embeddings.embed_documents(prompt_templates)
# Section 3: Create the similarity-based prompt picker
def find_most_similar_prompt(input):
   # Embed the question
   query_embedding = openai_embeddings.embed_query(input["query"])
   # Pick the most similar prompt
   similarity = cosine_similarity([query_embedding], prompt_embeddings)[0]
   best_match = prompt_templates[similarity.argmax()]
   print(
       "Directing to the Accounts Department"
       if best_match == accounts_template
       else "Directing to the HR Department"
   )
   # Also pick the retriever
   retriever = accunts_retriever if best_match == accounts_template else hr_retriever
   # Create the prompt template with the choosen prompt and retriever
   prompt_template =  PromptTemplate.from_template(
       best_match, partial_variables={"context": retriever}
   )
   return prompt_template
# Section 4: Define the full RAG chain
chain = (
   {"query": RunnablePassthrough()}
   | RunnableLambda(find_most_similar_prompt)
   | ChatOpenAI()
   | StrOutputParser()
)
# Execute the chain
print(
   chain.invoke(
       """
       I need more budget to buy the software we need.
       What should I do?
       """
   )
)
>> Directing to the Accounts Department
>> As an accounts manager at Fictional, Inc., you should create a budget proposal outlining the software needed, its cost, and the potential benefits to the company. Present this proposal to the appropriate department or upper management for their review and approval. Additionally, you can also explore cost-saving options or negotiate with the software provider for a better deal.

 

توضیح کد: در کد بالا تابعی را تعریف کردیم که مسیریابی را انجام می‌دهد. ما یک کپی از پرامپت‌های تعبیه‌شده را به‌صورت جداگانه نگه می‌داریم. همان‌طور که ورودی کاربر جدید به برنامه داده می‌شود، آن را Embed می‌کنیم. این کار را با هدف محاسبه شباهت کسینوس بین مجموعه پرامپت‌های خودمان انجام می‌دهیم. درنتیجه مشابه‌ترین پرامپت و Retriever آن برای ایجاد الگوی پرامپت استفاده می‌شود.

معایب مسیریابی معنایی کوئری

ایراد اصلی مسیریابی معنایی این است که حداکثر اندازه توکن ما را محدود می‌کند. برای مدل‌های Open AI، این میزان ۸۱۹۲ توکن است. این شیوه مشکلی را برای انجام کارهای کوچک‌تر ایجاد نمی‌کند؛ اما ممکن است یک سازمان بزرگ کلمات تخصصی و منحصربه‌فرد زیادی داشته باشد. بنابراین، اگر برنامه‌های اختصاصی بیشتری مانند “MySecret” داشته باشیم، این شیوه می‌تواند توکن‌های بیشتری را در پرامپت اشغال کند.

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

همچنین توانایی Semantic Routing برای مسیریابی کوئری‌های پیچیده خصوصی با تردیدهایی هم روبه‌رو است. کوئری‌های مربوط‌به برنامه MySecret باید به HR بروند؛ چون شنونده نگرانی‌های کارمندان است. اما اگر کسی در همین برنامه از چت‌بات بپرسد که: «چرا برنامه MySecret به‌کندی بارگذاری (Load) می‌شود»، مدل باید به بخش IT برود. یک رویکرد تشابه معنایی ممکن است در مسیریابی چنین کوئری‌هایی شکست بخورد.

آنچه در نحوه ساخت RAG های کارآمد با Query Routing خواندیم

یک پرامپت واحد نمی‌تواند همه‌چیز را مدیریت کند و یک منبع داده واحد هم ممکن است برای همه داده‌ها مناسب نباشد. برنامه‌های RAG اغلب به نگهدارنده‌ها و پرامپت‌های مختلف نیاز دارند. روتری که کوئری‌ها را به‌سمت نگهدارنده برداری صحیح هدایت کند هم در این مسیر نیازمان می‌شود. بنابراین مسیریابی منطقی و معنایی دو رویکرد رایج هستند که می‌توانیم از قدرت هردو برای رفع اشکالات RAG بهره ببریم؛ مانند ترکیب آن‌ها و تجزیه کوئری به بخش‌های کوچک‌تر با هدف دریافت پاسخ‌های متعدد. 

شما درباره این دو شیوه چه فکری می‌کنید؟ آیا از روش‌های دیگری هم برای هدایت کوئری‌ها استفاده کرده‌اید؟ ما در بخش نظرات این مقاله میزبان دیدگاه و دانش ارزش‌مند شما هستیم تا با همراهی دیگر مخاطبان، به گسترش علم هوش مصنوعی خودمان کمک کنیم.

چه رتبه ای می‌دهید؟

میانگین ۰ / ۵. از مجموع ۰

اولین نفر باش

title sign
معرفی نویسنده
نگین فاتحی
مقالات
35 مقاله توسط این نویسنده
محصولات
0 دوره توسط این نویسنده
نگین فاتحی

از اسفند 99 مشغول گشت‌وگذار توی دنیای کلمات هستم؛ با این هدف که خوب بنویسم و این چشم‌انداز که کمک‌های موثری کنم. حالا سه‌ ساله که توی زمینه‌های گوناگون بازاریابی آنلاین مطالعه می‌کنم و یکی از حوزه‌های موردعلاقم، رفتارشناسی مخاطبان این فضا هست. دستاوردهای این مطالعه شده نوشتن محتوایی که امیدوارم شما بخونی، لُب‌کلام رو متوجه بشی، لذت ببری و با دست پر صفحه رو ترک کنی؛ شایدم بقیه نوشته‌هام رو بخونی :)

title sign
دیدگاه کاربران