النماذج اللغوية الكبيرة (LLMs) مثل GPT-4 و Claude قوية بشكل استثنائي، لكنها تعاني من قيد أساسي: معرفتها مجمدة في وقت التدريب. لا يمكنها الوصول إلى مستنداتك الداخلية أو قاعدة بياناتك أو المعلومات في الوقت الفعلي. التوليد المعزز بالاسترجاع (RAG) يحل هذه المشكلة تحديداً من خلال الجمع بين القوة التوليدية لنماذج LLM والقدرة على استرجاع المعلومات من مصادر خارجية.
المشكلة: حدود نماذج LLM
قبل الحديث عن RAG، من المهم فهم لماذا نحتاجها.
- المعرفة الثابتة: نموذج LLM يعرف فقط ما رآه أثناء التدريب. إذا سألته عن حدث وقع بعد تاريخ قطعه، لا يمكنه الإجابة.
- الهلوسات: عندما لا يعرف نموذج LLM الإجابة، يميل إلى اختلاقها، مولداً معلومات تبدو معقولة لكنها خاطئة تماماً.
- عدم الوصول للبيانات الخاصة: نموذج LLM العام لا يمكنه الوصول إلى وثائق شركتك الداخلية أو التذاكر أو قاعدة الكود.
يعالج RAG هذه المشاكل الثلاث من خلال تزويد النموذج بـسياق ذي صلة مسترجع من مصادر خارجية في وقت الاستعلام.
ما هو RAG؟
التوليد المعزز بالاسترجاع هو بنية تثري الموجه المرسل إلى LLM بمعلومات مسترجعة من قاعدة معرفة خارجية. بدلاً من الاعتماد فقط على المعرفة البارامترية للنموذج، يقوم RAG أولاً بـالبحث عن المعلومات ذات الصلة ثم حقنها في الموجه، مما يمكّن النموذج من توليد إجابات دقيقة ومؤسسة.
كيف يعمل RAG بالتفصيل
تتكون بنية RAG من مرحلتين رئيسيتين: الفهرسة (غير متصلة) والاسترجاع + التوليد (متصلة).
المرحلة 1: الفهرسة (استيعاب المستندات)
تُعد مرحلة الفهرسة مستنداتك للبحث الدلالي. تتكون من أربع خطوات.
1. تحميل المستندات
يمكن أن تأتي المستندات من أي مصدر: ملفات PDF، صفحات ويب، قواعد بيانات، ملفات Markdown، واجهات برمجة التطبيقات. يقوم محمّل المستندات بقراءة هذه المستندات وتحويلها إلى نص منظم.
2. تقسيم النص (Chunking)
نماذج LLM لديها نافذة سياق محدودة، والمستندات يمكن أن تكون طويلة جداً. يقسم مقسّم النصوص المستندات إلى أجزاء أصغر تسمى chunks. جودة التقسيم حاسمة: الأجزاء الصغيرة جداً تفقد السياق، والأجزاء الكبيرة جداً تخفف الصلة.
الاستراتيجيات الأكثر شيوعاً هي:
- التقسيم التكراري بالأحرف: يقسم النص بشكل تكراري باستخدام فواصل مثل
\n\n،\n،.، مع احترام بنية المستند. - التقسيم الدلالي: يستخدم التضمينات لإيجاد نقاط الانقطاع الطبيعية في النص.
- تداخل الأجزاء: يتضمن تداخلاً بين الأجزاء المتتالية للحفاظ على السياق عند الحدود.
3. التضمين
يتم تحويل كل جزء إلى متجه رقمي (تضمين) عبر نموذج تضمين (مثل text-embedding-3-small من OpenAI). تلتقط هذه المتجهات المعنى الدلالي للنص: الجمل ذات المعاني المتشابهة سيكون لها متجهات قريبة في الفضاء متعدد الأبعاد.
4. مخزن المتجهات
تُحفظ المتجهات في مخزن متجهات (أو قاعدة بيانات متجهية)، مثل ChromaDB أو Pinecone أو Weaviate أو FAISS. قاعدة البيانات هذه محسّنة لـالبحث بالتشابه: بإعطاء استعلام، تجد المتجهات الأكثر تشابهاً (وبالتالي أجزاء النص الأكثر صلة).
المرحلة 2: الاسترجاع + التوليد
عندما يطرح المستخدم سؤالاً:
- يتم تحويل السؤال إلى تضمين باستخدام نفس نموذج التضمين.
- يجد مخزن المتجهات الأجزاء الأكثر تشابهاً عبر البحث بالتشابه (عادةً تشابه جيب التمام أو المسافة الإقليدية).
- يتم إدراج الأجزاء المسترجعة في الموجه كسياق.
- يولد نموذج LLM الإجابة بناءً على السياق المقدم.
بناء أنبوب RAG باستخدام LangChain
LangChain هو إطار عمل Python (و JavaScript) الأكثر شعبية لبناء تطبيقات تعتمد على LLM. يوفر تجريدات عالية المستوى لكل مكون من مكونات أنبوب RAG.
التثبيت
pip install langchain langchain-openai langchain-community chromadb
الخطوة 1: تحميل المستندات
يوفر LangChain عشرات محمّلات المستندات لمصادر بيانات مختلفة.
from langchain_community.document_loaders import ( PyPDFLoader, WebBaseLoader, DirectoryLoader, TextLoader, ) # تحميل ملف PDF pdf_loader = PyPDFLoader("docs/manual.pdf") pdf_docs = pdf_loader.load() # تحميل صفحة ويب web_loader = WebBaseLoader("https://docs.example.com/guide") web_docs = web_loader.load() # تحميل جميع ملفات .md من مجلد dir_loader = DirectoryLoader("./knowledge_base", glob="**/*.md", loader_cls=TextLoader) md_docs = dir_loader.load() all_docs = pdf_docs + web_docs + md_docs
الخطوة 2: تقسيم المستندات إلى أجزاء
from langchain.text_splitter import RecursiveCharacterTextSplitter text_splitter = RecursiveCharacterTextSplitter( chunk_size=1000, chunk_overlap=200, separators=["\n\n", "\n", ". ", " ", ""], ) chunks = text_splitter.split_documents(all_docs) print(f"المستندات الأصلية: {len(all_docs)}, الأجزاء: {len(chunks)}")
معامل chunk_overlap حاسم: يُنشئ تداخلاً بين الأجزاء المتتالية حتى لا يُفقد السياق عند الحدود.
الخطوة 3: إنشاء التضمينات ومخزن المتجهات
from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import Chroma embedding_model = OpenAIEmbeddings(model="text-embedding-3-small") vectorstore = Chroma.from_documents( documents=chunks, embedding=embedding_model, persist_directory="./chroma_db", )
الخطوة 4: إنشاء المسترجع
المسترجع هو المكون الذي، بإعطاء استعلام، يسترجع الأجزاء الأكثر صلة من مخزن المتجهات.
retriever = vectorstore.as_retriever( search_type="similarity", search_kwargs={"k": 4}, ) relevant_docs = retriever.invoke("كيف يعمل المصادقة؟") for doc in relevant_docs: print(doc.page_content[:200]) print("---")
الخطوة 5: بناء سلسلة RAG
الآن نجمع كل شيء مع LLM وقالب موجه.
from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from langchain_core.runnables import RunnablePassthrough from langchain_core.output_parsers import StrOutputParser llm = ChatOpenAI(model="gpt-4o", temperature=0) prompt = ChatPromptTemplate.from_template(""" أجب على السؤال بناءً على السياق المقدم فقط. إذا لم يحتوِ السياق على معلومات كافية، قل أنك لا تعرف. السياق: {context} السؤال: {question} الإجابة: """) def format_docs(docs): return "\n\n".join(doc.page_content for doc in docs) rag_chain = ( {"context": retriever | format_docs, "question": RunnablePassthrough()} | prompt | llm | StrOutputParser() ) response = rag_chain.invoke("كيف تعمل المصادقة في النظام؟") print(response)
تقنيات RAG المتقدمة
يعمل الأنبوب الأساسي بشكل جيد، لكن هناك عدة تقنيات لتحسين جودة الإجابات بشكل كبير.
الاسترجاع متعدد الاستعلامات
أحياناً يكون استعلام المستخدم غامضاً أو غير متوافق مع اللغة المستخدمة في المستندات. المسترجع متعدد الاستعلامات يولد تلقائياً متغيرات من السؤال الأصلي لالتقاط وجهات نظر متعددة.
from langchain.retrievers import MultiQueryRetriever multi_retriever = MultiQueryRetriever.from_llm( retriever=vectorstore.as_retriever(), llm=llm, ) docs = multi_retriever.invoke("ما هي أفضل الممارسات الأمنية؟")
الضغط السياقي
ليس كل محتوى الجزء ذا صلة بالاستعلام. مسترجع الضغط السياقي يستخدم LLM لاستخراج الأجزاء ذات الصلة فقط من كل جزء مسترجع.
from langchain.retrievers import ContextualCompressionRetriever from langchain.retrievers.document_compressors import LLMChainExtractor compressor = LLMChainExtractor.from_llm(llm) compression_retriever = ContextualCompressionRetriever( base_compressor=compressor, base_retriever=retriever, )
البحث الهجين
البحث الدلالي البحت ليس دائماً الأمثل. البحث الهجين يجمع بين البحث الدلالي (التضمينات) والبحث المعجمي (BM25، مطابقة الكلمات المفتاحية) للحصول على نتائج أفضل.
from langchain.retrievers import EnsembleRetriever from langchain_community.retrievers import BM25Retriever bm25_retriever = BM25Retriever.from_documents(chunks) bm25_retriever.k = 4 semantic_retriever = vectorstore.as_retriever(search_kwargs={"k": 4}) hybrid_retriever = EnsembleRetriever( retrievers=[bm25_retriever, semantic_retriever], weights=[0.4, 0.6], )
RAG المحادثي (مع الذاكرة)
لبناء روبوت محادثة RAG يتذكر سياق المحادثة، تحتاج إلى إضافة ذاكرة تعيد صياغة أسئلة المستخدم مع مراعاة سجل المحادثة.
from langchain.chains import create_history_aware_retriever from langchain_core.prompts import MessagesPlaceholder contextualize_prompt = ChatPromptTemplate.from_messages([ ("system", "بالنظر إلى سجل المحادثة وآخر سؤال للمستخدم، " "أعد صياغة السؤال بحيث يكون مفهوماً بدون السجل."), MessagesPlaceholder("chat_history"), ("human", "{input}"), ]) history_aware_retriever = create_history_aware_retriever( llm, retriever, contextualize_prompt )
أفضل الممارسات
- اختر حجم الجزء المناسب: جرب أحجاماً مختلفة (500-1500 رمز). أجزاء أصغر للإجابات الدقيقة، أكبر للسياق الواسع.
- استخدم بيانات وصفية للمستندات: أضف المصدر والتاريخ والفئة كبيانات وصفية للأجزاء. هذا يسمح بتصفية النتائج أثناء الاسترجاع.
- قيّم الجودة: استخدم أطر عمل مثل RAGAS لقياس مقاييس مثل الأمانة، الصلة ودقة السياق.
- أدر تحديثات المستندات: نفّذ أنبوب إعادة استيعاب للحفاظ على مخزن المتجهات متزامناً مع مصادر بياناتك.
- أضف مُعيد ترتيب: بعد الاسترجاع الأولي، استخدم نموذج إعادة ترتيب (مثل Cohere Rerank) لإعادة ترتيب النتائج بناءً على الصلة الفعلية.
الخلاصة
أصبح RAG البنية المعيارية لبناء تطبيقات الذكاء الاصطناعي التي تحتاج إلى الوصول لمعرفة محددة ومحدثة. يُبسط LangChain التنفيذ بشكل كبير من خلال توفير تجريدات لكل مكون من مكونات الأنبوب.
الخطوات التالية:
- جرب محلياً: ابدأ مع ChromaDB وبضعة مستندات للتعرف على الأنبوب.
- استكشف LangSmith: استخدم LangSmith لمراقبة وتصحيح سلاسلك في الإنتاج.
- جرب نماذج تضمين مختلفة: قارن نماذج مثل
text-embedding-3-smallوtext-embedding-3-largeونماذج مفتوحة المصدر من Sentence Transformers. - راجع التوثيق: توثيق LangChain مورد ممتاز ومحدث باستمرار.