أهلاً بك مرة أخرى.
في درسنا السابق، زودنا وكيلنا بمهارة الصلابة (Resilience) من خلال تنفيذ آلية إعادة المحاولة. أصبح الآن قادراً على التعامل مع الأخطاء المؤقتة بدلاً من الاستسلام عند أول عقبة. لكننا أنهينا الدرس بسؤال مهم: بعد أن تنجح الأداة في عملها وتعيد لنا بيانات، هل يمكننا الوثوق بهذه البيانات واستخدامها مباشرة؟
الجواب هو: ليس دائماً. هذا هو محور درسنا اليوم. هدفنا هو تعلم كيفية بناء نظام للتحقق من صحة مخرجات الأدوات قبل استخدامها. هذه الخطوة تعمل كـ "حارس جودة" يضمن أن المعلومات التي تصل إلى "عقل" الوكيل (LLM) ليست فقط خالية من الأخطاء البرمجية، بل هي أيضاً صحيحة، ومكتملة، وبالتنسيق المتوقع.
1. لماذا التحقق ضروري؟ عندما لا يكون النجاح كافياً
عندما تعمل أداة ما دون أن تطلق استثناءً (exception)، فهذا يعني أنها "نجحت" من منظور برمجي. لكن هذا لا يعني أن مخرجاتها مفيدة. تخيل السيناريوهات التالية:
- بيانات غير مكتملة: تطلب من أداة الطقس حالة الجو، فتعيد لك درجة الرطوبة وسرعة الرياح، ولكنها تنسى أهم معلومة: درجة الحرارة.
- تنسيق خاطئ: تطلب من أداة حاسبة سعر منتج، وبدلاً من أن تعيد لك رقماً مثل
1200، تعيد لك نصاً مثل"السعر هو 1200 دولار". هذا النص لا يمكن استخدامه في عمليات حسابية لاحقة. - معلومات غير ذات صلة: تطلب من أداة بحث معلومات عن "كوكب المشتري"، فتعيد لك فقرة عن "مشتريات البقالة". الأداة عملت، لكن النتيجة عديمة الفائدة.
- رسالة خطأ مخفية: تطلب من أداة البحث عن منتج غير موجود، فتعيد لك استجابة ناجحة (كود 200) ولكن محتواها هو
{ "error": "Product not found" }. إذا لم تتحقق من هذا المحتوى، قد يظن الوكيل أن المنتج موجود ولكنه بلا بيانات.
إدخال مثل هذه البيانات "السيئة" إلى نموذج اللغة الكبير يتبع مبدأ "Garbage In, Garbage Out" (مدخلات سيئة تؤدي إلى مخرجات سيئة). سيبني الوكيل قراراته واستنتاجاته على أساس خاطئ، مما يؤدي إلى فشل المهمة بأكملها.
2. أنواع التحقق من الصحة
يمكننا تقسيم عملية التحقق إلى نوعين رئيسيين:
أ. التحقق من البنية والشكل (Syntactic Validation)
هذا هو النوع الأساسي والأسهل. هنا، نحن نتأكد من أن البيانات تتبع "القواعد" أو "الشكل" الذي نتوقعه. الأسئلة التي نطرحها هي:
- هل البيانات من النوع المتوقع (مثلاً، هل هي قاموس
dictأم قائمةlist)؟ - هل يحتوي القاموس على جميع المفاتيح (keys) المطلوبة؟
- هل قيم هذه المفاتيح من النوع الصحيح (مثلاً، هل قيمة
priceهي رقم وليست نصاً)؟
هذا النوع من التحقق يحمي الوكيل من الأخطاء البرمجية التي قد تحدث عند محاولة الوصول إلى بيانات غير موجودة أو استخدامها بطريقة خاطئة.
ب. التحقق من المحتوى والصلة (Semantic Validation)
هذا النوع أكثر تقدماً. هنا، نحن لا نهتم فقط بشكل البيانات، بل بـ "معناها" و "مدى ملاءمتها". الأسئلة التي نطرحها هي:
- هل المعلومات التي أعادتها الأداة تجيب بالفعل على السؤال الأصلي؟
- هل تبدو البيانات منطقية؟ (مثلاً، هل من المعقول أن تكون درجة الحرارة في الرياض 50- درجة مئوية؟).
غالباً ما يتطلب هذا النوع من التحقق مساعدة من نموذج اللغة الكبير نفسه، في عملية تشبه "التفكير النقدي" أو "التحقق من الحقائق". يمكن للوكيل أن يأخذ المخرجات ويسأل نفسه (أو يسأل LLM آخر): "هل هذه الإجابة ذات صلة بطلبي الأصلي؟".
الصورة التالية توضح فكرة التحقق من المحتوى بشكل مبسط. يولد الوكيل استجابة أولية، ثم يولد "استجابات تحقق" أخرى لمقارنتها والتأكد من صحتها قبل إصدار الإجابة النهائية.

3. التنفيذ العملي: بناء دالة تحقق
دعنا نطبق النوع الأول (التحقق من البنية) باستخدام شيفرة بايثون بسيطة. سنبني دالة للتحقق من مخرجات أداة افتراضية لجلب أسعار المنتجات.
لنفترض أن لدينا هذه الأداة التي قد تعيد بيانات بتنسيقات مختلفة:
def get_product_price(product_name: str) -> dict:
"""أداة افتراضية لجلب سعر المنتج."""
print(f"البحث عن سعر المنتج: {product_name}...")
if product_name == "Laptop":
# حالة ناجحة وصحيحة
return {"product": "Laptop", "price": 4500, "currency": "SAR"}
elif product_name == "Mouse":
# حالة فشل: السعر نص وليس رقماً
return {"product": "Mouse", "price": "99 SAR"}
elif product_name == "Keyboard":
# حالة فشل: مفتاح السعر مفقود
return {"product": "Keyboard", "currency": "SAR"}
else:
# حالة فشل: المنتج غير موجود (خطأ مخفي)
return {"error": "Product not found"}
الآن، سنبني دالة "حارس الجودة" التي ستتحقق من هذه المخرجات:
def validate_price_output(output: dict) -> tuple[bool, str]:
"""
تتحقق من صحة مخرجات أداة السعر.
تعيد (True, "رسالة نجاح") إذا كانت البيانات صالحة.
تعيد (False, "رسالة خطأ") إذا كانت البيانات غير صالحة.
"""
# 1. تحقق من وجود خطأ صريح
if "error" in output:
return False, f"الأداة أعادت خطأ: {output['error']}"
# 2. تحقق من وجود المفاتيح المطلوبة
required_keys = ["product", "price", "currency"]
for key in required_keys:
if key not in output:
return False, f"المخرجات تفتقد للمفتاح المطلوب: '{key}'"
# 3. تحقق من نوع البيانات الصحيح
if not isinstance(output["price"], (int, float)):
return False, f"قيمة 'price' يجب أن تكون رقماً، ولكنها من نوع {type(output['price']).__name__}"
# إذا مرت كل الاختبارات
return True, "المخرجات صالحة."
# --- لنجرب الآن دالة التحقق ---
# الحالة 1: نجاح
output1 = get_product_price("Laptop")
is_valid, message = validate_price_output(output1)
print(f"النتيجة: {is_valid}, الرسالة: {message}\n") # سيطبع: True, المخرجات صالحة.
# الحالة 2: السعر نص
output2 = get_product_price("Mouse")
is_valid, message = validate_price_output(output2)
print(f"النتيجة: {is_valid}, الرسالة: {message}\n") # سيطبع: False, قيمة 'price' يجب أن تكون رقماً...
# الحالة 3: مفتاح مفقود
output3 = get_product_price("Keyboard")
is_valid, message = validate_price_output(output3)
print(f"النتيجة: {is_valid}, الرسالة: {message}\n") # سيطبع: False, المخرجات تفتقد للمفتاح المطلوب: 'price'
# الحالة 4: خطأ صريح
output4 = get_product_price("Screen")
is_valid, message = validate_price_output(output4)
print(f"النتيجة: {is_valid}, الرسالة: {message}\n") # سيطبع: False, الأداة أعادت خطأ: Product not found
هذا هو جوهر نظام التحقق: دالة منفصلة ومحددة، مهمتها الوحيدة هي فحص المخرجات وإعطاء حكم واضح: "صالح" أو "غير صالح"، مع توضيح السبب.
اختبر فهمك!
لنفترض أن لديك أداة search_news(topic) تبحث عن الأخبار، ونتوقع أن تعيد قائمة list من القواميس dict. كل قاموس يجب أن يحتوي على مفتاحين: title (نص) و url (نص).
اكتب دالة تحقق بسيطة اسمها validate_news_output(output) تضمن أن المخرجات تتبع هذا الهيكل. يجب أن تتحقق من:
- أن المخرجات هي قائمة
list. - أن كل عنصر في القائمة هو قاموس
dict. - أن كل قاموس يحتوي على المفتاحين
titleوurl.
Show answer
def validate_news_output(output):
# 1. تحقق من أن المخرجات هي قائمة
if not isinstance(output, list):
return False, "المخرجات ليست قائمة."
# إذا كانت القائمة فارغة، فهي صالحة تقنياً
if not output:
return True, "المخرجات قائمة فارغة وصالحة."
# 2. تحقق من كل عنصر في القائمة
for item in output:
if not isinstance(item, dict):
return False, "أحد عناصر القائمة ليس قاموساً."
# 3. تحقق من وجود المفاتيح المطلوبة
if "title" not in item or "url" not in item:
return False, "أحد القواميس يفتقد لمفتاح 'title' أو 'url'."
return True, "المخرجات صالحة."
# مثال للاستخدام
valid_output = [{"title": "خبر 1", "url": "http://example.com/1"}]
invalid_output_1 = {"title": "خبر 1", "url": "http://example.com/1"} # ليس قائمة
invalid_output_2 = [{"title": "خبر 1"}] # يفتقد لمفتاح url
print(validate_news_output(valid_output))
print(validate_news_output(invalid_output_1))
print(validate_news_output(invalid_output_2))
4. من التحقق إلى التصحيح الذاتي
الآن بعد أن أصبح وكيلنا قادراً على اكتشاف المخرجات غير الصالحة، ماذا يفعل بهذه المعلومة؟ هنا ننتقل من مجرد "التحقق" إلى "التصرف" بناءً على نتيجة التحقق. هذه هي الخطوة الأولى نحو التصحيح الذاتي (Self-Correction).
المخطط التالي يوضح هذه الفكرة بشكل ممتاز. بعد تنفيذ إجراء ما، يقوم الوكيل بتقييم النتيجة ويتخذ مساراً مختلفاً بناءً على نوع المشكلة.

دعنا نترجم هذا المخطط إلى ما تعلمناه:
- Execute Action (تنفيذ الإجراء): هذا هو استدعاء أداتنا، مثل
get_product_price("Mouse"). - Is result: (نتيجة التحقق هي):
- Malformed (مشوهة): هذا ما اكتشفته دالة التحقق
validate_price_outputعندما كان السعر نصاً. الإجراء المقترح هنا (Rephrase query) يمكن أن يعني لوكيلنا: "ربما يجب أن أعدل مدخلاتي للأداة وأحاول مرة أخرى". - Irrelevant (غير ذات صلة): هذا هو التحقق من المحتوى. الإجراء المقترح (
New search) يعني: "هذه النتيجة غير مفيدة، سأجرب أداة بحث مختلفة أو بكلمات مفتاحية أخرى". - Missing Data (بيانات مفقودة): هذا ما حدث عندما كان مفتاح السعر مفقوداً. الإجراء المقترح (
Try alternative source) يعني: "هذه الأداة لم تعطني ما أريد، سأجرب أداة أخرى". - Critical (حالة حرجة): إذا فشلت كل المحاولات، أو كان الخطأ لا يمكن إصلاحه، قد يقرر الوكيل طلب المساعدة البشرية (
Ask human for help). - Success (نجاح): إذا كانت المخرجات صالحة (مرت من دالة التحقق)، يستمر الوكيل في خطته (
Proceed).
- Malformed (مشوهة): هذا ما اكتشفته دالة التحقق
ببناء نظام التحقق، نحن لا نضيف فقط طبقة أمان، بل نمنح الوكيل نقاط قرار استراتيجية تمكنه من التفكير في جودة عمله وتصحيح مساره عند الحاجة.
خلاصة الدرس
في هذا الدرس، أضفنا مهارة "التفكير النقدي" الأساسية إلى وكيلنا. بدلاً من الثقة العمياء في مخرجات الأدوات، أصبح الآن يفحصها ويتحقق منها أولاً.
أهم النقاط التي تعلمناها:
- لماذا التحقق مهم: لأن النجاح البرمجي لا يضمن مخرجات مفيدة.
- أنواع التحقق: التحقق من البنية والشكل (لضمان التنسيق الصحيح) والتحقق من المحتوى والصلة (لضمان المعنى الصحيح).
- كيفية التنفيذ: يمكن بناء دوال تحقق بسيطة وفعالة باستخدام أساسيات بايثون (الشروط،
isinstance، التحقق من المفاتيح) لفحص بنية البيانات. - الخطوة التالية بعد التحقق: نتائج التحقق (صالح، غير صالح، غير مكتمل) هي مدخلات لعملية التصحيح الذاتي، حيث يقرر الوكيل ما يجب فعله بعد ذلك.
ماذا بعد؟
لقد تعلمنا الآن كيف نكتشف الفشل، وكيف نعيد المحاولة، وكيف نتحقق من صحة المخرجات. ولكن ماذا لو فشلت الأداة بشكل متكرر حتى بعد إعادة المحاولة؟ أو ماذا لو كانت مخرجاتها غير صالحة باستمرار؟ لا يمكن للوكيل أن يظل عالقاً في حلقة لا نهاية لها.
في الدرس القادم، سنتناول هذه المشكلة بالضبط. سنتعلم كيفية "تصميم استراتيجية بديلة (Fallback Strategy) في حال فشل الأداة المتكرر". سنقوم بتجهيز وكيلنا بـ "خطة بديلة" يلجأ إليها عندما تفشل الخطة الأساسية، مما يجعله أكثر ذكاءً وقدرة على التكيف.