سیستمهای پرسش و پاسخ هوشمند امروزه نقش حیاتی در تعامل بین انسان و ماشین ایفا میکنند. RAG (Retrieval-Augmented Generation) شیوهای است که به LLM کمک میکند تا پاسخ دقیق ارائه کند. این تکنولوژی با ترکیب قدرت مدلهای زبانی بزرگ و پایگاههای دانش، امکان ایجاد سیستمهای پرسش و پاسخ دقیق و کارآمد را فراهم میآورد.
در این مقاله، شما با نحوه ایجاد یک سیستم پرسش و پاسخ کاملاً فارسی با استفاده از تکنولوژی RAG آشنا خواهید شد. از مفاهیم پایه تا پیادهسازی عملی، تمام مراحل را به زبان ساده و روان توضیح خواهیم داد.
RAG چیست و چرا به آن نیاز داریم؟
تعریف RAG
RAG (Retrieval-Augmented Generation) شیوهای است که اطلاعات فعلی یا مرتبط با موضوع را از یک پایگاه داده خارجی استخراج کرده و در اختیار هوش مصنوعی مبتنی بر مدلهای زبانی بزرگ قرار میدهد. به زبان سادهتر، RAG یک پل ارتباطی بین دانش ذخیره شده در پایگاههای داده و قدرت تولید متن مدلهای زبانی است.
مولفههای اصلی RAG
RAG (Retrieval-Augmented Generation) شامل سه جزء حیاتی است:
1. بازیابی (Retrieval)
این مؤلفه به مدل کمک میکند تا اطلاعات مربوطه را از پایگاه دانش خارجی، مانند یک پایگاه داده برداری، برای هر درخواست کاربر دریافت کند.
2. افزودن (Augmentation)
این بخش شامل بهبود و افزودن زمینه مرتبط بیشتر به پاسخ بازیابیشده است.
3. تولید (Generation)
مرحله نهایی که در آن مدل زبانی با استفاده از اطلاعات بازیابی شده، پاسخ نهایی را تولید میکند.
چرا RAG؟
مدلهای زبان بزرگ (LLM) بر روی حجم وسیعی از دادهها آموزش میبینند و از میلیاردها پارامتر برای تولید خروجی اصلی برای کارهایی مانند پاسخ دادن به سؤالات، ترجمه زبانها و تکمیل جملات استفاده میکنند. اما این مدلها محدودیتهایی دارند:
- اطلاعات قدیمی: دانش آنها محدود به زمان آموزش است
- عدم دسترسی به اطلاعات خصوصی: نمیتوانند به اطلاعات سازمانی دسترسی داشته باشند
- احتمال توهم (Hallucination): ممکن است پاسخهای نادرست اما معقول ارائه دهند
چالشهای خاص زبان فارسی در RAG
پیادهسازی RAG برای زبان فارسی چالشهای منحصر به فردی دارد که باید به آنها توجه کرد:
1. پیچیدگیهای نگارشی
در زبان فارسی، به علت وجود پیچیدگیهای متنوع، اهمیت پیشپردازش بسیار زیاد است:
- تشخیص درست فاصلهها و نیمفاصلهها
- ضمایر متصل و پیشوندها و پسوندهای چسبیده به فعل
- تنوع در نحوه نگارش کلمات (مثلاً “میروم” در مقابل “میروم”)
2. کمبود منابع و مدلهای آموزش دیده
برخلاف زبان انگلیسی، تعداد مدلهای از پیش آموزش دیده و دیتاستهای با کیفیت برای زبان فارسی محدود است.
3. راست به چپ بودن زبان
این ویژگی در برخی کتابخانهها و ابزارها ممکن است چالشهایی ایجاد کند.
ابزارها و پیشنیازهای مورد نیاز
برای ایجاد یک سیستم RAG فارسی، به ابزارها و کتابخانههای زیر نیاز داریم:
1. کتابخانههای پردازش متن فارسی
Hazm
Hazm is a python library to perform natural language processing tasks on Persian text. این کتابخانه امکانات زیر را فراهم میکند:
pip install hazm
ویژگیهای کلیدی:
- نرمالسازی متن
- توکنسازی (جمله و کلمه)
- ریشهیابی و لمسازی
- برچسبگذاری اجزای کلام
2. مدلهای Embedding فارسی
ParsBERT
ParsBERT is a monolingual language model based on Google’s BERT architecture. این مدل بر روی بیش از 3.9 میلیون سند فارسی آموزش دیده است.
from transformers import AutoModel, AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("HooshvareLab/bert-base-parsbert-uncased")
model = AutoModel.from_pretrained("HooshvareLab/bert-base-parsbert-uncased")
3. Vector Database
برای ذخیره و جستجوی embedding ها، به یک پایگاه داده برداری نیاز داریم:
ChromaDB
Chroma DB is an open-source vector store used for storing and retrieving vector embeddings.
pip install chromadb
مزایای ChromaDB:
- رایگان و متنباز
- مناسب برای پروژههای کوچک تا متوسط
- امکان اجرای محلی
Pinecone
Pinecone emerges as a leading player in the realm of vector databases, offering a fully-managed service.
مزایای Pinecone:
- سرویس کاملاً مدیریت شده
- مقیاسپذیری بالا
- عملکرد سریع
4. LangChain
برای مدیریت و هماهنگی بین اجزای مختلف سیستم RAG:
pip install langchain langchain-community langchain-openai
معماری سیستم RAG فارسی
یک سیستم RAG فارسی از اجزای زیر تشکیل شده است:
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ │ │ │ │ │
│ متن ورودی │────▶│ پیشپردازش │────▶│ Embedding │
│ (سوال کاربر) │ │ (Hazm) │ │ (ParsBERT) │
│ │ │ │ │ │
└─────────────────┘ └──────────────────┘ └────────┬────────┘
│
▼
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ │ │ │ │ │
│ تولید پاسخ │◀────│ افزودن زمینه │◀────│ جستجو در │
│ (LLM) │ │ (Augmentation) │ │ Vector DB │
│ │ │ │ │ │
└─────────────────┘ └──────────────────┘ └─────────────────┘
جریان کار:
- دریافت سوال: کاربر سوال خود را به زبان فارسی وارد میکند
- پیشپردازش: با استفاده از Hazm، متن نرمالسازی و توکنسازی میشود
- تبدیل به Embedding: با ParsBERT، متن به بردار تبدیل میشود
- جستجو: در Vector Database به دنبال متون مشابه میگردیم
- افزودن زمینه: متون بازیابی شده به عنوان زمینه به prompt اضافه میشوند
- تولید پاسخ: LLM با استفاده از زمینه فراهم شده، پاسخ نهایی را تولید میکند
پیادهسازی گام به گام
گام 1: نصب کتابخانهها
# نصب کتابخانههای مورد نیاز
pip install hazm
pip install transformers torch
pip install chromadb
pip install langchain langchain-community
pip install openai
2: آمادهسازی ابزارهای پردازش متن فارسی
from hazm import Normalizer, word_tokenize, sent_tokenize
import torch
from transformers import AutoTokenizer, AutoModel
# راهاندازی نرمالساز متن فارسی
normalizer = Normalizer()
# بارگذاری مدل ParsBERT
tokenizer = AutoTokenizer.from_pretrained("HooshvareLab/bert-base-parsbert-uncased")
model = AutoModel.from_pretrained("HooshvareLab/bert-base-parsbert-uncased")
def get_persian_embedding(text):
"""تبدیل متن فارسی به embedding"""
# نرمالسازی متن
normalized_text = normalizer.normalize(text)
# توکنسازی
inputs = tokenizer(normalized_text, return_tensors="pt",
padding=True, truncation=True, max_length=512)
# محاسبه embedding
with torch.no_grad():
outputs = model(**inputs)
# استفاده از میانگین hidden states به عنوان embedding
embeddings = outputs.last_hidden_state.mean(dim=1)
return embeddings.numpy()[0]
3: ایجاد Vector Database
import chromadb
from chromadb.utils import embedding_functions
# ایجاد یک تابع embedding سفارشی برای ChromaDB
class PersianEmbeddingFunction(embedding_functions.EmbeddingFunction):
def __call__(self, texts):
embeddings = []
for text in texts:
embedding = get_persian_embedding(text)
embeddings.append(embedding.tolist())
return embeddings
# راهاندازی ChromaDB
client = chromadb.Client()
persian_ef = PersianEmbeddingFunction()
# ایجاد مجموعه
collection = client.create_collection(
name="persian_documents",
embedding_function=persian_ef
)
4: اضافه کردن اسناد به پایگاه داده
# نمونه اسناد فارسی
documents = [
{
"text": "هوش مصنوعی شاخهای از علوم کامپیوتر است که به ماشینها توانایی یادگیری و تصمیمگیری میدهد.",
"metadata": {"topic": "AI", "source": "wikipedia"}
},
{
"text": "یادگیری عمیق زیرمجموعهای از یادگیری ماشین است که از شبکههای عصبی عمیق استفاده میکند.",
"metadata": {"topic": "Deep Learning", "source": "academic"}
},
{
"text": "پردازش زبان طبیعی به کامپیوترها امکان درک و تولید زبان انسانی را میدهد.",
"metadata": {"topic": "NLP", "source": "textbook"}
}
]
# اضافه کردن اسناد به collection
for i, doc in enumerate(documents):
collection.add(
documents=[doc["text"]],
metadatas=[doc["metadata"]],
ids=[f"doc_{i}"]
)
print(f"تعداد {len(documents)} سند به پایگاه داده اضافه شد.")
5: پیادهسازی سیستم RAG
from langchain.prompts import PromptTemplate
from langchain.llms import OpenAI
from langchain.chains import LLMChain
class PersianRAG:
def __init__(self, collection, llm_api_key):
self.collection = collection
self.normalizer = Normalizer()
self.llm = OpenAI(api_key=llm_api_key, temperature=0.7)
# قالب prompt برای RAG
self.prompt_template = PromptTemplate(
input_variables=["context", "question"],
template="""بر اساس متنهای زیر به سوال پاسخ دهید. اگر پاسخ در متنها نیست، بگویید نمیدانم.
متنهای مرتبط:
{context}
سوال: {question}
پاسخ:"""
)
self.qa_chain = LLMChain(llm=self.llm, prompt=self.prompt_template)
def retrieve_relevant_docs(self, query, n_results=3):
"""بازیابی اسناد مرتبط با سوال"""
# نرمالسازی سوال
normalized_query = self.normalizer.normalize(query)
# جستجو در collection
results = self.collection.query(
query_texts=[normalized_query],
n_results=n_results
)
return results
def generate_answer(self, question):
"""تولید پاسخ با استفاده از RAG"""
# بازیابی اسناد مرتبط
results = self.retrieve_relevant_docs(question)
# ایجاد context از اسناد بازیابی شده
context = "\n".join(results['documents'][0])
# تولید پاسخ
answer = self.qa_chain.run(
context=context,
question=question
)
return {
"answer": answer,
"sources": results['documents'][0],
"metadata": results['metadatas'][0]
}
6: استفاده از سیستم
# راهاندازی سیستم RAG
rag_system = PersianRAG(
collection=collection,
llm_api_key="YOUR_OPENAI_API_KEY"
)
# طرح سوال
question = "یادگیری عمیق چیست؟"
result = rag_system.generate_answer(question)
print(f"سوال: {question}")
print(f"پاسخ: {result['answer']}")
print(f"\nمنابع استفاده شده:")
for i, source in enumerate(result['sources']):
print(f"{i+1}. {source[:50]}...")
بهینهسازی و نکات عملی
1. بهبود کیفیت Embedding ها
استفاده از مدلهای تخصصیتر
Hakim is a novel state-of-the-art Persian text embedding model that achieves a 8.5% performance improvement over existing approaches on the FaMTEB benchmark. برای نتایج بهتر، میتوانید از مدلهای جدیدتر مانند Hakim استفاده کنید.
Fine-tuning مدلها
برای دامنههای تخصصی، fine-tuning مدلهای embedding روی دادههای مربوط به دامنه میتواند دقت را بهبود بخشد.
2. بهینهسازی پیشپردازش
def advanced_preprocess(text):
"""پیشپردازش پیشرفته متن فارسی"""
# نرمالسازی
text = normalizer.normalize(text)
# حذف کاراکترهای اضافی
import re
text = re.sub(r'[^\u0600-\u06FF\u0750-\u077F\s]', '', text)
# حذف فاصلههای اضافی
text = ' '.join(text.split())
return text
3. استراتژیهای Chunking
برای اسناد طولانی، تقسیم متن به قطعات کوچکتر (chunks) ضروری است:
def chunk_persian_text(text, chunk_size=500, overlap=50):
"""تقسیم متن فارسی به قطعات با همپوشانی"""
sentences = sent_tokenize(text)
chunks = []
current_chunk = []
current_size = 0
for sentence in sentences:
sentence_size = len(sentence)
if current_size + sentence_size > chunk_size and current_chunk:
chunks.append(' '.join(current_chunk))
# حفظ overlap
overlap_sentences = []
overlap_size = 0
for s in reversed(current_chunk):
if overlap_size + len(s) <= overlap:
overlap_sentences.insert(0, s)
overlap_size += len(s)
else:
break
current_chunk = overlap_sentences
current_size = overlap_size
current_chunk.append(sentence)
current_size += sentence_size
if current_chunk:
chunks.append(' '.join(current_chunk))
return chunks
4. بهبود کیفیت بازیابی
استفاده از Hybrid Search
ترکیب جستجوی semantic با جستجوی keyword-based:
def hybrid_search(query, collection, keyword_weight=0.3, semantic_weight=0.7):
"""جستجوی ترکیبی برای نتایج بهتر"""
# جستجوی semantic
semantic_results = collection.query(
query_texts=[query],
n_results=10
)
# جستجوی keyword-based
keywords = word_tokenize(query)
keyword_results = []
# ترکیب نتایج با وزندهی
# ... (پیادهسازی منطق ترکیب)
return combined_results
5. مدیریت حافظه و عملکرد
# استفاده از batch processing برای اسناد زیاد
def batch_add_documents(documents, collection, batch_size=100):
"""اضافه کردن اسناد به صورت دستهای"""
for i in range(0, len(documents), batch_size):
batch = documents[i:i+batch_size]
texts = [doc["text"] for doc in batch]
metadatas = [doc["metadata"] for doc in batch]
ids = [f"doc_{j}" for j in range(i, i+len(batch))]
collection.add(
documents=texts,
metadatas=metadatas,
ids=ids
)
print(f"پردازش دسته {i//batch_size + 1} انجام شد.")
نمونه پروژه کامل
در اینجا یک نمونه کامل از سیستم RAG فارسی آورده شده است:
import os
from typing import List, Dict
import json
class PersianRAGSystem:
"""سیستم کامل RAG برای زبان فارسی"""
def __init__(self, config_path="config.json"):
self.load_config(config_path)
self.setup_components()
def load_config(self, config_path):
"""بارگذاری تنظیمات از فایل"""
with open(config_path, 'r', encoding='utf-8') as f:
self.config = json.load(f)
def setup_components(self):
"""راهاندازی اجزای سیستم"""
# راهاندازی Hazm
self.normalizer = Normalizer()
# راهاندازی ParsBERT
self.tokenizer = AutoTokenizer.from_pretrained(
self.config["embedding_model"]
)
self.model = AutoModel.from_pretrained(
self.config["embedding_model"]
)
# راهاندازی ChromaDB
self.setup_vector_db()
# راهاندازی LLM
self.setup_llm()
def setup_vector_db(self):
"""راهاندازی پایگاه داده برداری"""
persist_directory = self.config.get("persist_directory", "./chroma_db")
self.client = chromadb.PersistentClient(path=persist_directory)
self.collection = self.client.get_or_create_collection(
name="persian_knowledge_base",
embedding_function=PersianEmbeddingFunction()
)
def setup_llm(self):
"""راهاندازی مدل زبانی"""
from langchain.llms import OpenAI
self.llm = OpenAI(
api_key=self.config["openai_api_key"],
model_name=self.config.get("llm_model", "gpt-3.5-turbo"),
temperature=self.config.get("temperature", 0.7)
)
def add_document(self, text: str, metadata: Dict = None):
"""اضافه کردن سند جدید به پایگاه دانش"""
# پیشپردازش
processed_text = self.advanced_preprocess(text)
# تقسیم به chunks در صورت نیاز
if len(processed_text) > self.config.get("max_chunk_size", 1000):
chunks = self.chunk_persian_text(
processed_text,
chunk_size=self.config.get("chunk_size", 500),
overlap=self.config.get("chunk_overlap", 50)
)
else:
chunks = [processed_text]
# اضافه کردن به collection
for i, chunk in enumerate(chunks):
chunk_metadata = metadata.copy() if metadata else {}
chunk_metadata["chunk_index"] = i
chunk_metadata["total_chunks"] = len(chunks)
self.collection.add(
documents=[chunk],
metadatas=[chunk_metadata],
ids=[f"{metadata.get('doc_id', 'doc')}_{i}"]
)
def answer_question(self, question: str, n_results: int = 5) -> Dict:
"""پاسخ به سوال با استفاده از RAG"""
# پیشپردازش سوال
processed_question = self.advanced_preprocess(question)
# بازیابی اسناد مرتبط
results = self.retrieve_relevant_docs(processed_question, n_results)
# ایجاد context
context = self.create_context(results)
# تولید پاسخ
answer = self.generate_answer_with_llm(processed_question, context)
return {
"question": question,
"answer": answer,
"sources": results["documents"],
"metadata": results["metadatas"],
"confidence": self.calculate_confidence(results["distances"])
}
def create_context(self, results: Dict) -> str:
"""ایجاد context از نتایج بازیابی شده"""
contexts = []
for i, doc in enumerate(results["documents"][0]):
metadata = results["metadatas"][0][i]
source = metadata.get("source", "ناشناس")
contexts.append(f"منبع {i+1} ({source}):\n{doc}")
return "\n\n".join(contexts)
def calculate_confidence(self, distances: List[List[float]]) -> float:
"""محاسبه سطح اطمینان بر اساس فاصلهها"""
if not distances or not distances[0]:
return 0.0
# تبدیل فاصله به امتیاز اطمینان
avg_distance = sum(distances[0]) / len(distances[0])
confidence = max(0, 1 - avg_distance)
return round(confidence, 2)
def export_knowledge_base(self, output_path: str):
"""خروجی گرفتن از پایگاه دانش"""
# دریافت تمام اسناد
all_docs = self.collection.get()
# ذخیره در فایل JSON
with open(output_path, 'w', encoding='utf-8') as f:
json.dump({
"documents": all_docs["documents"],
"metadatas": all_docs["metadatas"],
"ids": all_docs["ids"],
"total_documents": len(all_docs["documents"])
}, f, ensure_ascii=False, indent=2)
print(f"پایگاه دانش در {output_path} ذخیره شد.")
کاربردهای عملی
1. چتبات پشتیبانی مشتری
# مثال: چتبات پشتیبانی برای فروشگاه آنلاین
customer_support_rag = PersianRAGSystem("customer_support_config.json")
# اضافه کردن اطلاعات محصولات و قوانین
products_info = [
{
"text": "محصول X دارای گارانتی 18 ماهه است و در رنگهای مشکی، سفید و نقرهای موجود میباشد.",
"metadata": {"category": "product", "product_id": "X001"}
},
{
"text": "زمان ارسال سفارشات برای تهران 1 روز کاری و برای شهرستانها 3 روز کاری است.",
"metadata": {"category": "shipping", "type": "policy"}
}
]
for doc in products_info:
customer_support_rag.add_document(doc["text"], doc["metadata"])
# پاسخ به سوال مشتری
response = customer_support_rag.answer_question(
"محصول X چه رنگهایی دارد و گارانتی آن چقدر است؟"
)
2. دستیار هوشمند اسناد سازمانی
# مثال: سیستم پرسش و پاسخ برای اسناد داخلی شرکت
corporate_rag = PersianRAGSystem("corporate_config.json")
# بارگذاری اسناد PDF
import PyPDF2
def load_pdf_to_rag(pdf_path, rag_system):
with open(pdf_path, 'rb') as file:
pdf_reader = PyPDF2.PdfReader(file)
for page_num, page in enumerate(pdf_reader.pages):
text = page.extract_text()
if text.strip():
rag_system.add_document(
text,
metadata={
"source": pdf_path,
"page": page_num + 1,
"doc_type": "policy_document"
}
)
# بارگذاری چندین سند
policy_documents = ["policies/hr_policy.pdf", "policies/it_policy.pdf"]
for doc_path in policy_documents:
load_pdf_to_rag(doc_path, corporate_rag)
3. سیستم آموزشی تعاملی
# مثال: دستیار آموزشی برای دانشجویان
education_rag = PersianRAGSystem("education_config.json")
# اضافه کردن محتوای آموزشی
course_materials = [
{
"text": "الگوریتم مرتبسازی حبابی یکی از سادهترین الگوریتمهای مرتبسازی است که عناصر مجاور را مقایسه و در صورت نیاز جابجا میکند.",
"metadata": {"course": "ساختمان داده", "topic": "مرتبسازی"}
}
]
# ایجاد رابط کاربری ساده
def educational_assistant():
print("دستیار آموزشی هوشمند - برای خروج 'exit' را تایپ کنید")
while True:
question = input("\nسوال شما: ")
if question.lower() == 'exit':
break
response = education_rag.answer_question(question)
print(f"\nپاسخ: {response['answer']}")
print(f"سطح اطمینان: {response['confidence']*100:.1f}%")
if response['sources']:
print("\nمنابع:")
for source in response['sources']:
print(f"- {source[:100]}...")
نتیجهگیری و منابع بیشتر
در این مقاله، مراحل ایجاد یک سیستم پرسش و پاسخ فارسی با استفاده از تکنولوژی RAG را بررسی کردیم. سیستمهای RAG ابزاری قدرتمند برای بهبود عملکرد مدلهای زبان بزرگ (LLMs) در زمینههای مختلف مانند پرسش و پاسخ، تولید متن، خلاصهسازی و ترجمه هستند.
نکات کلیدی:
- اهمیت پیشپردازش: برای زبان فارسی، استفاده از ابزارهای تخصصی مانند Hazm ضروری است
- انتخاب مدل Embedding مناسب: ParsBERT و مدلهای جدیدتر مانند Hakim نتایج بهتری ارائه میدهند
- بهینهسازی مداوم: با fine-tuning و تنظیم پارامترها میتوان دقت سیستم را افزایش داد
منابع برای مطالعه بیشتر:
- مستندات رسمی کتابخانهها:
- مقالات علمی:
- مقاله ParsBERT برای درک عمیقتر مدلهای BERT فارسی
- مقالات جدید در زمینه Persian NLP
- پروژههای متنباز:
- Awesome Persian NLP
- نمونههای کد در GitHub
گامهای بعدی:
- آزمایش با مدلهای مختلف: امتحان مدلهای جدیدتر embedding و LLM های فارسی
- ایجاد دیتاستهای تخصصی: برای دامنههای خاص، ایجاد دیتاستهای اختصاصی
- بهبود رابط کاربری: ایجاد رابطهای کاربری وب یا موبایل برای سیستم
با پیشرفتهای سریع در حوزه هوش مصنوعی و پردازش زبان طبیعی، آینده سیستمهای RAG فارسی بسیار روشن است. با ادامه تحقیقات و نوآوریها در این زمینه، میتوان انتظار داشت که شاهد کاربردهای وسیعتر و شگفتانگیزتر RAG در آینده باشیم.
