PyDA Course
مبتدئ موجّه ⚡ +100 XP +15 نقطة لكل خطوة

بناء وكيل ذكاء اصطناعي

AI AgentsLangChain

كل شيء في الدورة حتى الآن عمل في بيئة تجريبية معزولة داخل المتصفح ، حتى تتمكن من البدء بكتابة Python من اليوم الأول بلا أي إعداد. هذا المشروع هو خطوة التخرّج: ثبّت Python فعليًا على جهازك الخاص، ثم استخدمها لبناء شيء لم تستطع بيئة البرمجة تشغيله أبدًا ، وكيل ذكاء اصطناعي بمفتاح API خاص به، يستدعي نموذج لغوي حقيقي.

هذا اختياري وغير مُقيَّم ، خيار جيد بمجرد إنهاء Python 101 (أساسيات معالجة البيانات من تحليل البيانات ميزة إضافية، وليست شرطًا). راجع مشاريع من العالم الحقيقي للاطلاع على القائمة الكاملة، والتي تنمو باستمرار.

🎯 ما ستفعله

  1. تثبيت uv، أداة حديثة وسريعة لإدارة Python نفسها واعتماديات مشروعك ، دون الحاجة إلى مثبّت Python منفصل.
  2. الحصول على مفتاح API مجاني لنموذج ذكاء اصطناعي. أنت حرّ في استخدام أي مزوّد تفضله ، GitHub Models هو الخيار الافتراضي المقترح أدناه لأنه لا يحتاج تسجيلاً منفصلاً (لديك بالفعل حساب GitHub)، لكن Gemini وGroq وMistral وCerebras وOpenRouter لديها جميعًا مستويات مجانية قابلة للاستخدام أيضًا.
  3. إعداد مشروع صغير وتثبيت deepagents من LangChain.
  4. كتابة وكيل صغير واحد بأداتين تجريبيتين، وتشغيله محليًا، ورؤيته يختار الأداة المناسبة لكل سؤال.
  5. إضافة معالجة أخطاء لحدود المعدل وحلقة محادثة تفاعلية لتتمكن من طرح أسئلة متكررة دون إعادة تشغيل السكربت.

أين تُشغّل هذا

محليًا باستخدام uv هو المسار الذي تتبعه خطوات هذا الدرس، والموصى به ، إنه Python حقيقي يعمل على جهازك الخاص، نفس حركة “التخرّج إلى Python حقيقية” كما في كل مشروع آخر في هذا القسم. يشرح قسم الإعداد أدناه كيفية تثبيته.

GitHub Codespaces بديل بلا أي إعداد إن كنت تفضّل عدم تثبيت أي شيء محليًا الآن: افتح مستودع الدورة كاملاً في Codespace مجاني (Node وPython وuv مثبّتة مسبقًا، حسب ملف .devcontainer/devcontainer.json الخاص بالمستودع) وشغّل نفس أوامر uv تمامًا من طرفية في تبويب متصفحك.

Google Colab أو Kaggle Notebooks أو Binder تعمل أيضًا، لأن هذا المشروع لا يحتاج GPU ، نسخة دفتر ملاحظات حقيقية وقابلة للتشغيل من وكيل هذا المشروع (نفس الأدوات التجريبية وإعداد create_deep_agent كما في الخطوة 1 أدناه) موجودة في examples/ai-agent/notebook.ar.ipynb. انقر على شارة لتشغيله مباشرة، دون أي تثبيت محلي على الإطلاق:

Open In Colab Open In Kaggle Binder

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

الإعداد

كل ما تحتاجه قبل كتابة أي سطر من الوكيل نفسه: Python حقيقية، ومفتاح API مجاني، ومشروع صغير يحمل كليهما.

ثبّت uv

uv أداة واحدة تحل محل سلسلة “ثبّت Python، ثم ثبّت pip، ثم ثبّت أداة بيئة افتراضية، ثم ثبّت الحزم” المعتادة ، تستطيع تثبيت وإدارة إصدارات Python بنفسها، إلى جانب اعتماديات مشروعك.

macOS / Linux (الطرفية):

curl -LsSf https://astral.sh/uv/install.sh | sh

Windows (PowerShell):

powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

أغلق طرفيتك وأعد فتحها، ثم تأكد من أنها ثُبِّتت:

uv --version

تثبيت مفسّر Python حقيقي

على خلاف البيئات التجريبية داخل المتصفح، تستطيع uv جلب وإدارة مفسّر Python حقيقي على جهازك مباشرة ، لا تحتاج لزيارة python.org بشكل منفصل:

uv python install 3.12

هذه لحظة تخرّجك: Python حقيقية، مثبّتة ومُدارة على جهاز حاسوبك الخاص، لا داخل بيئة معزولة في متصفح.

احصل على مفتاح API مجاني لنموذج ذكاء اصطناعي

اختر أي مزوّد تفضله ، لا يتطلب أيٌّ منها بطاقة ائتمان وقت كتابة هذا النص، وهذه الدورة لا تفضّل واحدًا على آخر. الوكيل المثال في مستودع الدورة (examples/ai-agent/) يدعم الستة جميعًا جاهزين للاستخدام، ويُختار عبر إعداد واحد.

المزوّدأين تحصل على مفتاحلماذا قد تختاره
GitHub Models (الافتراضي المقترح)github.com/settings/tokens ، رمز وصول شخصي بصلاحية models: readلا تسجيل منفصل ، لديك بالفعل حساب GitHub. حدود مستوى مجاني أكثر سخاءً من Gemini.
GeminiGoogle AI Studioالخيار الأكثر شيوعًا في المراجع؛ استُخدم في مسودات سابقة من هذه الصفحة.
Groqconsole.groq.com/keysاستدلال سريع، مستوى مجاني سخي، بلا بطاقة.
Mistralconsole.mistral.ai/api-keysمن أكثر الحصص المجانية الدائمة سخاءً.
Cerebrascloud.cerebras.aiحجم رموز يومي مرتفع، بلا بطاقة.
OpenRouteropenrouter.ai/keysواجهة برمجة واحدة، نماذج مجانية عديدة ، جيدة لمقارنة المزوّدين.

أيًّا كان اختيارك، العملية نفسها:

  1. سجّل الدخول وولّد مفتاح API على موقع ذلك المزوّد.
  2. لا تلصق هذا المفتاح مطلقًا مباشرة في الكود أو تُودعه في مستودع. اضبطه كمتغيّر بيئة بدلاً من ذلك:
# macOS / Linux (أضفه إلى ~/.bashrc أو ~/.zshrc ليبقى دائمًا)
export GITHUB_TOKEN="your-key-here"   # أو GOOGLE_API_KEY، GROQ_API_KEY، إلخ -- حسب مزوّدك

# Windows (PowerShell)
$env:GITHUB_TOKEN = "your-key-here"

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

ملف .env غالبًا أكثر ملاءمة من export

بدلاً من استخدام export لمفتاح في كل جلسة طرفية جديدة، يمكنك وضعه في ملف .env داخل مجلد مشروعك (انظر .env.example في مثال المستودع) وتحميله تلقائيًا بحزمة python-dotenv ، مشروحة أدناه.

إعداد المشروع بـ uv

uv init ai-agent
cd ai-agent
uv add deepagents langchain-openai python-dotenv

ينشئ uv init مشروعًا صغيرًا (ملف pyproject.toml يتتبع اعتمادياتك)، ويثبّت uv add الحزم في بيئة معزولة لذلك المشروع ، تلقائيًا، دون إعداد بيئة افتراضية يدويًا. deepagents هو إطار عمل LangChain لبناء وكلاء مزوّدين بتخطيط واستخدام أدوات وتفويض إلى وكلاء فرعيين مدمج فيهم؛ langchain-openai هي حزمة التكامل التي يستخدمها هذا المثال للتحدث مع GitHub Models (واجهته البرمجية متوافقة مع OpenAI، لذا تعمل حزمة تكامل OpenAI معه ، انظر التلميح أدناه إن اخترت مزوّدًا مختلفًا)؛ python-dotenv تتيح لك إبقاء مفتاح API في ملف .env محلي بدلاً من استخدام export في كل جلسة.

إن اخترت مزوّدًا مختلفًا أعلاه، استبدل langchain-openai بحزمة ذلك المزوّد الخاصة ، langchain-google-genai (لـ Gemini)، langchain-groq (لـ Groq)، أو langchain-mistralai (لـ Mistral). Cerebras وOpenRouter متوافقان أيضًا مع OpenAI، لذا يستخدمان langchain-openai أيضًا، فقط بـ base_url مختلف.

تحقق من الوثائق الحالية ، واسم النموذج

أطر عمل الوكلاء تتطور بسرعة، وكذلك أسماء النماذج: تُعاد تسميتها ويُوقَف دعمها على مقياس أشهر لا سنوات. وسائط create_deep_agent نفسها تغيّرت بالفعل مرة منذ مسودات سابقة لهذه الصفحة (إنها system_prompt، لا instructions) ، تذكير بأن هذا المقتطف يمكن أن يصبح قديمًا حتى بعد التحقق منه مرة واحدة. استخدم معرّف نموذج صريحًا ومُرقّمًا بدلاً من لاحقة -latest: عدة مزوّدين، بما فيهم Google، أوقفوا دعم تلك اللواحق لأنها تستبدل النموذج بصمت بنسخة جديدة، مما قد يكسر كودًا يعمل دون أي تحذير. قبل تشغيل هذا، تحقق من صفحة التسعير/النماذج الحالية لمزوّدك، وتصفّح ملف README الخاص بـ deepagents نفسه لواجهته البرمجية الحالية.

✅ قائمة التحقق

  • ✅ يطبع uv --version رقم الإصدار.
  • ai-agent/ موجود مع ملف pyproject.toml، وحزم deepagents وlangchain-openai وpython-dotenv مثبّتة.
  • ✅ لديك مفتاح API حقيقي من مزوّد واحد، مُصدَّر كمتغيّر بيئة أو محفوظ في ملف .env في مجلد مشروعك ، غير مُلصَق في أي سكربت.

الخطوة 1: اكتب وكيلك الأول

هذه هي الخطوة الجوهرية: دالتا Python عاديتان تصبحان أدوات الوكيل، ونموذج موصول بمفتاح API الخاص بك، وcreate_deep_agent يربطهما معًا. سلاسل التوثيق (docstrings) على كل دالة هي ما يقرأه النموذج ليقرر أي أداة تناسب سؤالًا ، لا الكود داخل تلك الدوال.

1.1 اكتب agent.py

أنشئ ملف .env (لا تُودعه في المستودع أبدًا) بمفتاح المزوّد الذي اخترته:

# .env
GITHUB_TOKEN=your-key-here

الآن أنشئ agent.py:

👟 تلميح البداية :

دالتا Python عاديتان بنوعيّ حقل (type hints) وسلاسل توثيق تصبحان أدوات الوكيل؛ وcreate_deep_agent(model=..., tools=[...], system_prompt=...) يربطهما بالنموذج ، سلاسل التوثيق هي ما يقرأه النموذج ليقرر أي أداة تناسب سؤالًا، لا الكود داخل تلك الدوال:

import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from deepagents import create_deep_agent

load_dotenv()  # reads .env into the environment, if present

def search_course_topics(query: str) -> str:
    """A toy tool: pretends to look up whether a topic was covered in this course."""
    topics = ["variables", "loops", "functions", "csv files", "pandas", "dataframes", "groupby"]
    matches = [t for t in topics if query.lower() in t]
    return f"Matching topics: {matches}" if matches else "No matching topics found."

def count_weeks_remaining(current_week: int) -> str:
    """A second toy tool: how many weeks are left in the 10-week course."""
    remaining = max(0, 10 - current_week)
    return f"{remaining} week(s) remaining out of 10."

model = ChatOpenAI(
    model="gpt-4o-mini",  # confirm this still has a free tier before running — see the tip above
    api_key=os.environ["GITHUB_TOKEN"],
    base_url="https://models.github.ai/inference",
)

agent = create_deep_agent(
    model=model,
    tools=[search_course_topics, count_weeks_remaining],
    system_prompt="You help students figure out whether a topic was covered in their course.",
)

if __name__ == "__main__":
    result = agent.invoke({"messages": [{"role": "user", "content": "Did we cover groupby?"}]})
    print(result["messages"][-1].content)  # just the final answer, not the full internal trace

🎯 الناتج المتوقع :

يطبع uv run python agent.py سطرًا واحدًا ، إجابة الوكيل النهائية، شيء يشبه Yes, "groupby" was covered in the course.

🩹 إذا لم يعمل :

خطأ KeyError: 'GITHUB_TOKEN' يعني أن متغيّر البيئة/قيمة .env لا يُعثر عليها ، تأكد أن .env في نفس مجلد agent.py، دون خطأ إملائي في اسم المتغيّر. خطأ 401/403 يعني أن المفتاح نفسه خاطئ، منتهي الصلاحية، أو يفتقد الصلاحية الصحيحة ، أعد توليده. خطأ حد المعدل 429 متوقع ومُعالَج في الخطوة 3، وليس خطأً.

1.2 افهم ماذا يفعلان load_dotenv وcreate_deep_agent

يقرأ load_dotenv() ملف .env الخاص بك إلى os.environ قبل تشغيل أي شيء آخر، لذا يجد os.environ["GITHUB_TOKEN"] المفتاح الذي ضبطته في قسم الإعداد ، نفس مفهوم وحدة os كما في قراءة input() من لوحة المفاتيح، إلا أنه يقرأ من ملف بدلاً من ذلك. يربط create_deep_agent النموذج بقائمة من دوال Python يستطيع الوكيل استدعاءها كـ أدوات ، هذه هي الفكرة الجوهرية وراء الوكلاء: نموذج لغوي لا يكتفي بالرد بنص، بل يقرر استدعاء كودك، وقراءة النتيجة، واستخدامها لإثراء إجابته.

لاحظ tools=[search_course_topics, count_weeks_remaining] ، أداتان، لا واحدة. يختار النموذج أي أداة (إن وُجدت) تناسب السؤال، بمفرده تمامًا: اسأل “هل تناولنا groupby؟” فيستدعي search_course_topics؛ اسأل “كم أسبوعًا تبقى إن كنت في الأسبوع 4؟” فيستدعي count_weeks_remaining بدلاً من ذلك. لن تكتب أبدًا سلسلة if/elif توجّه الأسئلة إلى الأدوات بنفسك ، سلسلة التوثيق (docstring) على كل دالة (النص المُحاط بثلاث علامات اقتباس مباشرة بعد def) هي ما يقرأه النموذج ليقرر أي أداة تناسب أي طلب، تمامًا مثل سلاسل التوثيق في الأسبوع 4 من Python 101، إلا أن نموذجًا لغويًا هو من يقرأها هنا، لا إنسان يتصفح كودك.

1.3 تحقّق

✅ قائمة التحقق

  • ✅ يطبع uv run python agent.py إجابة متماسكة واحدة ، لا تتبّع خطأ ولا سلسلة فارغة.
  • ✅ تشير الإجابة إلى بيانات الأداة (تذكر أن “groupby” موضوع في الدورة)، لا مجرد استجابة عامة.
  • ✅ يمكنك أن تشرح، بكلماتك الخاصة، لماذا تمرّر tools=[search_course_topics, count_weeks_remaining] دالتين فيختار النموذج واحدة بمفرده.

🤔 سؤال (أسئلة) سقراطي(ة)

  • لو أضفت أداة ثالثة def get_weather(city: str) -> str: ... وسألت “هل تناولنا groupby؟”، هل سيستدعيها النموذج؟ لماذا ولماذا لا ، وما الذي يمنع النموذج من استدعاء أدوات لا تناسب السؤال؟
  • تقول سلسلة التوثيق على search_course_topics “يتظاهر بالبحث”. وكيل حقيقي كان ليبحث في قاعدة بيانات أو نظام ملفات بدلاً من ذلك. ما الذي كان سيتغير في استدعاء create_deep_agent لو تغيّر تنفيذ الدالة ، هل كان سلوك الوكيل سيتغير، أم فقط المنطق الداخلي للأداة؟

الخطوة 2: افهم كيف تعمل حلقة الوكيل

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

2.1 الحلقة ذات الخطوات الخمس

كل تفاعل مع الوكيل يتبع النمط نفسه:

  1. يذهب سؤالك إلى النموذج، إلى جانب قائمة الأدوات المتاحة (أسماؤها، معاملاتها، وسلاسل توثيقها ، لا كودها).
  2. يرد النموذج إما بإجابة نصية نهائية، أو بطلب استدعاء أداة محددة واحدة بمعاملات محددة.
  3. إن طلب استدعاء أداة، فإن كودك الخاص بـ Python (لا النموذج) هو من يشغّل تلك الدالة فعليًا ويحصل على نتيجة حقيقية.
  4. تعود تلك النتيجة إلى النموذج كسياق جديد، وتتكرر الحلقة من الخطوة 2 ، قد يستدعي النموذج أداة أخرى، أو تصبح لديه الآن معلومات كافية للإجابة.
  5. بمجرد أن يرد النموذج بنص دون طلب أداة آخر، تتوقف الحلقة وتلك هي إجابتك النهائية.

هذا بالضبط سبب إمكانية حدوث خطأ حد المعدل (انظر الخطوة 3) حتى فيما يبدو “سؤالاً واحدًا” ، سؤال يحتاج استدعاءَي أداتين يكلّف ثلاث رحلات ذهاب وإياب على الأقل إلى النموذج (قرار استدعاء الأداة أ، قرار استدعاء الأداة ب، إنتاج الإجابة النهائية)، لا رحلة واحدة.

2.2 افحص التتبّع الداخلي الكامل

يعرض result["messages"][-1].content أعلاه عمدًا الإجابة النهائية فقط. إن طبعت result كاملة بدلاً من ذلك، سترى شيئًا أكثر ضجيجًا بكثير ، كل رسالة تتبعها LangGraph داخليًا، وكل واحدة تحمل حقول دفترية إلى جانب المحتوى الفعلي:

result = agent.invoke({"messages": [{"role": "user", "content": "Did we cover groupby?"}]})
for message in result["messages"]:
    print(type(message).__name__, "->", message)

مُبسّطًا إلى ما يهم فعلاً، يبدو التتبّع وراء ذلك السؤال الواحد كالتالي:

#نوع الرسالةما تحمله
1HumanMessageسؤالك: "Did we cover groupby?"
2AIMessage (بلا نص)قرر النموذج استدعاء search_course_topics(query="groupby") ، لا إجابة بعد، مجرد طلب أداة
3ToolMessageالقيمة الحقيقية المُعادة من دالة Python الخاصة بك: "Matching topics: ['groupby']"
4AIMessage (نهائية)إجابة النموذج الفعلية، الآن وقد حصل على نتيجة الأداة: "Yes, groupby was covered."

الأجزاء المزعجة التي يمكنك تجاهلها بأمان عند قراءة تتبّع خام: حقول id/tool_call_id (دفترية لمطابقة استدعاء أداة بنتيجته)، آثار التفكير الداخلي الخاصة بالمزوّد (غير مخصصة ليقرأها إنسان)، وusage_metadata (عدّ الرموز، مفيد لتتبع التكلفة، غير ذي صلة بالمحادثة نفسها). هذا الشكل ذو الصفوف الأربعة ، سؤال، استدعاء أداة، نتيجة أداة، إجابة ، هو حلقة الوكيل بأكملها من القسم السابق، مكتوبة فقط كبيانات بدلاً من قائمة مرقّمة.

2.3 تحقّق الفهم

✅ قائمة التحقق

  • ✅ يمكنك تتبّع الشكل ذو الصفوف الأربعة (HumanMessage → AIMessage → ToolMessage → AIMessage) في مخرجات تشغيلك الخاص.
  • ✅ يمكنك أن تشرح لماذا ينتج سؤال يتطلب استدعاءَي أداتين ثلاث رحلات ذهاب وإياب عبر API على الأقل، لا رحلة واحدة.
  • ✅ يمكنك الإشارة إلى أي صف في التتبّع هو نتيجة الأداة الفعلي (من كود Python الخاص بك)، لا تنبؤ النموذج.

🤔 سؤال (أسئلة) سقراطي(ة)

  • يرى النموذج أسماء الأدوات وسلاسل توثيقها، لا الكود المصدري. ما الذي كان سيحدث لو كانت لأداتين نفس سلسلة التوثيق ، هل كان النموذج سيختار واحدة عشوائيًا، أم كان لا يزال قادرًا على التمييز بينهما؟ وكيف يؤثر ذلك على طريقة كتابتك لسلاسل توثيق الأدوات في مشاريع حقيقية؟
  • لا نص في الصف 2 من التتبّع ، إنه مجرد طلب أداة. لو طبعت message.content فقط لكل رسالة، ماذا كنت سترى لذلك الصف، ولماذا يجعل ذلك message.content وحده رؤية ناقصة لما حدث؟

الخطوة 3: تعامل مع حدود المعدل بإعادة المحاولة

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

3.1 اكتب غلاف إعادة المحاولة

👟 تلميح البداية :

لُف استدعاء agent.invoke(...) بـtry/except يلتقط الخطأ، وينتظر، ويعيد المحاولة تلقائيًا ، تمامًا النمط من الأسبوع 4 من Python 101:

import time

def ask_with_retry(agent, question: str, max_retries: int = 3) -> str:
    """Ask the agent a question, retrying automatically on rate-limit errors."""
    for attempt in range(max_retries):
        try:
            result = agent.invoke({"messages": [{"role": "user", "content": question}]})
            return result["messages"][-1].content
        except Exception as e:
            error_str = str(e)
            if "RESOURCE_EXHAUSTED" in error_str or "429" in error_str:
                wait = 30 * (attempt + 1)
                print(f"  Rate limited — waiting {wait}s before retry {attempt + 1}/{max_retries}...")
                time.sleep(wait)
            else:
                raise
    raise RuntimeError(f"Failed after {max_retries} retries due to rate limits.")

answer = ask_with_retry(agent, "Did we cover groupby?")
print(answer)

🎯 الناتج المتوقع :

نفس إجابة الخطوة 1، لكن إذا اصطدمت بحد معدل، تطبع الدالة رسالة “Rate limited ، waiting 30s…” وتعيد المحاولة بصمت.

🩹 إذا لم يعمل :

إذا رأيت رسالة إعادة المحاولة لكن الإجابة النهائية ما زالت خطأ حد المعدل، فـmax_retries ليست عالية بما يكفي أو وقت الانتظار قصير جدًا ، جرّب زيادتهما معًا. إذا كانت أخطاء غير متعلقة بحد المعدل تُبتلع، ففحص "RESOURCE_EXHAUSTED" لا يلتقط السلسلة الصحيحة ، اطبع error_str لترى رسالة الخطأ الدقيقة التي يعيدها مزوّدك.

3.2 تحقّق

✅ قائمة التحقق

  • ✅ تعيد ask_with_retry(agent, "Did we cover groupby?") نفس إجابة استدعاء agent.invoke الخام.
  • ✅ يطلق خطأ حد المعدل إعادة محاولة (ترى رسالة “Rate limited”)، لا تعطلًا فوريًا.
  • ✅ بعد max_retries من أخطاء حد المعدل المتتالية، ترفع الدالة RuntimeError، لا تعيد None بصمت.

🤔 سؤال (أسئلة) سقراطي(ة)

  • ينتظر الإعادة 30 * (attempt + 1) ثانية ، 30 ثانية، 60 ثانية، 90 ثانية. من أين يأتي هذا الرقم، وما الذي كان سيحدث لو استخدمت انتظارًا ثابتًا مدته 5 ثوانٍ بدلاً من ذلك على مزوّد يقترح الانتظار 60 ثانية؟
  • ما الذي كان سيحدث لو أعدت المحاولة على كل استثناء، لا على أخطاء حد المعدل فقط؟ أعطِ سيناريو ملموسًا واحدًا حيث كانت إعادة المحاولة الفورية ستجعل المشكلة أسوأ.

الخطوة 4: ابنِ حلقة محادثة تفاعلية

سؤال لمرة واحدة عرض توضيحي. الحلقة التفاعلية ، حيث تكتب أسئلة ويجيب الوكيل، واحدة بعد أخرى، حتى تقرر الخروج ، أداة كنت لتستخدمها فعلًا. تربط هذه الخطوة معالج إعادة المحاولة بـ REPL يعمل في طرفيتك.

4.1 اكتب حلقة المحادثة

👟 تلميح البداية :

while True هو نمط REPL القياسي ، اطبع، اقرأ، عالج، اطبع، كرر. فحص input() للخروج هو شرط الخروج:

def chat(agent):
    """Interactive REPL: type questions, get answers, type 'quit' to exit."""
    print("Agent chat — type 'quit' to exit.\n")
    while True:
        try:
            question = input("You: ").strip()
        except (EOFError, KeyboardInterrupt):
            print("\nGoodbye!")
            break
        if not question:
            continue
        if question.lower() in {"quit", "exit", "q"}:
            print("Goodbye!")
            break
        answer = ask_with_retry(agent, question)
        print(f"Agent: {answer}\n")

if __name__ == "__main__":
    chat(agent)

🎯 الناتج المتوقع :

يطبع السكربت “Agent chat ، type ‘quit’ to exit.” وينتظر إدخالًا. كل سؤال ينتج إجابة من الوكيل، وكتابة quit تُخرج من الحلقة نظيفًا.

🩹 إذا لم يعمل :

إذا رفع input() فورًا EOFError (يحدث في بعض بيئات دفاتر الملاحظات)، فأنت لست في طرفية حقيقية ، استخدم uv run python agent.py من طرفية بدلاً من التشغيل داخل خلية دفتر ملاحظات. إذا طبع السكربت “Goodbye!” دون أن يطلب إدخالًا، فاستدعاء input() قد تُخطئه ، تحقق أن chat(agent) داخل كتلة if __name__ == "__main__":.

4.2 تحقّق

✅ قائمة التحقق

  • ✅ يدخل uv run python agent.py حلقة تفاعلية ويطبع “You: ”.
  • ✅ كتابة سؤال تطبع إجابة؛ وكتابة quit أو exit تُخرج نظيفًا.
  • Ctrl+C (KeyboardInterrupt) يُخرج من الحلقة نظيفًا أيضًا مع “Goodbye!”.

🤔 سؤال (أسئلة) سقراطي(ة)

  • تستدعي حلقة المحادثة ask_with_retry على كل سؤال، ما يعني أن إعادة محاولات حد المعدل تحدث داخل الحلقة. ما الذي يحدث إذا كتب مستخدم سؤالاً، واصطدم بحد معدل، وانتظرت إعادة المحاولة 30 ثانية ، المستخدم يحدّق في طرفية فارغة. كيف ستحسّن تجربة المستخدم أثناء الانتظار؟
  • لا ذاكرة محادثة لهذه الحلقة ، كل سؤال مستقل. ما الذي كان سيتغير في agent.invoke({"messages": [...]}) لو أردت أن يتذكر الوكيل آخر ثلاثة أسئلة وإجابات؟ وكيف يدعم messages كقائمة هذا بالفعل؟

⚠️ المآزق الشائعة

  • KeyError: 'GITHUB_TOKEN' عند الإقلاع. ملف .env في مجلد خاطئ (يجب أن يكون في نفس مجلد agent.py)، أو لم يُصدَّر متغيّر البيئة في جلسة الطرفية الحالية. تحقق بـ echo $GITHUB_TOKEN (macOS/Linux) أو echo $env:GITHUB_TOKEN (PowerShell) ، إذا لم يطبع شيئًا، فالمفتاح غير محمّل.
  • أخطاء حد المعدل (429 RESOURCE_EXHAUSTED) على أسئلة متتالية. هذا ليس خطأً ، إنه حد طلبات الدقيقة في المستوى المجاني. كل دورة من الوكيل (قرار استدعاء أداة → قراءة النتيجة → إجابة) تكلف استدعاءَي أو ثلاثة عبر API، لذا تصطدم بالحد أسرع مما تتوقع. معالج إعادة المحاولة في الخطوة 3 يتعامل مع هذا؛ والانتظار للمدة المقترحة وإعادة التشغيل جيدٌ أيضًا.
  • النموذج يتجاهل أدواتك ويجيب من المعرفة العامة. إذا أجاب النموذج “ليس لديّ وصول لبيانات الدورة” بدلاً من استدعاء أداة، فسلسلة النظام أو سلاسل توثيق الأدوات ليست واضحة بما يكفي حول ما تفعله الأدوات. اجعل سلسلة النظام صريحة: “استخدم الأدوات المتاحة للإجابة على الأسئلة. لا تجب من المعرفة العامة.”
  • تغيّر واجهة create_deep_agent بين الإصدارات. كانت وسيطة الكلمة system_prompt تسمى instructions في إصدارات سابقة ، إذا رأيت TypeError: unexpected keyword argument 'system_prompt'، تحقق من ملف README الحالي لـ deepagents وحدّث لتوافقه. أطر عمل الوكلاء تتطور بسرعة؛ ثبّت اعتمادياتك في pyproject.toml إن أردت إعادة انتاجية.

ما بنيته للتو

وكيل ذكاء اصطناعي عامل بمفتاح API خاص به، وأداتين قابلين للاستدعاء، ومعالج إعادة محاولة لحدود المعدل، وحلقة محادثة تفاعلية ، يعمل محليًا على جهازك بـ Python حقيقية، لا داخل بيئة معزولة في متصفح. الأدوات بسيطة عمدًا، لكن الشكل هو نفسه الذي يشغّل أنظمة أقدر بكثير: نموذج يفكّر في مهمة، يقرر أي أداة يستدعي وبأي معاملات، يقرأ ناتج الأداة، ويواصل ، أحيانًا يستدعي عدة أدوات بالتتابع قبل أن يرد. لقد بنيت للتو أصغر نسخة ممكنة من تلك الحلقة، من الصفر.

شغّل نسخة أوفى دون أي إعداد محلي

examples/ai-agent/ في مستودع الدورة ليست نسخة من الكود أعلاه ، إنها نسخة أوفى عمدًا، بأدوات حقيقية (تبحث في ملفات دروس هذه الدورة الفعلية وتحلل بياناتها الفعلية بـ pandas، بدلاً من قائمة مواضيع ثابتة مكتوبة) ودعم لكل المزوّدين الستة من الجدول أعلاه، يُختار بإعداد واحد. استنسخها، أو افتح المستودع كاملاً في GitHub Codespace (Node وPython وuv مثبّتون مسبقًا) وشغّلها من هناك.

إلى أين تذهب من هنا

  • امنح وكيلك أداة مفيدة حقًا، لا مجرد أداة تجريبية ، أداة تقرأ ملفًا محليًا حقيقيًا، أو تستدعي واجهة برمجة عامة حقيقية. نسخة examples/ai-agent/ في المستودع تفعل هذا بالفعل: تبحث في ملفات دروس هذه الدورة الحقيقية وتحلل بياناتها الحقيقية بـ pandas، بدلاً من التخمين.
  • انظر إلى دعم deepagents لـ الوكلاء الفرعيين ، تفويض جزء من مهمة إلى وكيل معطى تعليمات منفصلة، مشابه لكيفية تفويض مدير مهمة فرعية إلى متخصص:
from deepagents import create_deep_agent

research_subagent = {
    "name": "topic-researcher",
    "description": "Looks up whether a topic was covered in the course, in detail.",
    "system_prompt": "You research course topics thoroughly using the available tools.",
    "tools": [search_course_topics],
}

agent = create_deep_agent(
    model=model,
    tools=[search_course_topics, count_weeks_remaining],
    subagents=[research_subagent],
    system_prompt="Delegate topic-research questions to the topic-researcher sub-agent.",
)

يستطيع الوكيل الرئيسي الآن تسليم مهمة فرعية إلى topic-researcher بدلاً من فعل كل شيء بنفسه ، مفيد بمجرد أن تبدأ تعليمات وقائمة أدوات وكيل واحد بالتضخم أكثر من اللازم للتفكير فيها في مكان واحد.

  • راجع محتوى try/except والـ class المكافئ من Python 101 ، كود الوكلاء الحقيقي يعتمد على كليهما باستمرار (التقاط استدعاء أداة فاشل، تغليف حالة مترابطة في كلاس) بطرق تجنّبها منهج هذه الدورة الأساسي عمدًا.

شارك وكيلك مع الصف

بنيت شيئًا تفتخر به؟ examples/student-projects/ معرض لوكلاء أرسلها طلاب آخرون ، وملف README الخاص به يحتوي شرحًا كاملاً وودودًا للمبتدئين لإضافة وكيلك عبر طلب سحب (pull request)، حتى لو لم تستخدم git من قبل: عمل fork للمستودع، إنشاء فرع، تثبيت ملفاتك، وفتح طلب السحب، خطوة بخطوة. لا يُفترض أي خبرة سابقة بـ git.

مرحبًا بك في كتابة Python خارج المتصفح. 🎓

أكمل كل خطوة ثم حدد المشروع كمكتمل لجمع نقاطه.