آموزش RAG در LLM
⚡ خلاصه سریع — اصل کاریها
اینها مهمترین چیزهایی هستن که اگه هیچی ندونی، فقط اینا رو بدونی کافیه:
- RAG یعنی «نسل تقویتشده با بازیابی»؛ یعنی اول سند مرتبط پیدا کن، بعد بذار LLM جواب بده.
- چانکینگ یعنی شکستن متنهای بزرگ به تیکههای کوچیک؛ بهترین شروع معمولاً ۵۰۰ توکن با همپوشانی ۵۰ توکنه.
- Embedding متن رو به بردار عددی تبدیل میکنه تا بشه شباهت معنایی رو محاسبه کرد.
- Vector Store مثل Chroma یا Pinecone جای ذخیره و جستوجوی همین بردارهاست.
- Retriever از вектор استور، مرتبطترین تیکهها رو برای سوال کاربر بیرون میکشه.
- Prompt باید صریح بگه: «فقط بر اساس متن دادهشده جواب بده، اگر نیست بگو نمیدانم».
- دما (temperature) رو صفر بذار تا LLM خلاقیت بیربط نکنه و پایبند سند باشه.
- همیشه source یا metadata رو ذخیره کن تا بتونی نشون بدی جواب از کدوم صفحه/سند اومده.
۳ اشتباه رایج که باید ازشون پرهیز کنی:
- چانکهای خیلی بزرگ یا خیلی کوچیک؛ هر دو باعث میشه بازیابی کیفیت نیاره.
- استفاده از دو مدل Embedding متفاوت برای ذخیره و جستوجو؛ حتماً باید یکسان باشن.
- اعتماد کورکورانه به خروجی RAG بدون ارزیابی کیفیت بازیابی و نبودن فیدبک.
اگر فقط ۵ دقیقه وقت داری، اینها رو یاد بگیر:
- RAG یعنی بازیابی اطلاعات مرتبط + تزریق به پرامپت + تولید پاسخ.
- کلید شروع کار اینه: متن را تقسیم کن، Embedding بگیر، داخل Vector Store بریز، Retriever بساز.
- از یک پرامپت سختگیر استفاده کن تا LLM فقط از context جواب بده، نه از حافظه خودش.
۱. مقدمه
RAG چیه؟
RAG مخفف Retrieval-Augmented Generation یعنی «نسل تقویتشده با بازیابی». مدلهای زبانی بزرگ مثل GPT یا Claude دانش عمومی خیلی زیادی دارن، اما چند مشکل اساسی دارن:
- دانششون محدود به تاریخ آموزشه و رویدادهای جدید رو نمیدونن.
- ممکنه «توهم» بزنن و اطلاعات غیرواقعی بگن.
- به اسناد خصوصی یا داخلی شرکت تو دسترسی ندارن.
- هر بار دانش جدید دادن، باید مدل رو دوباره آموزش بدی که پرهزینه و زمانبره.
RAG این مشکل رو حل میکنه: به جای اینکه همه دانش رو داخل مدل فرو کنی، اول سند مرتبط رو پیدا میکنی، بعد همون متن را همراه سوال به LLM میدی تا پاسخ تولید کنه.
یک تشبیه ساده: LLM مثل یک دانشآموز باهوشه که بعضی وقتها حرف مفت میزنه. RAG یعنی سر جلسه امتحان بهش یه کتاب باز میدی و میگی «فقط از روی این کتاب جواب بده». خروجی خیلی قابلاعتمادتر میشه.
چرا باید RAG یاد بگیری؟
- کاهش توهم
- اتصال LLM به دادههای خصوصی بدون فاینتیون
- بهروزرسانی آسان دانش با تغییر اسناد
- کاهش هزینه نسبت به آموزش مجدد مدل
- شفافیت: میتونی نشون بدی جواب از کدوم منبع اومده
کجا استفاده میشه؟
- چتبات پشتیبانی مشتری روی مستندات شرکت
- دستیار حقوقی/پزشکی روی پروندهها و مقالات
- جستوجوی هوشمند در کتابخانه داخلی
- تحلیل قراردادها و گزارشهای مالی
۲. پیشنیازها
برای شروع نیاز داری:
- پایتون پایه: متغیر، تابع، نصب پکیج با
pip - مفهوم API Key: کلید دسترسی به سرویسهای بیرونی مثل OpenAI
- خط فرمان: کار با ترمینال و محیط مجازی
- آشنایی اولیه با LLM: اینکه ورودی متن میگیره و خروجی متن میده
ابزارهای لازم
- Python نسخه 3.9 یا بالاتر
- ویرایشگر کد مثل VS Code
- یک API Key از OpenAI (یا مدل جایگزین)
- پکیجهایی که جلوتر نصب میکنیم:
langchain,langchain-openai,langchain-chroma,langchain-community,pypdf
اگه OpenAI نداری، میتونی از مدلهای باز مثل sentence-transformers برای Embedding و مدلهای Hugging Face برای تولید استفاده کنی، اما برای سادگی آموزش از OpenAI استفاده میکنیم.
۳. نصب و راهاندازی
گام ۱: ساخت پوشه پروژه و محیط مجازی
mkdir my_rag_project
cd my_rag_project
python -m venv venv
فعالسازی محیط مجازی:
# لینوکس و مک
source venv/bin/activate
# ویندوز
venv\Scripts\activate
گام ۲: نصب پکیجها
pip install langchain langchain-openai langchain-chroma langchain-community langchain-text-splitters pypdf
گام ۳: تنظیم کلید OpenAI
در ترمینال (یا ابتدای اسکریپت):
export OPENAI_API_KEY="sk-..."
روی ویندوز:
set OPENAI_API_KEY="sk-..."
یا داخل پایتون:
import os
os.environ["OPENAI_API_KEY"] = "sk-..."
گام ۴: اولین پروژه RAG
بیایید یک RAG خیلی ساده بسازیم که از سه جمله به سوال جواب بده:
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_chroma import Chroma
from langchain.chains import RetrievalQA
# ۱. دادههای ساده
docs = [
"پایتخت فرانسه پاریس است.",
"پایتخت ژاپن توکیو است.",
"پایتخت کانادا اتاوا است."
]
# ۲. Embedding و ذخیره در Vector Store
embeddings = OpenAIEmbeddings()
vectorstore = Chroma.from_texts(docs, embedding=embeddings)
# ۳. ساخت Retriever
retriever = vectorstore.as_retriever(search_kwargs={"k": 2})
# ۴. ساخت زنجیره پرسش و پاسخ
qa = RetrievalQA.from_chain_type(
llm=ChatOpenAI(model="gpt-3.5-turbo", temperature=0),
chain_type="stuff",
retriever=retriever
)
# ۵. پرسش
result = qa.invoke("پایتخت فرانسه کجاست؟")
print(result["result"])
خروجی باید چیزی شبیه این باشه:
پایتخت فرانسه پاریس است.
۴. مفاهیم پایه
Embedding چیست؟
Embedding یعنی تبدیل متن به یک بردار عددی. این بردار طوری ساخته میشه که جملههای هممعنی فاصله کمی از هم داشته باشن. مثلاً بردار «پایتخت فرانسه کجاست؟» به «پاریس پایتخت فرانسه است» نزدیکه ولی به «امروز باران میبارد» دور.
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings()
vector = embeddings.embed_query("سلام دنیا")
print(len(vector)) # مثلاً 1536
print(vector[:5]) # چند عدد اول
Vector Store چیست؟
یک پایگاهداده برای ذخیره و جستوجوی بردارهاست. وقتی سوالی میپرسی، خودش نزدیکترین بردارها رو از نظر معنایی پیدا میکنه.
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings
texts = [
"پایتخت فرانسه پاریس است",
"پایتخت ژاپن توکیو است",
"رنگ آسمان آبی است"
]
vectorstore = Chroma.from_texts(texts, embedding=OpenAIEmbeddings())
# جستوجو
docs = vectorstore.similarity_search("شهر عشاق کجاست؟", k=1)
print(docs[0].page_content)
Chunking چیست؟
اسناد بلند رو نمیشه یکجا Embedding کرد یا به LLM داد. متن رو به تیکههای کوچیک با کمی همپوشانی تقسیم میکنیم تا زمینه حفظ بشه.
from langchain_text_splitters import RecursiveCharacterTextSplitter
long_text = "این یک متن طولانی است. " * 1000
splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50
)
chunks = splitter.split_text(long_text)
print(len(chunks))
print(chunks[0])
Retriever چیست؟
ریتریور فقط یک رابط برای گرفتن چند سند مرتبط از Vector Store است:
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
relevant_docs = retriever.invoke("سوال کاربر")
for doc in relevant_docs:
print(doc.page_content)
Prompt و Generator
وقتی چانکهای مرتبط پیدا شدن، آنها رو داخل یک پرامپت میریزیم و از LLM میخواهیم فقط از آنها جواب بده:
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
context = "پایتخت فرانسه پاریس است."
question = "پایتخت فرانسه کجاست؟"
prompt = f"""به سوال فقط بر اساس متن داده شده پاسخ بده.
اگر جواب در متن نیست بگو «نمیدانم».
متن:
{context}
سوال: {question}
پاسخ:"""
response = llm.invoke(prompt)
print(response.content)
۵. مثالهای کد ساده
مثال ۱: ساخت Embedding از چند متن
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings()
sentence = "هوش مصنوعی در حال تغییر دنیاست"
vector = embeddings.embed_query(sentence)
print("طول بردار:", len(vector))
print("چند عدد اول:", vector[:3])
مثال ۲: ذخیره و جستوجو در Chroma
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings
documents = [
"گربه حیوانی اهلی و بازیگوش است.",
"سگ به وفاداری معروف است.",
"نهنگ بزرگترین پستاندار دریاست."
]
vectorstore = Chroma.from_texts(documents, embedding=OpenAIEmbeddings())
result = vectorstore.similarity_search("کدوم حیوان وفادار است؟", k=1)
print(result[0].page_content)
مثال ۳: ساخت زنجیره کامل RAG با LCEL
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_chroma import Chroma
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
docs = [
"جاوا یک زبان برنامهنویسی شیگراست.",
"پایتون زبانی ساده و محبوب است.",
"HTML زبان نشانهگذاری وب است."
]
vectorstore = Chroma.from_texts(docs, embedding=OpenAIEmbeddings())
retriever = vectorstore.as_retriever()
template = """فقط بر اساس متن زیر پاسخ بده. اگر نبود بگو نمیدانم.
متن:
{context}
سوال: {question}
پاسخ:"""
prompt = ChatPromptTemplate.from_template(template)
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
def format_docs(docs):
return "\n\n".join(doc.page_content for doc in docs)
chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
print(chain.invoke("پایتون چه نوع زبانی است؟"))
۶. مثال واقعی و کاربردی
ساخت چتبات پرسش و پاسخ روی PDF
گام ۱: آمادهسازی
فرض کن یک فایل PDF به نام book.pdf داری. میخواهیم راجع به محتوای آن سوال کنیم.
گام ۲: بارگذاری PDF
from langchain_community.document_loaders import PyPDFLoader
loader = PyPDFLoader("book.pdf")
pages = loader.load()
print(f"تعداد صفحات: {len(pages)}")
print(pages[0].page_content[:200])
گام ۳: تقسیم متن
from langchain_text_splitters import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50
)
chunks = text_splitter.split_documents(pages)
print(f"تعداد چانکها: {len(chunks)}")
گام ۴: ساخت Vector Store و Retriever
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=OpenAIEmbeddings(),
persist_directory="./chroma_db"
)
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
گام ۵: ساخت زنجیره QA
from langchain.chains import RetrievalQA
from langchain_openai import ChatOpenAI
qa = RetrievalQA.from_chain_type(
llm=ChatOpenAI(model="gpt-3.5-turbo", temperature=0),
chain_type="stuff",
retriever=retriever,
return_source_documents=True
)
query = "خلاصه فصل اول چیست؟"
result = qa.invoke(query)
print("پاسخ:", result["result"])
print("منابع:")
for doc in result["source_documents"]:
print("- صفحه:", doc.metadata.get("page"), "|", doc.page_content[:100])
گام ۶: تست با چند سوال
for question in [
"نویسنده در فصل دوم چه ادعایی دارد؟",
"آیا در کتاب به یادگیری ماشین اشاره شده است؟"
]:
res = qa.invoke(question)
print("سوال:", question)
print("پاسخ:", res["result"])
print("---")
۷. مباحث پیشرفته
۱. انواع جستوجو
- جستوجوی بردار خالص: فقط شباهت کسینوسی بین Embedding ها.
- جستوجوی کلیدواژهای (BM25): بر اساس کلمات دقیق، خوب برای اعداد و کد.
- Hybrid Search: ترکیب هر دو؛ بهترین دقت در بسیاری از موارد.
- Reranking: بعد از بازیابی اولیه، یک مدل دوباره به اسناد امتیاز دقیقتر میده.
مثال تنظیم آستانه شباهت:
retriever = vectorstore.as_retriever(
search_type="similarity_score_threshold",
search_kwargs={"score_threshold": 0.5, "k": 3}
)
۲. فیلتر متادیتا
میتونی اسناد رو با metadatas ذخیره و فیلتر کنی:
vectorstore = Chroma.from_texts(
texts=["سند گزارش مالی ۲۰۲۰", "سند گزارش مالی ۲۰۲۱"],
metadatas=[{"year": 2020}, {"year": 2021}],
embedding=OpenAIEmbeddings()
)
retriever = vectorstore.as_retriever(
search_kwargs={"filter": {"year": 2021}}
)
docs = retriever.invoke("گزارش مالی")
print(docs[0].page_content)
۳. استراتژیهای چانکینگ پیشرفته
- Recursive: ساده و عمومی.
- Semantic chunking: بر اساس شباهت معنایی جملهها.
- Parent-child: چانک بزرگ برای context، چانک کوچیک برای جستوجو.
- Document-specific: مثلاً در HTML بر اساس تگها.
۴. بهبود پرسوجو
- Query rewriting: بازنویسی سوال کاربر برای بازیابی بهتر.
- HyDE: اول از LLM بخواه یک پاسخ فرضی بده، بعد با همان پاسخ فرضی جستوجو کن.
- Multi-query: چند نسخه متفاوت از سوال بساز و نتایج را ادغام کن.
۵. ارزیابی
- Faithfulness: آیا جواب به context وفادار است؟
- Relevance: آیا context بازیابیشده مرتبط با سوال است؟
- Context Precision/Recall: معیارهای دقت بازیابی.
- ابزار: RAGAS، TruLens، LangSmith.
۶. بهینهسازی
- Embedding ها را کش کن تا دوباره هزینه نشود.
- برای دادههای ایستا، Vector Store را یک بار بساز و ذخیره کن.
- از مدل کوچکتر برای پاسخدهی ساده استفاده کن.
- محدودیت توکن را مدیریت کن: چانکهای زیاد را خلاصه کن یا
map_reduceاستفاده کن.
۸. اشتباهات رایج
- چانکهای خیلی بزرگ: باعث میشه مدل بین اطلاعات بیربط گم بشه و دقت پایین بیاد.
- چانکهای خیلی کوچیک: زمینه از بین میره و پاسخ ناقص میشه.
- عدم پاکسازی اسناد: PDF با جدول و تصویر و ستون، استخراج متن خراب میکنه.
- استفاده از Embedding ناسازگار: مثلاً با مدل A ذخیره کنی و با مدل B جستوجو کنی.
- غفلت از Prompt Injection: اگر سند شامل دستورات مخرب باشه، LLM ممکنه فریب بخوره.
- عدم محدودیت در پرامپت: اگر نگویی «فقط از context بگو»، LLM از دانش قبلی خودش جواب میده.
- نادیده گرفتن متادیتا: بدون منبعنامه، اعتماد به خروجی کم میشه.
- ارزیابی نکردن: نمیدونی بازیابی خوبه یا نه؛ باید معیار داشته باشی.
۹. بهترین روشها (Best Practices)
- چانکسایز استاندارد: ۳۰۰ تا ۵۰۰ توکن، همپوشانی ۱۰ تا ۲۰ درصد.
- ذخیره منبع: همیشه
source,page,titleو تاریخ را داخل متادیتا نگه دار. - پرامپت سختگیر:
فقط بر اساس متن دادهشده پاسخ بده.
اگر پاسخ در متن نیست، بگو «نمیدانم».
هیچ اطلاعاتی از دانش قبلی خودت اضافه نکن.
- دما صفر: برای QA واقعی،
temperature=0بذار. - Rerank کن: اگر دقت خیلی مهمه، بعد از بازیابی اولیه از مدل reranker استفاده کن.
- کش کردن Embedding: برای صرفهجویی در هزینه و زمان.
- پایش و لاگ: پرسشها، اسناد بازیابیشده و پاسخها را ذخیره کن.
- فیدبک انسانی: به کاربر اجازه بده پاسخ را درست/غلط علامت بزنه.
- Fallback: اگر امتیاز شباهت پایین بود، بگو «پاسخی پیدا نشد» به جای تولید خودسرانه.
۱۰. منابع و ادامه مسیر
مستندات رسمی
- LangChain: https://python.langchain.com
- LlamaIndex: https://docs.llamaindex.ai
- Chroma: https://docs.trychroma.com
- OpenAI Cookbook: https://cookbook.openai.com
دورههای پیشنهادی
- DeepLearning.AI – LangChain for LLM Application Development
- DeepLearning.AI – Building and Evaluating Advanced RAG Applications
مقاله پایه
- Lewis et al. (2020) – Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks
ابزارهای مفید
- LlamaIndex: فریمورک تخصصی RAG
- Haystack: پایپلاینهای بازیابی و QA
- Weaviate / Pinecone: Vector Store های ابری
- RAGAS: ارزیابی کیفیت RAG
- LangSmith: دیباگ و مانیتورینگ زنجیرهها
مسیر ادامه
- پروژه ساده با اسناد خودت بساز.
- چانکسایزها را تست و نتایج را مقایسه کن.
- Hybrid Search + Reranking را پیاده کن.
- یک سیستم ارزیابی ساده با RAGAS راه بنداز.
- به فکر استقرار با FastAPI و رابط کاربری باش.
حالا تو آمادهای که RAG را از صفر شروع کنی و به سطح حرفهای برسی. موفق باشی!