انتقل إلى المحتوى الرئيسي

الوحدة 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: كسر حلقات التفويض

إن رأيت تبادل تفويض بين وكيلَين، ثلاثة إجراءات مرتّبة بسرعة أثرها:

  1. عطّل allow_delegation على أحدهما. القاعدة: وكيل واحد فقط في الفريق يفوّض (المسؤول عادةً).
  2. راجع الأهداف. إن كان الاثنان مسؤولَين عن نفس القرار، أعِد كتابة الأهداف بحدود واضحة.
  3. أضف 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)

الرسالة الصريحة تجعل النموذج يفهم أنّ الأداة اشتغلت، لكنّ النتيجة سلبيّة. يقرّر عادةً تجربة مصطلح آخر أو المتابعة بلا الأداة.

الخطوة 5: زيادة الملاحظة في الإنتاج

في الإنتاج، verbose=True غير مناسب (آلاف الأسطر لكلّ تنفيذ). البدائل:

  • step_callback على Crew: دالّة تُستدعى بعد كلّ خطوة وكيل، تتلقّى الحالة، وتكتب في نظام سجلّات مُهيكَل (JSON، OpenTelemetry).
  • task_callback على Crew: مثله، لكن على مستوى المهمّة (مفيد للفواتير).
  • تكامل LangSmith: عبر متغيّرات بيئة، ينتج شجرة نداءات مرئيّة قابلة للاستكشاف.
def journal_etape(etape):
logger.info({"agent": etape.agent, "action": etape.action, "duree_ms": etape.duration_ms})

equipe = Crew(..., step_callback=journal_etape)

قصّة تصحيح فعليّة

فريق تحرير كان يتوقّف بعد جولة كتابة على وثيقة معيّنة. الأثر أظهر:

  1. المحلِّل استخرج 22 وظيفة (الحدّ في expected_output كان 15).
  2. المحرِّر بدأ يكتب، وبعد الوظيفة السابعة استدعى أداة بحث لتأكيد اسم دقيق.
  3. الأداة SerperDevTool أعادت خطأ 429 Too Many Requests بدل نتيجة.
  4. النموذج قرّر: «أعيد الأداة». ثلاث مرّات متتاليات، الحدّ الافتراضيّ max_iter=25 يقترب.
  5. الفريق تجاوز الوقت المخصّص وتوقّف بمخرَج ناقص.

التصحيح:

  • تصحيح 1: expected_output للمحلِّل عُدِّل إلى «10 إلى 15 وظيفة قصوى».
  • تصحيح 2: SerperDevTool غُلِّف بأداة مخصّصة تتعامل مع 429 وتعيد رسالة نصّيّة.
  • تصحيح 3: max_iter=8 على المحرِّر.

بعد التصحيحات، الفريق أنجز الوثيقة في 45 ثانية.

قاعدة عمل

احتفظ بمجلّد ./traces/ مع آخر عشرة آثار كاملة للتنفيذ في الإنتاج، مع طابع زمنيّ. حين يظهر بلاغ خلل، لن تحتاج أكثر من دقائق لإعادة السيناريو.

الخلاصة

  • ابدأ التشخيص دومًا بـverbose=True وحفظ الأثر؛ 80٪ من الحالات تُحلّ من قراءته.
  • خمس علامات إنذار: وكيل يُعيد نفسه، تفويض متبادَل، max_iter reached، مخرَج قصير فجأة، أداة تُستدعى بوسائط قريبة.
  • الحلول الأربعة الأشهر: expected_output أدقّ، إلغاء allow_delegation عن جميع من ليس مسؤولًا، max_iter صريح، أدوات تُعيد رسائل واضحة.
  • في الإنتاج، verbose=True يُستبدَل بـstep_callback مُهيكَل، مع الاحتفاظ بآخر عشرة آثار لتشخيص سريع.