الدرس 17 من 23

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

إطار RAGAS

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

RAGAS (تقييم التوليد المعزز بالاسترجاع) هو الإطار القياسي لتقييم خطوط أنابيب RAG. يوفر تقييماً آلياً بدون مرجع باستخدام النماذج اللغوية الكبيرة.

لماذا RAGAS؟

┌────────────────────────────────────────────────────────────┐
│                    التقييم التقليدي                         │
├────────────────────────────────────────────────────────────┤
│  • يتطلب حقيقة أرضية موسومة بشرياً                        │
│  • مكلف ويستغرق وقتاً                                      │
│  • صعب التوسيع                                             │
│  • مجموعات الاختبار الثابتة تصبح قديمة                     │
└────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────┐
│                    تقييم RAGAS                              │
├────────────────────────────────────────────────────────────┤
│  • يستخدم LLMs لتقييم مخرجات LLM                           │
│  • بدون مرجع (لا حاجة لحقيقة أرضية)                        │
│  • قابل للتوسيع وآلي                                       │
│  • يقيم أبعاداً متعددة                                     │
└────────────────────────────────────────────────────────────┘

التثبيت والإعداد

pip install ragas langchain-openai
import os
from ragas import evaluate
from ragas.metrics import (
    faithfulness,
    answer_relevancy,
    context_precision,
    context_recall,
)
from datasets import Dataset

# إعداد مزود LLM الخاص بك
os.environ["OPENAI_API_KEY"] = "your-key"

مقاييس RAGAS الأساسية

1. الأمانة

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

from ragas.metrics import faithfulness

# كيف يعمل:
# 1. استخراج العبارات من الإجابة
# 2. لكل عبارة، تحقق إذا كان السياق يدعمها
# 3. الدرجة = العبارات_المدعومة / إجمالي_العبارات

# تفسير الدرجة:
# 1.0 = كل ادعاء في الإجابة مدعوم بالسياق
# 0.5 = نصف الادعاءات غير مدعومة (هلوسات)
# 0.0 = الإجابة ملفقة بالكامل

2. صلة الإجابة

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

from ragas.metrics import answer_relevancy

# كيف يعمل:
# 1. توليد N سؤال من الإجابة
# 2. حساب تشابه جيب التمام مع السؤال الأصلي
# 3. الدرجة = متوسط التشابه

# تفسير الدرجة:
# 1.0 = الإجابة تعالج السؤال بشكل مثالي
# 0.5 = الإجابة ذات صلة جزئياً
# 0.0 = الإجابة خارج الموضوع تماماً

3. دقة السياق

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

from ragas.metrics import context_precision

# كيف يعمل:
# 1. لكل قطعة سياق، حدد إذا كانت ذات صلة
# 2. وزن حسب الموضع (الأبكر = أهم)
# 3. الدرجة = الدقة الموزونة

# تفسير الدرجة:
# 1.0 = كل السياقات المسترجعة ذات صلة
# 0.5 = نصف السياقات ضوضاء
# 0.0 = لا سياقات ذات صلة مسترجعة

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

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

from ragas.metrics import context_recall

# يتطلب إجابة الحقيقة الأرضية
# كيف يعمل:
# 1. استخراج الادعاءات من إجابة الحقيقة الأرضية
# 2. تحقق إذا كان كل ادعاء يُنسب للسياقات
# 3. الدرجة = الادعاءات_المنسوبة / إجمالي_الادعاءات

# تفسير الدرجة:
# 1.0 = كل المعلومات المطلوبة في السياق
# 0.5 = نصف المعلومات الضرورية مفقود
# 0.0 = لا شيء من المعلومات المطلوبة مسترجع

تشغيل تقييم RAGAS

from ragas import evaluate
from ragas.metrics import (
    faithfulness,
    answer_relevancy,
    context_precision,
    context_recall,
)
from datasets import Dataset

# إعداد بيانات التقييم
eval_data = {
    "question": [
        "ما هي عاصمة فرنسا؟",
        "من كتب روميو وجولييت؟",
        "ما هو التمثيل الضوئي؟",
    ],
    "answer": [
        "باريس هي عاصمة فرنسا.",
        "وليام شكسبير كتب روميو وجولييت.",
        "التمثيل الضوئي هو كيف تحول النباتات ضوء الشمس إلى طاقة.",
    ],
    "contexts": [
        ["باريس هي عاصمة وأكبر مدينة في فرنسا."],
        ["روميو وجولييت مأساة لوليام شكسبير."],
        ["التمثيل الضوئي عملية تستخدمها النباتات لتحويل الضوء إلى طاقة كيميائية."],
    ],
    "ground_truth": [
        "عاصمة فرنسا هي باريس.",
        "وليام شكسبير كتب روميو وجولييت.",
        "التمثيل الضوئي هو العملية التي تستخدمها النباتات لتحويل ضوء الشمس إلى غذاء.",
    ],
}

# إنشاء مجموعة البيانات
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.92,
#     'context_precision': 0.88,
#     'context_recall': 0.90
# }

تقييم خط أنابيب RAG الخاص بك

from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy, context_precision
from datasets import Dataset

# خط أنابيب RAG الخاص بك
def run_rag(question: str) -> dict:
    """تشغيل RAG وإرجاع النتائج بتنسيق RAGAS."""

    # الاسترجاع
    docs = vectorstore.similarity_search(question, k=5)
    contexts = [doc.page_content for doc in docs]

    # التوليد
    prompt = f"""أجب بناءً على السياق:
    السياق: {contexts}
    السؤال: {question}
    """
    answer = llm.invoke(prompt).content

    return {
        "question": question,
        "answer": answer,
        "contexts": contexts,
    }

# أسئلة التقييم
test_questions = [
    "كيف أعيد تعيين كلمة المرور؟",
    "ما هي مستويات التسعير؟",
    "كيف أتواصل مع الدعم؟",
]

# تشغيل RAG على كل الأسئلة
results = [run_rag(q) for q in test_questions]

# التنسيق لـ RAGAS
eval_dataset = Dataset.from_dict({
    "question": [r["question"] for r in results],
    "answer": [r["answer"] for r in results],
    "contexts": [r["contexts"] for r in results],
})

# التقييم
scores = evaluate(
    eval_dataset,
    metrics=[faithfulness, answer_relevancy, context_precision],
)

print(f"الأمانة: {scores['faithfulness']:.2f}")
print(f"صلة الإجابة: {scores['answer_relevancy']:.2f}")
print(f"دقة السياق: {scores['context_precision']:.2f}")

تفسير النتائج

المقياس جيد مقبول ضعيف
الأمانة > 0.9 0.7 - 0.9 < 0.7
صلة الإجابة > 0.85 0.7 - 0.85 < 0.7
دقة السياق > 0.8 0.6 - 0.8 < 0.6
استدعاء السياق > 0.8 0.6 - 0.8 < 0.6

تشخيص المشاكل

def diagnose_rag_issues(scores: dict) -> list[str]:
    """تحديد مشاكل خط أنابيب RAG من درجات RAGAS."""
    issues = []

    if scores.get("faithfulness", 1) < 0.7:
        issues.append(
            "أمانة منخفضة: LLM يهلوس. "
            "جرب: تعليمات أقوى، حرارة أقل، سياق أفضل."
        )

    if scores.get("answer_relevancy", 1) < 0.7:
        issues.append(
            "صلة منخفضة: الإجابات لا تطابق الأسئلة. "
            "جرب: تعليمات أفضل، إعادة كتابة الاستعلام، استرجاع محسن."
        )

    if scores.get("context_precision", 1) < 0.6:
        issues.append(
            "دقة منخفضة: سياق غير ذي صلة كثير. "
            "جرب: تضمينات أفضل، إعادة الترتيب، تصفية البيانات الوصفية."
        )

    if scores.get("context_recall", 1) < 0.6:
        issues.append(
            "استدعاء منخفض: معلومات ذات صلة مفقودة. "
            "جرب: قطع أكثر، تقطيع أفضل، بحث هجين."
        )

    return issues

# مثال
issues = diagnose_rag_issues({
    "faithfulness": 0.85,
    "answer_relevancy": 0.90,
    "context_precision": 0.55,  # مشكلة!
    "context_recall": 0.75,
})

# المخرج:
# ["دقة منخفضة: سياق غير ذي صلة كثير. جرب: تضمينات أفضل، إعادة الترتيب، تصفية البيانات الوصفية."]

المقاييس المخصصة

from ragas.metrics.base import Metric

class AnswerConciseness(Metric):
    """مقياس مخصص لملاءمة طول الإجابة."""

    name = "answer_conciseness"

    def _score(self, row: dict) -> float:
        answer = row["answer"]
        question = row["question"]

        # استدلال بسيط: عاقب الإجابات الطويلة جداً
        word_count = len(answer.split())

        if word_count < 10:
            return 0.5  # قصيرة جداً
        elif word_count < 100:
            return 1.0  # طول جيد
        elif word_count < 200:
            return 0.8  # تصبح طويلة
        else:
            return 0.5  # مطولة جداً

# استخدام المقياس المخصص
scores = evaluate(
    dataset,
    metrics=[faithfulness, AnswerConciseness()],
)

رؤية رئيسية: RAGAS يوفر تقييماً آلياً على نطاق واسع، لكن تحقق دورياً مقابل الأحكام البشرية. LLM كحَكَم يمكن أن يكون له نقاط عمياء، خاصة للمحتوى الخاص بمجال معين.

التالي، لنتعلم كيف نبني مجموعات بيانات اختبار شاملة. :::

اختبار

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

خذ الاختبار