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

الوحدة 3 — المهامّ والاعتماديات والمخرجات المتوقّعة

بنينا في الوحدة 2 أربعة وكلاء بأدوار وأهداف. لكنّ وكيلًا بلا مهمّة يبقى دورًا فارغًا. فئة Task هي التي تُحرِّك الوكيل، وتُخبره ما يفعل الآن، وما الشكل الذي يجب أن يأخذه المخرَج، وأيّ مهمّة سابقة يقرأ سياقها. القواعد المُحكَمة هنا تُنقذنا لاحقًا من نصف مشاكل الفريق.

حقول المهمّة الأساسيّة

Task تتقاطع مع Agent في المسؤوليّة: الوكيل «يعرف الأسلوب»، والمهمّة «تعرف الطلب المحدّد اليوم». الحقول الجوهريّة:

  • description: وصف نصّيّ للطلب. جملتان إلى خمس تحدّد ما نريده تحديدًا. يمكن حقن متغيّرات بأسلوب {clé} تُملأ عند التنفيذ.
  • expected_output: وصف الشكل الذي يجب أن يأخذه المخرَج. هذا الحقل حاسم، وسنعود إليه.
  • agent: الوكيل الذي سيُنفّذ. في العمليّة الهرميّة (الوحدة 4) يمكن حذفه ليختار المسؤول.
  • context: قائمة مهامّ سابقة يُنقَل مخرَجها إلى هذه المهمّة كسياق مقروء.
  • output_file: مسار ملفّ يُكتَب فيه المخرَج تلقائيًّا (نستعمله لتسليم الوثيقة).

المخرَج المتوقّع: العمود الفقريّ

الفرق بين فريق يُنتج وثيقة قابلة للاستعمال وفريق يُنتج نصًّا عائمًا هو حقل expected_output. النموذج يستعمل هذا الوصف كـ«عقد» يعرف أنّه سيُقاس عليه. وصف غامض («ملخّص للمواصفات») يُنتج ملخّصًا في فقرة، أو خطّة من عشرين صفحة، بحسب مزاج النموذج. وصف دقيق («قائمة مُرقَّمة من الوظائف، لكلّ وظيفة اسم فرعيّ وثلاث جمل تصف السلوك وفئة المستخدم المستهدف») يُنتج تقريبًا ما نريد.

from crewai import Task

tache_analyse = Task(
description=(
"اقرأ ملفّ المواصفات في {chemin_specs} واستخرج قائمة الوظائف. "
"اذكر لكلّ وظيفة اسمها ومختصر سلوكها وفئة المستخدم المستهدف."
),
expected_output=(
"قائمة مُرقَّمة بصياغة Markdown، ثلاث جمل لكلّ وظيفة، "
"لا تتجاوز خمس عشرة وظيفة."
),
agent=analyste,
)

ربط المهامّ بالسياق

المهمّة التالية تحتاج نتيجة السابقة. حقل context هو الرباط: قائمة كائنات Task سابقة، ومخرجاتها تُقدَّم للنموذج كسياق يقرأه قبل أن يبدأ.

tache_redaction = Task(
description=(
"اكتب وثيقة عشر صفحات تشرح كلّ وظيفة من القائمة المُحلَّلة. "
"لكلّ وظيفة قسم من صفحة، مع مثال استخدام."
),
expected_output="ملفّ Markdown من ~10 صفحات، عناوين مستوى ثانٍ لكلّ وظيفة.",
agent=redacteur,
context=[tache_analyse],
output_file="./out/vue-produit.md",
)

النموذج لا يرى «تحليلًا سحريًّا»، بل يرى فقرة نظام تقول: «مخرَج المهمّة السابقة كان كالتالي: [قائمة الوظائف]». هذا يعني أنّ المخرَج السابق يجب أن يكون قابلًا للقراءة، لا شجرة JSON عميقة لا تُفهَم إلاّ بمُحلِّل.

المخرجات المُهيكَلة

إن كنت تحتاج بيانات صارمة لخطوة تالية غير NLM (سكربت، نداء API)، يمكن طلب مخرَج JSON صريح:

from pydantic import BaseModel

class Fonction(BaseModel):
nom: str
description: str
cible: str

class ListeFonctions(BaseModel):
fonctions: list[Fonction]

tache_analyse.output_json = ListeFonctions

CrewAI حينها يفرض مخرَج JSON مطابقًا للمخطّط، ويرفع خطأً إن لم يستطع النموذج. الفائدة: المهمّة التالية تتلقّى بيانات مُتحقَّقًا منها لا نصًّا حرًّا.

output_file: تسليم إلى الملفّ

الحقل output_file يُخبر CrewAI أن يكتب المخرَج مباشرة إلى ملفّ. مفيد لوثائق نهائيّة، ومهمّ في مسارات CI: ملفّ ينتظره سكربت آخر بلا حاجة لتحليل ناتج التنفيذ.

يمكن الجمع بين المخرَج القابل للقراءة (نصّ Markdown) وoutput_file وحفظ JSON مُهيكَل: نُلخّص للوكيل التالي، ونحفظ التفاصيل للتالي غير المذكّي.

الفخّ: expected_output مفقود

المشروع الذي رأينا أكثر من مرّة: Task بلا expected_output. النموذج يفهم description كطلب مفتوح، ويُنتج شيئًا بلا شكل محدّد. المهمّة التالية تتلقّى ذلك النصّ العائم كـcontext، ولا تعرف كيف تُفكّكه.

في CrewAI الحديث، expected_output صار إلزاميًّا لهذا السبب بالذات. لو حاولت إنشاء Task بدونه، يرفع الإطار خطأ فورًا. لا تُحاول التحايل عليه بكتابة نقطتين فارغتين؛ اكتبه بجدّيّة، فهو نصف نجاح الفريق.

الخيط الأحمر بعد الوحدة 3

نُعرِّف الآن أربع مهامّ متسلسلة: تحليل، ثمّ كتابة، ثمّ مراجعة، ثمّ تحكيم نهائيّ:

equipe = Crew(
agents=[analyste, redacteur, relecteur, responsable],
tasks=[tache_analyse, tache_redaction, tache_relecture, tache_finale],
process=Process.sequential,
)
resultat = equipe.kickoff(inputs={"chemin_specs": "./specs/produit.md"})

عند التنفيذ، تُنفَّذ المهامّ الأربع تباعًا. كلّ واحدة ترى مخرَج ما قبلها، ويُكتَب المخرَج النهائيّ في ./out/vue-produit.md.

احترس من التسلسل الخفيّ

Task بلا context صريح لا ترى المهمّة السابقة، حتّى في العمليّة التتابعيّة. لا تفترض أنّ الترتيب في القائمة يكفي: صرّح بالاعتماديّة.

الخلاصة

  • كلّ Task تحتاج description (ماذا) وexpected_output (كيف يظهر)؛ الحقل الثاني نصف نجاح الفريق.
  • context=[tache_precedente] ينقل مخرَج المهامّ السابقة كسياق مقروء للنموذج.
  • المخرجات المُهيكَلة عبر output_json = ModelePydantic تعطي بيانات مُتحقَّقًا منها للمهامّ التالية.
  • output_file يكتب المخرَج مباشرة إلى ملفّ؛ مفيد في تسليم نهائيّ أو ربط بسكربتات لاحقة.