إنشاء وكلاء الذكاء الاصطناعي باستخدام حزمة ADK: تنظيم سير العمل في الرسم البياني

1. نظرة عامة

لتلبية طلب سفر يتضمّن قيودًا متعددة (مثل التخطيط لرحلة يومية في كيوتو بقيمة 100 دولار أمريكي تشمل زيارة المعابد التاريخية وتناول الرامن المحلي وعدم زيارة متاحف الفن الحديث)، يجب استخراج التفضيلات المنظَّمة والبحث عن الأماكن في الوقت نفسه وتجميع مسودة مفصّلة ومراجعة التكلفة الإجمالية قبل الردّ. في الإصدار 1.x من ADK، كان تنظيم هذه المراحل يتطلّب دمج برامج تضمين SequentialAgent وParallelAgent وLoopAgent، وحفظ كل ناتج وسيط في حالة الجلسة، واستدعاء أداة LLM إضافية للخروج من حلقة التحسين.

في هذا الدرس التطبيقي حول الترميز، يمكنك إنشاء مخطِّط رحلات متعدّد الوكلاء على Agent Platform باستخدام مهام سير عمل الرسم البياني (Workflow) في Agent Development Kit (ADK) 2. على مدار ثلاث مراحل متقدّمة، يمكنك ربط عُقد Agent متخصّصة (مستندة إلى gemini-3.8-flash) في سلسلة حواف متسلسلة (node_input)، وتوزيع باحثي google_search متزامنين مع JoinNode، وفرض قواعد الميزانية والاستبعاد باستخدام موجّه Python حتمي (Event(route=...)) يتخطّى التحسين عندما تجتاز المسودة الأولية عملية التدقيق.

المتطلبات الأساسية

ما ستتعرّف عليه

  • كيفية تعريف مسار متعدد الوكلاء في ADK 2 باستخدام صفوف حواف Workflow
  • كيفية تمرير ADK 2 للمخرجات مباشرةً على طول الحواف كـ node_input وربط مفاتيح حالة الجلسة كمَعلمات دالة
  • كيفية توزيع فروع البحث المتزامنة ودمج نتائجها باستخدام JoinNode
  • كيفية توجيه دورات صانع-مدقّق محدودة بشكل حتمي في Python باستخدام Event(route=...)
  • كيفية فحص الرسوم البيانية لسير العمل المجمّع والمخططات الزمنية للتنفيذ الموازي وفروق الحالة في واجهة مستخدم الويب الخاصة بـ ADK

ما تحتاج إليه

  • حساب Google (يُرجى استخدام حساب @gmail.com شخصي في حال المطالبة برصيد ورشة العمل)
  • رابط رصيد ورشة العمل من المدرّب (بالإضافة إلى كاميرا الهاتف لمسح رمز الاستجابة السريعة ضوئيًا) أو حساب فوترة نشط على Cloud
  • Google Chrome (ننصح باستخدام نافذة تصفّح متخفٍّ لتجنُّب الخلط بين الحسابات الشخصية وحسابات العمل)

2. طلب الحصول على أرصدة Google Cloud (اختياري)

إذا كنت تحضر ورشة عمل تتضمّن رابطًا لرصيد الحدث، يمكنك الحصول على رصيد Google Developer Program لتوفير حساب فوترة بدون بطاقة ائتمان.

طلب رصيد الفعالية

  1. افتح رابط رصيد ورشة العمل في نافذة التصفّح المتخفي وسجِّل الدخول باستخدام حسابك على "@gmail.com".
  2. في صفحة طلب الاستفادة من الميزة، انقر على طلب الرصيد:

بطاقة "المطالبة بالمزايا" في Google Builders Hub مع زر "المطالبة بالرصيد"

  1. إذا طُلب منك إنشاء ملف شخصي في Google Developer Program، أكمِل النموذج.
  2. عند ظهور رسالة يُرجى مسح الرمز ضوئيًا لإثبات أنّك شخص حقيقي، امسح رمز الاستجابة السريعة ضوئيًا باستخدام هاتفك وأكمِل عملية التحقّق (أعِد تحميل الصفحة وانقر على طلب الرصيد مرة أخرى إذا لم يظهر رمز الاستجابة السريعة):

مربّع حوار رمز الاستجابة السريعة "يُرجى مسح الرمز ضوئيًا لإثبات أنّك شخص حقيقي" في Google Developer Program

  1. تأكَّد من ظهور الرسالة تمت المطالبة برصيد الحدث بنجاح، ثم انقر على فتح حساب الفوترة:

صفحة Google Builders Hub تعرض رسالة "تمت المطالبة برصيد الحدث بنجاح" وزر "فتح حساب الفوترة"

🔍 التحقّق من عملك

يستغرق إعداد حساب الفوترة مدة تصل إلى 3 دقائق:

  1. افتح Google Cloud Billing (أو انسخ عنوان URL في نافذة التصفّح المتخفي) واختَر حسابات الفوترة:
https://console.cloud.google.com/billing
  1. أعِد تحميل الصفحة إلى أن تظهر حالة حسابك على GDP Credit على أنّها نشط:

صفحة إدارة حساب الفوترة في Google Cloud تعرض حساب فوترة نشطًا لرصيد GDP

3- إنشاء مشروع على Google Cloud (اختياري)

أنشئ مشروعًا مخصّصًا على Google Cloud لتبقى جميع الموارد من هذا الدرس التطبيقي حول الترميز معزولة ويسهل حذفها.

  1. افتح إنشاء مشروع (أو انسخ عنوان URL في نافذة التصفّح المتخفي):
https://console.cloud.google.com/projectcreate
  1. أدخِل تفاصيل مشروعك:
    • اسم المشروع: على سبيل المثال، google-cloud-workshop
    • حساب الفوترة (إذا ظهر): اختَر GDP Credit (أو حساب الفوترة النشط)
    • المورد الرئيسي: بدون مؤسسة (أو اختَر مؤسستك إذا لزم الأمر)
  2. دوِّن رقم تعريف المشروع الذي تم إنشاؤه أسفل اسم المشروع، ثم انقر على إنشاء:

شاشة "إنشاء مشروع على Google Cloud" تعرض اسم المشروع ورقم تعريفه والمورد الرئيسي وزر "إنشاء"

🔍 التحقّق من عملك

انقر على الإشعارات في شريط الأدوات في أعلى يسار الصفحة، ثم انقر على اختيار مشروع بعد الانتهاء من إنشاء المشروع:

قائمة "الإشعارات" في وحدة تحكّم Google Cloud التي تعرض رابطَي "إنشاء مشروع" و"اختيار مشروع"

4. إعداد البيئة

اضبط Google Cloud Shell وفعِّل Agent Platform API وأنشئ بيئة افتراضية معزولة في Python 3.12 باستخدام uv.

إعداد Cloud Shell

  1. انقر على تفعيل Cloud Shell (>_) في شريط أدوات Google Cloud Console، أو افتح Google Cloud Shell مباشرةً (أو انسخ عنوان URL في نافذة التصفّح المتخفي):
https://shell.cloud.google.com/?show=ide,terminal

زر "تفعيل Cloud Shell" في شريط أدوات وحدة تحكّم Google Cloud

  1. انقر على تفويض إذا طُلب منك ذلك:

مربّع حوار تفويض Cloud Shell

  1. اختَر رقم تعريف مشروع Google Cloud النشط من القائمة المرقّمة في وحدة التحكّم:
unset CLOUDSDK_CORE_PROJECT
PS3="Select your project (e.g. 1): "
select PROJECT_ID in $(gcloud projects list --format="value(projectId)"); do
  [ -n "$PROJECT_ID" ] && gcloud config set project "$PROJECT_ID" && break
done

من المفترض أن تظهر لك نتيجة مشابهة لما يلي (تطبع المشاريع غير المُصنَّفة أيضًا سطرًا إعلاميًا [environment: untagged]):

1) google-cloud-workshop-4210
2) my-cloud-project-8812
Select your project (e.g. 1): 1
Updated property [core/project].

تأكَّد من ظهور رقم تعريف المشروع الذي اخترته مميّزًا باللون الأصفر في طلب Cloud Shell:

وحدة Cloud Shell الطرفية تعرض رقم تعريف المشروع المحدّد مميّزًا باللون الأصفر

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

فعِّل Agent Platform API لتتمكّن وكلاء ADK من استدعاء نماذج Gemini:

gcloud services enable aiplatform.googleapis.com

من المفترَض أن تظهر لك نتيجة مثل:

Operation "operations/acat.p2-..." finished successfully.

إنشاء بيئة Python افتراضية وتثبيت ADK

أنشِئ مساحة عمل ~/adk-graph-agents وفعِّل بيئة Python 3.12 افتراضية:

cd ~
mkdir -p adk-graph-agents && cd ~/adk-graph-agents

which uv >/dev/null 2>&1 || curl -LsSf https://astral.sh/uv/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"

uv venv .venv --python 3.12
source .venv/bin/activate

تأكَّد من ظهور البادئة (.venv) في بداية طلب Terminal:

وحدة Cloud Shell الطرفية تعرض بادئة ‎ .venv في بيئة Python افتراضية نشطة

ثبِّت ADK 2 داخل البيئة الافتراضية النشطة:

uv pip install --no-cache "google-adk==2.14.0"

🔍 التحقّق من عملك

تحقَّق من إصدار واجهة سطر الأوامر (CLI) المثبَّت في adk:

adk --version

من المفترَض أن تظهر لك نتيجة مثل:

adk, version 2.14.0

5- توفير الدعم في أداة تخطيط الرحلات

أنشِئ حزمة travel_planner الأساسية باستخدام adk create واختبِر أداء الوكيل الواحد التلقائي في adk web.

إنشاء الحزمة الأساسية

تشغيل "adk create" من "~/adk-graph-agents":

cd ~/adk-graph-agents
adk create travel_planner

حدِّد الخيارات التالية عندما يُطلب منك ذلك (إذا طُلب منك إدخال Enable telemetry? [Y/n]: عند التشغيل لأول مرة، اضغط على مفتاح Enter أو اكتب n واضغط على مفتاح Enter):

  1. اختيار نموذج للوكيل الجذر: اكتب 2 لاختيار gemini-3.8-flash (يؤدي الخيار 1 إلى اختيار gemini-3.5-flash) واضغط على مفتاح Enter.
  2. اختيار الخلفية: اكتب 2 لاختيار خلفية Agent Platform في Google Cloud (الخيار 2 في قائمة واجهة سطر الأوامر أدناه) واضغط على مفتاح Enter.
  3. إدخال رقم تعريف مشروع Google Cloud: اضغط على مفتاح Enter لقبول رقم تعريف المشروع الذي تم رصده بين قوسين.
  4. أدخِل منطقة Google Cloud: اكتب global (لاستخدام نقطة نهاية Gemini العالمية) واضغط على مفتاح Enter.

يكتب adk create السمة GOOGLE_GENAI_USE_ENTERPRISE=1 ورقم تعريف مشروعك والسمة GOOGLE_CLOUD_LOCATION=global في travel_planner/.env، وبذلك تظل بيانات الاعتماد سليمة عند استبدال agent.py لاحقًا.

من المفترَض أن تظهر لك نتيجة مثل:

Choose a model for the root agent:
1. gemini-3.5-flash
2. gemini-3.8-flash
3. Other models (fill later)
Choose model (1, 2, 3): 2
1. Google AI
2. Vertex AI
3. Login with Google
Choose a backend (1, 2, 3): 2

You need an existing Google Cloud account and project, check out this link for details:
https://google.github.io/adk-docs/get-started/quickstart/#gemini---google-cloud-vertex-ai

Enter Google Cloud project ID [google-cloud-workshop-4210]:
Enter Google Cloud region [us-central1]: global

Agent created in /home/your_username/adk-graph-agents/travel_planner:
- .env
- .gitignore
- __init__.py
- agent.py

⚠️  WARNING: Secrets (like GOOGLE_API_KEY) are stored in .env.

Next steps:
- Chat with your agent in the terminal: adk run travel_planner
- Or in the browser: adk web .
- Have your coding agent evaluate and deploy it: uvx google-agents-cli setup
  (other install options: https://adk.dev/get-started/agents-cli/)

افتح ~/adk-graph-agents في أداة تعديل الرموز في Cloud Shell:

which cloudshell >/dev/null 2>&1 && cloudshell workspace ~/adk-graph-agents || true

أعِد تفعيل البيئة الافتراضية في الوحدة الطرفية التي تمت إعادة تحميلها:

cd ~/adk-graph-agents
source .venv/bin/activate

اختبار الوكيل الفردي المبدئي

ابدأ واجهة مستخدم الويب الخاصة بـ ADK لملاحظة كيف يجيب وكيل مبتدئ واحد غير منسَّق عن طلب سفر:

cd ~/adk-graph-agents
adk web --allow_origins "*"

انقر على رمز معاينة الويب في شريط أدوات Cloud Shell في أعلى يسار الصفحة، ثم اختَر تغيير المنفذ، وأدخِل 8000، وانقر على تغيير ومعاينة:

تم ضبط مربّع حوار إعدادات منفذ "معاينة الويب" في Cloud Shell على 8000

🔍 التحقّق من عملك

اختَر travel_planner في القائمة المنسدلة أعلى يمين "adk web" وأرسِل "Help me plan a trip to Japan":

واجهة مستخدم الويب الخاصة بتطوير ADK تعرض محادثة أساسية مع وكيل واحد

من المفترض أن يظهر لك ردّ حواري من خطوة واحدة في لوحة المحادثة (مع استدعاء واحد لـ root_agent في الأحداث):

root_agent -> responds in one pass without structured preference extraction, live search, or budget verification

اضغط على Ctrl + C في وحدة طرفية Cloud Shell لإيقاف adk web قبل تعديل الملفات في الخطوة التالية.

6. إنشاء سير عمل رسم بياني خطي

أنشئ أول ADK 2 Workflow: سلسلة متتابعة تربط نقطة الدخول المحجوزة "START" بـ user_profiler_agent وitinerary_synthesizer_agent (أي عقدة ليس لها حواف صادرة تعمل كعقدة طرفية). تُشغّل كل حافة (a, b) تلقائيًا عُقد Agent (اسم بديل لـ LlmAgent) في وضع single_turn المعزول وتُرسل مخرجات العقدة a كـ node_input (رسالة المستخدم الواردة) إلى العقدة b، بينما يُدرج {user_preferences} في سلسلة تعليمات مفتاح حالة الجلسة المطابق الذي تم حفظه عبر output_key.

مخطّط بياني للبنية يعرض travel_workflow (سير العمل) الذي يربط START بـ user_profiler_agent وitinerary_synthesizer_agent

إنشاء المتخصّصين الأوّليين

تأكَّد من إيقاف adk web (Ctrl + C)، ثم افتح ملفًا جديدًا باسم travel_planner/specialists.py:

cloudshell edit ~/adk-graph-agents/travel_planner/specialists.py

الصِق الرمز التالي في travel_planner/specialists.py لتحديد user_profiler_agent وitinerary_synthesizer_agent (استيراد google_search مسبقًا للخطوة التالية):

from google.adk import Agent
from google.adk.tools import google_search

MODEL = "gemini-3.8-flash"

# --- Specialist 1: The User Profiler ---
user_profiler_agent = Agent(
    model=MODEL,
    name="user_profiler_agent",
    description="Extracts travel preferences and constraints from user input.",
    instruction="""
        Extract destination, duration (default 1 day), budget, interests, and exclusions.
        Return a single JSON object with keys: "destination", "duration", "budget", "interests", "exclusions".
    """,
    output_key="user_preferences",  # Saved for multi-hop constraints downstream
)

# --- Specialist 2: The Synthesizer ---
itinerary_synthesizer_agent = Agent(
    model=MODEL,
    name="itinerary_synthesizer_agent",
    description="Compiles preferences and research into a final Markdown itinerary.",
    instruction="""
        You are an elite Travel Concierge. Using the incoming input and
        User Preferences ({user_preferences}), write a Markdown itinerary
        with an itemized USD and local currency cost breakdown.
    """,
    output_key="final_itinerary",
)

تحديد سلسلة الحواف المتسلسلة

افتح ملفًا جديدًا باسم travel_planner/workflows.py:

cloudshell edit ~/adk-graph-agents/travel_planner/workflows.py

ألصِق تعريف Workflow التالي (حيث يتم تجميع صف من 3 عُقد أو أكثر في سلسلة متسلسلة من "START"):

from google.adk import Workflow
from .specialists import (
    itinerary_synthesizer_agent,
    user_profiler_agent,
)

# 1. SEQUENTIAL GRAPH: START -> user_profiler_agent -> itinerary_synthesizer_agent
travel_workflow = Workflow(
    name="travel_workflow",
    description="Orchestrates the end-to-end travel planning process as a graph workflow.",
    edges=[
        ("START", user_profiler_agent, itinerary_synthesizer_agent),
    ],
)

عرض سير العمل كـ root_agent

افتح travel_planner/agent.py:

cloudshell edit ~/adk-graph-agents/travel_planner/agent.py

استبدِل محتوى travel_planner/agent.py بالكامل بهذين السطرَين:

from .workflows import travel_workflow

root_agent = travel_workflow

🔍 التحقّق من عملك

بدء adk web من ~/adk-graph-agents:

cd ~/adk-graph-agents
adk web --allow_origins "*"

افتح "معاينة الويب" (Web Preview) على المنفذ 8000، وانقر على + جلسة جديدة (+ New Session)، ثم أرسِل:

Help me plan a trip to Kyoto

في عرض الأحداث، من المفترض أن يظهر لك تسلسل أحداث مشابه لما يلي:

1. user_profiler_agent -> outputs JSON {"destination": "Kyoto", "duration": "1 day", ...} and writes state["user_preferences"]
2. itinerary_synthesizer_agent -> receives preferences via node_input and outputs the Markdown itinerary

واجهة الويب ADK 2.14.0 تعرض تنفيذ الرسم البياني من START إلى user_profiler_agent إلى itinerary_synthesizer_agent وبرنامج الرحلة الذي تم إنشاؤه إلى كيوتو

انقر على أي حدث في الأحداث، ثم انقر على رمز الرسم البياني (account_tree في شريط أدوات تفاصيل الحدث) لفحص بنية START -> user_profiler_agent -> itinerary_synthesizer_agent المجمّعة، ثم راجِع علامة التبويب الحالة للتأكّد من تعبئة user_preferences وfinal_itinerary.

اضغط على Ctrl + C في الوحدة الطرفية لإيقاف adk web قبل المتابعة.

7. التوزيع والانضمام باستخدام JoinNode

يمكنك ربط برنامج الرحلة ببيانات الويب المباشرة من خلال إضافة خبيرَين متخصصَين في google_search (activity_finder_agent وrestaurant_finder_agent) ومزامنتهما مع JoinNode:

  • مجموعة البيانات المتعدّدة المخرجات: يؤدي وضع (activity_finder_agent, restaurant_finder_agent) داخل مجموعة بيانات الحافة إلى إرسال مخرجات user_profiler_agent على شكل node_input إلى كلا الباحثَين في الوقت نفسه.
  • حاجز Fan-in (JoinNode): يؤدي وضع JoinNode(name="research_join") مباشرةً بعد الصف إلى الانتظار حتى يكتمل كل فرع وارد ويمرّر dict ({"activity_finder_agent": ..., "restaurant_finder_agent": ...}) مدمجًا واحدًا كـ node_input إلى itinerary_synthesizer_agent بدلاً من تشغيل عملية التركيب مرتين.

مخطّط بياني للبنية يعرض travel_workflow يتفرّع من user_profiler_agent عبر node_input إلى activity_finder_agent وrestaurant_finder_agent بالتوازي، ثم يندمج في research_join (JoinNode)، ويمرّر node_input (dict) إلى itinerary_synthesizer_agent

إضافة المتخصّصين في الأبحاث

تأكَّد من إيقاف adk web (Ctrl + C)، ثم افتح travel_planner/specialists.py:

cloudshell edit ~/adk-graph-agents/travel_planner/specialists.py

أضِف activity_finder_agent وrestaurant_finder_agent إلى أسفل travel_planner/specialists.py (لا يحتاج أي منهما إلى output_key لأنّ research_join يعيد توجيه كلا الناتجَين مباشرةً عبر node_input):

# --- Specialist 3: The Activity Finder ---
activity_finder_agent = Agent(
    model=MODEL,
    name="activity_finder_agent",
    description="Finds 3 attractions matching the user's destination and interests.",
    instruction="""
        Using the travel preferences in the input, call google_search to find 3 specific attractions
        (including budget-friendly options).
        Return a JSON list with keys: "attraction_name", "estimated_cost_local", "estimated_cost_usd", "notes".
    """,
    tools=[google_search],
)

# --- Specialist 4: The Restaurant Finder ---
restaurant_finder_agent = Agent(
    model=MODEL,
    name="restaurant_finder_agent",
    description="Finds 3 restaurants matching the destination and budget.",
    instruction="""
        Using the travel preferences in the input, call google_search to find 3 highly-rated restaurants
        across price points.
        Return a JSON list with keys: "restaurant_name", "cuisine", "estimated_cost_local", "estimated_cost_usd".
    """,
    tools=[google_search],
)

إضافة fan-out وJoinNode إلى workflows.py

افتح travel_planner/workflows.py:

cloudshell edit ~/adk-graph-agents/travel_planner/workflows.py

استبدِل محتوى travel_planner/workflows.py بالكامل بالرسم البياني لعملية التوزيع وJoinNode:

from google.adk import Workflow
from google.adk.workflow import JoinNode
from .specialists import (
    activity_finder_agent,
    itinerary_synthesizer_agent,
    restaurant_finder_agent,
    user_profiler_agent,
)

research_join = JoinNode(name="research_join")

# 2. FAN-OUT / FAN-IN GRAPH: Profile -> (Activities || Restaurants) -> Join -> Synthesize
travel_workflow = Workflow(
    name="travel_workflow",
    description="Orchestrates the end-to-end travel planning process as a graph workflow.",
    edges=[
        (
            "START",
            user_profiler_agent,
            (activity_finder_agent, restaurant_finder_agent),
            research_join,
            itinerary_synthesizer_agent,
        ),
    ],
)

🔍 التحقّق من عملك

ابدأ adk web لاختبار البحث المتزامن ومزامنة JoinNode:

cd ~/adk-graph-agents
adk web --allow_origins "*"

افتح "معاينة الويب" (Web Preview) على المنفذ 8000، وانقر على + جلسة جديدة (+ New Session)، ثم أرسِل:

Help me plan a trip to Kyoto focused on historic temples and ramen

في عرض الأحداث، من المفترض أن يظهر لك تسلسل أحداث مشابه لما يلي:

1. user_profiler_agent -> emits JSON preferences via node_input
2. activity_finder_agent & restaurant_finder_agent -> invoke google_search concurrently
3. research_join -> merges both researcher outputs into one dict node_input
4. itinerary_synthesizer_agent -> drafts final_itinerary from the merged dict + {user_preferences}

اضغط على Ctrl + C في الوحدة الطرفية لإيقاف adk web قبل المتابعة.

8. دورات مراجعة المسارات

يمكنك فرض قيود الميزانية والقيود السلبية من خلال إضافة itinerary_critic_agent وitinerary_refiner_agent وعقدتَي دالة Python حتميتَين (route_critique وfinalize_itinerary) تُرجعان عناصر Event في ADK:

  • ‫route: يختار مفتاح الفرع المُعنوَن في حافة قاموس ({"revise": ..., "approved": ...}) الذي سيتم تشغيله بعد ذلك.
  • ‫output: يمرّر قيمة أو سلسلة Python مكتوبة على طول الحافة كـ node_input إلى العقدة التالية بدون عرض رسالة محادثة.
  • ‫message وstate: تُصدر السمة message نصًا مرئيًا للمستخدم في نص المحادثة، بينما تُنفّذ السمة state تعديلات على أزواج المفتاح والقيمة (stateDelta) في حالة الجلسة.

مخطّط بياني للبنية يعرض travel_workflow مع itinerary_critic_agent الذي يرسل البيانات إلى route_critique، والذي يوجّه "المراجعة" إلى itinerary_refiner_agent (يعود إلى itinerary_critic_agent) أو "تمت الموافقة" إلى finalize_itinerary

إضافة المتخصّصين في النقد والتحسين

تأكَّد من إيقاف adk web (Ctrl + C)، ثم افتح travel_planner/specialists.py:

cloudshell edit ~/adk-graph-agents/travel_planner/specialists.py

أضِف itinerary_critic_agent وitinerary_refiner_agent إلى أسفل travel_planner/specialists.py (لا يحتاج المنقّح إلى أداة google_search أو exit_loop لأنّه يبدّل العناصر التي تم جمعها في {final_itinerary} ولا يعمل إلا عندما يختار route_critique "revise"):

# --- Specialist 5: The Budget & Constraint Critic ---
itinerary_critic_agent = Agent(
    model=MODEL,
    name="itinerary_critic_agent",
    description="Audits the itinerary draft against budget and negative constraints.",
    instruction="""
        Audit the itinerary in the input against User Preferences ({user_preferences}).
        Verify that the USD total stays within the user's budget and no excluded venue types appear.
        If all checks pass, respond EXACTLY with the single word "APPROVED".
        Otherwise, output 1-2 concise revision instructions explaining what to swap or adjust.
    """,
)

# --- Specialist 6: The Itinerary Refiner ---
itinerary_refiner_agent = Agent(
    model=MODEL,
    name="itinerary_refiner_agent",
    description="Revises the itinerary draft using the critic's feedback.",
    instruction="""
        Revise Current Itinerary ({final_itinerary}) using the auditor critique in the input
        so all USD budget and exclusion rules in {user_preferences} pass.
        Output ONLY the updated Markdown itinerary.
    """,
    output_key="final_itinerary",
)

إضافة route_critique والدورة

افتح travel_planner/workflows.py:

cloudshell edit ~/adk-graph-agents/travel_planner/workflows.py

استبدِل محتوى travel_planner/workflows.py بالكامل بسير عمل الرسم البياني الكامل:

from google.adk import Event, Workflow
from google.adk.workflow import JoinNode
from .specialists import (
    activity_finder_agent, itinerary_critic_agent, itinerary_refiner_agent,
    itinerary_synthesizer_agent, restaurant_finder_agent, user_profiler_agent,
)

MAX_REVISIONS = 2
research_join = JoinNode(name="research_join")


def route_critique(node_input: str, revision_count: int = 0):
    """Routes to 'approved' when constraints pass, or 'revise' up to MAX_REVISIONS."""
    approved = node_input.strip().rstrip(".").upper() == "APPROVED"
    if approved or revision_count >= MAX_REVISIONS:
        return Event(route="approved", output=approved)
    return Event(
        route="revise", output=node_input, state={"revision_count": revision_count + 1}
    )


def finalize_itinerary(node_input: bool, final_itinerary: str, revision_count: int = 0):
    """Emits the verified itinerary and resets revision_count for the next turn."""
    status = (
        f"Itinerary approved after {revision_count} revision(s)."
        if node_input
        else f"Max revisions ({revision_count}) reached; returning best-effort draft."
    )
    return Event(
        message=f"{status}\n\n{final_itinerary}",
        output=final_itinerary,
        state={"revision_count": 0},
    )


travel_workflow = Workflow(
    name="travel_workflow",
    description="Orchestrates the end-to-end travel planning process as a graph workflow.",
    edges=[
        (
            "START",
            user_profiler_agent,
            (activity_finder_agent, restaurant_finder_agent),
            research_join,
            itinerary_synthesizer_agent,
            itinerary_critic_agent,
            route_critique,
        ),
        (route_critique, {"revise": itinerary_refiner_agent, "approved": finalize_itinerary}),
        (itinerary_refiner_agent, itinerary_critic_agent),
    ],
)

🔍 التحقّق من عملك

تأكَّد من أنّ travel_planner/workflows.py يتم تجميعه بشكل صحيح ويطبع حواف الرسم البياني الموجَّه:

cd ~/adk-graph-agents && source .venv/bin/activate
python3 -c "from travel_planner.agent import root_agent; print('\n'.join(f'{e.from_node.name} -> {e.to_node.name} (route={e.route})' for e in root_agent.graph.edges))"

من المفترَض أن تظهر لك نتيجة مثل:

__START__ -> user_profiler_agent (route=None)
user_profiler_agent -> activity_finder_agent (route=None)
user_profiler_agent -> restaurant_finder_agent (route=None)
activity_finder_agent -> research_join (route=None)
restaurant_finder_agent -> research_join (route=None)
research_join -> itinerary_synthesizer_agent (route=None)
itinerary_synthesizer_agent -> itinerary_critic_agent (route=None)
itinerary_critic_agent -> route_critique (route=None)
route_critique -> itinerary_refiner_agent (route=revise)
route_critique -> finalize_itinerary (route=approved)
itinerary_refiner_agent -> itinerary_critic_agent (route=None)

بعد ذلك، ابدأ adk web لاختبار كل من الموافقة الفورية والمراجعات التي يتم إجراؤها استنادًا إلى الميزانية:

adk web --allow_origins "*"

افتح "معاينة الويب" (Web Preview) على المنفذ 8000، وانقر على + جلسة جديدة (+ New Session)، وأرسِل الطلب المتعدّد الشروط حول كيوتو:

Design a full-day itinerary for Kyoto with a total budget of $100. My main interests are historical landmarks, temples, and authentic local cuisine. Please do not include any modern art museums or galleries.

لمشاهدة حلقة route="revise" أثناء عملها، أرسِل طلب متابعة بميزانية محدودة تبلغ 15 دولار أمريكي:

Now design a full-day Kyoto itinerary with paid temple admissions, lunch, and dinner on a strict total budget of $15 USD.

في عرض الأحداث، من المفترض أن يظهر لك تسلسل تنفيذ مشابه لما يلي:

1. user_profiler_agent -> writes JSON constraints to state["user_preferences"]
2. (activity_finder_agent, restaurant_finder_agent) -> run google_search concurrently -> research_join
3. itinerary_synthesizer_agent -> writes initial draft to state["final_itinerary"]
4. itinerary_critic_agent -> outputs "APPROVED" (or revision instructions when over budget)
5. route_critique -> emits route="approved" (skipping refiner) or route="revise" -> itinerary_refiner_agent -> itinerary_critic_agent
6. finalize_itinerary -> emits "Itinerary approved after N revision(s)." (or max-revisions notice) and resets revision_count=0

اضغط على Ctrl + C في الوحدة الطرفية لإيقاف adk web.

9. تحديد المشاكل وحلّها

القائمة المنسدلة للوكيل فارغة في adk web

  • السبب: تم بدء adk web داخل travel_planner/ بدلاً من الدليل الرئيسي ~/adk-graph-agents، أو تمت إعادة تشغيل جلسة Cloud Shell.
  • الحلّ: اضغط على Ctrl + C، ثم ارجع إلى الدليل الرئيسي، وأعِد التشغيل باستخدام cd ~/adk-graph-agents && source .venv/bin/activate && adk web --allow_origins "*".

خطأ في نموذج "404 NOT_FOUND"

  • السبب: ضغطت على مفتاح Enter لقبول منطقة us-central1 التلقائية أثناء adk create بدلاً من كتابة global.
  • الحلّ: افتح ~/adk-graph-agents/travel_planner/.env وتأكَّد من ضبط GOOGLE_CLOUD_LOCATION=global.

‫ERROR: [Errno 98] address already in use على المنفذ 8000

  • السبب: لا يزال خادم adk web السابق قيد التشغيل في Cloud Shell.
  • الحلّ: اضغط على Ctrl + C في الوحدة الطرفية النشطة أو أوقِف العملية الحالية باستخدام pkill -f "adk web" قبل بدء adk web مرة أخرى.

10. تَنظيم

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

حذف ملفات المشروع على جهاز المستخدم

أزِل دليل ~/adk-graph-agents والبيئة الافتراضية:

cd ~
rm -rf ~/adk-graph-agents

إيقاف Agent Platform API (اختياري)

أوقِف Agent Platform API إذا لم تعُد بحاجة إليها في هذا المشروع:

gcloud services disable aiplatform.googleapis.com --quiet

🔍 التحقّق من عملك

تأكيد إزالة "~/adk-graph-agents":

test ! -d ~/adk-graph-agents && echo "Workspace ~/adk-graph-agents removed"

من المفترَض أن تظهر لك نتيجة مثل:

Workspace ~/adk-graph-agents removed

11. تهانينا

لقد أنشأت مخطِّط سفر متعدّد الوكلاء باستخدام ADK 2 Workflow، ووحّدت السلاسل المتسلسلة، والتوزيع المتزامن google_search باستخدام JoinNode، ودورات صانع-مدقّق شرطية في رسم بياني حافة تعريفي واحد.

إنجازاتك

  • تم الإعلان عن مسار متعدد الوكلاء باستخدام ADK 2 Workflow(edges=[...])
  • تم تمرير البيانات مباشرةً بين عُقد الدوال Agent وJoinNode وPython من خلال node_input والمَعلمات المرتبطة بالحالة مع الحفاظ على الحد الأدنى من حالة الجلسة
  • تم توزيع google_search فرع متزامن ومزامنتها مع JoinNode
  • تم استبدال LoopAgent والتصعيد المتكرر المستند إلى الأدوات بجهاز توجيه Event(route=...) حتمي يتخطّى طلبات النماذج اللغوية الكبيرة غير الضرورية في المسودات الموافَق عليها

الخطوات التالية