المستوى: محترف — لمن يدير LLMs في الإنتاج ويملك خبرة بـ Python والـ Anthropic SDK.
LLM-as-a-Judge للمحترف: Eval Pipeline يمسك Regression قبل الإنتاج
لو deployت تحديث جديد للـ prompt بتاع chatbot شركتك يوم الخميس، وعرفت يوم الإتنين إن الردود بقت أسوأ من الـ retention الـ dropping، انت دفعت ثمن غياب الـ eval pipeline. LLM-as-a-Judge بيخلّيك تمسك الـ regression في 12 دقيقة من الـ deploy بدل 4 أيام، بـ Spearman correlation 0.87 مع تقييم البشر.
المشكلة باختصار
الـ unit tests بتمسك bugs الكود. assert response.status_code == 200 سهل. لكن ازاي تختبر إن "الرد ده مفيد للعميل؟" أو "الترجمة دي دقيقة؟" أو "الـ summary غطّى النقاط المهمة؟". دي أسئلة subjective، ومحدش عنده فلوس يدفع لمراجع بشري يحكم على 5,000 رد يومياً.
المفهوم بمثال واقعي: مفتش جودة في مصنع شوكولاتة
تخيّل مصنع بينتج 10,000 بار شوكولاتة في اليوم. مفيش وقت تتذوّق كل بار. الحل: شوكولاتييه senior بياخد عيّنة عشوائية 500 بار يومياً، يحط ليهم درجات (مظهر، ملمس، طعم) من 0 لـ 10. لو متوسط الدرجة وقع تحت 8.0، الخط بيقف ويتفحّص قبل ما 9,500 بار سيئ يوصل للعميل.
LLM-as-a-Judge بالظبط نفس الفكرة بس في عالم النماذج: انت بتشغّل Claude Haiku 4.5 في الإنتاج علشان رخيص وسريع، وبتستخدم Claude Opus 4.7 كـ judge يقيّم 200 رد عشوائي يومياً على معايير محددة (دقة المعلومة، اتباع التعليمات، النبرة). لو متوسط الـ score نزل تحت threshold، الـ CI/CD pipeline بيوقف الـ deploy التالي تلقائياً.
التعريف العلمي وإيه اللي بيخلّيه يشتغل
LLM-as-a-Judge أول مرة اتعرّفت بشكل ممنهج في ورقة G-Eval (Liu et al., 2023) و MT-Bench (Zheng et al., 2023). الفكرة بسيطة: نموذج قوي (الـ judge) بيقيّم مخرجات نموذج أضعف (الـ candidate) بـ Chain-of-Thought مع rubric محدد. الـ correlation مع تقييم البشر بيوصل لـ Spearman 0.85 إلى 0.88 على MT-Bench، وده قريب جداً من اختلاف البشر مع بعض على نفس المهمة (0.81).
الافتراض هنا: الـ judge لازم يكون نموذج أقوى من الـ candidate بمستوى ملحوظ. لو الـ judge والـ candidate نفس النموذج، الـ self-preference bias بياكل النتائج.
الـ Eval Pipeline في 47 سطر
الكود ده بيشغّل eval batch على 200 sample، بيحسب average score، وبيرفع exit code 1 لو الـ regression أكبر من 0.4 نقطة عن الـ baseline. الـ exit code بيوقف الـ CI تلقائي.
import anthropic, json, statistics
from concurrent.futures import ThreadPoolExecutor
client = anthropic.Anthropic()
RUBRIC = """قيّم الرد بناءً على ثلاث معايير:
- accuracy: دقة المعلومة (0-10)
- instruction_following: مدى تنفيذ طلب المستخدم (0-10)
- tone: النبرة المهنية بالعربي (0-10)
ارجع JSON فقط بدون أي نص إضافي:
{"accuracy": int, "instruction_following": int, "tone": int, "reasoning": "سطر واحد"}"""
def judge(prompt: str, response: str) -> dict:
msg = client.messages.create(
model="claude-opus-4-7",
max_tokens=300,
temperature=0,
system=RUBRIC,
messages=[{
"role": "user",
"content": f"السؤال:\n{prompt}\n\nالرد:\n{response}"
}]
)
return json.loads(msg.content[0].text)
def evaluate_batch(samples: list[dict], baseline: float) -> dict:
with ThreadPoolExecutor(max_workers=8) as pool:
scores = list(pool.map(
lambda s: judge(s["prompt"], s["response"]), samples
))
avg = statistics.mean(
(s["accuracy"] + s["instruction_following"] + s["tone"]) / 3
for s in scores
)
return {
"avg_score": round(avg, 2),
"baseline": baseline,
"delta": round(avg - baseline, 2),
"regression": avg < baseline - 0.4,
"samples": len(scores),
}
if __name__ == "__main__":
with open("eval_samples.json", encoding="utf-8") as f:
samples = json.load(f)
result = evaluate_batch(samples, baseline=8.3)
print(json.dumps(result, ensure_ascii=False, indent=2))
if result["regression"]:
raise SystemExit(1)