الوحدة 9 — تصحيح فريق لا يصل إلى نتيجة
الفريق كان يعمل جيّدًا في التطوير. أطلقناه على مواصفات جديدة، فدار في حلقة أربعين دقيقة قبل أن يُصدر مخرجًا ركيكًا. هذا سيناريو حقيقيّ يواجهه كلّ من يُشغّل CrewAI في الإنتاج. الوحدة تعرض منهج تشخيص متدرّجًا يحلّ 80٪ من الحالات.
الخطوة 1: تفعيل الآثار التفصيليّة
أوّل شيء عند مواجهة فشل: تفعيل verbose=True على كلّ الوكلاء وعلى Crew، وإعادة التنفيذ مع حفظ الأثر:
equipe = Crew(
agents=[analyste, redacteur, relecteur, responsable],
tasks=[...],
process=Process.sequential,
verbose=True,
)
import sys, io
buffer = io.StringIO()
old_stdout = sys.stdout
sys.stdout = buffer
try:
resultat = equipe.kickoff(inputs={"chemin_specs": "./specs/nouveau.md"})
finally:
sys.stdout = old_stdout
with open("./traces/exec.log", "w", encoding="utf-8") as f:
f.write(buffer.getvalue())
الملفّ الناتج يحتوي ما يلي لكلّ خطوة: تعليمة النظام المُرسَلة، فكرة الوكيل، الأداة المُستدعاة إن وُجدت، النتيجة، الجواب النهائيّ. اقرأ الأثر من الأعلى، وابحث عن أوّل خطوة لا تُنتج ما تتوقّعه.
علامات إنذار في الآثار
خمس علامات نراها في 80٪ من الفشل:
- نفس الوكيل يُنفّذ نفس المهمّة مرّتَين متتاليتَين: مؤشّر على أنّ
expected_outputغامض، فالوكيل يُعيد المحاولة ظنًّا أنّه لم ينجح. - تفويض من A إلى B ثمّ من B إلى A: حلقة تفويض. المسؤوليّات متداخلة.
GraphRecursionErrorأو رسالةmax_iter reached: الوكيل استنفد حلقته الداخليّة. غالبًا بسبب أداة تفشل صامتة.- مخرَج قصير جدًّا فجأة: النموذج يظنّ أنّ المهمّة سُلّمت بمخرَج نظير قصير. راجع
expected_output. - استدعاء أداة بوسائط مختلفة قليلًا في كلّ مرّة: النموذج يجرّب تنويعات لأنّ الأولى لم تُنتج ما توقّع.
الخط وة 2: تشخيص المخرَج الغامض
إن رأيت الوكيل نفسه يُنفّذ مهمّته مرّتَين، السبب في 90٪ من الحالات هو expected_output غير محدّد. اختبار سريع: اعرض على النموذج مباشرة الوصف والمُتوقّع، واسأله «كيف ستعرف أنّك أنجزت المهمّة؟». إن كانت الإجابة غامضة، صحّح.
مثال قبل وبعد:
# قبل: غامض
expected_output = "قائمة الوظائف"
# بعد: قابل للقياس
expected_output = (
"قائمة Markdown مُرقَّمة، 5 إلى 15 عنصرًا، "
"كلّ عنصر: اسم الوظيفة، ثلاث جمل تصف السلوك، "
"فئة المستخدم المستهدف (إداريّ/عاديّ/مطوّر)."
)
في تجربتنا، هذا التصحيح وحده حلّ 40٪ من حالات «الوكيل يعيد نفسه».
الخطوة 3: كسر حلقات التفويض
إن رأيت تبادل تفويض بين وكيلَين، ثلاثة إجراءات مرتّبة بسرعة أثرها:
- عطّل
allow_delegationعلى أحدهما. القاعدة: وكيل واحد فقط في الفريق يفوّض (المسؤول عادةً). - راجع الأهداف. إن كان الاثنان مسؤولَين عن نفس القرار، أعِد كتابة الأهداف بحدود واضحة.
- أضف
max_iterصريحًا.max_iter=5يمنع أيّ وكيل من الإطالة إلى ما لا نهاية.
redacteur = Agent(
role="محرّر تقنيّ",
goal="...",
backstory="...",
allow_delegation=False, # كسر الحلقة
max_iter=6,
)
الخطوة 4: أدوات تفشل صامتة
الفخّ الخطر: أداة تُعيد نصًّا فارغًا بدل رفع خطأ. النموذج يقرأ الفراغ ويُفسّره كـ«لم أحصل على شيء بعد»، فيستدعي الأداة مرّة أخرى. حلقة صامتة.
قاعدة صارمة: كلّ أداة يجب أن تُعيد رسالة صريحة عند الفشل، لا سلسلة فارغة:
@tool("chercher_terme")
def chercher_terme(terme: str) -> str:
"""..."""
resultats = _rechercher(terme)
if not resultats:
return f"لم يُعثر على أيّ نتيجة للمصطلح «{terme}». جرّب مصطلحًا مختلفًا."
return "\n".join(resultats)
الرسالة الصريحة تجعل النموذج يفهم أنّ الأداة اشتغلت، لكنّ النتيجة سلبيّة. يقرّر عادةً تجربة مصطلح آخر أو المتابعة بلا الأداة.