الدرس 16 من 23

تقييم واختبار RAG

مقاييس تقييم RAG

3 دقيقة للقراءة

تقييم أنظمة RAG يتطلب مقاييس تتجاوز دقة التعلم الآلي التقليدية. تحتاج لقياس جودة الاسترجاع وأمانة التوليد معاً.

تحدي تقييم RAG

المقاييس التقليدية لا تلتقط إخفاقات RAG المحددة:

# المقاييس التقليدية تفتقد مشاكل حرجة:

# السيناريو 1: درجة BLEU عالية، لكن حقائق مُهلوَسة
generated = "الشركة تأسست في 2015 بواسطة جون سميث"
reference = "الشركة تأسست في 2015 بواسطة جين سميث"
# BLEU: 0.85 (يبدو جيداً!)
# الواقع: اسم مؤسس خاطئ (خطأ حرج)

# السيناريو 2: درجة BLEU منخفضة، لكن صحيحة واقعياً
generated = "جين سميث أسست العمل في 2015"
reference = "الشركة تأسست في 2015 بواسطة جين سميث"
# BLEU: 0.42 (يبدو سيئاً!)
# الواقع: نفس الحقائق، صياغة مختلفة (مقبول)

التقييم المبني على المكونات

أنظمة RAG لديها ثلاثة مكونات للتقييم:

┌─────────────────────────────────────────────────────────────┐
│                    إطار تقييم RAG                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐     │
│  │   جودة     │───▶│   جودة     │───▶│   جودة     │     │
│  │ الاسترجاع  │    │  السياق    │    │  التوليد   │     │
│  └─────────────┘    └─────────────┘    └─────────────┘     │
│        │                  │                  │              │
│        ▼                  ▼                  ▼              │
│  • استدعاء السياق    • الصلة          • الأمانة           │
│  • دقة السياق       • نسبة الضوضاء    • صلة الإجابة       │
│  • MRR, NDCG       • التغطية         • الصحة             │
│                                                             │
└─────────────────────────────────────────────────────────────┘

مقاييس الاسترجاع

دقة السياق

تقيس إذا كانت المستندات المسترجعة ذات صلة:

def context_precision(retrieved_contexts: list, relevant_contexts: list) -> float:
    """
    ما نسبة السياقات المسترجعة ذات الصلة فعلاً؟

    دقة عالية = مستندات غير ذات صلة قليلة مسترجعة
    دقة منخفضة = مستندات كثيرة غير ذات صلة تلوث السياق
    """
    relevant_retrieved = set(retrieved_contexts) & set(relevant_contexts)

    if not retrieved_contexts:
        return 0.0

    return len(relevant_retrieved) / len(retrieved_contexts)

# مثال
retrieved = ["doc1", "doc2", "doc3", "doc4", "doc5"]
relevant = ["doc1", "doc3", "doc7"]

precision = context_precision(retrieved, relevant)
# النتيجة: 2/5 = 0.4 (فقط 2 من 5 مستندات مسترجعة ذات صلة)

استدعاء السياق

يقيس إذا تم استرجاع كل المعلومات ذات الصلة:

def context_recall(retrieved_contexts: list, relevant_contexts: list) -> float:
    """
    ما نسبة السياقات ذات الصلة التي تم استرجاعها؟

    استدعاء عالي = كل المعلومات ذات الصلة وُجدت
    استدعاء منخفض = سياق مهم مفقود
    """
    relevant_retrieved = set(retrieved_contexts) & set(relevant_contexts)

    if not relevant_contexts:
        return 1.0

    return len(relevant_retrieved) / len(relevant_contexts)

# مثال
retrieved = ["doc1", "doc2", "doc3", "doc4", "doc5"]
relevant = ["doc1", "doc3", "doc7"]

recall = context_recall(retrieved, relevant)
# النتيجة: 2/3 = 0.67 (استرجع 2 من 3 مستندات ذات صلة، فقد doc7)

متوسط الرتبة المتبادلة (MRR)

يقيس مدى ارتفاع رتبة أول نتيجة ذات صلة:

def mean_reciprocal_rank(queries_results: list[list], relevant_docs: list[set]) -> float:
    """
    متوسط 1/الرتبة لأول نتيجة ذات صلة لكل استعلام.

    MRR = 1.0 يعني النتيجة الأولى دائماً ذات صلة
    MRR = 0.5 يعني أول نتيجة ذات صلة عادةً في الرتبة 2
    """
    reciprocal_ranks = []

    for results, relevant in zip(queries_results, relevant_docs):
        for rank, doc in enumerate(results, 1):
            if doc in relevant:
                reciprocal_ranks.append(1 / rank)
                break
        else:
            reciprocal_ranks.append(0)

    return sum(reciprocal_ranks) / len(reciprocal_ranks)

# مثال
query_results = [
    ["doc2", "doc1", "doc3"],  # استعلام 1: doc1 ذات الصلة في الرتبة 2
    ["doc5", "doc6", "doc7"],  # استعلام 2: لا مستندات ذات صلة
    ["doc8", "doc9", "doc4"],  # استعلام 3: doc4 ذات الصلة في الرتبة 3
]
relevant = [{"doc1"}, {"doc10"}, {"doc4"}]

mrr = mean_reciprocal_rank(query_results, relevant)
# النتيجة: (1/2 + 0 + 1/3) / 3 = 0.278

مقاييس التوليد

الأمانة

تقيس إذا كانت الإجابة مؤسسة على السياق المسترجع:

def assess_faithfulness(answer: str, context: str) -> dict:
    """
    الأمانة تتحقق إذا كان كل ادعاء في الإجابة
    يمكن التحقق منه من السياق المسترجع.

    تستخدم نهج LLM كحَكَم.
    """
    # الخطوة 1: استخراج الادعاءات من الإجابة
    claims_prompt = f"""
    استخرج كل الادعاءات الواقعية من هذه الإجابة:
    الإجابة: {answer}

    اذكر كل ادعاء في سطر جديد.
    """

    # الخطوة 2: التحقق من كل ادعاء مقابل السياق
    verify_prompt = f"""
    لكل ادعاء، حدد إذا كان يمكن التحقق منه من السياق.

    السياق: {context}
    الادعاءات: {claims}

    لكل ادعاء، أجب بـ:
    - مدعوم: الادعاء مدعوم مباشرة من السياق
    - غير_مدعوم: الادعاء لا يمكن التحقق منه من السياق
    """

    # الخطوة 3: حساب درجة الأمانة
    # الأمانة = الادعاءات_المدعومة / إجمالي_الادعاءات

    return {
        "score": supported_claims / total_claims,
        "unsupported_claims": unsupported_list
    }

# مثال للمخرج
# {
#     "score": 0.75,  # 3 من 4 ادعاءات مدعومة
#     "unsupported_claims": ["الشركة لديها 500 موظف"]
# }

صلة الإجابة

تقيس إذا كانت الإجابة تعالج السؤال:

def assess_answer_relevancy(question: str, answer: str) -> float:
    """
    صلة الإجابة تتحقق إذا كانت الإجابة فعلاً
    تعالج ما سُئل.

    النهج: توليد أسئلة من الإجابة،
    مقارنة التشابه الدلالي مع السؤال الأصلي.
    """
    # توليد أسئلة ستعالجها الإجابة
    generated_questions = generate_questions_from_answer(answer, n=3)

    # مقارنة كل سؤال مولد مع الأصلي
    similarities = []
    for gen_q in generated_questions:
        sim = cosine_similarity(
            embed(question),
            embed(gen_q)
        )
        similarities.append(sim)

    return sum(similarities) / len(similarities)

# مثال
question = "ما هي عاصمة فرنسا؟"
answer = "باريس هي عاصمة فرنسا، تقع على نهر السين."

# الأسئلة المولدة من الإجابة:
# - "ما هي عاصمة فرنسا؟"
# - "أين تقع باريس؟"
# - "أي نهر يمر عبر باريس؟"

# التشابه مع الأصلي: [0.95, 0.3, 0.2]
# درجة الصلة: 0.48

دليل اختيار المقاييس

المقياس يقيس استخدم عندما
دقة السياق دقة الاسترجاع لديك تسميات حقيقة أرضية
استدعاء السياق تغطية الاسترجاع المعلومات المفقودة تسبب فشلاً
MRR جودة الترتيب النتائج الأولى مهمة أكثر
الأمانة منع الهلوسة الدقة حرجة
صلة الإجابة جودة الاستجابة الإجابات تبدو خارج الموضوع

التسجيل المجمع

def rag_quality_score(
    context_precision: float,
    context_recall: float,
    faithfulness: float,
    answer_relevancy: float,
    weights: dict = None
) -> float:
    """
    مزيج موزون من مقاييس RAG.
    اضبط الأوزان بناءً على أولوياتك.
    """
    weights = weights or {
        "context_precision": 0.2,
        "context_recall": 0.2,
        "faithfulness": 0.4,  # عادةً الأهم
        "answer_relevancy": 0.2
    }

    score = (
        weights["context_precision"] * context_precision +
        weights["context_recall"] * context_recall +
        weights["faithfulness"] * faithfulness +
        weights["answer_relevancy"] * answer_relevancy
    )

    return score

# مثال
quality = rag_quality_score(
    context_precision=0.8,
    context_recall=0.7,
    faithfulness=0.9,
    answer_relevancy=0.85
)
# النتيجة: 0.2*0.8 + 0.2*0.7 + 0.4*0.9 + 0.2*0.85 = 0.83

رؤية رئيسية: الأمانة عادةً هي المقياس الأكثر أهمية لأنظمة RAG الإنتاجية. المستخدمون يمكنهم تحمل إجابات خارج الموضوع قليلاً، لكن الحقائق المُهلوَسة تدمر الثقة.

التالي، لننفذ هذه المقاييس باستخدام إطار RAGAS. :::

اختبار

الوحدة 5: تقييم واختبار RAG

خذ الاختبار