أهلاً بك في درس جديد ضمن رحلتنا لبناء وكلاء ذكاء اصطناعي أذكياء.
في الدرس السابق، تعرفنا على مفهوم التصحيح الذاتي (Self-Correction)، وهو القدرة التي تجعل الوكيل يتعلم من أخطائه ويعدّل سلوكه. ورأينا أن هذه القدرة تعتمد على حلقة مستمرة من (فعل ← ملاحظة ← تفكير). لقد فهمنا لماذا يجب على الوكيل أن يصحح نفسه، ولكننا لم نتطرق إلى كيف يمكنه فعل ذلك عمليًا.
اليوم، سنخطو أولى خطواتنا العملية في هذا الاتجاه. هدفنا هو تحديد مؤشرات النجاح والفشل في مخرجات الأدوات. قبل أن يتمكن الوكيل من "تصحيح" أي شيء، يجب أن يكون قادرًا على التمييز بين ما إذا كانت الأداة التي استخدمها قد نجحت في مهمتها أم فشلت. هذا الدرس هو الجسر بين المفهوم النظري للتصحيح الذاتي والتطبيق البرمجي له.
1. ما هو "مخرج الأداة" (Tool Output)؟
عندما يقرر الوكيل استخدام أداة (سواء كانت دالة بايثون محلية، أو طلب لواجهة برمجية API)، فإن هذه الأداة تعيد له نتيجة. هذه النتيجة هي ما نسميه مخرج الأداة أو الملاحظة (Observation) في دورة ReAct.
هذا المخرج ليس مجرد نص بسيط دائمًا. يمكن أن يكون:
- كائن JSON معقد من واجهة برمجية (API).
- قيمة مُرجعة من دالة بايثون (مثل رقم، نص, قائمة، أو قاموس).
- والأهم من ذلك، يمكن أن يكون خطأ (Exception) برمجيًا يوقف التنفيذ.
مهمتنا كمبرمجين هي كتابة "غلاف" حول الأداة لتحليل هذا المخرج وتحديد ما إذا كان يمثل نجاحًا أم فشلًا قبل إعادته إلى "عقل" الوكيل (LLM) لاتخاذ القرار التالي.
المخطط التالي يوضح دورة عمل الوكيل. تركيزنا اليوم سيكون على مرحلة "معالجة الملاحظات" (Observation Processing).

2. أنواع مؤشرات النجاح والفشل
يمكننا تقسيم المؤشرات التي تدل على نجاح أو فشل أداة ما إلى فئتين رئيسيتين: صريحة وضمنية.
أ. المؤشرات الصريحة (Explicit Indicators)
هذه هي الإشارات الواضحة والمباشرة التي تدل على وجود خطأ. لا تحتاج إلى تفسير عميق لاكتشافها.
-
أكواد حالة HTTP (HTTP Status Codes): عند التعامل مع واجهات برمجة التطبيقات (APIs)، يعد كود الحالة الذي يعود مع الاستجابة أفضل مؤشر صريح. كما تعلمنا في الوحدة الثانية:
2xx(مثل200 OK): تعني أن الطلب نجح. هذا هو مؤشر النجاح الأساسي.4xx(مثل404 Not Found,401 Unauthorized): تعني أن هناك خطأ من جانب العميل (Client Error). ربما أرسل الوكيل طلبًا بمعلومات ناقصة أو خاطئة. هذا فشل واضح.5xx(مثل500 Internal Server Error): تعني أن هناك خطأ في الخادم نفسه (Server Error). الأداة نفسها لا تعمل بشكل صحيح. هذا فشل حرج.
-
الاستثناءات البرمجية (Python Exceptions): عندما نكتب أدواتنا كدوال في بايثون، فإن أسهل طريقة للإشارة إلى فشل حرج هي من خلال إطلاق استثناء (
Exception). كما تعلمنا في الوحدة الأولى، يمكننا استخدام كتلةtry...exceptلالتقاط هذه الأخطاء ومعالجتها. إذا حدث استثناء، فهذا مؤشر فشل لا لبس فيه.
ب. المؤشرات الضمنية (Implicit Indicators)
هذه هي الحالات الأصعب، حيث "تنجح" الأداة من الناحية الفنية (مثل إعادة كود 200 OK)، لكن المخرج الفعلي لا يفيد الوكيل في تحقيق هدفه.
-
النتائج الفارغة أو اللاغية (Empty or Null Results): قد تبحث عن منتج في متجر إلكتروني، فيعود الطلب ناجحًا (200 OK) ولكن بقائمة نتائج فارغة
[]. تقنيًا، البحث نجح، لكن عمليًا، لم يتم العثور على المنتج. يجب على الكود الخاص بنا التحقق مما إذا كانت النتائج فارغة. -
رسائل الخطأ داخل استجابة ناجحة: بعض الواجهات البرمجية (APIs) مصممة لتعيد دائمًا
200 OK، ولكنها تضمن رسالة الخطأ داخل جسم الاستجابة (JSON Body).- مثال على استجابة ناجحة:
{"status": "success", "data": {"temp": 25}} - مثال على استجابة فاشلة:
{"status": "error", "message": "Invalid city name provided"}
في هذه الحالة، يجب على الكود تحليل JSON والبحث عن حقل معين (مثل"status") لتحديد النجاح من الفشل.
- مثال على استجابة ناجحة:
-
النتائج غير ذات الصلة (Irrelevant Results): هذه هي أصعب أنواع الفشل في اكتشافها برمجيًا. قد يطلب الوكيل "أخبارًا عن أسهم شركة Apple"، فتعيد أداة البحث نتائج عن "فوائد فاكهة التفاح". هنا، لا يوجد خطأ فني، لكن النتيجة عديمة الفائدة. غالبًا ما يتطلب اكتشاف هذا النوع من الفشل إعادة الملاحظة إلى نموذج اللغة الكبير (LLM) ليقوم هو بتقييم مدى صلة النتيجة بالطلب الأصلي.
3. مثال عملي: بناء غلاف حول أداة
دعنا نرى كيف يمكننا تطبيق هذه المفاهيم في بايثون. سنتخيل أن لدينا أداة على شكل دالة اسمها get_weather تحاكي طلبًا من واجهة برمجة تطبيقات الطقس.
# محاكاة لدالة (أداة) تجلب الطقس
def get_weather(city: str) -> dict:
"""
تحاكي هذه الدالة طلب الطقس من واجهة برمجية.
يمكنها أن تعيد نجاحًا، أو خطأ ضمنيًا، أو تطلق استثناءً.
"""
weather_data = {
"Riyadh": {"temp": 35, "condition": "Sunny"},
"London": {"temp": 15, "condition": "Cloudy"},
}
# 1. محاكاة خطأ ضمني (رسالة خطأ في جسم الاستجابة)
if city == "InvalidCity":
return {"status": "error", "message": "City not found"}
# 2. محاكاة خطأ صريح (فشل في الاتصال بالخادم)
if city == "ServerError":
raise ConnectionError("Failed to connect to weather service")
# 3. محاكاة نجاح مع بيانات
if city in weather_data:
return {"status": "success", "data": weather_data[city]}
else:
# 4. محاكاة نجاح ولكن بنتيجة فارغة
return {"status": "success", "data": {}}
الآن، سنكتب "الغلاف" أو "المُشغّل" لهذه الأداة. هذه هي الدالة التي سيستخدمها الوكيل، وهي مسؤولة عن استدعاء الأداة وتفسير مخرجاتها.
# الغلاف الذي يحلل مخرجات الأداة
def run_weather_tool_and_interpret(city: str) -> str:
"""
تستدعي هذه الدالة أداة الطقس وتفسر النتيجة،
وتعيد نصًا واضحًا يصف النجاح أو الفشل.
"""
try:
# --- خطوة الإجراء (Action) ---
result = get_weather(city)
# --- خطوة الملاحظة والتفسير (Observation & Interpretation) ---
# التحقق من المؤشرات الضمنية
if isinstance(result, dict):
if result.get("status") == "error":
return f"فشل: أبلغت الأداة عن خطأ: {result.get('message')}"
if result.get("status") == "success" and not result.get("data"):
return f"فشل: نجحت الأداة ولكنها لم تجد بيانات للمدينة: {city}"
# إذا مرت كل الاختبارات، فهذا نجاح
if isinstance(result, dict) and result.get("status") == "success":
data = result['data']
return f"نجاح: الطقس في {city} هو {data['temp']} درجة مئوية و {data['condition']}."
# في حالة وجود مخرج غير متوقع
return f"فشل: الأداة أعادت مخرجًا غير معروف: {result}"
except ConnectionError as e:
# التحقق من المؤشرات الصريحة (الاستثناءات)
return f"فشل حرج: الأداة نفسها فشلت في العمل. الخطأ: {e}"
دعنا نختبر هذا الغلاف مع سيناريوهات مختلفة:
- السيناريو الناجح:
print(run_weather_tool_and_interpret("Riyadh")) # المخرج: نجاح: الطقس في Riyadh هو 35 درجة مئوية و Sunny. - فشل ضمني (رسالة خطأ):
print(run_weather_tool_and_interpret("InvalidCity")) # المخرج: فشل: أبلغت الأداة عن خطأ: City not found - فشل ضمني (نتيجة فارغة):
print(run_weather_tool_and_interpret("Cairo")) # المخرج: فشل: نجحت الأداة ولكنها لم تجد بيانات للمدينة: Cairo - فشل صريح (استثناء):
print(run_weather_tool_and_interpret("ServerError")) # المخرج: فشل حرج: الأداة نفسها فشلت في العمل. الخطأ: Failed to connect to weather service
كما ترى، بدلاً من إعطاء الوكيل بيانات JSON خام أو رسالة خطأ برمجية معقدة، نحن نقدم له "ملاحظة" (Observation) نظيفة وواضحة ("نجاح: ..." أو "فشل: ..."). هذا يسهل على نموذج اللغة الكبير (LLM) أن "يفكر" في الخطوة التالية.
اختبر فهمك!
لنفترض أن وكيلًا يستخدم أداة للبحث عن كتب لمؤلف معين. الأداة عبارة عن واجهة برمجية (API) تعيد استجابة بصيغة JSON.
تأمل استجابة الـ API التالية:
{
"request_status": "Completed",
"http_code": 200,
"data": {
"books": []
}
}
هل هذه الاستجابة تمثل نجاحًا أم فشلًا من منظور هدف الوكيل؟ ما هو نوع المؤشر الذي تراه؟ وماذا يمكن أن تكون "الفكرة" التالية للوكيل؟
Show answer
- النتيجة: تعتبر فشلًا ضمنيًا (Implicit Failure). على الرغم من أن الطلب اكتمل تقنيًا (
http_code: 200)، إلا أن قائمة الكتب فارغة[]، مما يعني أن الهدف (العثور على كتب) لم يتحقق. - نوع المؤشر: فشل ضمني من نوع "نتيجة فارغة".
- الفكرة التالية المحتملة للوكيل: "لقد نجح البحث تقنيًا ولكنه لم يعثر على كتب لهذا المؤلف. قد يكون اسم المؤلف مكتوبًا بشكل خاطئ، أو قد لا يكون لديه أي كتب منشورة في قاعدة البيانات هذه. سأحاول البحث عن المؤلف في أداة بحث مختلفة أو أسأل المستخدم للتحقق من الاسم."
خلاصة الدرس
لقد تعلمنا اليوم كيف ننتقل من النظرية إلى التطبيق في عملية التصحيح الذاتي. الخطوة الأولى والأكثر أهمية هي القدرة على التمييز بين النجاح والفشل.
أهم النقاط التي تعلمناها:
- مخرجات الأدوات هي "الملاحظات" التي يبني عليها الوكيل قراراته.
- مؤشرات الفشل يمكن أن تكون صريحة (أخطاء HTTP، استثناءات بايثون) أو ضمنية (نتائج فارغة، رسائل خطأ داخل JSON).
- نستخدم كودًا (مثل كتل
try/exceptوالتحقق من قيم القواميس) لإنشاء "غلاف" حول الأدوات يقوم بتفسير هذه المؤشرات. - الهدف هو تحويل المخرجات الفنية المعقدة إلى ملاحظة نصية واضحة (مثل "نجاح" أو "فشل" مع السبب) يمكن لنموذج اللغة الكبير فهمها والتصرف بناءً عليها.
ماذا بعد؟
الآن بعد أن أصبح وكيلنا قادرًا على اكتشاف الفشل، ما هي الخطوة التالية؟ بالطبع، هي التعامل معه!
في الدرس القادم، سنتعلم أول استراتيجية بسيطة وفعالة للتعامل مع الأخطاء، وهي "تنفيذ آلية إعادة محاولة بسيطة (Retry Mechanism) عند فشل أداة ما". سنرى كيف يمكننا برمجة الوكيل ليعيد المحاولة تلقائيًا عند مواجهة أنواع معينة من الفشل، مما يجعله أكثر متانة وقدرة على الصمود.