يا جماعة الخير، خلوني أحكيلكم قصة صارت معي ومع فريقي قبل كم شهر. كنا شغالين على مشروع كبير، بنبني مساعد ذكي لشركة ضخمة عندها آلاف الوثائق الداخلية: سياسات موارد بشرية، أدلة فنية، تقارير سنوية… إشي من كل لون وشكل. الهدف كان بسيط: الموظف يسأل الشات بوت أي سؤال، والبوت يجاوبه فوراً بدل ما يضيع ساعات وهو يبحث في الملفات.
في البداية، استخدمنا واحد من النماذج اللغوية الكبيرة (LLM) المشهورة. عملنا شوية تعديلات وبدأنا الاختبارات الأولية. أول كم يوم كنا طايرين من الفرحة، النموذج كان ذكي وبيفهم الأسئلة المعقدة. لكن… الكارثة بلشت تبين شوي شوي.
موظف يسأل: “ما هي سياسة الإجازة المرضية الطارئة؟” النموذج يخترع سياسة جديدة من راسه! يعطي عدد أيام غلط وشروط مش موجودة أصلاً. مهندس يسأل عن كود معين في دليل فني، النموذج يعطيه دالة مش موجودة ويحلف إنها الحل السحري. صار النموذج “يهلوس” أو “يهذي” (Hallucination)، زي ما بنحكي بالمصطلح التقني. صار يحكي من راسه، زي كأنه شارب فنجان قهوة زيادة وبده يفتي بكل إشي. الإدارة العليا كانت على وشك تلغي المشروع كله، والوضع كان متأزم جداً.
في عز اليأس، لمعت في بالنا فكرة: “ليش نعتمد على ذاكرة النموذج المحدودة والمُدربة مسبقاً؟ ليش ما نخليه يعمل زينا إحنا البشر لما ننسى إشي؟ يفتح الكتاب ويدور على المعلومة قبل ما يحكي!”. ومن هنا بدأت رحلتنا مع المنقذ: التوليد المعزز بالاسترجاع أو الـ RAG. وهذه هي القصة اللي بدي أحكيلكم إياها اليوم.
شو قصة “هلوسة” الذكاء الاصطناعي؟
قبل ما نغوص في الحل، لازم نفهم أصل المشكلة. النماذج اللغوية الكبيرة، مثل GPT-4 وغيرها، هي بالآخر عبارة عن محركات إحصائية عملاقة للتنبؤ بالكلمات. هي لا “تفهم” العالم مثلنا، بل تعلمت من كميات هائلة من نصوص الإنترنت كيف تكمل الجمل بطريقة منطقية ومقنعة.
المشكلة بتصير لما تسألها عن معلومة مش موجودة في بيانات تدريبها، أو معلومة متخصصة جداً (مثل سياسات شركتك الداخلية). هنا، النموذج بوقع في حيرة. وبدل ما يحكيلك “ما بعرف”، بحاول “يرضيك” عن طريق توليد إجابة تبدو مقنعة إحصائياً، لكنها في الحقيقة مجرد تخمين إبداعي… أو كذبة جميلة. هذه هي الهلوسة.
الحل القديم: الضبط الدقيق (Fine-Tuning) وليش ما كان كافي
أول إشي فكرنا فيه كان الـ Fine-tuning. يعني ناخذ النموذج الأساسي و”نعيد تدريبه” على وثائقنا الخاصة. هذا الأسلوب مفيد لتحسين أسلوب النموذج أو تعليمه مصطلحات معينة. لكن بالنسبة لمشكلتنا، كان له عيوب قاتلة:
- مكلف جداً: إعادة تدريب نموذج عملاق تحتاج قوة حاسوبية هائلة ووقت طويل.
- المعلومات بتصير قديمة: لو تغيرت سياسة واحدة في الشركة، لازم نعيد التدريب من جديد. هذا إشي مش عملي بالمرة.
- لا يمنع الهلوسة تماماً: حتى بعد التدريب، النموذج ممكن يظل يهلوس لو سألته سؤال بطريقة غير متوقعة.
كنا محتاجين حل أذكى، أكثر ديناميكية، وأقل تكلفة. حل يخلي النموذج “يقرأ” المستندات الحقيقية قبل كل إجابة.
المنقذ RAG: التوليد المعزز بالاسترجاع
وهنا يأتي دور بطل قصتنا، الـ RAG. فكرته بسيطة بشكل عبقري، وتقدر تشبهها تماماً بـ “امتحان الكتاب المفتوح” (Open-book exam) للذكاء الاصطناعي.
إيش الفكرة بالضبط؟
بدل ما نطلب من النموذج إنه يجاوب من “ذاكرته” (بيانات تدريبه)، إحنا بنقوم بالخطوات التالية قبل ما نخليه يكتب حرف واحد:
- الاسترجاع (Retrieval): لما المستخدم يسأل سؤال، أول إشي بنعمله هو البحث في قاعدة بياناتنا المعرفية (وثائق الشركة في حالتنا) عن أكثر المعلومات صلة بالسؤال.
- التعزيز (Augmentation): بناخذ المعلومات اللي لقيناها وبنضيفها لسؤال المستخدم الأصلي.
- التوليد (Generation): بنقدم هذا “الطلب المعزز” (السؤال + المعلومات المسترجعة) للنموذج اللغوي، وبنطلب منه يجاوب على السؤال بالاعتماد على السياق اللي أعطيناه إياه فقط.
بهذه الطريقة، إحنا بنجبر النموذج على الاعتماد على مصدر الحقيقة (وثائقنا) بدل الاعتماد على ذاكرته العامة اللي ممكن تكون خاطئة أو غير محدثة.
آلية عمل RAG خطوة بخطوة
عشان الصورة تكون أوضح، خلينا نفصل العملية لمراحل تقنية أكثر. في مرحلتين أساسيتين: مرحلة التجهيز (مرة واحدة)، ومرحلة الاستعلام (مع كل سؤال).
المرحلة الأولى: الفهرسة والتجهيز (Indexing)
هذه المرحلة بنعملها مرة واحدة في البداية، وكل ما نضيف وثائق جديدة بنعيدها.
- تقطيع المستندات (Chunking): بنمسك كل مستنداتنا (PDFs, DOCX, etc.) وبنقطعها لقطع صغيرة ومنطقية (chunks). كل قطعة ممكن تكون فقرة أو بضع جمل.
- الترميز وتحويلها لمتجهات (Embedding): بنستخدم نموذج embedding خاص (مثل نماذج Sentence-Transformers) عشان نحول كل قطعة نصية إلى متجه رقمي (vector). هذا المتجه هو تمثيل رياضي لمعنى النص.
- التخزين في قاعدة بيانات متجهات (Vector Database): بنخزن كل هذه المتجهات النصية في قاعدة بيانات متخصصة مثل ChromaDB, Pinecone, أو FAISS. هذه القاعدة بتسمحلنا نبحث بسرعة عن المتجهات المتشابهة.
المرحلة الثانية: الاسترجاع والتوليد (مع كل سؤال)
- المستخدم يسأل سؤال: مثلاً “كم عدد أيام الإجازة السنوية؟”.
- نحول السؤال إلى متجه رقمي باستخدام نفس نموذج الـ embedding.
- نبحث في قاعدة البيانات عن المتجهات (القطع النصية) الأكثر تشابهاً لمتجه السؤال. هذا هو “الاسترجاع”.
- نأخذ أفضل 3-5 قطع نصية وجدناها ونبني “موجه الأوامر” (Prompt) النهائي.
- نرسل هذا الموجه للنموذج اللغوي الكبير (LLM).
مثال على الموجه النهائي الذي يراه الـ LLM:
“أجب على السؤال التالي بناءً على السياق المرفق فقط. إذا كانت الإجابة غير موجودة في السياق، قل ‘لا أعرف’.— السياق —
القطعة النصية 1: ‘…وفقاً للمادة 5 من قانون العمل، يستحق الموظف الذي أكمل سنة من الخدمة إجازة سنوية مدتها 21 يوماً…’
القطعة النصية 2: ‘…تزيد الإجازة السنوية إلى 30 يوماً للموظفين الذين تجاوزت مدة خدمتهم عشر سنوات…’
— نهاية السياق —السؤال: كم عدد أيام الإجازة السنوية؟”
الآن، النموذج عنده كل المعلومات اللي بيحتاجها عشان يعطي إجابة دقيقة ومبنية على مصدر موثوق، بدل ما “يهلوس”.
يلا نبرمج: مثال بسيط لـ RAG باستخدام Python
الحكي النظري حلو، بس خلينا نشوف كود. هذا مثال مبسط جداً باستخدام مكتبات `langchain` و `chromadb` و `sentence-transformers` عشان نوضح الفكرة.
# First, install the necessary libraries
# pip install langchain langchain-community langchain-openai sentence-transformers chromadb
from langchain_community.document_loaders import TextLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings.sentence_transformer import (
SentenceTransformerEmbeddings,
)
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
# --- 1. Indexing Phase ---
# Imagine this is our internal document
document_text = """
سياسة الإجازات في شركة 'تقنية بلا حدود'
المادة 1: الإجازة السنوية
يستحق كل موظف إجازة سنوية مدفوعة الأجر مدتها 25 يوم عمل بعد إكمال السنة الأولى من الخدمة.
المادة 2: الإجازة المرضية
يُمنح الموظف إجازة مرضية تصل إلى 14 يوماً في السنة بناءً على تقرير طبي معتمد.
المادة 3: إجازة الأبوة
يستحق الأب إجازة مدتها 5 أيام عمل عند ولادة طفل جديد.
"""
# Save it to a temporary file to simulate loading
with open("policy.txt", "w", encoding="utf-8") as f:
f.write(document_text)
# Load the document
loader = TextLoader("policy.txt", encoding="utf-8")
documents = loader.load()
# Split the document into chunks
text_splitter = CharacterTextSplitter(chunk_size=300, chunk_overlap=0)
docs = text_splitter.split_documents(documents)
# Create the embedding model
embedding_function = SentenceTransformerEmbeddings(model_name="all-MiniLM-L6-v2")
# Load it into ChromaDB (our vector store)
vectorstore = Chroma.from_documents(docs, embedding_function)
# --- 2. Retrieval and Generation Phase ---
# Create a retriever which finds relevant documents
retriever = vectorstore.as_retriever()
# Define the prompt template
template = """
Answer the question based only on the following context:
{context}
Question: {question}
"""
prompt = ChatPromptTemplate.from_template(template)
# Define the LLM model (you need an OpenAI API key for this)
model = ChatOpenAI()
# Create the RAG chain
rag_chain = (
{"context": retriever, "question": RunnablePassthrough()}
| prompt
| model
| StrOutputParser()
)
# Now, let's ask a question!
question = "كم يوم إجازة الأبوة في الشركة؟"
response = rag_chain.invoke(question)
print(f"السؤال: {question}")
print(f"الإجابة: {response}")
# Expected Output: الإجابة: يستحق الأب إجازة مدتها 5 أيام عمل عند ولادة طفل جديد.
# Ask another question
question_2 = "ما هي سياسة الشركة بخصوص إجازة الحج؟"
response_2 = rag_chain.invoke(question_2)
print(f"السؤال: {question_2}")
print(f"الإجابة: {response_2}")
# The model might say it doesn't know, which is the correct behavior!
نصيحة من أبو عمر:
الكود اللي فوق هو مثال بسيط. في المشاريع الحقيقية، الموضوع بصير أعقد. بدك تهتم بجودة تقطيع النصوص (Chunking Strategy)، واختيار نموذج الـ embedding المناسب، وتحسين أداء الـ retriever. كل خطوة من هدول هي علم بحد ذاتها وبتحتاج تجربة وقياس.
ليش الـ RAG غير قواعد اللعبة؟
- دقة وموثوقية: قلل الهلوسة بشكل كبير جداً. الإجابات صارت مبنية على حقائق موثقة.
- تحديث فوري للمعلومات: إذا تغيرت وثيقة، كل اللي علينا نعمله هو تحديثها في قاعدة البيانات وإعادة فهرستها. لا حاجة لإعادة تدريب النموذج بالكامل.
- الشفافية: نقدر بسهولة نعرض للمستخدم المصدر اللي جبنا منه الإجابة (أي قطعة نصية استخدمناها)، وهذا بيزيد الثقة في النظام.
- فعالية من حيث التكلفة: أرخص بكثير من الـ Fine-tuning المستمر لنماذج عملاقة.
الخلاصة: من جحيم الهلوسة إلى أرض الثقة 🚀
في النهاية، تقنية RAG ما كانت مجرد حل تقني لمشكلتنا، بل كانت نقلة نوعية في طريقة تفكيرنا في بناء تطبيقات الذكاء الاصطناعي. حولت النماذج اللغوية من “مبدعين” لا يمكن التنبؤ بهم إلى “باحثين” دقيقين ومساعدين موثوقين.
إذا كنت تبني أي تطبيق يعتمد على نموذج لغوي كبير للإجابة على أسئلة بناءً على مجموعة محددة من المعرفة (سواء كانت وثائق شركة, قاعدة بيانات منتجات, أو حتى كتب علمية)، فأنصحك بشدة أن تضع RAG في قلب تصميمك. هي الجسر الذي ينقلنا من الإعجاب بقدرات الذكاء الاصطناعي إلى الثقة الحقيقية في مخرجاته.
بالآخر، زي ما بحكي المثل، “اسأل مجرّب ولا تسأل حكيم”. وأنا كمجرّب، بحكيلكم إن الـ RAG هو طوق النجاة اللي كنا بندور عليه. يلا شدوا حيلكم وجربوها!