الوحدة 5 — الأدوات المشتركة بين الوكلاء
الوكيل بلا أدوات مساعد نصّيّ ذكيّ لكن معزول: يعرف ما تعلّمه النموذج فقط، ولا يستطيع قراءة ملفّ، ولا الاتّصال بواجهة برمجيّة، ولا حفظ نتيجة. الأدوات هي ما يجعل الوكيل قادرًا على العمل الحقيقيّ. CrewAI يعرض ثلاث طبقات للأدوات، ولكلّ منها استعمال مبرَّر.
الطبقة الأولى: الأدوات المدمجة
حزمة crewai_tools تحوي عشرات الأدوات الجاهزة: قراءة ملفّات، بحث في PDF، استفسار موقع ويب، تنفيذ استعلام SQL، وغيرها. الاستيراد مباشر:
from crewai_tools import FileReadTool, PDFSearchTool, SerperDevTool
lecture_specs = FileReadTool(file_path="./specs/produit.md")
recherche_pdf = PDFSearchTool(pdf="./specs/ancien-produit.pdf")
recherche_web = SerperDevTool() # يتطلّب SERPER_API_KEY
قاعدة: قبل كتابة أداة مخصّصة، ابحث في crewai_tools عن مكافئ. تُنقذك ساعات، وتُعطيك إدارة أخطاء ناضجة تعامل عليها مطوّرو المكتبة.
الطبقة الثانية: أدوات مخصّصة بـ@tool
للأدوات البسيطة، @tool من crewai_tools أو langchain_core.tools تُنشئ أداة من دالّة بايثونيّة. docstring تُصبح وصف الأداة للنموذج، وتلميحات الأنواع (type hints) تُصبح مخطّط الوسائط:
from crewai.tools import tool
@tool("chercher_terme")
def chercher_terme(terme: str, dossier: str = "./docs") -> str:
"""يبحث عن مصطلح في كلّ الملفّات النصّيّة داخل مجلّد،
ويُرجع أوّل خمس نتائج مع اسم الملفّ ورقم السطر."""
import pathlib, re
resultats = []
for f in pathlib.Path(dossier).rglob("*.md"):
for i, ligne in enumerate(f.read_text(encoding="utf-8").splitlines(), 1):
if re.search(terme, ligne, re.IGNORECASE):
resultats.append(f"{f.name}:{i} — {ligne.strip()[:80]}")
if len(resultats) >= 5:
return "\n".join(resultats)
return "\n".join(resultats) or "لا نتائج"
النموذج يقرأ docstring كتعليمة استعمال، ويرى وسيطًا terme من نوع str ووسيطًا اختياريًّا dossier. الوسائط الخاطئة تُرفَض قبل التنفيذ.
الطبقة الثالثة: أدوات عبر BaseTool
للأدوات الأعقد (حالة داخليّة، إعدادات، تصنيف صريح)، نرث BaseTool:
from crewai.tools import BaseTool
from pydantic import BaseModel, Field
class LireFichierArgs(BaseModel):
chemin: str = Field(description="مسار الملفّ المطلق أو النسبيّ")
max_octets: int = Field(default=20000, description="حدّ أعلى للقراءة")
class LireFichierAvance(BaseTool):
name: str = "lire_fichier_avance"
description: str = "قراءة ملفّ نصّيّ مع حدّ حجم قابل للتعديل."
args_schema: type[BaseModel] = LireFichierArgs
def _run(self, chemin: str, max_octets: int = 20000) -> str:
with open(chemin, "r", encoding="utf-8") as f:
return f.read(max_octets)
هذا الأسلوب أوضح للتوثيق التقنيّ ولإعادة استعمال الأداة في مشاريع متعدّدة، لكنّه أطول بقليل من @tool.