نشر وكلاء ADK على Google Kubernetes Engine (GKE)

1- مقدمة

نظرة عامة

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

في هذا المختبر، ستستخدم نظامًا متعدد الوكلاء تم إنشاؤه باستخدام Google Agent Development Kit (ADK) ونشره في بيئة إنتاجية على Google Kubernetes Engine (GKE).

وكيل فريق تطوير أفكار الأفلام

التطبيق النموذجي المستخدَم في هذا التمرين هو "فريق مفهوم فيلم" يتألف من عدة وكلاء متعاونين: باحث وكاتب سيناريو وكاتب ملفات. تعمل هذه الوكلاء معًا لمساعدة المستخدم في تبادل الأفكار ووضع مخطط لعرض فيلم عن شخصية تاريخية.

مخطّط بياني لمسار الوكلاء

لماذا يجب النشر على GKE؟

لإعداد وكيلك لتلبية متطلبات بيئة الإنتاج، تحتاج إلى منصة مصمَّمة لتوفير قابلية التوسّع والأمان وفعالية التكلفة. توفّر Google Kubernetes Engine (GKE) هذه الأسس القوية والمرنة لتشغيل تطبيقك المحفوظ في حاوية.

يوفّر ذلك العديد من المزايا لوحدة عمل الإنتاج:

  • التوسّع التلقائي والأداء: يمكنك التعامل مع الزيارات غير المتوقّعة باستخدام HorizontalPodAutoscaler (HPA) الذي يضيف أو يزيل نُسخًا طبق الأصل من الوكيل تلقائيًا استنادًا إلى الحمل. بالنسبة إلى مهام عمل الذكاء الاصطناعي الأكثر تطلّبًا، يمكنك إرفاق أدوات تسريع الأجهزة، مثل وحدات معالجة الرسومات ووحدات معالجة الموتّرات.
  • إدارة الموارد بفعالية من حيث التكلفة: يمكنك تحسين التكاليف باستخدام GKE Autopilot، الذي يدير البنية الأساسية تلقائيًا، وبالتالي لا تدفع إلا مقابل الموارد التي يطلبها تطبيقك.
  • الأمان والمراقبة المتكاملان: يمكنك الاتصال بشكل آمن بخدمات Google Cloud الأخرى باستخدام Workload Identity، ما يغنيك عن إدارة مفاتيح حساب الخدمة وتخزينها. يتم تلقائيًا نقل جميع سجلّات التطبيقات إلى Cloud Logging لإجراء عمليات المراقبة وتصحيح الأخطاء بشكل مركزي.
  • التحكّم وإمكانية النقل: تجنَّب الاعتماد الحصري على مورّد واحد باستخدام Kubernetes المفتوحة المصدر. تطبيقك قابل للنقل ويمكن تشغيله على أي مجموعة Kubernetes، سواء في المؤسسة أو في سُحب أخرى.

أهداف الدورة التعليمية

في هذه الميزة الاختبارية، ستتعرّف على كيفية تنفيذ المهام التالية:

  • توفير مجموعة GKE Autopilot
  • تضمين تطبيق في حاوية باستخدام Dockerfile ودفع الصورة إلى Artifact Registry
  • يمكنك ربط تطبيقك بشكل آمن بواجهات Google Cloud APIs باستخدام Workload Identity.
  • كتابة بيانات Kubernetes وتطبيقها على عملية نشر وخدمة
  • عرض تطبيق على الإنترنت باستخدام LoadBalancer
  • اضبط إعدادات التوسّع التلقائي باستخدام HorizontalPodAutoscaler (HPA).

2. إعداد المشروع

حساب Google

إذا لم يكن لديك حساب شخصي على Google، عليك إنشاء حساب على Google.

استخدام حساب شخصي بدلاً من حساب تابع للعمل أو تديره مؤسسة تعليمية

تسجيل الدخول إلى Google Cloud Console

سجِّل الدخول إلى Google Cloud Console باستخدام حساب Google شخصي.

تفعيل الفوترة

إعداد حساب فوترة شخصي

إذا أعددت الفوترة باستخدام أرصدة Google Cloud، يمكنك تخطّي هذه الخطوة.

لإعداد حساب فوترة شخصي، يُرجى الانتقال إلى هنا لتفعيل الفوترة في Cloud Console.

ملاحظات:

  • يجب أن تكلّف إكمال هذا المختبر أقل من دولار أمريكي واحد من موارد السحابة الإلكترونية.
  • يمكنك اتّباع الخطوات في نهاية هذا المختبر لحذف الموارد وتجنُّب المزيد من الرسوم.
  • يكون المستخدمون الجدد مؤهّلين للاستفادة من الفترة التجريبية المجانية بقيمة 300 دولار أمريكي.

إنشاء مشروع (اختياري)

إذا لم يكن لديك مشروع حالي تريد استخدامه في هذا المختبر، يمكنك إنشاء مشروع جديد هنا.

3- فتح Cloud Shell Editor

  1. انقر على هذا الرابط للانتقال مباشرةً إلى محرّر Cloud Shell
  2. إذا طُلب منك منح الإذن في أي وقت اليوم، انقر على منح الإذن للمتابعة.انقر لتفويض Cloud Shell
  3. إذا لم تظهر المحطة الطرفية في أسفل الشاشة، افتحها باتّباع الخطوات التالية:
    • انقر على عرض.
    • انقر على Terminalفتح نافذة طرفية جديدة في "محرِّر Cloud Shell"
  4. في الوحدة الطرفية، اضبط مشروعك باستخدام الأمر التالي:
    gcloud config set project [PROJECT_ID]
    
    • مثال:
      gcloud config set project lab-project-id-example
      
    • إذا تعذّر عليك تذكُّر معرّف مشروعك، يمكنك إدراج جميع معرّفات المشاريع باستخدام:
      gcloud projects list
      
      ضبط رقم تعريف المشروع في نافذة Cloud Shell Editor
  5. من المفترض أن تظهر لك هذه الرسالة:
    Updated property [core/project].
    

4. تفعيل واجهات برمجة التطبيقات

لاستخدام GKE وArtifact Registry وCloud Build وVertex AI، عليك تفعيل واجهات برمجة التطبيقات الخاصة بها في مشروعك على Google Cloud.

  • في وحدة التحكّم، فعِّل واجهات برمجة التطبيقات:
    gcloud services enable \
      container.googleapis.com \
      artifactregistry.googleapis.com \
      cloudbuild.googleapis.com \
      aiplatform.googleapis.com
    
    عند انتهاء هذا الأمر، من المفترض أن تظهر لك نتيجة مشابهة لما يلي:
    Operation "operations/acf.p2-176675280136-b03ab5e4-3483-4ebf-9655-43dc3b345c63" finished successfully.
    

لمحة عن واجهات برمجة التطبيقات

  • تتيح لك Google Kubernetes Engine API (container.googleapis.com) إنشاء وإدارة مجموعة GKE التي تشغّل وكيلك. توفّر GKE بيئة مُدارة لنشر تطبيقاتك المحفوظة في حاويات وإدارتها وتوسيع نطاقها باستخدام البنية الأساسية من Google.
  • توفّر Artifact Registry API (artifactregistry.googleapis.com) مستودعًا آمنًا وخاصًا لتخزين صورة حاوية الوكيل. وهي عبارة عن تطوير لخدمة Container Registry وتتكامل بسلاسة مع GKE وCloud Build.
  • يتم استخدام واجهة برمجة التطبيقات Cloud Build (cloudbuild.googleapis.com) من خلال الأمر gcloud builds submit لإنشاء صورة الحاوية في السحابة الإلكترونية من ملف Dockerfile. وهي منصة CI/CD بدون خادم تنفّذ عمليات الإنشاء على بنية Google Cloud الأساسية.
  • تتيح واجهة برمجة التطبيقات Vertex AI API (aiplatform.googleapis.com) للوكيل الذي تم نشره التواصل مع نماذج Gemini لتنفيذ مهامه الأساسية. توفّر هذه المنصة واجهة برمجة تطبيقات موحّدة لجميع خدمات الذكاء الاصطناعي من Google Cloud.

5- إعداد بيئة التطوير

إنشاء بنية الدليل

  1. في وحدة التحكّم، أنشئ دليل المشروع والأدلة الفرعية اللازمة:
    mkdir -p ~/adk_multiagent_system_gke/workflow_agents
    cd ~/adk_multiagent_system_gke
    
  2. في الوحدة الطرفية، نفِّذ الأمر التالي لفتح الدليل في مستكشف "محرِّر Cloud Shell".
    cloudshell open-workspace ~/adk_multiagent_systems
    
  3. سيتم إعادة تحميل لوحة "المستكشف" على اليمين. من المفترض أن تظهر لك الآن الدلائل التي أنشأتها.
    لقطة شاشة لبنية الملف الحالية
    أثناء إنشاء الملفات في الخطوات التالية، ستظهر الملفات في هذا الدليل.

إنشاء ملفات أولية

ستنشئ الآن ملفات البدء اللازمة للتطبيق.

  1. أنشئ callback_logging.py من خلال تنفيذ ما يلي في الوحدة الطرفية. يتعامل هذا الملف مع تسجيل البيانات لأغراض المراقبة.
    cat <<EOF > ~/adk_multiagent_systems/callback_logging.py
    """
    Provides helper functions for observability. Handles formatting and sending 
    agent queries, responses, and tool calls to Google Cloud Logging to aid 
    in monitoring and debugging.
    """
    import logging
    import google.cloud.logging
    
    from google.adk.agents.callback_context import CallbackContext
    from google.adk.models import LlmResponse, LlmRequest
    
    
    def log_query_to_model(callback_context: CallbackContext, llm_request: LlmRequest):
        cloud_logging_client = google.cloud.logging.Client()
        cloud_logging_client.setup_logging()
        if llm_request.contents and llm_request.contents[-1].role == 'user':
             if llm_request.contents[-1].parts and "text" in llm_request.contents[-1].parts:
                last_user_message = llm_request.contents[-1].parts[0].text
                logging.info(f"[query to {callback_context.agent_name}]: " + last_user_message)
    
    def log_model_response(callback_context: CallbackContext, llm_response: LlmResponse):
        cloud_logging_client = google.cloud.logging.Client()
        cloud_logging_client.setup_logging()
        if llm_response.content and llm_response.content.parts:
            for part in llm_response.content.parts:
                if part.text:
                    logging.info(f"[response from {callback_context.agent_name}]: " + part.text)
                elif part.function_call:
                    logging.info(f"[function call from {callback_context.agent_name}]: " + part.function_call.name)
    EOF
    
  2. أنشئ workflow_agents/__init__.py من خلال تنفيذ ما يلي في الوحدة الطرفية. يؤدي ذلك إلى وضع علامة على الدليل باعتباره حزمة Python.
    cat <<EOF > ~/adk_multiagent_systems/workflow_agents/__init__.py
    """
    Marks the directory as a Python package and exposes the agent module, 
    allowing the ADK to discover and register the agents defined within.
    """
    from . import agent
    EOF
    
  3. أنشئ workflow_agents/agent.py من خلال تنفيذ ما يلي في الوحدة الطرفية. يحتوي هذا الملف على المنطق الأساسي لفريقك المتعدّد الوكلاء.
    cat <<EOF > ~/adk_multiagent_systems/workflow_agents/agent.py
    """
    Defines the core multi-agent workflow. Configures individual agents (Researcher, 
    Screenwriter, File Writer), assigns their specific tools, and orchestrates 
    their collaboration using the ADK's SequentialAgent pattern.
    """
    import os
    import logging
    import google.cloud.logging
    
    from callback_logging import log_query_to_model, log_model_response
    from dotenv import load_dotenv
    
    from google.adk import Agent
    from google.adk.agents import SequentialAgent, LoopAgent, ParallelAgent
    from google.adk.tools.tool_context import ToolContext
    from google.adk.tools.langchain_tool import LangchainTool  # import
    from google.genai import types
    
    from langchain_community.tools import WikipediaQueryRun
    from langchain_community.utilities import WikipediaAPIWrapper
    
    
    cloud_logging_client = google.cloud.logging.Client()
    cloud_logging_client.setup_logging()
    
    load_dotenv()
    
    model_name = os.getenv("MODEL")
    print(model_name)
    
    # Tools
    
    
    def append_to_state(
        tool_context: ToolContext, field: str, response: str
    ) -> dict[str, str]:
        """Append new output to an existing state key.
    
        Args:
            field (str): a field name to append to
            response (str): a string to append to the field
    
        Returns:
            dict[str, str]: {"status": "success"}
        """
        existing_state = tool_context.state.get(field, [])
        tool_context.state[field] = existing_state + [response]
        logging.info(f"[Added to {field}] {response}")
        return {"status": "success"}
    
    
    def write_file(
        tool_context: ToolContext,
        directory: str,
        filename: str,
        content: str
    ) -> dict[str, str]:
        target_path = os.path.join(directory, filename)
        os.makedirs(os.path.dirname(target_path), exist_ok=True)
        with open(target_path, "w") as f:
            f.write(content)
        return {"status": "success"}
    
    
    # Agents
    
    file_writer = Agent(
        name="file_writer",
        model=model_name,
        description="Creates marketing details and saves a pitch document.",
        instruction="""
        PLOT_OUTLINE:
        { PLOT_OUTLINE? }
    
        INSTRUCTIONS:
        - Create a marketable, contemporary movie title suggestion for the movie described in the PLOT_OUTLINE. If a title has been suggested in PLOT_OUTLINE, you can use it, or replace it with a better one.
        - Use your 'write_file' tool to create a new txt file with the following arguments:
            - for a filename, use the movie title
            - Write to the 'movie_pitches' directory.
            - For the 'content' to write, extract the following from the PLOT_OUTLINE:
                - A logline
                - Synopsis or plot outline
        """,
        generate_content_config=types.GenerateContentConfig(
            temperature=0,
        ),
        tools=[write_file],
    )
    
    screenwriter = Agent(
        name="screenwriter",
        model=model_name,
        description="As a screenwriter, write a logline and plot outline for a biopic about a historical character.",
        instruction="""
        INSTRUCTIONS:
        Your goal is to write a logline and three-act plot outline for an inspiring movie about a historical character(s) described by the PROMPT: { PROMPT? }
    
        - If there is CRITICAL_FEEDBACK, use those thoughts to improve upon the outline.
        - If there is RESEARCH provided, feel free to use details from it, but you are not required to use it all.
        - If there is a PLOT_OUTLINE, improve upon it.
        - Use the 'append_to_state' tool to write your logline and three-act plot outline to the field 'PLOT_OUTLINE'.
        - Summarize what you focused on in this pass.
    
        PLOT_OUTLINE:
        { PLOT_OUTLINE? }
    
        RESEARCH:
        { research? }
    
        CRITICAL_FEEDBACK:
        { CRITICAL_FEEDBACK? }
        """,
        generate_content_config=types.GenerateContentConfig(
            temperature=0,
        ),
        tools=[append_to_state],
    )
    
    researcher = Agent(
        name="researcher",
        model=model_name,
        description="Answer research questions using Wikipedia.",
        instruction="""
        PROMPT:
        { PROMPT? }
    
        PLOT_OUTLINE:
        { PLOT_OUTLINE? }
    
        CRITICAL_FEEDBACK:
        { CRITICAL_FEEDBACK? }
    
        INSTRUCTIONS:
        - If there is a CRITICAL_FEEDBACK, use your wikipedia tool to do research to solve those suggestions
        - If there is a PLOT_OUTLINE, use your wikipedia tool to do research to add more historical detail
        - If these are empty, use your Wikipedia tool to gather facts about the person in the PROMPT
        - Use the 'append_to_state' tool to add your research to the field 'research'.
        - Summarize what you have learned.
        Now, use your Wikipedia tool to do research.
        """,
        generate_content_config=types.GenerateContentConfig(
            temperature=0,
        ),
        tools=[
            LangchainTool(tool=WikipediaQueryRun(api_wrapper=WikipediaAPIWrapper())),
            append_to_state,
        ],
    )
    
    film_concept_team = SequentialAgent(
        name="film_concept_team",
        description="Write a film plot outline and save it as a text file.",
        sub_agents=[
            researcher,
            screenwriter,
            file_writer
        ],
    )
    
    root_agent = Agent(
        name="greeter",
        model=model_name,
        description="Guides the user in crafting a movie plot.",
        instruction="""
        - Let the user know you will help them write a pitch for a hit movie. Ask them for   
          a historical figure to create a movie about.
        - When they respond, use the 'append_to_state' tool to store the user's response
          in the 'PROMPT' state key and transfer to the 'film_concept_team' agent
        """,
        generate_content_config=types.GenerateContentConfig(
            temperature=0,
        ),
        tools=[append_to_state],
        sub_agents=[film_concept_team],
    )
    EOF
    

يجب أن تبدو بنية الملف الآن على النحو التالي:
لقطة شاشة لبنية الملف الحالية

إعداد البيئة الافتراضية

  • في وحدة التحكّم، أنشئ بيئة افتراضية وفعّلها باستخدام uv. يضمن ذلك عدم تعارض تبعيات مشروعك مع لغة Python في النظام.
    uv venv
    source .venv/bin/activate
    

متطلبات التثبيت

  1. نفِّذ الأمر التالي في الوحدة الطرفية لإنشاء ملف requirements.txt.
    cat <<EOF > ~/adk_multiagent_systems/requirements.txt
    # Lists all Python dependencies required to run the multi-agent system,
    # including the Google ADK, LangChain community tools, and web server libraries.
    langchain-community==0.3.20
    wikipedia==1.4.0
    google-adk==1.8.0
    fastapi==0.121.2
    uvicorn==0.38.0
    EOF
    
  2. ثبِّت الحِزم المطلوبة في بيئتك الافتراضية في نافذة الأوامر.
    uv pip install -r requirements.txt
    

إعداد المتغيرات البيئية

  1. استخدِم الأمر التالي في وحدة التحكّم لإنشاء ملف .env، مع إدراج رقم تعريف مشروعك ومنطقتك تلقائيًا.
    cat <<EOF > ~/adk_multiagent_systems/.env
    GOOGLE_CLOUD_PROJECT="$(gcloud config get-value project)"
    GOOGLE_CLOUD_PROJECT_NUMBER="$(gcloud projects describe $(gcloud config get-value project) --format='value(projectNumber)')"
    GOOGLE_CLOUD_LOCATION="us-central1"
    GOOGLE_GENAI_USE_VERTEXAI=true
    MODEL="gemini-2.5-flash"
    EOF
    
  2. في وحدة التحكّم، حمِّل المتغيّرات في جلسة shell.
    source .env
    

ملخّص

في هذا القسم، وضعت الأساس المحلي لمشروعك:

  • تم إنشاء بنية الدليل وملفات بدء تشغيل الوكيل اللازمة (agent.py وcallback_logging.py وrequirements.txt).
  • عزل التبعيات باستخدام بيئة افتراضية (uv)
  • تم ضبط متغيّرات البيئة (.env) لتخزين تفاصيل خاصة بالمشروع، مثل رقم تعريف المشروع والمنطقة.

6. استكشاف ملف الوكيل

لقد أعددت رمز المصدر الخاص بالمختبر، بما في ذلك نظام مكتوب مسبقًا يستند إلى عدّة وكلاء. قبل نشر التطبيق، من المفيد فهم كيفية تحديد العملاء. تتوفّر منطق الوكيل الأساسي في workflow_agents/agent.py.

  1. في "محرِّر Cloud Shell"، استخدِم مستكشف الملفات على اليمين للانتقال إلى adk_multiagent_system_gke/workflow_agents/ وفتح الملف agent.py.
  2. يُرجى تخصيص بعض الوقت للاطّلاع على الملف. لست بحاجة إلى فهم كل سطر، ولكن لاحظ البنية العامة:
    • الوكلاء الفرديون: يحدّد الملف ثلاثة عناصر Agent مميزة: researcher وscreenwriter وfile_writer. يتم تزويد كل وكيل بـ instruction (طلبه) وقائمة بـ tools يُسمح له باستخدامها (مثل أداة WikipediaQueryRun أو أداة write_file مخصّصة).
    • تركيب الوكيل: يتم ربط الوكلاء الفرديين معًا في SequentialAgent يُسمى film_concept_team. يطلب ذلك من حزمة تطوير البرامج (ADK) تشغيل هذه البرامج واحدًا تلو الآخر، مع نقل الحالة من برنامج إلى آخر.
    • الوكيل الجذر: يتم تحديد root_agent (يُسمى "المرحِّب") للتعامل مع تفاعل المستخدم الأوّلي. عندما يقدّم المستخدم طلبًا، يحفظ هذا الوكيل الطلب في حالة التطبيق ثم ينقل عنصر التحكّم إلى سير عمل film_concept_team.

يساعدك فهم هذا الهيكل في توضيح ما أنت بصدد نشره: ليس مجرد وكيل واحد، بل فريق منسّق من الوكلاء المتخصّصين الذين يديرهم ADK.

7. إنشاء مجموعة GKE Autopilot

بعد إعداد بيئتك، تتمثّل الخطوة التالية في توفير البنية الأساسية التي سيتم تشغيل تطبيقك عليها. ستنشئ مجموعة GKE Autopilot، والتي ستكون الأساس لعملية النشر. نستخدم وضع التشغيل الآلي لأنّه يتولّى الإدارة المعقّدة للعُقد الأساسية للمجموعة، والتوسيع، والأمان، ما يتيح لك التركيز فقط على نشر تطبيقك.

  1. في وحدة التحكّم، أنشئ مجموعة جديدة من GKE Autopilot باسم adk-cluster.
    gcloud container clusters create-auto adk-cluster \
      --location=$GOOGLE_CLOUD_LOCATION \
      --project=$GOOGLE_CLOUD_PROJECT
    
    يوفّر هذا الأمر مجموعة Kubernetes مُدارة بالكامل. تضبط ميزة GKE Autopilot تلقائيًا الإعدادات المتعلقة بالعُقد وقابلية التوسّع والأمان، ما يسهّل عمليات المجموعات.
  2. بعد إنشاء المجموعة، اضبط kubectl للاتصال بها من خلال تنفيذ ما يلي في الوحدة الطرفية:
    gcloud container clusters get-credentials adk-cluster \
      --location=$GOOGLE_CLOUD_LOCATION \
      --project=$GOOGLE_CLOUD_PROJECT
    
    يربط هذا الأمر بيئتك المحلية بمجموعة GKE الجديدة. يتم تلقائيًا جلب نقطة نهاية المجموعة وبيانات اعتماد المصادقة وتعديل ملف إعداد محلي (~/.kube/config). ومن هذه النقطة فصاعدًا، ستتم مصادقة أداة سطر الأوامر kubectl وتوجيهها للتواصل مع adk-cluster.

ملخّص

في هذا القسم، تم توفير البنية الأساسية:

  • أنشأنا مجموعة GKE Autopilot مُدارة بالكامل باستخدام gcloud.
  • ضبطت أداة kubectl المحلية للمصادقة والتواصل مع المجموعة الجديدة.

8. إنشاء حاوية للتطبيق ونشره

لا يتوفّر رمز البرنامج حاليًا إلا في بيئة Cloud Shell. لتشغيله على GKE، يجب أولاً تجميعه في صورة حاوية. صورة الحاوية هي ملف ثابت وقابل للنقل يجمع الرمز البرمجي لتطبيقك مع جميع العناصر التابعة له. عند تشغيل هذه الصورة، تصبح حاوية نشطة.

تتضمّن هذه العملية ثلاث خطوات رئيسية:

  • إنشاء نقطة دخول: حدِّد ملف main.py لتحويل منطق الوكيل إلى خادم ويب قابل للتنفيذ.
  • تحديد صورة الحاوية: أنشئ Dockerfile يعمل كمخطط لإنشاء صورة الحاوية.
  • الإنشاء والدفع: استخدِم Cloud Build لتنفيذ Dockerfile، ما يؤدي إلى إنشاء صورة الحاوية ودفعها إلى Google Artifact Registry، وهو مستودع آمن لصورك.

تجهيز التطبيق للنشر

يحتاج وكيل ADK إلى خادم ويب لتلقّي الطلبات. سيعمل الملف main.py كنقطة دخول، وسيستخدم إطار عمل FastAPI لعرض وظائف وكيلك عبر HTTP.

  1. في جذر دليل adk_multiagent_system_gke في الطرفية، أنشئ ملفًا جديدًا باسم main.py.
    cat <<EOF > ~/adk_multiagent_systems/main.py
    """
    Serves as the application entry point. Initializes the FastAPI web server, 
    discovers the agents defined in the workflow directory, and exposes them 
    via HTTP endpoints for interaction.
    """
    
    import os
    
    import uvicorn
    from fastapi import FastAPI
    from google.adk.cli.fast_api import get_fast_api_app
    
    # Get the directory where main.py is located
    AGENT_DIR = os.path.dirname(os.path.abspath(__file__))
    
    # Configure the session service (e.g., SQLite for local storage)
    SESSION_SERVICE_URI = "sqlite:///./sessions.db"
    
    # Configure CORS to allow requests from various origins for this lab
    ALLOWED_ORIGINS = ["http://localhost", "http://localhost:8080", "*"]
    
    # Enable the ADK's built-in web interface
    SERVE_WEB_INTERFACE = True
    
    # Call the ADK function to discover agents and create the FastAPI app
    app: FastAPI = get_fast_api_app(
        agents_dir=AGENT_DIR,
        session_service_uri=SESSION_SERVICE_URI,
        allow_origins=ALLOWED_ORIGINS,
        web=SERVE_WEB_INTERFACE,
    )
    
    # You can add more FastAPI routes or configurations below if needed
    # Example:
    # @app.get("/hello")
    # async def read_root():
    #     return {"Hello": "World"}
    
    if __name__ == "__main__":
        # Get the port from the PORT environment variable provided by the container runtime
        # Run the Uvicorn server, listening on all available network interfaces (0.0.0.0)
        uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", 8080)))
    EOF
    
    يستخدم هذا الملف مكتبة ADK لاكتشاف البرامج في مشروعك وتضمينها في تطبيق ويب FastAPI. يشغّل الخادم uvicorn هذا التطبيق، ويستمع إلى المضيف 0.0.0.0 لقبول الاتصالات من أي عنوان IP وعلى المنفذ المحدّد بواسطة متغيّر البيئة PORT، والذي سنحدّده لاحقًا في بيان Kubernetes.

    في هذه المرحلة، يجب أن يظهر هيكل الملف كما هو موضّح في لوحة المستكشف في "محرّر Cloud Shell" على النحو التالي: لقطة شاشة لبنية الملف الحالية

تضمين وكيل "حزمة تطوير الوكلاء" في حاوية باستخدام Docker

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

  1. في جذر دليل adk_multiagent_system_gke في الطرفية، أنشئ ملفًا جديدًا باسم Dockerfile.
    cat <<'EOF' > ~/adk_multiagent_systems/Dockerfile
    # Defines the blueprint for the container image. Installs dependencies,
    # sets up a secure non-root user, and specifies the startup command to run the 
    # agent web server.
    
    # Use an official lightweight Python image as the base
    FROM python:3.13-slim
    
    # Set the working directory inside the container
    WORKDIR /app
    
    # Create a non-root user for security best practices
    RUN adduser --disabled-password --gecos "" myuser
    
    # Copy and install dependencies first to leverage Docker's layer caching
    COPY requirements.txt .
    RUN pip install --no-cache-dir -r requirements.txt
    
    # Copy all application code into the container
    COPY . .
    
    # Create the directory where the agent will write files at runtime
    # The -p flag ensures the command doesn't fail if the directory already exists
    RUN mkdir -p movie_pitches
    
    # Change ownership of EVERYTHING in /app to the non-root user
    # Without this, the running agent would be denied permission to write files.
    RUN chown -R myuser:myuser /app
    
    # Switch the active user from root to the non-root user
    USER myuser
    
    # Add the user's local binary directory to the system's PATH
    ENV PATH="/home/myuser/.local/bin:$PATH"
    
    # Define the command to run when the container starts
    CMD ["sh", "-c", "uvicorn main:app --host 0.0.0.0 --port $PORT"]
    EOF
    
    في هذه المرحلة، يجب أن يبدو بنية الملف كما تظهر في لوحة المستكشف في "محرّر Cloud Shell" على النحو التالي: لقطة شاشة لبنية الملف الحالية

إنشاء صورة الحاوية ونقلها إلى Artifact Registry

بعد إنشاء Dockerfile، ستستخدم Cloud Build لإنشاء الصورة ونقلها إلى Artifact Registry، وهو سجلّ خاص وآمن ومدمج مع خدمات Google Cloud. سيجلب GKE الصورة من هذا السجلّ لتشغيل تطبيقك.

  1. في الطرفية، أنشئ مستودع Artifact Registry جديدًا لتخزين صورة الحاوية.
    gcloud artifacts repositories create adk-repo \
      --repository-format=docker \
      --location=$GOOGLE_CLOUD_LOCATION \
      --description="ADK repository"
    
  2. في وحدة التحكّم، استخدِم gcloud builds submit لإنشاء صورة الحاوية ونقلها إلى المستودع.
    gcloud builds submit \
      --tag $GOOGLE_CLOUD_LOCATION-docker.pkg.dev/$GOOGLE_CLOUD_PROJECT/adk-repo/adk-agent:latest \
      --project=$GOOGLE_CLOUD_PROJECT \
      .
    
    يستخدم هذا الأمر الفردي Cloud Build، وهي منصة CI/CD بلا خادم، لتنفيذ الخطوات في Dockerfile. تنشئ هذه الأداة الصورة في السحابة الإلكترونية، وتضع عليها علامة بعنوان مستودع Artifact Registry، ثم تنقلها إلى هناك تلقائيًا.
  3. من وحدة التحكّم، تأكَّد من إنشاء الصورة:
    gcloud artifacts docker images list \
      $GOOGLE_CLOUD_LOCATION-docker.pkg.dev/$GOOGLE_CLOUD_PROJECT/adk-repo \
      --project=$GOOGLE_CLOUD_PROJECT
    

ملخّص

في هذا القسم، تم تجميع الرمز البرمجي لنشره:

  • تم إنشاء نقطة دخول main.py لتضمين البرامج الوكيلة في خادم ويب FastAPI.
  • تم تحديد Dockerfile لتجميع الرمز والتبعيات في صورة قابلة للنقل.
  • استخدَمنا Cloud Build لإنشاء الصورة ونقلها إلى مستودع Artifact Registry آمن.

9- إنشاء ملفات بيانات Kubernetes

بعد إنشاء صورة الحاوية وتخزينها في Artifact Registry، عليك توجيه GKE بشأن كيفية تشغيلها. يتضمّن ذلك نشاطَين رئيسيَّين:

  • ضبط الأذونات: عليك إنشاء هوية مخصّصة للوكيل داخل المجموعة ومنحه إذن الوصول الآمن إلى واجهات Google Cloud APIs التي يحتاجها (Vertex AI على وجه التحديد).
  • تحديد حالة التطبيق: ستكتب ملف بيان Kubernetes، وهو مستند YAML يحدّد بشكل تصريحي كل ما يحتاج إليه تطبيقك للتشغيل، بما في ذلك صورة الحاوية ومتغيرات البيئة وطريقة عرضها على الشبكة.

ضبط حساب خدمة Kubernetes لـ Vertex AI

يحتاج الوكيل إلى إذن للتواصل مع واجهة برمجة التطبيقات Vertex AI من أجل الوصول إلى نماذج Gemini. الطريقة الأكثر أمانًا والمقترَحة لمنح هذا الإذن في GKE هي Workload Identity. تتيح لك ميزة Workload Identity ربط هوية أصلية في Kubernetes (حساب خدمة Kubernetes) بهوية في Google Cloud (حساب خدمة في "إدارة الهوية وإمكانية الوصول")، ما يغنيك تمامًا عن الحاجة إلى تنزيل مفاتيح JSON الثابتة وإدارتها وتخزينها.

  1. في وحدة التحكّم، أنشئ حساب خدمة Kubernetes (adk-agent-sa). يؤدي ذلك إلى إنشاء هوية للوكيل داخل مجموعة GKE التي يمكن أن تستخدمها وحداتك.
    kubectl create serviceaccount adk-agent-sa
    
  2. في وحدة التحكّم، اربط حساب خدمة Kubernetes بـ "إدارة الهوية وإمكانية الوصول" في Google Cloud من خلال إنشاء ربط سياسة. يمنح هذا الأمر الدور aiplatform.user إلى adk-agent-sa، ما يسمح له باستدعاء واجهة Vertex AI API بشكل آمن.
    gcloud projects add-iam-policy-binding projects/${GOOGLE_CLOUD_PROJECT} \
        --role=roles/aiplatform.user \
        --member=principal://iam.googleapis.com/projects/${GOOGLE_CLOUD_PROJECT_NUMBER}/locations/global/workloadIdentityPools/${GOOGLE_CLOUD_PROJECT}.svc.id.goog/subject/ns/default/sa/adk-agent-sa \
        --condition=None
    

إنشاء ملفات بيان Kubernetes

يستخدم Kubernetes ملفات بيان YAML لتحديد الحالة المطلوبة لتطبيقك. ستنشئ ملف deployment.yaml يحتوي على عنصرَين أساسيَّين من عناصر Kubernetes: عملية نشر وخدمة.

  1. من وحدة التحكّم، أنشئ ملف deployment.yaml.
    cat <<EOF > ~/adk_multiagent_systems/deployment.yaml
    # Defines the Kubernetes resources required to deploy the application to GKE. 
    # Includes the Deployment (to run the container pods) and the Service 
    # (to expose the application via a Load Balancer).
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: adk-agent
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: adk-agent
      template:
        metadata:
          labels:
            app: adk-agent
        spec:
          # Assign the Kubernetes Service Account for Workload Identity
          serviceAccountName: adk-agent-sa
          containers:
          - name: adk-agent
            imagePullPolicy: Always
            # The path to the container image in Artifact Registry
            image: ${GOOGLE_CLOUD_LOCATION}-docker.pkg.dev/${GOOGLE_CLOUD_PROJECT}/adk-repo/adk-agent:latest
            # Define the resources for GKE Autopilot to provision
            resources:
              limits:
                memory: "1Gi"
                cpu: "1000m"
                ephemeral-storage: "512Mi"
              requests:
                memory: "1Gi"
                cpu: "1000m"
                ephemeral-storage: "512Mi"
            ports:
            - containerPort: 8080
            # Environment variables passed to the application
            env:
            - name: PORT
              value: "8080"
            - name: GOOGLE_CLOUD_PROJECT
              value: ${GOOGLE_CLOUD_PROJECT}
            - name: GOOGLE_CLOUD_LOCATION
              value: ${GOOGLE_CLOUD_LOCATION}
            - name: GOOGLE_GENAI_USE_VERTEXAI
              value: "true"
            - name: MODEL
              value: "gemini-2.5-flash"
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: adk-agent
    spec:
      # Create a public-facing Network Load Balancer with an external IP
      type: LoadBalancer
      ports:
      - port: 80
        targetPort: 8080
      selector:
        app: adk-agent
    EOF
    
    في هذه المرحلة، يجب أن يظهر هيكل الملف كما هو موضّح في لوحة المستكشف في "محرّر Cloud Shell" على النحو التالي:لقطة شاشة لبنية الملف الحالية

ملخّص

في هذا القسم، حدّدت إعدادات الأمان والنشر:

  • أنشأت حساب خدمة Kubernetes وربطته بخدمة إدارة الهوية وإمكانية الوصول (IAM) في Google Cloud باستخدام Workload Identity، ما يتيح لوحدات pod الوصول بأمان إلى Vertex AI بدون إدارة المفاتيح.
  • تم إنشاء ملف deployment.yaml يحدّد عملية النشر (كيفية تشغيل الوحدات) والخدمة (كيفية عرضها من خلال موازن التحميل).

10. نشر التطبيق على GKE

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

  1. في وحدة التحكّم، طبِّق بيان deployment.yaml على مجموعتك.
    kubectl apply -f deployment.yaml
    
    يرسل الأمر kubectl apply ملف deployment.yaml إلى خادم واجهة برمجة التطبيقات في Kubernetes. يقرأ الخادم بعد ذلك إعداداتك وينسّق عملية إنشاء كائنَي Deployment وService.
  2. في نافذة الأوامر، اطّلِع على حالة عملية النشر في الوقت الفعلي. انتظِر إلى أن تصبح الحاويات في حالة Running.
    kubectl get pods -l=app=adk-agent --watch
    
    ستلاحظ أنّ البود ينتقل عبر عدة مراحل:
    • في انتظار المراجعة: قبلت المجموعة الوحدة، ولكن لم يتم إنشاء الحاوية بعد.
    • إنشاء الحاوية: يسحب GKE صورة الحاوية من Artifact Registry ويبدأ الحاوية.
    • الركض: تمّت الإضافة بنجاح. الحاوية قيد التشغيل، وتطبيق الوكيل متاح.
  3. عندما تظهر الحالة Running، اضغط على CTRL+C في الوحدة الطرفية لإيقاف الأمر watch والرجوع إلى موجّه الأوامر.

ملخّص

في هذا القسم، تم إطلاق عبء العمل:

  • يُستخدَم kubectl apply لإرسال بيانك إلى المجموعة.
  • تمت مراقبة دورة حياة وحدة Pod (في انتظار المراجعة -> ContainerCreating -> Running) لضمان بدء تشغيل التطبيق بنجاح.

11. التفاعل مع الوكيل

يعمل وكيل ADK الآن مباشرةً على GKE ويمكن الوصول إليه على الإنترنت من خلال موازن تحميل عامة. سيتم ربطك بواجهة الويب الخاصة بالوكيل للتفاعل معه والتأكّد من أنّ النظام بأكمله يعمل بشكل صحيح.

العثور على عنوان IP الخارجي لخدمتك

للوصول إلى الوكيل، عليك أولاً الحصول على عنوان IP متاح للجميع وفّره GKE لخدمتك.

  1. في الوحدة الطرفية، شغِّل الأمر التالي للحصول على تفاصيل خدمتك.
    kubectl get service adk-agent
    
  2. ابحث عن القيمة في العمود EXTERNAL-IP. قد يستغرق تعيين عنوان IP دقيقة أو دقيقتين بعد نشر الخدمة لأول مرة. إذا ظهرت pending، انتظِر دقيقة واحدة وأعِد تنفيذ الأمر. ستبدو النتيجة مشابهة لما يلي:
    NAME                TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)        AGE
    adk-agent-service   LoadBalancer   10.120.12.234   34.123.45.67    80:31234/TCP   5m
    
    العنوان المُدرَج ضمن EXTERNAL-IP (مثلاً ‫34.123.45.67) هي نقطة الدخول العامة إلى الوكيل.

اختبار الوكيل الذي تم نشره

يمكنك الآن استخدام عنوان IP العام للوصول إلى واجهة مستخدم الويب المضمّنة في "حزمة تطوير التطبيقات" مباشرةً من المتصفّح.

  1. انسخ عنوان IP الخارجي (EXTERNAL-IP) من النافذة الطرفية.
  2. افتح علامة تبويب جديدة في متصفّح الويب واكتب http://[EXTERNAL-IP]، مع استبدال [EXTERNAL-IP] بعنوان IP الذي نسخته.
  3. من المفترض أن تظهر لك الآن واجهة ADK على الويب.
  4. تأكَّد من اختيار workflow_agents في القائمة المنسدلة الخاصة بالوكيل.
  5. فعِّل الخيار بث الرموز المميزة.
  6. اكتب hello واضغط على Enter لبدء محادثة جديدة.
  7. راقِب النتيجة. يجب أن يردّ المساعد بسرعة بعبارة الترحيب: "يمكنني مساعدتك في كتابة أغنية ترويجية لفيلم ناجح. ما هي الشخصية التاريخية التي تريد صناعة فيلم عنها؟"
  8. عندما يُطلب منك اختيار شخصية تاريخية، اختَر شخصية تهمّك. في ما يلي بعض الأفكار:
    • the most successful female pirate in history
    • the woman who invented the first computer compiler
    • a legendary lawman of the American Wild West

ملخّص

في هذا القسم، تحقّقنا من عملية النشر:

  • تم استرداد عنوان IP الخارجي الذي خصّصه LoadBalancer.
  • تم الوصول إلى واجهة مستخدم ADK على الويب من خلال متصفّح للتأكّد من أنّ نظام الوكلاء المتعدّدين يستجيب ويعمل بشكل سليم.

12. ضبط ميزة "التوسّع التلقائي"

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

ستُعدِّل HorizontalPodAutoscaler (HPA)، وهي أداة تحكّم في Kubernetes تعمل على تعديل عدد الوحدات الجارية تلقائيًا في عملية النشر استنادًا إلى استخدام وحدة المعالجة المركزية في الوقت الفعلي.

  1. في وحدة التحكّم في "محرِّر Cloud Shell"، أنشئ ملف hpa.yaml جديدًا في جذر الدليل adk_multiagent_system_gke.
    cloudshell edit ~/adk_multiagent_systems/hpa.yaml
    
  2. أضِف المحتوى التالي إلى ملف hpa.yaml الجديد:
    # Configures the HorizontalPodAutoscaler (HPA) to automatically scale 
    # the number of running agent pods up or down based on CPU utilization 
    # to handle varying traffic loads.
    
    apiVersion: autoscaling/v1
    kind: HorizontalPodAutoscaler
    metadata:
      name: adk-agent-hpa
    spec:
      scaleTargetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: adk-agent
      minReplicas: 1
      maxReplicas: 5
      targetCPUUtilizationPercentage: 50
    
    يستهدف كائن HPA هذا عملية النشر adk-agent. يضمن هذا الإعداد تشغيل وحدة واحدة على الأقل دائمًا، ويضبط الحد الأقصى على 5 وحدات، كما سيضيف/يزيل النسخ المتماثلة للحفاظ على متوسط استخدام وحدة المعالجة المركزية (CPU) عند %50 تقريبًا. في هذه المرحلة، يجب أن يبدو هيكل الملف كما هو معروض في لوحة المستكشف في Cloud Shell Editor على النحو التالي: لقطة شاشة لبنية الملف الحالية
  3. طبِّق HPA على مجموعتك عن طريق لصق هذا الرمز في النافذة الطرفية.
    kubectl apply -f hpa.yaml
    

التحقّق من أداة التحجيم التلقائي

أصبحت HPA نشطة الآن وتراقب عملية النشر. يمكنك فحص حالتها لمعرفة كيفية عملها.

  1. نفِّذ الأمر التالي في الوحدة الطرفية للحصول على حالة HPA.
    kubectl get hpa adk-agent-hpa
    
    ستبدو النتيجة مشابهة لما يلي:
    NAME            REFERENCE          TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
    adk-agent-hpa   Deployment/adk-agent   0%/50%    1         5         1          30s
    
    سيتم الآن توسيع نطاق الوكيل تلقائيًا استجابةً لحجم الزيارات.

ملخّص

في هذا القسم، تم تحسين عدد الزيارات في مرحلة الإنتاج:

  • تم إنشاء ملف hpa.yaml لتحديد قواعد تغيير الحجم.
  • تم نشر HorizontalPodAutoscaler (HPA) لتعديل عدد نُسخ الوحدات تلقائيًا استنادًا إلى استخدام وحدة المعالجة المركزية.

13. الاستعداد للإصدار العلني

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

تحسين الأداء من خلال تخصيص الموارد

في وضع التشغيل Autopilot في GKE، يمكنك التحكّم في مقدار وحدة المعالجة المركزية والذاكرة المخصّصة لتطبيقك من خلال تحديد requests الموارد في deployment.yaml.

إذا لاحظت أنّ الوكيل بطيء أو يتعطّل بسبب نقص الذاكرة، يمكنك زيادة تخصيص الموارد له من خلال تعديل الحظر resources في deployment.yaml وإعادة تطبيق الملف باستخدام kubectl apply.

على سبيل المثال، لمضاعفة الذاكرة:

# In deployment.yaml
# ...
        resources:
          requests:
            memory: "2Gi"  # Increased from 1Gi
            cpu: "1000m"
# ...

تنفيذ سير العمل تلقائيًا باستخدام CI/CD

في هذا التمرين المعملي، شغّلت الأوامر يدويًا. تتمثّل الممارسة الاحترافية في إنشاء مسار CI/CD (التكامل المستمر/النشر المستمر). من خلال ربط مستودع الرمز المصدر (مثل GitHub) بمشغّل Cloud Build، يمكنك أتمتة عملية النشر بأكملها.

باستخدام خط أنابيب، يمكن لخدمة Cloud Build تلقائيًا في كل مرة تدفع فيها تغييرًا في الرمز:

  1. أنشئ صورة الحاوية الجديدة.
  2. ادفع الصورة إلى Artifact Registry.
  3. طبِّق بيانات Kubernetes المعدَّلة على مجموعة GKE.

إدارة الأسرار بشكل آمن

في هذا الدرس التطبيقي، خزّنت إعدادات الضبط في ملف .env ونقلتها إلى تطبيقك. هذا مناسب للتطوير ولكنه غير آمن للبيانات الحساسة مثل مفاتيح واجهة برمجة التطبيقات. أفضل الممارسات المقترَحة هي استخدام Secret Manager لتخزين الأسرار بأمان.

تتضمّن خدمة GKE عملية دمج أصلية مع Secret Manager تتيح لك تثبيت الأسرار مباشرةً في وحدات pod كمتغيرات بيئية أو ملفات، بدون أن يتم إدراجها في رمز المصدر.

إليك قسم تنظيف الموارد الذي طلبته، وقد أدرجناه قبل قسم الخلاصة مباشرةً.

14. تنظيف الموارد

لتجنُّب تحمّل رسوم في حسابك على Google Cloud مقابل الموارد المستخدَمة في هذا البرنامج التعليمي، احذف المشروع الذي يحتوي على الموارد أو احتفظ بالمشروع واحذف الموارد الفردية.

حذف مجموعة GKE

مجموعة GKE هي المحرك الأساسي للتكلفة في هذا التمرين العملي. ويؤدي حذفها إلى إيقاف رسوم الحوسبة.

  1. في الوحدة الطرفية، شغِّل الأمر التالي:
    gcloud container clusters delete adk-cluster \
      --location=$GOOGLE_CLOUD_LOCATION \
      --quiet
    

حذف مستودع Artifact Registry

تتسبب صور الحاويات المخزَّنة في Artifact Registry في تكاليف التخزين.

  1. في الوحدة الطرفية، شغِّل الأمر التالي:
    gcloud artifacts repositories delete adk-repo \
      --location=$GOOGLE_CLOUD_LOCATION \
      --quiet
    

حذف المشروع (اختياري)

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

  1. في الوحدة الطرفية، شغِّل الأمر التالي (استبدِل [YOUR_PROJECT_ID] بمعرّف مشروعك الفعلي):
    gcloud projects delete [YOUR_PROJECT_ID]
    

15. الخاتمة

تهانينا! لقد نشرت بنجاح تطبيقًا متعدد الوكلاء في "حزمة تطوير التطبيقات" على مجموعة GKE ذات مستوى الإنتاج. هذا إنجاز مهم يغطّي دورة الحياة الأساسية لتطبيق حديث مستند إلى السحابة الإلكترونية، ما يوفّر لك أساسًا متينًا لنشر أنظمتك المعقّدة المستندة إلى الوكلاء.

ملخّص

في هذا التمرين المعملي، تعلّمت ما يلي:

مراجع مفيدة