مقدمة: لماذا أصبح RAG حجر الأساس في تطبيقات الذكاء الاصطناعي؟
إذا كنت تعمل في مجال الذكاء الاصطناعي التطبيقي، فمن شبه المؤكد أنك صادفت مصطلح RAG — اختصاراً لـ Retrieval-Augmented Generation (التوليد المعزّز بالاسترجاع). هذه التقنية أصبحت في 2026 المعيار الفعلي لبناء تطبيقات ذكاء اصطناعي تعتمد على بيانات محدّثة ودقيقة، بدلاً من الاعتماد الحصري على ما حفظه النموذج اللغوي أثناء تدريبه.
الفكرة الجوهرية بسيطة لكنها — بصراحة — ثورية: بدلاً من أن تطلب من النموذج اللغوي أن يتذكر كل شيء (وهو أمر مستحيل عملياً بالطبع)، تجلب له المعلومات ذات الصلة من مصادر خارجية وقت الاستعلام، ثم تطلب منه صياغة إجابة بناءً عليها. النتيجة؟ إجابات أدق، أحدث، وأقل عرضة للهلوسة.
لكن هناك فجوة كبيرة بين نظام RAG بسيط يعمل على جهازك المحلي وبين نظام إنتاجي موثوق يخدم آلاف المستخدمين يومياً. في هذه المقالة، سنعبر هذه الفجوة خطوة بخطوة — من فهم القيود الأساسية، مروراً باستراتيجيات التقسيم المتقدمة وقواعد البيانات المتجهية، وصولاً إلى RAG الوكيلي وGraphRAG ومنهجيات التقييم.
هيا نبدأ.
أنظمة RAG البسيطة: نقطة البداية وحدودها
كيف يعمل RAG الأساسي؟
لنبدأ من الأساس. نظام RAG في أبسط صوره يتكون من ثلاث مراحل:
- الفهرسة (Indexing): تقسيم المستندات إلى قطع نصية، تحويلها إلى متجهات (Embeddings)، وتخزينها في قاعدة بيانات متجهية.
- الاسترجاع (Retrieval): عند استلام استعلام المستخدم، يُحوَّل إلى متجه ويُبحث عن أقرب القطع في قاعدة البيانات.
- التوليد (Generation): تُمرَّر القطع المسترجعة مع الاستعلام الأصلي إلى النموذج اللغوي لتوليد الإجابة.
إليك مثالاً عملياً باستخدام LangChain:
from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_chroma import Chroma
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
# 1. تحميل المستند وتقسيمه
loader = PyPDFLoader("company_docs.pdf")
docs = loader.load()
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200
)
chunks = splitter.split_documents(docs)
# 2. إنشاء المتجهات وتخزينها
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
vectorstore = Chroma.from_documents(chunks, embeddings)
retriever = vectorstore.as_retriever(search_kwargs={"k": 5})
# 3. بناء سلسلة RAG
template = """أجب على السؤال بناءً على السياق التالي فقط:
السياق: {context}
السؤال: {question}
الإجابة:"""
prompt = ChatPromptTemplate.from_template(template)
llm = ChatOpenAI(model="gpt-4o", temperature=0)
chain = (
{"context": retriever, "question": RunnablePassthrough()}
| prompt
| llm
)
response = chain.invoke("ما هي سياسة الإجازات في الشركة؟")
هذا الكود يعمل بلا مشاكل — لكن لحظة، لا تتسرع في نشره على الإنتاج. هناك قيود جوهرية ستواجهك.
خمس مشاكل أساسية في RAG البسيط
بعد تجارب كثيرة في نشر أنظمة RAG إنتاجية، هذه المشاكل تظهر مراراً وتكراراً:
- مقايضة الدقة والاستدعاء: إذا استرجعت عدداً قليلاً من القطع (مثلاً 3) حصلت على دقة عالية لكن قد تفوتك معلومات مهمة. وإذا استرجعت عدداً كبيراً (مثلاً 20) زاد الاستدعاء لكنك أدخلت ضوضاء تُربك النموذج. لا يوجد حل سحري هنا.
- الاستعلامات متعددة الخطوات: سؤال مثل "قارن بين سياسة الإجازات في 2024 و2025 وأخبرني بالتغييرات" يحتاج استرجاعاً من مصدرين مختلفين ثم مقارنة بينهما — وهو ما لا يستطيعه RAG البسيط بتمريرة واحدة.
- فقدان السياق الهيكلي: تقسيم المستندات يكسر العلاقات بين الأقسام. جدول بيانات يُقسَّم إلى أجزاء يفقد معناه تماماً.
- عدم القدرة على التحقق الذاتي: النظام لا يعرف إن كانت القطع المسترجعة كافية أو ذات صلة فعلية — يولّد إجابة بغض النظر عن جودة ما استرجعه (وهذا مخيف بصراحة).
- هشاشة صياغة الاستعلام: تغيير بسيط في صياغة السؤال قد يؤدي إلى نتائج مختلفة جذرياً. مطابقة المتجهات حساسة للصياغة أكثر مما تتصور.
استراتيجيات التقسيم المتقدمة: أساس كل نظام RAG ناجح
دعني أشاركك حقيقة تعلّمها كل من بنى نظام RAG إنتاجي بالطريقة الصعبة: جودة التقسيم (Chunking) هي العامل الأهم في أداء النظام بأكمله. حتى أفضل نموذج استرجاع في العالم لن ينقذك إذا كانت القطع النصية سيئة الإعداد.
الأبحاث الحديثة تؤكد ذلك — التقسيم التراجعي مع تداخل بنسبة 10-20% يحقق دقة استرجاع أعلى بنسبة 30-50% مقارنةً بالتقسيم الثابت البسيط. فارق ضخم من مجرد تغيير طريقة التقسيم.
1. التقسيم الثابت الحجم (Fixed-Size Chunking)
أبسط الطرق على الإطلاق: قسّم النص كل N حرف مع تداخل اختياري. سريع وسهل التنفيذ، لكنه يكسر الجمل والفقرات بشكل عشوائي وبلا رحمة.
from langchain_text_splitters import CharacterTextSplitter
splitter = CharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separator="\n"
)
chunks = splitter.split_text(document_text)
متى تستخدمه: للنماذج الأولية السريعة فقط، أو عندما تكون المستندات بسيطة ومتجانسة البنية.
2. التقسيم التراجعي (Recursive Chunking)
هذا هو الخيار الافتراضي الذي أنصح به لمعظم الحالات. يحاول التقسيم عند حدود طبيعية — فقرات أولاً، ثم جمل، ثم كلمات — مع الحفاظ على حجم أقصى للقطعة.
from langchain_text_splitters import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separators=["\n\n", "\n", ". ", " ", ""],
length_function=len
)
chunks = splitter.split_documents(documents)
السر هنا في قائمة الفواصل المُرتبة: يحاول التقسيم عند فقرة جديدة أولاً، فإن لم يكفِ ينتقل لنهاية الجملة، وهكذا. هذا يحافظ على التماسك الدلالي بشكل أفضل بكثير من التقسيم الأعمى.
3. التقسيم الدلالي (Semantic Chunking)
هنا الأمور تصبح أكثر ذكاءً. هذه الطريقة تستخدم نماذج التضمين ذاتها لتحديد أين تنتهي كل قطعة. تحسب التشابه بين الجمل المتتالية، وتضع حدّ القطعة عندما ينخفض التشابه بشكل ملحوظ — أي عند تغيّر الموضوع فعلياً.
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
semantic_splitter = SemanticChunker(
embeddings,
breakpoint_threshold_type="percentile",
breakpoint_threshold_amount=90
)
semantic_chunks = semantic_splitter.split_documents(documents)
المعامل breakpoint_threshold_amount=90 يعني أن القطع تنفصل عندما ينخفض التشابه الدلالي إلى أقل من المئين التسعيني. يمكنك ضبط هذا الرقم حسب طبيعة مستنداتك — جرّب وقِس.
متى تستخدمه: للمستندات الطويلة متعددة المواضيع، أو عندما يكون فصل المواضيع بدقة أمراً حاسماً.
4. التقسيم الواعي بالبنية (Structure-Aware Chunking)
للمستندات المهيكلة (HTML، Markdown، كود برمجي)، لماذا لا نستغل البنية نفسها؟
from langchain_text_splitters import MarkdownHeaderTextSplitter
headers_to_split = [
("#", "header_1"),
("##", "header_2"),
("###", "header_3"),
]
markdown_splitter = MarkdownHeaderTextSplitter(
headers_to_split_on=headers_to_split
)
# كل قطعة تحتفظ بالعناوين كبيانات وصفية
structured_chunks = markdown_splitter.split_text(markdown_document)
الميزة الكبرى هنا أن كل قطعة تحمل العناوين الرئيسية كبيانات وصفية (metadata)، مما يتيح تصفية نتائج الاسترجاع بناءً على أقسام محددة. مفيد جداً عندما تعرف مسبقاً أي قسم يهم المستخدم.
5. التقسيم بالنماذج اللغوية (LLM-Based Chunking)
أحدث الأساليب وأكثرها دقة — لكنها الأبطأ والأغلى ثمناً. تستخدم نموذجاً لغوياً لفهم المحتوى وتحديد أين يجب أن تنتهي كل قطعة:
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
chunking_prompt = ChatPromptTemplate.from_template("""
حلل النص التالي وقسّمه إلى وحدات موضوعية مستقلة.
كل وحدة يجب أن تكون متماسكة دلالياً وتتناول موضوعاً واحداً.
أعد القطع مفصولة بـ "===CHUNK===".
النص:
{text}
""")
chain = chunking_prompt | llm
result = chain.invoke({"text": long_document})
llm_chunks = result.content.split("===CHUNK===")
متى تستخدمه: فقط للمستندات عالية القيمة حيث جودة القطع تبرر التكلفة الإضافية — مثل العقود القانونية أو الأبحاث الطبية. لا تستخدمه لآلاف الصفحات العادية.
قواعد البيانات المتجهية: اختيار المحرك المناسب
قاعدة البيانات المتجهية هي القلب النابض لأي نظام RAG. هي التي تُخزّن المتجهات وتُجري عمليات البحث بالتشابه عليها. واختيار القاعدة المناسبة يؤثر مباشرة على الأداء والتكلفة وقابلية التوسع.
مقارنة بين أبرز الخيارات في 2026
Pinecone: خدمة سحابية مُدارة بالكامل. الخيار الأنسب إذا كنت تريد البدء سريعاً دون صداع إدارة البنية التحتية. يدعم التصفية بالبيانات الوصفية والتحديثات الفورية، مع زمن استجابة أقل من 50 مللي ثانية لمعظم الاستعلامات.
Chroma: مفتوح المصدر وخفيف. يعمل محلياً أو كخادم مستقل. مثالي للنماذج الأولية والمشاريع الصغيرة والمتوسطة.
Weaviate: مفتوح المصدر مع بحث هجين (متجهي + نصي كامل). يدعم التعامل مع أنواع بيانات متعددة (نصوص، صور، فيديو). خيار ممتاز إذا كنت تحتاج مرونة في أنماط البحث.
Qdrant: مفتوح المصدر ومكتوب بـ Rust — مما يعني أداءً عالياً جداً. يدعم التصفية المتقدمة والتحديث الفوري. خيار مفضّل للأنظمة التي تحتاج سرعة فائقة.
pgvector: إضافة لـ PostgreSQL. الخيار الأمثل إذا كنت تستخدم PostgreSQL أصلاً ولا تريد إضافة خدمة جديدة لبنيتك. حل عملي وبسيط.
تحسين أداء البحث المتجهي
بغض النظر عن القاعدة التي اخترتها، هذه ممارسات أساسية لتحسين الأداء:
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
collection_metadata={
"hnsw:space": "cosine", # مقياس التشابه
"hnsw:M": 32, # عدد الروابط لكل عقدة
"hnsw:construction_ef": 200, # دقة بناء الفهرس
"hnsw:search_ef": 100 # دقة البحث
}
)
# البحث الهجين: متجهي + تصفية بالبيانات الوصفية
results = vectorstore.similarity_search(
query="سياسة العمل عن بُعد",
k=5,
filter={"department": "human_resources", "year": 2026}
)
المعاملات M وef تتحكم في التوازن بين الدقة والسرعة في فهارس HNSW. نصيحتي: ابدأ بالقيم الافتراضية، ثم اضبطها بناءً على قياسات فعلية. استهدف زمن استرجاع أقل من 100 مللي ثانية مع معدل استدعاء أعلى من 95%.
إعادة الترتيب باستخدام Cross-Encoders
هذه تقنية لا يعرفها كثيرون لكنها تُحدث فرقاً كبيراً: استرجع عدداً أكبر من القطع (مثلاً 20) بالبحث المتجهي السريع، ثم أعد ترتيبها باستخدام نموذج Cross-Encoder أبطأ لكن أدق بكثير:
from langchain.retrievers import ContextualCompressionRetriever
from langchain_cohere import CohereRerank
# المسترجع الأساسي يجلب 20 نتيجة
base_retriever = vectorstore.as_retriever(search_kwargs={"k": 20})
# إعادة الترتيب تختار أفضل 5
reranker = CohereRerank(model="rerank-v3.5", top_n=5)
compression_retriever = ContextualCompressionRetriever(
base_compressor=reranker,
base_retriever=base_retriever
)
# النتائج الآن أدق بكثير
results = compression_retriever.invoke("ما هي متطلبات الترقية؟")
هذا النهج ذو المرحلتين يجمع بين سرعة البحث المتجهي ودقة Cross-Encoder. من تجربتي الشخصية، التحسّن في الدقة يكون ملحوظاً بشكل خاص في المجالات المتخصصة كالقانون والطب.
RAG المتقدم: تقنيات ما بعد الأساسيات
إعادة كتابة الاستعلام (Query Rewriting)
لنكن صريحين: استعلامات المستخدمين نادراً ما تكون مثالية للبحث المتجهي. الناس يكتبون أسئلتهم بطريقة محادثاتية عامية، والبحث المتجهي يحتاج صياغات أكثر تحديداً. الحل؟ أعد كتابة الاستعلام قبل البحث:
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
query_rewriter = ChatOpenAI(model="gpt-4o-mini", temperature=0)
rewrite_prompt = ChatPromptTemplate.from_template("""
أعد صياغة السؤال التالي ليكون أكثر ملاءمة للبحث في قاعدة معرفية.
أنتج 3 صياغات بديلة مفصولة بسطر جديد.
السؤال الأصلي: {question}
الصياغات البديلة:
""")
rewrite_chain = rewrite_prompt | query_rewriter
# البحث بكل الصياغات وجمع النتائج
alternative_queries = rewrite_chain.invoke(
{"question": "كيف أترقى هنا؟"}
)
# النتيجة قد تكون:
# 1. ما هي معايير الترقية الوظيفية في الشركة؟
# 2. ما هي خطوات التقدم المهني والمسار الوظيفي؟
# 3. ما هي متطلبات وشروط الحصول على ترقية؟
لاحظ كيف تحوّل سؤال غامض مثل "كيف أترقى هنا؟" إلى ثلاث صياغات محددة ودقيقة. هذا وحده يمكن أن يحسّن جودة الاسترجاع بشكل ملموس.
الاسترجاع الأصلي-الفرعي (Parent-Child Retrieval)
هذه فكرة ذكية حقاً تحل معضلة حجم القطع: استخدم قطعاً صغيرة للاسترجاع الدقيق، لكن أعد القطعة الأم الأكبر كسياق للنموذج. أفضل ما في العالمين!
from langchain.retrievers import ParentDocumentRetriever
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain.storage import InMemoryStore
# قطع صغيرة للبحث
child_splitter = RecursiveCharacterTextSplitter(chunk_size=400)
# قطع كبيرة للسياق
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=2000)
store = InMemoryStore()
parent_retriever = ParentDocumentRetriever(
vectorstore=vectorstore,
docstore=store,
child_splitter=child_splitter,
parent_splitter=parent_splitter,
)
# الفهرسة تخزّن كلا المستويين
parent_retriever.add_documents(documents)
# البحث يجد القطعة الصغيرة الأدق ثم يعيد الأم الأكبر
results = parent_retriever.invoke("شروط الإجازة المرضية")
بهذا النمط تحصل على دقة القطع الصغيرة في الاسترجاع مع شمولية السياق الكبير في التوليد. بسيط المفهوم، قوي التأثير.
GraphRAG: عندما لا يكفي البحث المتجهي وحده
من أهم التطورات في مجال RAG خلال الفترة الأخيرة هو GraphRAG — أي دمج الرسوم البيانية المعرفية (Knowledge Graphs) مع أنظمة RAG. الفكرة ببساطة أن البحث المتجهي وحده يعجز عندما تحتاج الإجابة إلى فهم العلاقات بين كيانات مختلفة.
لماذا GraphRAG؟
تخيل سؤالاً مثل: "مَن هم المديرون الذين أداروا مشاريع بقيمة تتجاوز مليون دولار في آخر سنتين وما هي الفرق التي عملوا معها؟"
هذا السؤال يتطلب:
- ربط كيانات (مديرون) بعلاقات (أداروا) وكيانات أخرى (مشاريع)
- تصفية بشروط (قيمة > مليون، آخر سنتين)
- تتبع علاقات إضافية (الفرق المرتبطة)
البحث المتجهي سيحاول إيجاد قطع نصية تشبه صياغة السؤال — لكنه ببساطة لن يستطيع تتبع هذه الشبكة من العلاقات. GraphRAG يحل المشكلة بجعل العلاقات صريحة ومفهرسة وقابلة للاستعلام المباشر.
بنية GraphRAG الأساسية
نظام GraphRAG يجمع بين مكونين أساسيين:
- رسم بياني معرفي: يُستخلص من المستندات باستخدام نماذج لغوية لاستخراج الكيانات والعلاقات بينها
- قاعدة بيانات متجهية: للبحث التقليدي بالتشابه الدلالي
عند الاستعلام، يُستخدم كلا المصدرين معاً: البحث المتجهي للعثور على القطع ذات الصلة، والرسم البياني لتتبع العلاقات بين الكيانات.
from langchain_community.graphs import Neo4jGraph
from langchain_openai import ChatOpenAI
from langchain.chains import GraphCypherQAChain
# الاتصال بقاعدة بيانات الرسم البياني
graph = Neo4jGraph(
url="bolt://localhost:7687",
username="neo4j",
password="password"
)
# استخراج الكيانات والعلاقات من المستندات
extraction_llm = ChatOpenAI(model="gpt-4o", temperature=0)
# بناء سلسلة استعلام الرسم البياني
graph_chain = GraphCypherQAChain.from_llm(
llm=extraction_llm,
graph=graph,
verbose=True,
validate_cypher=True,
return_intermediate_steps=True
)
# استعلام يستغل العلاقات المهيكلة
result = graph_chain.invoke({
"query": "ما هي المشاريع التي أدارها أحمد وما هي ميزانياتها؟"
})
الاسترجاع الهجين: أقوى من كليهما منفردين
أقوى أنظمة RAG في 2026 تجمع بين الاستراتيجيتين: البحث المتجهي للعثور على المعلومات ذات الصلة الموضوعية، والرسم البياني لاستخراج العلاقات والكيانات المتصلة. تُدمج النتائج وتُمرر للنموذج اللغوي.
الأبحاث تؤكد أن هذا النهج الهجين يتفوق على كل أسلوب بمفرده، خاصة في الأسئلة التي تتطلب استدلالاً متعدد الخطوات. إذا كانت بياناتك غنية بالعلاقات، فهذا هو الطريق الصحيح.
RAG الوكيلي: مستقبل أنظمة الاسترجاع الذكية
والآن نصل إلى الجزء الأكثر إثارة في رأيي. RAG الوكيلي (Agentic RAG) يحوّل نظام RAG من خط أنابيب ثابت ومحدود إلى نظام ذكي يتخذ قراراته بنفسه.
ما الذي يجعل RAG وكيلياً؟
بدلاً من التدفق الجامد "استعلام ← استرجاع ← توليد"، النظام الوكيلي يستطيع:
- التخطيط: تحليل الاستعلام وتقرير ما إذا كان يحتاج استرجاعاً أصلاً
- التحلل: تقسيم الأسئلة المعقدة إلى استعلامات فرعية مستقلة
- التقييم الذاتي: فحص جودة النتائج المسترجعة — هل هي كافية أم تحتاج جولة أخرى؟
- إعادة الاستعلام: تعديل الصياغة وإعادة البحث تلقائياً إذا كانت النتائج ضعيفة
- التوليف: دمج معلومات من مصادر متعددة في إجابة متماسكة
بناء نظام RAG وكيلي باستخدام LangGraph
إليك مثالاً عملياً متكاملاً. لاحظ كيف يحتوي على حلقة تقييم وإعادة محاولة — وهذا ما يجعله "وكيلياً" فعلاً:
from langgraph.graph import START, END, StateGraph
from typing_extensions import TypedDict
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_chroma import Chroma
class RAGState(TypedDict):
question: str
rewritten_query: str
retrieved_docs: list
relevance_score: float
answer: str
iteration: int
llm = ChatOpenAI(model="gpt-4o", temperature=0)
vectorstore = Chroma(
persist_directory="./chroma_db",
embedding_function=OpenAIEmbeddings()
)
def analyze_query(state: RAGState):
"""تحليل الاستعلام وتحديد استراتيجية الاسترجاع"""
response = llm.invoke(
f"أعد صياغة هذا السؤال ليكون أفضل للبحث: {state['question']}"
)
return {
"rewritten_query": response.content,
"iteration": state.get("iteration", 0)
}
def retrieve(state: RAGState):
"""استرجاع المستندات ذات الصلة"""
query = state.get("rewritten_query", state["question"])
docs = vectorstore.similarity_search_with_score(query, k=5)
return {
"retrieved_docs": [doc for doc, _ in docs],
"relevance_score": sum(s for _, s in docs) / len(docs)
}
def evaluate_relevance(state: RAGState):
"""تقييم جودة النتائج المسترجعة"""
docs_text = "\n".join([d.page_content for d in state["retrieved_docs"]])
response = llm.invoke(
f"""قيّم مدى ملاءمة هذه المستندات للإجابة على السؤال.
السؤال: {state['question']}
المستندات: {docs_text}
أجب بـ 'كافية' أو 'غير كافية' فقط."""
)
if "كافية" in response.content and state["iteration"] < 3:
return {"relevance_score": 0.9}
return {"relevance_score": 0.3, "iteration": state["iteration"] + 1}
def should_retry(state: RAGState):
"""قرار: هل نعيد المحاولة أم ننتقل للتوليد؟"""
if state["relevance_score"] > 0.5 or state["iteration"] >= 3:
return "generate"
return "rewrite"
def rewrite_query(state: RAGState):
"""إعادة صياغة الاستعلام بناءً على النتائج السابقة"""
response = llm.invoke(
f"""الاستعلام السابق لم يعطِ نتائج كافية.
السؤال الأصلي: {state['question']}
الاستعلام المُعاد: {state['rewritten_query']}
أعد صياغة الاستعلام بطريقة مختلفة تماماً."""
)
return {"rewritten_query": response.content}
def generate(state: RAGState):
"""توليد الإجابة النهائية"""
context = "\n\n".join([d.page_content for d in state["retrieved_docs"]])
response = llm.invoke(
f"""أجب على السؤال بناءً على السياق التالي.
إذا لم يكن السياق كافياً، اذكر ذلك بوضوح.
السياق: {context}
السؤال: {state['question']}"""
)
return {"answer": response.content}
# بناء الرسم البياني
graph = StateGraph(RAGState)
graph.add_node("analyze", analyze_query)
graph.add_node("retrieve", retrieve)
graph.add_node("evaluate", evaluate_relevance)
graph.add_node("rewrite", rewrite_query)
graph.add_node("generate", generate)
graph.add_edge(START, "analyze")
graph.add_edge("analyze", "retrieve")
graph.add_edge("retrieve", "evaluate")
graph.add_conditional_edges("evaluate", should_retry, {
"generate": "generate",
"rewrite": "rewrite"
})
graph.add_edge("rewrite", "retrieve")
graph.add_edge("generate", END)
app = graph.compile()
result = app.invoke({"question": "ما هي سياسة العمل عن بُعد الجديدة؟"})
إذا لم تكن النتائج المسترجعة كافية، يُعاد صياغة الاستعلام ويُعاد البحث — حتى 3 محاولات. هذا السلوك "التأملي" هو ما يميز RAG الوكيلي عن RAG التقليدي بشكل جوهري.
أنماط RAG الوكيلي المتخصصة
الأبحاث الحديثة تُصنّف عدة أنماط:
- وكيل التوجيه (Routing Agent): يحلل الاستعلام ويوجّهه لخط الأنابيب الأنسب. أسئلة الحقائق تذهب لـ RAG الكثيف، وأسئلة العلاقات تذهب لـ GraphRAG.
- وكيل التخطيط أحادي الخطوة: يفكك السؤال المعقد إلى استعلامات فرعية ينفّذها دفعة واحدة، ثم يجمع النتائج.
- وكيل التفكير التكراري: ينفّذ خطوات استرجاع متعاقبة، حيث نتائج كل خطوة توجّه الخطوة التالية. مثالي للأسئلة التي تتطلب عدة "قفزات" للوصول للإجابة.
المثير في الأمر أن حتى الأنماط الوكيلية المبسّطة تتفوق على أنظمة RAG التقليدية المعقدة في عدة معايير قياسية. هذا يعني أن منح النموذج حرية أكبر في اتخاذ قرارات الاسترجاع غالباً أفضل من تصميم خوارزميات ثابتة مهما كانت متقدمة.
تقييم أنظمة RAG: كيف تعرف أن نظامك يعمل فعلاً؟
بناء نظام RAG شيء، والتأكد من أنه يعمل بالجودة المطلوبة شيء آخر تماماً. بدون تقييم منهجي، أنت حرفياً تعمل في الظلام.
إطار RAGAS أصبح المعيار الفعلي لهذه المهمة، مع أكثر من 400,000 تحميل شهري.
المقاييس الأربعة الأساسية
RAGAS يُقيّم أربعة أبعاد جوهرية:
- الأمانة (Faithfulness): هل الإجابة متسقة مع المعلومات المسترجعة؟ أم أن النموذج "هلوس" وأضاف من عنده؟
- ملاءمة الإجابة (Answer Relevancy): هل الإجابة تعالج السؤال المطروح فعلاً؟
- دقة السياق (Context Precision): ما نسبة القطع المسترجعة ذات الصلة الفعلية؟
- استدعاء السياق (Context Recall): هل تم استرجاع كل المعلومات اللازمة للإجابة الصحيحة؟
from ragas import evaluate
from ragas.metrics import (
faithfulness,
answer_relevancy,
context_precision,
context_recall
)
from datasets import Dataset
# تجهيز بيانات التقييم
eval_data = {
"question": [
"ما هي سياسة الإجازات؟",
"كيف أقدم طلب ترقية؟"
],
"answer": [
"يحق لكل موظف 21 يوم إجازة سنوية...",
"يمكنك تقديم طلب ترقية عبر نظام HR..."
],
"contexts": [
[
"سياسة الإجازات: يحق لكل موظف 21 يوم إجازة سنوية مدفوعة..."
],
[
"للترقية: قم بتسجيل الدخول لنظام HR واختر تقديم طلب ترقية..."
]
],
"ground_truth": [
"21 يوم إجازة سنوية مدفوعة لكل موظف",
"تقديم طلب الترقية يتم عبر نظام HR الإلكتروني"
]
}
dataset = Dataset.from_dict(eval_data)
results = evaluate(
dataset,
metrics=[
faithfulness,
answer_relevancy,
context_precision,
context_recall
]
)
print(results)
# {'faithfulness': 0.95, 'answer_relevancy': 0.88,
# 'context_precision': 0.90, 'context_recall': 0.85}
التقييم المستمر في الإنتاج
في بيئة الإنتاج، التقييم ليس شيئاً تفعله مرة واحدة ثم تنساه. هو عملية مستمرة. أفضل ممارسة في 2026 هي الجمع بين عدة طبقات:
- RAGAS أو DeepEval لحساب المقاييس الأساسية تلقائياً على كل استعلام (أو عيّنة منها)
- Langfuse أو LangSmith لتتبع كل استعلام وردّه في الإنتاج، مع مراقبة التكلفة وزمن الاستجابة
- مراجعة بشرية دورية لعيّنة عشوائية من الاستعلامات — لاكتشاف المشاكل التي لا تلتقطها المقاييس الآلية
الأرقام التي يجب أن تستهدفها: أمانة أعلى من 90%، ملاءمة أعلى من 85%، وزمن استجابة أقل من ثانيتين.
قائمة مرجعية: قبل نشر نظامك في الإنتاج
قبل أن تضغط زر النشر، راجع هذه القائمة:
- التقسيم: اختبرت استراتيجية التقسيم على عيّنة تمثيلية وتأكدت أن القطع متماسكة دلالياً
- نموذج التضمين: اخترت نموذجاً مناسباً للغة مستنداتك (مثلاً text-embedding-3-large لدعم ممتاز للعربية)
- إعادة الترتيب: أضفت طبقة Cross-Encoder إذا كانت الدقة حاسمة في حالتك
- التقييم: بنيت مجموعة اختبار تمثيلية مع معايير قبول واضحة
- المراقبة: ربطت أدوات تتبع لمراقبة الأداء والتكلفة والجودة
- التحديث: وضعت آلية لتحديث قاعدة المعرفة عند إضافة أو تعديل المستندات
- الحدود: حددت بوضوح ما يستطيع النظام الإجابة عليه وما لا يستطيع
- الاحتياطي: صممت استجابة مناسبة عندما لا يجد النظام معلومات كافية — بدلاً من الهلوسة
الخلاصة: خارطة طريقك العملية
بناء نظام RAG إنتاجي في 2026 ليس مسألة اختيار تقنية واحدة "سحرية" — بل هو تجميع نظام متكامل من عدة طبقات، كل واحدة تخدم هدفاً محدداً.
إليك الخطوات بالترتيب:
- ابدأ بسيطاً: نظام RAG أساسي مع تقسيم تراجعي وChroma. اختبره واجمع ملاحظات حقيقية من مستخدمين حقيقيين.
- حسّن التقسيم: جرّب التقسيم الدلالي أو الواعي بالبنية. قِس التحسن فعلياً باستخدام RAGAS — لا تعتمد على الحدس.
- أضف إعادة الترتيب: طبقة Cross-Encoder. تحسّن كبير بجهد قليل نسبياً.
- أضف إعادة كتابة الاستعلام: لمعالجة التنوع في طريقة صياغة الأسئلة.
- انتقل للوكيلي: عندما تحتاج معالجة أسئلة معقدة متعددة الخطوات.
- أضف GraphRAG: فقط إذا كانت بياناتك غنية بالعلاقات والكيانات المترابطة.
وتذكّر دائماً: أفضل نظام RAG هو الذي يحل مشكلة مستخدميك الفعلية بأبسط بنية ممكنة. لا تبالغ في التعقيد مبكراً. ابنِ، قِس، وحسّن بناءً على بيانات فعلية. المجال يتطور بسرعة مذهلة، ومن يبني أساساً متيناً اليوم سيكون الأقدر على استيعاب ما هو قادم.