في الفصل السابق رأينا أن وكيل الذكاء الاصطناعي يتكوّن من أربعة عناصر: العقل (LLM)، والأدوات، والذاكرة، والحلقة (التحكّم). في هذا الفصل نبدأ العمل بأيدينا أخيراً. الهدف بسيط: أن نبني «وكيلاً واحداً يعمل» بأدنى تركيب يجمع العناصر الأربعة، ونشغّله فعلاً ونراقب سلوكه. لن نضيف مزايا مبهرة بعد. أولاً نجعل قلب الوكيل يدور بأيدينا: «نجعل الـLLM يفكّر، ونستدعي أداة، ونعيد النتيجة، ونجعله يفكّر ثانية، ونتوقّف عند الإنجاز».

هدف هذا الفصل

«أن تركّب وكيلاً أدنى واحداً بنفسك وتشغّله»

تفهم الحلقة
تستطيع شرح دورة الوكيل الواحدة: «يفكّر ← يستدعي ← يعيد ← يفكّر ثانية ← يتوقّف».
تستطيع تسليم أداة
تفهم آلية تعريف الأداة ووسائطها، فتستطيع أن تعطي الـLLM أداةً واحدة.
تستطيع المراقبة
ترى في السجلّات أين يحكم وأين يفشل، فـتصل ذلك بالفصل التالي.

ما هو الوكيل الأدنى

«الوكيل الأدنى» هو أن تركّب عناصر الفصل السابق الأربعة كلاً منها في أصغر صورة له. لا حاجة لذاكرة فاخرة، ولا لأدوات كثيرة، ولا لوكلاء متعدّدين. كما في الجدول التالي، نبدأ من تركيب اقتُطع منه كلّ ما يمكن اقتطاعه. إن فهمت هذا، فما بقي هو مجرد تسمين كلّ عنصر.

🧠 العقل

مجرد استدعاء نموذج واحد من Claude. اكتب في التعليمة سطراً واحداً للدور: «أنت مساعد يستطيع استخدام الأدوات».

🛠 الأدوات

أداة واحدة فقط. بحث أو حساب، لا فرق. اجعلها أوّلاً دالّة بسيطة مضمونة العمل.

📝 الذاكرة

يكفي مصفوفة واحدة لسجلّ المحادثة. تكدّس التبادلات بالترتيب فحسب. لا تفكّر بالحفظ الدائم بعد.

🔄 الحلقة

عبارة while واحدة. تدور بمنطق «إن وُجد طلب أداة فنفّذ، وإلا فاكتملت». حدّد عدد المرّات الأقصى فقط.

💡 الفرق عن «استدعاء API واحد» هو الحلقة فقط. إن سألت الـLLM مرّةً واحدة وأخذت الجواب فحسب، فذاك مجرد استخدام API. وفي اللحظة التي يُضاف فيها التكرار «نفّذ أداة، أعِد النتيجة، اجعله يفكّر مجدّداً»، يصير وكيلاً. الزائد عملياً هو هذه الحلقة الواحدة فقط. لذا فإن فهم الحلقة فهماً تامّاً أولاً هو الطريق الأقصر.

ما بداخل حلقة الوكيل

قلب الوكيل هو حلقة واحدة. ندير بالبرنامج ما يفعله الإنسان نفسه: «يبحث، ويفكّر، ويحرّك يده، ويرى النتيجة، وإن لم يكتمل بعد فيكرّر». لنمسك أوّلاً بمسار الدورة الواحدة عبر الرسم.

STEP 1
اجعله يفكّر

مرّر إلى الـLLM سجلّ المحادثة حتى الآن وقائمة الأدوات. يقرّر الـLLM: «يجيب» أم «يستخدم أداة».

STEP 2
استدعِ الأداة

إذا أجاب الـLLM «أريد استخدام هذه الأداة بهذه الوسائط»، فأنت من ينفّذ تلك الدالّة فعلاً.

STEP 3
أعِد النتيجة

أضِف نتيجة التنفيذ (القيمة التي أعادتها الأداة) إلى سجلّ المحادثة، ومرّرها إلى الـLLM ثانية. هنا نعود إلى STEP 1.

STEP 4
توقّف عند الإنجاز

إذا أعاد الـLLM جواباً نهائياً دون استخدام أداة، فاعتبر الهدف محقّقاً واخرج من الحلقة.

النقطة هي أنّك تدور في STEP 1~3 مراراً، ولا تخرج إلا في STEP 4. قد لا ينتهي الـLLM في دورة واحدة. مثل «ابحث أوّلاً، وبعد رؤية النتيجة ابحث مرّة أخرى»، يدور دورات كثيرة ليقترب من الهدف. إن كتبته بشيفرة زائفة، فسيكون قصيراً على نحو مذهل.

# حلقة الوكيل الأدنى (شيفرة زائفة)
messages = [ user("ابحث عن متوسط حرارة الشهر الماضي ولخّصه") ]

while True:
    reply = llm.think(messages, tools=[search_tool])   # STEP1 يفكّر

    if reply.wants_tool:                              # هل هناك طلب أداة؟
        result = run_tool(reply.tool_name, reply.args) # STEP2 يستدعي
        messages.append(assistant(reply))
        messages.append(tool_result(result))           # STEP3 يعيد
    else:
        return reply.text                             # STEP4 يتوقّف عند الإنجاز

⚠️ ضع حدّاً أقصى لعدد المرّات دائماً. جعلنا الشيفرة الزائفة أعلاه while True للشرح فقط، لكن في الواقع ضع دائماً مكبحاً بـ«حتى N دورات كأقصى». الحلقة اللانهائية التي يستمرّ فيها الـLLM باستدعاء الأدوات بلا توقّف حادثٌ يقع فعلاً وكثيراً. نتناول التفصيل في نقاط التعثّر.

أعطه أداةً واحدة

تالياً نعطي الـLLM أداةً واحدة. المهمّ هنا أنّ الـLLM لا يستطيع تنفيذ الشيفرة بنفسه. كلّ ما يستطيعه هو أن يطلب بالكلام «أريد استخدام هذه الأداة بهذه الوسائط». أمّا تشغيل الدالّة فعلاً وإعادة النتيجة، فهو دائماً مهمّتك أنت (برنامجك).

ولأجل ذلك نمرّر أوّلاً إلى الـLLM تعريف الأداة. تعريف الأداة هو كتيّب يشرح «ماذا تفعل هذه الأداة وأيّ وسائط تأخذ». يقرأه الـLLM ليحكم متى يستخدمها وكيف.

① الاسم والوصف

مثال: search_weather «حين تمرّر اسم مكان وشهراً يعيد متوسط الحرارة». الوصف مادّةُ حكم الـLLM، فاكتبه بعناية.

② شكل الوسائط

حدّد بمخطّط (schema بصيغة JSON) أيّ وسائط يستقبل وبأيّ نوع. مثال: city (سلسلة نصّية) وmonth (رقم).

③ الدالّة الفعلية

متن الشيفرة الذي يعمل فعلاً. يستقبل الوسائط التي أعادها الـLLM ويعيد النتيجة. هذه مجرّد دالّة عادية.

الحيلة أن تمرّر ①② إلى الـLLM، وتُبقي ③ عندك. إذا طلب الـLLM «أريد استخدام search_weather(city="باريس", month=6)»، فتستقبل ذلك الاسم والوسائط، وتستدعي دالّة ③، وتعيد قيمتها إلى المحادثة كنتيجة أداة. لنرَ ذلك بشيفرة زائفة.

# ① و ② … تعريف الأداة المُمرّر إلى الـLLM
search_tool = {
    "name": "search_weather",
    "description": "يعيد متوسط الحرارة (℃) حين تمرّر مدينة وشهراً",
    "input_schema": {
        "city":  "string",
        "month": "number",
    },
}

# ③ … الدالّة الفعلية عندك. مجرّد دالّة
def run_tool(name, args):
    if name == "search_weather":
        return weather_db.lookup(args["city"], args["month"])
    return "unknown tool"

📊 الوصف جزءٌ من التعليمة. إنّ description الأداة هو الدليل الوحيد الذي يقرّر به الـLLM «متى يستخدمها». إن كان غامضاً فقد لا يستخدمها، أو يستخدمها في موضع خاطئ. الحيلة أن تكتبه كأنّك تشرح لإنسان «ماذا يعيد ومتى ينبغي استخدامه». نعمّق تصميم الأدوات أكثر في فصل MCP التالي.

صورة التنفيذ ― الحلقة اليدوية والـSDK

حين تركّب الأجزاء حتى الآن، يعمل أول وكيل لك. للتنفيذ طريقان كبيران: الكتابة اليدوية إن أردت تعلّم الآلية، والـSDK إن أردت الراحة في العمل الفعلي — وكلاهما في جوهره الحلقة نفسها. لننظّم الفرق أوّلاً.

أ. الحلقة اليدوية
تدير عبارة while بنفسك

تدير استدعاء الـAPI وتنفيذ الأدوات بعبارة while خاصّة بك. ترى كلّ ما يجري في الداخل، فهي مثالية للتعلّم. والتحكّم حرّ تماماً.

مناسبة حين: تريد فهم الآلية / تريد تحكّماً دقيقاً.

ب. مُشغّل الأدوات في الـSDK
تكل الحلقة إليه

تسجّل تعريف الأداة والدالّة في حلقة تنفيذ الأدوات التي توفّرها أطر مثل Claude Agent SDK وتكلها إليها. توفّر عليك كتابة الجزء النمطي، ومتينة.

مناسب حين: تريد البناء بسرعة / تريد أن تكل المعالجة النمطية في الإنتاج.

الصورة الكاملة لـأ. الحلقة اليدوية ليست إلا وصلاً مباشراً للأجزاء السابقة. تحتفظ بسجلّ محادثة، وتستدعي الـLLM، وإن وُجد طلب أداة نفّذته وأعدت النتيجة، وإن لم يوجد خرجت — لا أكثر.

# أ. الحلقة اليدوية (شيفرة زائفة / بأسلوب رسائل Claude)
messages = [ user(goal) ]

for step in range(MAX_STEPS):          # ← مكبح بالحدّ الأقصى
    reply = client.messages.create(
        model="claude-…",
        messages=messages,
        tools=[search_tool],
    )
    log(step, reply)                       # ← سجّل دائماً للمراقبة

    if reply.stop_reason == "tool_use":
        out = run_tool(reply.tool_name, reply.tool_args)
        messages += [ assistant(reply), tool_result(out) ]
    else:
        break                              # ظهر الجواب النهائي ← انتهِ

حين تستخدم ب. مُشغّل الأدوات في الـSDK، تتكفّل المكتبة بحلقة for نفسها. كلّ ما تكتبه هو «تعريف الأداة» و«الدالّة الفعلية» و«الهدف الأوّل» فقط. أمّا الحلقة وإدارة السجلّ وإعادة نتائج الأدوات فتُعالَج داخلياً.

# ب. تكل الأمر إلى مُشغّل الأدوات في الـSDK (شيفرة زائفة)
runner = ToolRunner(
    model="claude-…",
    tools=[search_tool],       # التعريف
    handlers={"search_weather": run_tool},  # الدالّة الفعلية
)
answer = runner.run(goal)      # تدور الحلقة داخلياً

💡 الأسماء الدقيقة للدوال في الوثائق الرسمية. إنّ client.messages.create وToolRunner هنا شيفرة زائفة لنقل الروح فقط. أسماء الوسائط الفعلية وشكل القيم المُعادة ومعرّفات النماذج تختلف بين SDK كلّ شركة، وتتحدّث كثيراً، فتحقّق دائماً من أحدث نسخة في الوثائق الرسمية. أمسِك بالمفهوم أوّلاً، واملأ التفاصيل من الوثائق — هذه هي الطريقة التي لا تدور بك في طرق ملتوية. للمدخل خطوةً خطوة راجع أيضاً كيف تبني وكيل ذكاء اصطناعي (مدخل).

ننصح بأن «تكتب يدوياً بالطريقة أ مرّةً واحدة، ثم تستريح بالطريقة ب بعد ذلك». حين تدير الحلقة بنفسك ولو مرّة، لا يعود الـSDK صندوقاً أسود، وتصبح قادراً حين تتعثّر على تخيّل ما يجري في الداخل.

شغّله وراقبه

حين ينتهي التركيب، شغّله. المهمّ هنا ألّا تشغّله فحسب، بل أن تراقب «أين فكّر بماذا، وأين فشل». في تطوير الوكلاء، جودةُ هذه المراقبة هي ما يفصل بين النتائج. لذا نزرع السجلّات منذ البداية.

سجّل الحدّ الأدنى التالي في كلّ دورة. في الحلقة اليدوية يكفي سطر log(step, reply) واحد.

🧭 بماذا قرّر

هل حكم الـLLM في تلك الدورة بأن «يستخدم أداة» أم جعلها جواباً نهائياً. نقطة تفرّع الحكم.

🛠 بأيّ وسائط استدعى

اسم الأداة ووسائطها. إن اختلّ هذا اختلّت النتيجة. أكثر موضع يظهر فيه سوء فهم الـLLM.

📦 ماذا أُعيد

القيمة التي أعادتها الأداة. تكمن هنا مواد تفسد الحكم التالي: كبيرة جداً أو فارغة أو خطأ.

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

✅ السجلّ ليس «ميزة راقية» بل ضروريّ منذ البداية. لأنّ داخل الوكيل صعب الرؤية، فبلا سجلّات لا تفهم إطلاقاً «لماذا تحرّك هكذا». الآن يكفي حتى سطر print واحد. أمّا التقييم والقابلية للمراقبة اللذان ينظّمان هذه المراقبة فنتناولهما جدّياً في الفصل الخامس. أوّلاً اكتسب هنا «عادة النظر».

نقاط التعثّر

حتى بأدنى تركيب، حين تشغّله ستصطدم حتماً ببضعة جدران. كلّها تعثّرات نمطية في تطوير الوكلاء، سنحلّها فصلاً بعد فصل. هنا نمسك بـ«حقيقتها» و«الإسعاف الممكن الآن»، ونؤجّل المعالجة الجدّية إلى الفصول اللاحقة.

🔁 الحلقة اللانهائية

يستمرّ باستدعاء الأداة نفسها بلا توقّف ولا يكتمل أبداً. يدور فارغاً لأنّه لا يحكم بأنّ الهدف تحقّق.

الإسعاف: ضع حدّاً أقصى لعدد الدورات دائماً. اكشف الاستدعاءات المتتالية بالوسائط نفسها وأوقفها.

📦 قيمة مُعادة ضخمة جداً

تعيد الأداة JSON ضخماً أو صفحة كاملة، فتُعاد بكاملها إلى المحادثة فتنتفخ التكلفة والتأخير.

الإسعاف: ضيّق من جهة الأداة إلى الحقول اللازمة فقط / لخّص قبل الإعادة. لا تمرّر النصّ الكامل دون معالجة.

🎈 تضخّم السياق

كلّما تراكمت الدورات تكدّس سجلّ المحادثة واقترب من الحدّ، فيصير بطيئاً وباهظاً وغير مستقرّ.

الإسعاف: لخّص التبادلات القديمة واضغطها / تخلّص من نتائج الأدوات غير اللازمة. معالجة جدّية من الفصل التالي.

📊 الثلاثة كلّها مشكلة «تصميم السياق». الحلقة اللانهائية وتضخّم القيمة المُعادة وتضخّم السياق، جذرها كلّه يعود إلى تصميم السياق: «ماذا نمرّر للـLLM وكم». هذه الفكرة منظّمة تحت اسم هندسة السياق، وهي عمود تطوير الوكلاء الفقري. اجعل شعارك أوّلاً «لا تمرّر أكثر من اللازم، ولا تكدّس أكثر من اللازم».

خلاصة هذا الفصل
  • الوكيل الأدنى = العناصر الأربعة مركّبة في أصغر صورة. الفرق عن استخدام API واحد هو الحلقة فقط.
  • الحلقة تكرّر دورة «يفكّر ← يستدعي أداة ← يعيد النتيجة ← يفكّر ثانية ← يتوقّف عند الإنجاز». مكبح الحدّ الأقصى للمرّات ضروريّ.
  • الأداة: تمرّر التعريف (الاسم والوصف والوسائط) للـLLM، وتنفّذ الدالّة الفعلية عندك. الـLLM يطلب فقط.
  • للتنفيذ طريقان: الحلقة اليدوية (للتعلّم) ومُشغّل الأدوات في الـSDK (للعمل الفعلي). الجوهر واحد.
  • راقب بالسجلّات منذ البداية، واكتشف التعثّرات النمطية: الحلقة اللانهائية وتضخّم القيمة المُعادة وتضخّم السياق.

حين تشغّل وكيلاً واحداً، فالدور التالي أن تزيد الأدوات جدّياً. في الفصل الثالث «MCP وربط الأدوات» نربط الأدوات بالخدمات والبيانات الخارجية بطريقة معيارية (MCP)، فنوسّع يد الوكيل دفعةً واحدة. وحين تريد مراجعة الصورة الكاملة للفصل السابق، فارجع إلى الفصل الأول.