إنشاء أداة لإنشاء عروض تقديمية بالذكاء الاصطناعي الوكيل: التنسيق والتقييم والإحصاءات باستخدام Google Cloud

1. مقدمة

بانر جلسة تعليمية احترافية للوكلاء

تعرَّف على كيفية إنشاء محرّك متعدد الوسائط ومتعدد الوكلاء يأخذ موجزًا أوليًا للموضوع وينسّق عملية إنشاء مواد عرض جذابة للغاية على وسائل التواصل الاجتماعي. باستخدام "حزمة تطوير الوكلاء" (ADK) من Google وبروتوكول Agent2Agent (A2A)، ستنشئ وكلاء مصغّرين مستقلين، وتضمّن قواعد العلامة التجارية كمهارة وكيل قابلة لإعادة الاستخدام، وتنشئ خدمة وكيل على Cloud Run، وتقيِّم المرئيات الرئيسية التي تم إنشاؤها للتأكّد من امتثالها للعلامة التجارية باستخدام وظائف الذكاء الاصطناعي في BigQuery.

الإجراءات التي ستنفذّها

في هذا الدرس التطبيقي، ستتعرّف على كيفية تنفيذ المهام التالية:

  • يمكنك تنسيق عدة وكلاء ADK باستخدام سير عمل رسم بياني قطعي.
  • تضمين إرشادات العلامة التجارية للحزمة كمهارة وكيل يتم تحميلها فقط عند الحاجة
  • تزويد وكيل بأداة إنشاء صور تحفظ نتائجها كعنصر.
  • عرض وكيل كخدمة A2A واستدعاؤه من وكيل آخر كعقدة بعيدة
  • انشر Visual Director على Cloud Run واستدعِ سير العمل عبر A2A.
  • تقييم المرئيات الرئيسية التي تم إنشاؤها من حيث مدى ملاءمتها للعلامة التجارية في BigQuery باستخدام دوال الذكاء الاصطناعي

المتطلبات

  • متصفّح ويب (مثل Chrome)
  • حساب Google Cloud ومشروع Google Cloud تم تفعيل الفوترة فيه

2. قبل البدء

إنشاء مشروع على Google Cloud

  1. في Google Cloud Console، اختَر مشروعًا على Google Cloud أو أنشِئ مشروعًا جديدًا في صفحة اختيار المشاريع.
  2. تأكَّد من تفعيل الفوترة لمشروعك على السحابة الإلكترونية. كيفية التحقّق مما إذا كانت الفوترة مفعَّلة في مشروع

بدء Cloud Shell

ستستخدم Google Cloud Shell لتنزيل الرمز البرمجي وتشغيل نصوص الإعداد ونشر التطبيق.

  1. افتح Google Cloud Shell في المتصفّح (أو انقر على تفعيل Cloud Shell في أعلى Google Cloud Console):
    https://shell.cloud.google.com/
    
    انقر على متابعة إذا تلقّيت إشعارات إضافية.انتظِر إلى أن يتم تحميل كلّ من نافذة الأوامر والمحرّر بالكامل. قد يستغرق ذلك دقيقة أو دقيقتين.
  2. اضبط رقم تعريف مشروعك ومنطقتك على Google Cloud (مثلاً، us-central1) في بيئتك من خلال لصق هذا الرمز في نافذة الأوامر (استبدِل و بقيمك):
    export PROJECT_ID="<YOUR_PROJECT_ID>"
    export REGION="<YOUR_REGION>"
    gcloud config set project $PROJECT_ID
    
    اللصق في النافذة الطرفيةستظهر لك رسالة مشابهة لما يلي:
    Your active configuration is: [cloudshell-####]
    Updated property [core/project]
    
  3. فعِّل واجهات Google Cloud APIs المطلوبة لمشروعك:
    gcloud services enable \
      compute.googleapis.com \
      cloudresourcemanager.googleapis.com \
      aiplatform.googleapis.com \
      storage.googleapis.com \
      storage-component.googleapis.com \
      bigqueryconnection.googleapis.com \
      bigquery.googleapis.com \
      run.googleapis.com \
      secretmanager.googleapis.com \
      cloudbuild.googleapis.com \
      artifactregistry.googleapis.com \
      iap.googleapis.com \
      iam.googleapis.com
    

إنشاء نسخة طبق الأصل من المستودع

لدينا نموذج للرمز البرمجي لمساعدتك في البدء في درس تطبيقي حول الترميز. وهو صغير نسبيًا، ولكن مستودع GitHub الذي يتضمّنه كبير جدًا. لتسريع عملية الاستنساخ وحفظ مساحة القرص، نستخدم عادةً git clone مع الخيار --filter=blob:none وgit sparse-checkout وبعض الأوامر الأخرى.

يبدو هذا الأمر معقّدًا للغاية، ولكن يمكن لـ Antigravity أن ينفّذ كل ذلك نيابةً عنّا.

أولاً، لنضبط إعدادات Antigravity CLI حتى تتمكّن من حلّ مشكلتنا مباشرةً، ثم لنشغّلها.

في Cloud Shell، شغِّل الأمر التالي:

source <(curl -fsSL https://github.com/GoogleCloudPlatform/devrel-demos/raw/refs/heads/vladkol/cloud-shell-configure-agy/codelabs/labs-scripts/bwg-multi-agent-pitch-generator/cloud_shell_configure_agy.sh)

يضبط الأمر Antigravity CLI ويشغّله في الوحدة الطرفية نفسها. عند الخروج من Antigravity CLI، ستعود إلى طلب Cloud Shell.

Antigravity CLI

نحتاج فقط إلى المجلد codelabs/bwg-multi-agent-pitch-generator في المستودع. لنطلب من Antigravity تنزيل هذا المجلد فقط، وسيكون متاحًا على جهازك في ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/.

أعطِ Antigravity CLI هذه المهمة:

Download files from under `https://github.com/GoogleCloudPlatform/devrel-demos/tree/main/codelabs/bwg-multi-agent-pitch-generator`
to folder `~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/`.
If `devrel-demos` folder already exists, rename it with a `.backup` suffix.

طلب Antigravity CLI

سينفّذ Antigravity المهمة. سيطلب منك منح أذونات لتنفيذ بعض الأوامر. بعد تنزيل الرمز، اخرج من واجهة سطر الأوامر Antigravity CLI من خلال تنفيذ الأمر /exit:

/exit

الخروج من Antigravity CLI

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

لنفتح الآن مساحة عمل الرمز في Cloud Shell. نفِّذ الأمر التالي في وحدة Cloud Shell الطرفية:

cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/
cloudshell workspace .

تشغيل نص التهيئة البرمجي

لتسهيل هذه التجربة، شغِّل نص التهيئة البرمجي من المستودع. تضبط هذه السمة متغيرات البيئة، وتثبّت agents-cli، وتنشئ حزمة Cloud Storage للعناصر، وتنشئ عملية ربط "مورد السحابة الإلكترونية" في BigQuery، وتمنح أذونات "إدارة الهوية وإمكانية الوصول" المطلوبة لحساب الخدمة مع إعادة المحاولة تلقائيًا.

يستدعي نص التهيئة البرمجي أيضًا setenv.sh، وهو برنامج مساعد صغير يستمد المتغيرات التي يحتاجها كلا الوكيلين، أي GOOGLE_CLOUD_PROJECT وGOOGLE_CLOUD_REGION وGOOGLE_CLOUD_LOCATION وGOOGLE_GENAI_USE_VERTEXAI، من PROJECT_ID وREGION اللذين تم تصديرهما أعلاه. يحفظ أيضًا هاتين القيمتين في ملف .env في الدليل الرئيسي حتى تتمكّن من استعادتهما لاحقًا.

  1. في Cloud Shell، اجعل نص التهيئة البرمجي قابلاً للتنفيذ وشغِّله:
    chmod +x codelab-setup.sh setenv.sh
    ./codelab-setup.sh
    
  2. سيستغرق تنفيذ هذا الأمر حوالي دقيقة واحدة. من المفترض أن تظهر لك الرسالة التالية التي تشير إلى النجاح: "🎉 🦄 اكتمل تنفيذ البرنامج النصي."نجاح
  3. تم تشغيل نص التهيئة البرمجي في غلاف خاص به، لذا لم تصل متغيراته إلى جهازك. حمِّل هذه الملفات في جلستك:
    source ./setenv.sh
    
    من المفترض أن يظهر لك مشروعك ومنطقتك وموقعك الجغرافي.

3- تنظيم الوكلاء باستخدام سير عمل رسومي

في هذه المهمة، ستنشئ مشروعًا لإنشاء عروض تقديمية وتصمّم سير عمل بيانيًا يشغّل وكيلَين ويجمع ناتجَيهما.

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

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

إنشاء بنية مشروع الوكيل الأول

  1. في Cloud Shell، أنشئ المشروع من خلال لصق ما يلي في الوحدة الطرفية:
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator
    agents-cli create pitch-generator \
      --agent adk \
      --agent-directory pitch_generator \
      --deployment-target cloud_run \
      --session-type in_memory \
      --cicd-runner skip \
      --region <YOUR_REGION> \
      --auto-approve
    
    يكتب هذا الأمر مشروعًا كاملاً وقابلاً للتنفيذ: وكيل وDockerfile واختبارات وTerraform لاستخدامه لاحقًا وملف .env مع نموذج .env.example الخاص به. يتضمّن هذا الدرس التطبيقي حول الترميز جزأين مهمّين:
    • pitch_generator/agent.py هو الوكيل، والملف الذي تعيد كتابته.
    • ‫pitch_generator/ هو اسم الحزمة الذي تم ضبطه بواسطة --agent-directory.
    يقرأ الوكيل مشروعه ومنطقته من البيئة التي تم تصديرها باستخدام setenv.sh، لذلك ليس هناك ما يجب ضبطه هنا.
  2. ثبِّت التبعيّات:
    cd pitch-generator
    agents-cli update -y && agents-cli install && uv sync --upgrade-package google-adk
    
    ملاحظة: يربط النموذج أيضًا المشروع بعرض A2A، وهو البروتوكول المستخدَم لاحقًا في هذا الدرس العملي. اترك هذه الملفات كما هي في الوقت الحالي.

فهم الرسم البياني

يتوافق الإصدار 2.x من "حزمة تطوير التطبيقات" مع سير عمل الرسومات البيانية. يتم إنشاء الرسم البياني من عُقد، وهي وحدات العمل، وحواف، وهي التي تربط بينها. عادةً ما تكون العُقدة عبارة عن وكيل، على الرغم من أنّ دالة Python العادية تعمل أيضًا. تشير الحافة إلى أنّه عند انتهاء إحدى العُقد، يتم تشغيل عقدة أخرى بعدها.

تتم كتابة الحواف على شكل قائمة، ويحدّد "مجموعة تطوير التطبيقات" الترتيب:

edges=[
    ("START", creative_director),        ## the graph begins here
    (creative_director, copywriter),     ## then this
]

يمنح التداخل الحافة معناها:

Edge

المعنى

(a, b, c)

سلسلة: أ، ثم ب، ثم ج، واحدًا تلو الآخر

(a, (b, c))

توزيع موسَّع: أ، ثم ب وج في الوقت نفسه

((a, b), c)

يجب أن ينتظر c كلاً من a وb

بالنسبة إلى عملية الدمج، يمكنك استخدام JoinNode، التي تنتظر كل عملية سابقة وتمرّر مخرجاتها معًا.

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

الرسم البياني الأوّلي لسير العمل

إنشاء مسار المعالجة

  1. افتح ملف الوكيل من خلال لصق ما يلي في الوحدة الطرفية:
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator
    cloudshell edit pitch_generator/agent.py
    
    سيظهر لك الرمز البرمجي الذي تم إنشاؤه agent.py في محرّر Cloud Shell.💡 ملاحظة: في أي وقت، يمكنك اختيار الرمز البرمجي واستخدام رمز المصباح لطلب شرحه من Gemini. يمكنك أيضًا طرح السؤال على واجهة سطر الأوامر Antigravity.استخدام Gemini Code Assist
  2. استبدِل الملف بأكمله في pitch_generator/agent.py في المحرّر بما يلي:
    import mimetypes
    import os
    
    from dotenv import load_dotenv
    from google.adk.agents import Agent
    from google.adk.agents.context import Context
    from google.adk.agents.remote_a2a_agent import (
       AGENT_CARD_WELL_KNOWN_PATH,
       RemoteA2aAgent,
    )
    from google.adk.apps import App
    from google.adk.events.event import Event
    from google.adk.models import Gemini
    from google.adk.workflow import JoinNode, Workflow
    from google.genai import types
    
    load_dotenv()
    
    MODEL = "gemini-3.8-flash"
    
    def _model() -> Gemini:
       return Gemini(
          model=MODEL,
          client_kwargs={"location": "global"},
          retry_options=types.HttpRetryOptions(attempts=3),
       )
    
    """ ⬇️ Configure visual-director RemoteA2aAgent agent below this comment """
    
    
    creative_director = Agent(
       name="creative_director",
       model=_model(),
       description="Turns a raw idea into a campaign concept.",
       instruction=(
          "You are the Creative Director. Turn the idea you are given into ONE "
          "punchy campaign concept line with the rationale explanation behind it."
       ),
       output_key="creative_director",
    )
    
    copywriter = Agent(
       name="copywriter",
       model=Gemini(model=MODEL, retry_options=types.HttpRetryOptions(attempts=3)),
       description="Writes social copy for a campaign concept.",
       instruction=(
          "You are the Copywriter. Write ONE short social caption for the "
          "campaign concept you are given. Under 25 words."
       ),
    )
    
    """ Waits for every node wired into it, then hands their outputs on together."""
    assemble = JoinNode(name="assemble")
    
    def package(node_input: dict):
       """Render the joined branches as the finished pitch.
       """
    
       pitch = "\n\n".join(
          [
                f"CONCEPT\n{node_input['creative_director']}",
                f"COPY\n{node_input['copywriter']}",
          ]
       )
       """ The first event is what the user sees. The second is the node's output,
       which any downstream node would receive."""
       yield Event(content=types.Content(role="model", parts=[types.Part(text=pitch)]))
       yield Event(output=pitch)
    
    """ we put creative_director before `assemble`  so that we can
       gather the `Concept` output at assemble"""
    root_agent = Workflow(
       name="pitch_generator",
       edges=[
          ("START", creative_director),
          (creative_director, copywriter),
          ((creative_director, copywriter), assemble),
          (assemble, package),
       ],
    )
    
    app = App(
       root_agent=root_agent,
       name="pitch_generator",
    )
    
  3. تتضمّن agents-cli واجهة مستخدم مدمجة لتطوير الويب تُسمى playground، وهي تتيح إجراء عمليات فحص مشابهة لتلك التي تتيحها حزمة تطوير التطبيقات (ADK). لبدء استخدام "مساحة المرح"، اتّبِع الخطوات التالية:
    agents-cli playground --port 8080
    
    سيُطلب منك تحديد ما إذا كنت تريد تفعيل القياس عن بُعد. اكتب n واضغط على Enter.
  4. انقر على معاينة الويب، أي رمز العين في أعلى يسار النافذة الطرفية، ثم اختَر المعاينة على المنفذ 8080.انقر على &quot;معاينة الويب&quot;
  5. ستظهر لك الواجهة التالية للدردشة مع موظّف الدعم. جرِّب إرسال الطلب "ألواح تزلّج طائرة للقطط". سيظهر لك ردّ كما هو موضّح أدناه، حيث يمكنك أيضًا فحص الرسم البياني الكامل لسير العمل.سير عمل العرض التوضيحيفحص طريقة إنشاء هذه الإجابة تعرض ساحة اللعب كل خطوة اتّخذها الرسم البياني، بالترتيب: creative_director، ثم copywriter، ثم assemble، ثم package. هذا التسلسل هو الرسم البياني الذي ربطته، وبهذه الطريقة يمكنك التأكّد بسرعة من الرسم البياني الذي أنشأته للتو.
  6. ارجع إلى النافذة الطرفية وأوقِف عملية agents-cli playground الجارية بالضغط على Ctrl+C مرّتين.

4. منح الوكيل مهارة

في هذه المهمة، ستنشئ وكيلاً ثانيًا وتزوّده بمجموعة من إرشادات العلامة التجارية المجمّعة كمهارة.

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

المهارة هي مجلد يحتوي على ملف SKILL.md. تبدو بنية دليل المهارات مثل المثال التالي (باستخدام visual_director، وهو الوكيل التالي الذي ستنشئه):

visual_director/
   ...
   agent.py
   skills/
      brand-guidelines/
         SKILL.md          ## required: frontmatter + instructions
         references/       ## optional: longer supporting documents
         assets/           ## optional: templates, images, data
         scripts/          ## optional: executable helpers
   ...

نقطة دخول مهارة الوكيل هي الملف SKILL.md، الذي يتضمّن قسم frontmatter بتنسيق YAML مثل القسم الموضّح أدناه:

---
name: brand-guidelines
description: The house brand rules for a key visual. Load this before writing art direction, so the visual looks like it came from this brand.
---

## Brand guidelines

Deep indigo and slate as the ground. One warm accent, amber or terracotta...

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

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

سنبدأ في إنشاء الوكيل الثاني، visual-director، باستخدام مهارات الوكيل كما هو موضّح في الرسم البياني أدناه:

تزويد الوكيل الثاني بالمهارة

إنشاء بنية Visual Director

ستنشئ وكيلًا متخصصًا للتعامل مع المكوّن المرئي، visual-director. وهو مشروع منفصل لأنّه سيصبح لاحقًا خدمة مستقلة لتوضيح كيفية تواصل عدة نماذج وكيلة مع بعضها البعض باستخدام بروتوكول A2A. سينتهي بك الأمر بمشروعَين جنبًا إلى جنب:

bwg-multi-agent-pitch-generator/
├── pitch-generator/              ## built in the last task
│   ├── pitch_generator/
│   │   ├── agent.py              ## the graph you wrote
│   │   ├── app_utils/            ## generated, A2A wiring
│   │   └── fast_api_app.py       ## generated, serves the agent
│   └── pyproject.toml
└── visual-director/              ## created below
    └── ...                       ## the same shape again
  1. أنشئ المشروع وثبِّت التبعيات من خلال لصق ما يلي في الوحدة الطرفية:
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator
    agents-cli create visual-director \
      --agent adk \
      --agent-directory visual_director \
      --deployment-target cloud_run \
      --session-type in_memory \
      --cicd-runner skip \
      --region <YOUR_REGION> \
      --auto-approve
    cd visual-director
    agents-cli update -y && agents-cli install && uv sync --upgrade-package google-adk
    

كتابة إرشادات بناء هوية العلامة التجارية

  1. أنشئ مجلد المهارة وافتح الملف:
    mkdir -p visual_director/skills/brand-guidelines
    cloudshell edit visual_director/skills/brand-guidelines/SKILL.md
    
  2. ألصِق المحتوى التالي في ملف SKILL.md المفتوح في "المحرّر" واحفظه:المهارة
    ---
    name: brand-guidelines
    description: The house brand rules for a key visual — palette, light, composition, subject treatment, and what never appears. Load this before writing art direction or rendering an image, so the visual looks like it came from this brand rather than from a generic image model.
    ---
    
    ## Brand guidelines
    
    House style for campaign key visuals. Every rule here beats a preference in the
    brief. If the brief asks for something forbidden below, follow the brief for the
    subject and the guidelines for the treatment.
    
    ## Palette
    
    Deep indigo and slate as the ground. One warm accent — amber or terracotta —
    carrying the eye to the subject. Nothing neon, nothing fluorescent.
    
    At most three colors with any weight. A fourth reads as clutter.
    
    ## Light
    
    One dominant source, low and raking. Long shadows are wanted. Golden hour or a
    single hard studio key, never flat overhead light and never a ring light.
    
    Rim light is allowed on the subject only, and only to separate it from the
    ground.
    
    ## Composition
    
    Subject off-center, roughly a third in. Generous negative space on the opposite
    side — copy lands there later, so leave it genuinely empty.
    
    Eye level or slightly below. No top-down flat lays. No fisheye, no Dutch tilt.
    
    ## Subject
    
    One subject. If the idea implies a crowd, pick the one that carries it.
    
    Photographic realism with a shallow depth of field. Not illustration, not 3D
    render, not clay, not pixel art.
    
    ## Never
    
    - Text, logos, watermarks, or signage anywhere in the frame
    - Collage or split-screen layouts
    - Chrome, lens flare, bokeh sparkle
    - Cool-toned neon cyan or magenta
    
    ## Rendering the prompt
    
    When you turn art direction into an image prompt, state the palette, the light,
    and the composition explicitly. An image model will not infer them from the
    subject alone. Put the "Never" items in as plain description of what the frame
    does contain, not as negatives — image models handle a positive description far
    better than a list of prohibitions.
    

تعديل visual_director/agent.py

عدِّل ملف agent.py واضبطه ليتضمّن المهارة المحدّدة سابقًا.

  1. افتح ملف الوكيل:
    cloudshell edit ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/visual-director/visual_director/agent.py
    
  2. استبدِل محتوى ملف visual_director/agent.py بالكامل بالرمز التالي:
    import mimetypes
    from pathlib import Path
    
    from google import genai
    from google.adk.agents import Agent
    from google.adk.apps import App
    from google.adk.models import Gemini
    from google.adk.skills import load_skill_from_dir
    from google.adk.tools import ToolContext
    from google.adk.tools.skill_toolset import SkillToolset
    from google.genai import types
    MODEL = "gemini-3.8-flash"
    
    ## Configure the `brand-guidelines` skill for the agent
    BRAND_SKILL = load_skill_from_dir(Path(__file__).parent / "skills" / "brand-guidelines")
    
    ## Add definition of `generate_key_visual` tool below this comment
    
    root_agent = Agent(
       name="visual_director",
       model=Gemini(
          model=MODEL,
          client_kwargs={"location": "global"},
          retry_options=types.HttpRetryOptions(attempts=3),
       ),
       description="Turns a campaign concept into art direction and a key visual.",
       instruction="""You are the Visual Director on a campaign pitch team.
    
       Call `load_skill` for `brand-guidelines` before you write anything. The house
       style is not optional and it is not in this prompt.
    
       You are given a campaign concept. Then, in order:
    
       1. Write art direction for ONE key visual that sells it, obeying the brand
       guidelines: subject, composition, lighting, color, mood. Three or four
       sentences.
       2. Call `generate_key_visual` with exactly that art direction.
    
       Your final reply is the art direction itself and nothing else. No preamble, no
       alternatives, no questions back. Do not mention the skill, the tool, or the
       image file — the image travels on its own.""",
       tools=[
          SkillToolset(skills=[BRAND_SKILL]),
          ## Supply the `generate_key_visual` tool below this comment
       ],
    )
    
    app = App(
       root_agent=root_agent,
       name="visual_director",
    )
    
    تمّ الآن إعداد وكيل visual-director بشكل صحيح باستخدام مهارة وكيل. قبل اختبارها، عليك تزويد هذا الوكيل بأداة لإنشاء مواد عرض صورية كإحدى قدراته الأساسية.

5- تزويد وكيل بأداة إنشاء صور

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

الأداة والناتج الذي تكتبه

تنفيذ الأداة وإضافتها إلى الوكيل

  1. افتح ملف الوكيل:
    cloudshell edit ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/visual-director/visual_director/agent.py
    
  2. انسخ أداة generate_key_visual التالية قبل تعريف root_agent:
    async def generate_key_visual(art_direction: str, tool_context: ToolContext) -> dict:
       """Render the art direction as the campaign's key visual.
    
       Args:
          art_direction: The art direction to render, in full.
    
       Returns:
          The saved artifact's filename, version, size and media type.
       """
       IMAGE_MODEL = "gemini-3.1-flash-lite-image"
    
       response = await genai.Client(location="global").aio.models.generate_content(
          model=IMAGE_MODEL, contents=art_direction
       )
    
       ## The image arrives as inline bytes on one of the response parts.
       image = next(
          (
             part.inline_data
             for part in response.candidates[0].content.parts or []
             if part.inline_data
          ),
          None,
       )
       if image is None:
          raise ValueError(f"{IMAGE_MODEL} returned no image for: {art_direction[:120]}")
    
       filename = "key_visual" + (
          mimetypes.guess_extension(image.mime_type or "") or ".bin"
       )
       version = await tool_context.save_artifact(filename, types.Part(inline_data=image))
    
       ## Return the artifact filename and version
       return {
          "filename": filename,
          "version": version,
          "bytes": len(image.data),
          "mime_type": image.mime_type,
       }
    
  3. أضِف generate_key_visual أسفل SkillToolset لتزويد الوكيل بالأداة:
       tools=[
          SkillToolset(skills=[BRAND_SKILL]),
          ## Supply the `generate_key_visual` tool below this comment
          generate_key_visual,
       ],
    
  4. تتضمّن agents-cli واجهة مستخدم مدمجة لتطوير الويب تُسمى playground، وهي تتيح إجراء عمليات فحص مشابهة لتلك التي تتيحها حزمة تطوير التطبيقات (ADK). لبدء استخدام "مساحة المرح"، اتّبِع الخطوات التالية:
    agents-cli playground --port 8080
    
  5. انقر على معاينة الويب، أي رمز العين في أعلى يسار النافذة الطرفية، ثم اختَر المعاينة على المنفذ 8080.انقر على &quot;معاينة الويب&quot;
  6. ستظهر لك الواجهة التالية للدردشة مع موظّف الدعم. جرِّب إرسال الطلب "تسع أرواح. انعدام الجاذبية".سير عمل العرض التوضيحييمكنك الاطّلاع من التفاعلات على كيفية تنفيذ الوكيل visual_director لـ load_skill("brand-guidelines") لقراءة مهارة إرشادات العلامة التجارية، ثم تنفيذ generate_key_visual لبدء إنشاء الصورة. يتم تخزين الصورة الناتجة كعنصر، ويتم عرضها أيضًا في واجهة المستخدم ويمكن فحصها بسهولة.
  7. ارجع إلى النافذة الطرفية وأوقِف عملية agents-cli playground الجارية بالضغط على Ctrl+C مرّتين.

6. بنية متعددة الوكلاء باستخدام بروتوكول A2A

في هذه المهمة، ستعرض "المخرج المرئي" كخدمة يمكن للوكلاء الآخرين طلبها، وستضبطها للرد بالصورة التي تم إنشاؤها.

‫Agent2Agent (A2A) هو بروتوكول مفتوح يتيح لوكيل واحد استدعاء وكيل آخر عبر شبكة. العنصر الأساسي هو بطاقة الوكيل، وهي مستند JSON ينشره الوكيل عن نفسه من خلال عنوان URL معروف، ويقدّم اسمه ووصفه وإمكاناته. يسترد المتصل البطاقة ويعرف كيفية التحدث إلى الوكيل الذي يقف وراءها، بدون رمز مصدر أو تبعيات أو لغة مشتركة.

بروتوكول A2A

لقد أعدّت agents-cli هذا الإعداد تلقائيًا. يتم إنشاء visual_director/app_utils/a2a.py، وهو العنصر الذي ينشئ البطاقة من وكيلك ويحمّل المسارات، وvisual_director/fast_api_app.py، وهو العنصر الذي يعرضها. ما عليك سوى إجراء تعديل بسيط للسماح له أيضًا بإعادة العناصر إلى العميل.

ستبدو البنية كما هو موضّح أدناه. يُرجى العِلم أنّ الوكيل لم يتم دمجه بعد في سير العمل الأوّلي:

إعداد خادم A2A

بنية خادم agents-cli A2A

يتم توفير الرمز والوحدة النمطية التاليَين عند إعداد وكيل visual-director باستخدام agents-cli. افتح هذه الملفات من خلال لصق الأوامر التالية في الوحدة الطرفية:

cloudshell edit visual_director/fast_api_app.py
cloudshell edit visual_director/app_utils/a2a.py

يبدأ visual_director/fast_api_app.py الوكيل ويحمّل مسارات A2A ضمن مسار تم إنشاؤه من اسم التطبيق:

await attach_a2a_routes(
    app,
    agent=root_agent,
    runner=runner,
    rpc_path=f"/a2a/{adk_app.name}",   ## becomes /a2a/visual_director
)

وستنجز أداة visual_director/app_utils/a2a.py باقي الخطوات في ثلاث حركات.

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

agent_card = await AgentCardBuilder(
    agent=agent,
    rpc_url=f"{resolved_app_url}{rpc_path}",
).build()

ثانيًا، يضع منفِّذًا بين البروتوكول والوكيل. تتم ترجمة طلبات A2A الواردة إلى عمليات تشغيل ADK، وتتم ترجمة النتائج مرة أخرى:

request_handler = DefaultRequestHandler(
    agent_executor=A2aAgentExecutor(runner=runner, force_new_version=True),
    task_store=task_store,
    agent_card=agent_card,
)

ثانيًا، يتم إنشاء مسارَين: أحدهما لعرض البطاقة، والآخر لتلقّي المكالمات.

مسار

الغرض

/a2a/visual_director/.well-known/agent-card.json

البطاقة يقرأ المتصل هذا النص أولاً للتعرّف على ما يمكن أن يفعله الوكيل

/a2a/visual_director

نقطة نهاية JSON-RPC المكان الذي يتم إرسال الطلبات الفعلية إليه

السماح للعناصر بتجاوز الحدود

تحتوي استجابة A2A تلقائيًا على نص. الصورة هي عنصر، ويظل العنصر في متجر وكيل العرض ما لم يتم ضبطه ليتم تضمينه.

  1. افتح ملف A2A الذي تم إنشاؤه:
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/visual-director
    cloudshell edit visual_director/app_utils/a2a.py
    
  2. ابحث عن عملية الاستيراد AgentCardBuilder وأضِف عمليات الاستيراد A2aAgentExecutorConfig وinclude_artifacts_in_a2a_event_interceptor أدناه:
    ## ...
    ## from google.adk.a2a.utils.agent_card_builder import AgentCardBuilder
    ## Add the following two below `AgentCardBuilder` import
    from google.adk.a2a.executor.config import A2aAgentExecutorConfig
    from google.adk.a2a.executor.interceptors.include_artifacts_in_a2a_event import (
       include_artifacts_in_a2a_event_interceptor,
    )
    
  3. ابحث عن السطر agent_executor=A2aAgentExecutor(runner=runner), واستبدله بما يلي (مع إضافة A2aAgentExecutorConfig):
             agent_executor=A2aAgentExecutor(
                runner=runner,
                force_new_version=True,
                config=A2aAgentExecutorConfig(
                   execute_interceptors=[include_artifacts_in_a2a_event_interceptor]
                ),
             ),
    

بدء الخدمة وقراءة بطاقة A2A المنشورة

  1. ابدأ تشغيل الخادم في الخلفية. عليك تقديم بيان APP_URL، لأنّ هذا هو العنوان الذي تعلن عنه البطاقة على أنّه عنوانها. ألصِق ما يلي في الوحدة الطرفية:
    APP_URL=http://localhost:8801 nohup uv run uvicorn visual_director.fast_api_app:app --port 8801 > /tmp/visual-director.log 2>&1 &
    
  2. انتظِر بضع ثوانٍ حتى يبدأ، ثم تأكَّد من أنّه يعرض المحتوى:
    sleep 10
    tail -2 /tmp/visual-director.log
    
    يجب أن يظهر لك ما يلي:
    INFO:     Application startup complete.
    INFO:     Uvicorn running on http://127.0.0.1:8801 (Press CTRL+C to quit)
    
  3. اقرأ البطاقة التي ينشرها الوكيل الآن:
    curl -s http://localhost:8801/a2a/visual_director/.well-known/agent-card.json | python3 -m json.tool
    
    تعرض البطاقة اسم الوكيل ووصفه وعنوان URL الخاص به ومهاراته، بما في ذلك generate_key_visual. يتم إنشاؤه مباشرةً من تعريف الوكيل في الرمز.
    {
       "name": "visual_director",
       "description": "Turns a campaign concept into art direction and a key visual.",
       "skills": [
            {
                "id": "visual_director",
                "name": "model",
                "tags": [
                    "llm"
                ]
            },
            {
                "id": "visual_director-generate_key_visual",
                "name": "generate_key_visual",
                "description": "Render the art direction as the campaign's key visual.\n\nArgs:\n   art_direction: The art direction to render, in full.\n\nReturns:\n   The saved artifact's filename, version, size and media type.",
                "tags": [
                    "llm",
                    "tools"
                ]
            }
       ],
       "preferredTransport": "JSONRPC",
       "protocolVersion": "0.3",
       "url": "http://localhost:8801/a2a/visual_director"
    }
    
    أنت الآن جاهز لتضمين وكيل visual-director في سير عمل pitch-generator الرئيسي.

7. تضمين الوكيل البعيد A2A في سير العمل

في هذه المهمة، ستضيف الوكيل البعيد إلى الرسم البياني كفرع ثالث.

يتم تضمين وكيل بعيد في RemoteA2aAgent ووضعه في الرسم البياني مثل أي عقدة أخرى. تحتفظ الحواف بالشكل نفسه، ولا يعرف المنسّق أيّ من عُقده محلية.

توجيه المنسّق إلى الخدمة وتعديل سير عمل الرسم البياني

  1. افتح ملف الوكيل:
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator
    cloudshell edit pitch_generator/agent.py
    
  2. أضِف عمليات الاستيراد هذه في أعلى الملف، إلى جانب عمليات الاستيراد الحالية:
    import asyncio
    
    import subprocess
    import google.auth.transport.requests
    import google.oauth2.id_token
    import httpx
    from google.adk.a2a.converters.part_converter import convert_genai_part_to_a2a_part
    
  3. أضِف تعريف RemoteA2aAgent التالي فوق تعريف creative_director:
    VISUAL_DIRECTOR_URL = os.getenv("VISUAL_DIRECTOR_URL", "http://localhost:8801")
    
    ## For deployment on Cloud Run
    def _cloud_run_client() -> httpx.AsyncClient | None:
       """An httpx client that signs each request with a Cloud Run ID token.
    
       Cloud Run deploys private, so both the agent-card fetch and the A2A calls
       need one.
       """
       if not VISUAL_DIRECTOR_URL.startswith("https://"):
          return None
    
       async def sign(request: httpx.Request) -> None:
          token = None
          try:
             token = await asyncio.to_thread(
                   google.oauth2.id_token.fetch_id_token,
                   google.auth.transport.requests.Request(),
                   VISUAL_DIRECTOR_URL,
             )
          except Exception:
             token = subprocess.check_output(
                ["gcloud", "auth", "print-identity-token", "-q"],
                stderr=subprocess.PIPE
             ).decode().strip()
          if not token:
                raise Exception("Failed to fetch ID token")
          request.headers["Authorization"] = f"Bearer {token}"
    
       return httpx.AsyncClient(event_hooks={"request": [sign]}, timeout=600)
    
    ## Keep the approval's tool traffic out of the A2A request
    def _pitch_parts_only(part: types.Part):
       """Sends the Visual Director text and media only.
    
       RemoteA2aAgent replays the session on every call, and the Visual Director
       runs downstream of the human approval, so the approval's function_response
       would travel alongside the concept text. An ADK server reads a function
       response as "resume the paused invocation" and rejects a message that also
       carries text, so drop the tool parts here.
       """
       if part.function_call or part.function_response:
          return None
       return convert_genai_part_to_a2a_part(part)
    
    visual_director = RemoteA2aAgent(
       name="visual_director",
       description="Turns a campaign concept into art direction for one key visual.",
       agent_card=(
          f"{VISUAL_DIRECTOR_URL}/a2a/visual_director{AGENT_CARD_WELL_KNOWN_PATH}"
       ),
       httpx_client=_cloud_run_client(),
       genai_part_converter=_pitch_parts_only,
    )
    
  4. استبدِل دالة Python لعقدة package أو أعد كتابتها باستخدام التعريف التالي:
    async def package(ctx: Context, node_input: dict):
       """Render the joined branches as the finished pitch."""
       empty = [name for name, value in node_input.items() if not value]
       if empty:
          raise ValueError(f"nothing reached the join from: {', '.join(empty)}")
    
       def _key_visual(ctx: Context) -> types.Blob | None:
          """The image the Visual Director sent back, or None if it sent none."""
          for event in reversed(ctx.session.events):
             if event.author != "visual_director" or not event.content:
                   continue
             for part in event.content.parts or []:
                   if part.inline_data and part.inline_data.data:
                      return part.inline_data
          return None
    
       image = _key_visual(ctx)
       if image is None:
          raise ValueError("the Visual Director returned art direction but no image")
    
       suffix = mimetypes.guess_extension(image.mime_type or "") or ".bin"
       filename = f"key_visual{suffix}"
       await ctx.save_artifact(filename, types.Part(inline_data=image))
    
       pitch = "\n\n".join(
          [
             f"CONCEPT\n{node_input['creative_director']}",
             f"COPY\n{node_input['copywriter']}",
             f"ART DIRECTION\n{node_input['visual_director']}",
             f"KEY VISUAL\n{filename}, {len(image.data)} bytes, {image.mime_type}",
          ]
       )
       yield Event(
          content=types.Content(
             role="model",
             parts=[types.Part(text=pitch), types.Part(inline_data=image)],
          )
       )
       yield Event(output=pitch)
    
  5. أضِف بوابة موافقة بشرية قبل المتابعة إلى عملية الإنشاء اللاحقة. في pitch_generator/agent.py، حدِّد عُقد الموافقة فوق root_agent:
    @node(rerun_on_resume=False)
    async def approve_concept(ctx: Context):
        yield RequestInput(
            message="Please approve the campaign concept (yes/no).",
            response_schema=str
        )
    
    @node(rerun_on_resume=True)
    async def user_approval(ctx: Context):
       user_response = await ctx.run_node(approve_concept)
       if str(user_response).lower() in ("yes", "y"):
          approved_concept = f"## Approved Concept\n\n{ctx.session.state['creative_director']}"
          yield Event(
             content=types.Content(
                role="model",
                parts=[types.Part(text=approved_concept)]
             )
          )
       else:
          raise ValueError("User rejected the concept")
    
  6. أخيرًا، عدِّل عمليات الاستيراد في أعلى الصفحة لتضمين RequestInput وnode من google.adk.events وgoogle.adk.workflow:
    from google.adk.events import Event, RequestInput
    from google.adk.workflow import JoinNode, Workflow, node
    
    وعدِّل سير عمل root_agent edges للتوجيه من خلال user_approval:
        edges=[
            ("START", creative_director),
            (creative_director, user_approval, (copywriter, visual_director)),
            ((creative_director, copywriter, visual_director), assemble),
            (assemble, package),
        ],
    
    في ما يلي بعض التفاصيل الجديرة بالملاحظة:
    • الموافقة على المشاركة البشرية (HITL): يؤدي approve_concept إلى RequestInput. عندما يصل سير العمل إلى هذه العقدة، يتوقف التنفيذ مؤقتًا ويصدر مقاطعة للمتصل. لن يتم تشغيل الفروع اللاحقة (copywriter وvisual_director) إلى أن يتم تأكيد موافقة المستخدم.
    • يظهر visual_director في الحواف تمامًا مثل copywriter، على الرغم من أنّ أحدهما لا يتطلّب سوى استدعاء دالة، بينما يتطلّب الآخر الاتصال بشبكة.
    • تتم قراءة الصورة المعروضة من أحداث الجلسة بدلاً من node_input، لأنّه في الوقت الحالي يتم الترويج للنص فقط إلى ناتج العقدة. عبرت الصورة الحدّ من خلال أداة اعتراض A2A، ولكن تم إرفاقها بأحداث الوكيل البعيد، لذا تسترجعها الدالة المساعدة _key_visual من هناك.

اختبار عملية الدمج

ستختبر الآن إمكانية التواصل بين Visual Director (الذي يعمل في الخلفية) وPitch Generator من خلال بروتوكول A2A.

  1. تأكَّد من أنّ تطبيق Visual Director لا يزال يعمل في الخلفية:
    curl -s -o /dev/null -w "visual director: %{http_code}\n" http://localhost:8801/a2a/visual_director/.well-known/agent-card.json
    
    من المفترض أن تظهر لك النتيجة visual director: 200 هنا.في حال ظهور أي حالة أخرى، نفِّذ الأمر التالي لإعادة تشغيلها:
    fuser -k 8801/tcp 2>/dev/null || true
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/visual-director
    APP_URL=http://localhost:8801 nohup uv run uvicorn visual_director.fast_api_app:app --port 8801 > /tmp/visual-director.log 2>&1 &
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator
    sleep 10
    curl -s -o /dev/null -w "visual director: %{http_code}\n" http://localhost:8801/a2a/visual_director/.well-known/agent-card.json
    
  2. لبدء المعاينة، اتّبِع الخطوات التالية:
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator/pitch_generator
    agents-cli playground --port 8080
    
  3. انقر على معاينة الويب، أي رمز العين في أعلى يسار النافذة الطرفية، ثم اختَر المعاينة على المنفذ 8080.انقر على &quot;معاينة الويب&quot;
  4. ستظهر لك واجهة للدردشة مع الوكيل. جرِّب إرسال الطلب "ألواح تزلج طائرة للقطط".سير عمل العرض التوضيحيسير عمل العرض التوضيحي سيظهر لك التفاعل الكامل حيث يبدأ سير العمل ويتم تشغيل كل وكيل. بعد أن creative_director يضع مسودة للمفهوم، يتوقف سير العمل عند بوابة الموافقة ويطلب منك تأكيد ما يلي: "يُرجى الموافقة على مفهوم الحملة (نعم/لا)." أدخِل yes في حقل الإدخال للمتابعة.بعد الموافقة، يتوسّع الرسم البياني: يمكنك الاطّلاع على الوقت الذي يبدأ فيه الوكيل visual-director بتحميل المهارة، وفي منتصف عملية إنشاء الصورة، ينهي الوكيل copywriter إنشاء التعليق على وسائل التواصل الاجتماعي، ما يوضّح أنّ كلا الوكيلين يعملان بالتوازي بدون انتظار أحدهما الآخر. بعد انتهاء كلتا العمليتين، تربط العقدة assemble النتائج معًا بالمفهوم الموافق عليه الذي تم إنشاؤه بواسطة creative_director، ما يؤدي إلى إكمال عملية تسليم محتوى الأغنية المقترَحة في الدالة package.الخدمتان المرتبطتان عبر A2A

8. تخزين العناصر المرئية الرئيسية بشكل دائم في Cloud Storage

حتى الآن، تم تخزين كل العناصر المرئية الرئيسية في الذاكرة. وعندما تتوقف العملية، تختفي الصورة، وهو أمر مقبول في ساحة اللعب وغير مفيد في الحملة. في هذه المهمة، ستمنح سير العمل حزمة للكتابة إليها، وستنشئ مجموعة بيانات BigQuery ستستخدمها في المهمة الأخيرة.

يحدّد ADK مكان عرض العناصر عند بدء التشغيل. يقرأ ملف app_utils/services.py الذي تم إنشاؤه متغيّر بيئة واحدًا:

def get_artifact_service():
    """Process-wide artifact service: GCS when a bucket is set, else in-memory."""
    if bucket := os.environ.get("LOGS_BUCKET_NAME"):
        return GcsArtifactService(bucket_name=bucket)
    return InMemoryArtifactService()

إذا ضبطت القيمة على LOGS_BUCKET_NAME، سيتم تخزين كل مكالمة save_artifact في Cloud Storage بدلاً من ذلك.

دمج خدمة العناصر مع حزمة GCS

إنشاء مجموعة بيانات BigQuery

  1. في Cloud Shell، نفِّذ الأمر التالي لإنشاء مجموعة بيانات BigQuery:
    bq mk --dataset --location=<YOUR_REGION> --project_id=<YOUR_PROJECT_ID> bwg
    
  2. وجِّه متجر عناصر المنسّق إلى الحزمة:
    تأخذ
    export LOGS_BUCKET_NAME="<YOUR_PROJECT_ID>-bwg"
    
    GcsArtifactService اسم حزمة مجرّدًا، بدون البادئة gs://. وكما هو الحال مع المتغيرات الأخرى، يكون هذا المتغير متاحًا في shell، وتمرّره مهمة النشر إلى Cloud Run.
  3. تحقَّق من محتوى الحزمة. في علامة تبويب جديدة في المتصفّح، افتح عنوان URL التالي:
    https://console.cloud.google.com/storage/browser?project=<YOUR_PROJECT_ID>
    
    انقر على الحزمة. واستنادًا إلى اختباراتك السابقة، قد يحتوي على صور تم إنشاؤها سابقًا.

9. نشر وكيل Visual Director على Cloud Run

تم تشغيل كل شيء حتى الآن على جهاز واحد، ويمكن الوصول إلى Visual Director على localhost:8801. في هذه المهمة، ستفعّل وكيل Visual Director كخدمة Cloud Run مستقلة وتسمح لمولّد الأغاني بالتواصل معه عبر الشبكة باستخدام بروتوكول Agent2Agent (A2A).

يحصل Visual Director على عنوان URL حقيقي https://، لذا يبدأ _cloud_run_client الذي كتبته سابقًا في توقيع الطلبات بدلاً من عرض None. بالإضافة إلى ذلك، تنشر Cloud Run المحتوى بشكل خاص تلقائيًا.

كلا الوكيلَين كخدمات Cloud Run

تفعيل Director المرئي

  1. أوقِف خدمة تُشغَّل في الخلفية المحلية والملعب إذا كانا لا يزالان يعملان:
    fuser -k 8801/tcp 2>/dev/null || true
    fuser -k 8000/tcp 2>/dev/null || true
    
  2. نشر الوكيل يؤدي هذا الإجراء إلى إنشاء حاوية وتحميلها.⏰ سيستغرق ذلك بضع دقائق.
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/visual-director
    source .env
    agents-cli deploy \
      --service-name visual-director \
      --project <YOUR_PROJECT_ID> \
      --region <YOUR_REGION> \
      --no-confirm-project \
      --labels dev-tutorial=bwg001-devlab-1 \
      --update-env-vars "LOGS_BUCKET_NAME=$LOGS_BUCKET_NAME,GOOGLE_GENAI_USE_VERTEXAI=true,GOOGLE_CLOUD_PROJECT=$GOOGLE_CLOUD_PROJECT,GOOGLE_CLOUD_LOCATION=$GOOGLE_CLOUD_LOCATION,GOOGLE_CLOUD_REGION=$GOOGLE_CLOUD_REGION"
    

تشغيل أداة Pitch Generator التي تستخدم Visual Director التي تم نشرها على Cloud Run

  1. اقرأ عنوان URL الخاص بـ "المخرج المرئي"، وشغِّل "أداة إنشاء الأغاني المقترَحة" التي تستخدمه:
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator
    source .env
    export VISUAL_DIRECTOR_URL=$(gcloud run services list \
      --project <YOUR_PROJECT_ID> \
      --region <YOUR_REGION> \
      --flatten='spec.template.spec.containers[0].env' \
      --filter='metadata.name=visual-director AND spec.template.spec.containers[0].env.name=APP_URL' \
      --format='value(spec.template.spec.containers[0].env.value)')
    echo "Visual Director is at $VISUAL_DIRECTOR_URL"
    APP_URL=http://localhost:8000 nohup uv run uvicorn pitch_generator.fast_api_app:app --port 8000 > /tmp/visual-pitch_generator.log 2>&1 &
    

استدعاء سير العمل الذي تم نشره

يتوافق كلا الوكيلين مع بروتوكول A2A. يتضمّن المستودع برنامجًا صغيرًا من جهة العميل لإرسال طلبات إلى Pitch Generator من خلال A2A.

يجب ضبط PITCH_GENERATOR_URL على عنوان URL الخاص بأداة إنشاء الأغاني الذي يتم تشغيله محليًا. في الوقت نفسه، يعمل "المخرج المرئي" في Cloud Run، وتتواصل معه "أداة إنشاء الأغاني المقترَحة" عبر A2A.

  1. انسخ العميل إلى مشروعك وشغِّله:
    cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/pitch-generator
    cp ../solution/pitch-generator/call_agent.py .
    PITCH_GENERATOR_URL=http://localhost:8000 uv run python call_agent.py "Flying skateboards for cats" --save-image ./key_visual.png
    
    بما أنّ سير العمل يتضمّن بوابة موافقة بشرية، يتم إيقاف النص البرمجي مؤقتًا بعد إنشاء مسودة التصميم:
    === HUMAN APPROVAL REQUIRED ===
    
    [DRAFT CONCEPT]
    Concept Line: "Gravity is a Suggestion."
    ...
    
    Please approve the campaign concept (yes/no):
    
    اكتب yes واضغط على Enter للموافقة على سير العمل ومتابعته. (يمكنك أيضًا تمرير -y أو --auto-approve للموافقة تلقائيًا).
  2. افتح call_agent.py وافحص كيفية تفاعله مع الوكيل باستخدام a2a-sdk:
    • تنشئ ClientFactory استهدافًا رسميًا Client في A2A https://SERVICE_URL/a2a/pitch_generator.
    • يرسل generate_pitch_async مبلغًا أوليًا بقيمة SendMessageRequest. عندما يعود الردّ مع TaskState.TASK_STATE_INPUT_REQUIRED، يتم رصد الجزء adk_request_input، وعرض مسودّة المفهوم، وجمع موافقتك، وإرسال طلب استئناف يحمل ردّك إلى المهمة.
    • تقرأ حلقة التحليل الأقسام CONCEPT وCOPY وART DIRECTION من عناصر المهمة المكتملة، وتحفظ الصورة الثنائية في key_visual.png.
    من المفترض أن تظهر أقسام النصوص الثلاثة مطبوعة، وأن يتم حفظ الصورة في key_visual.png.
  3. حمِّل الصورة الرئيسية إلى الحزمة ضمن بادئة يمكنك توجيه BigQuery إليها:
    gcloud storage cp ./key_visual.png gs://<YOUR_PROJECT_ID>-bwg/key-visuals/cats.png
    
  4. أنشئ أغنية مقترَحة ثانية لتتمكّن من المقارنة بينهما، ثم حمِّلها أيضًا:
    PITCH_GENERATOR_URL=http://localhost:8000 uv run python call_agent.py "A commuter bike built for rainy cities" --save-image ./bike.png
    gcloud storage cp ./bike.png gs://<YOUR_PROJECT_ID>-bwg/key-visuals/bike.png
    

10. تسجيل النتائج المرئية الرئيسية من حيث مدى ملاءمتها للعلامة التجارية في BigQuery

أصبحت لديك الآن صور في حزمة وإرشادات العلامة التجارية التي من المفترض أن تشكّلها. في هذه المهمة، يمكنك الجمع بين الطريقتين: الإشارة إلى الصور من BigQuery، ثم الطلب من أحد نماذج Gemini تقييم كل صورة وفقًا للإرشادات، بدون نقل أي بايت من Cloud Storage.

تتولّى ميزتان من ميزات BigQuery تنفيذ هذه المهمة:

  • ‫ObjectRef هي قيمة تشير إلى عنصر Cloud Storage. تنشئ OBJ.MAKE_REF واحدًا من معرّف موارد موحّد بالإضافة إلى عملية ربط تمنح BigQuery إذن وصول مفوَّضًا إلى الحزمة. إنّ pitch-connection الذي تم إنشاؤه بواسطة نص التهيئة البرمجي هو هذا الاتصال، ويتضمّن حاليًا Storage Object Viewer وAgent Platform User.
  • تتلقّى AI.SCORE طلبًا وتعرض FLOAT64. يمكن أن يجمع الطلب بين السلاسل وقيم ObjectRef، وبالتالي يرى النموذج قواعد التقييم والصورة معًا.

يقرأ BigQuery الحزمة

إنشاء جدول للعناصر المرئية الرئيسية

  1. افتح BigQuery Studio في علامة تبويب متصفّح جديدة:
    https://console.cloud.google.com/bigquery?project=<YOUR_PROJECT_ID>
    
  2. انقر على إنشاء طلب بحث جديد (رمز +)، ثم الصِق ما يلي ونفِّذه (مع استبدال و برقم تعريف مشروعك ومنطقتك). ينشئ هذا الجدول صفًا واحدًا لكل صورة رئيسية، ويربط مفهوم الحملة بمرجع إلى الصورة:
    SET @@location = '<YOUR_REGION>';
    
    CREATE OR REPLACE TABLE bwg.key_visuals AS
    SELECT
      campaign,
      concept,
      OBJ.FETCH_METADATA(
        OBJ.MAKE_REF(uri, '<YOUR_REGION>.pitch-connection')
      ) AS key_visual
    FROM UNNEST([
      STRUCT(
        'cats' AS campaign,
        'Flying skateboards for cats' AS concept,
        'gs://<YOUR_PROJECT_ID>-bwg/key-visuals/cats.png' AS uri
      ),
      STRUCT(
        'bike',
        'A commuter bike built for rainy cities',
        'gs://<YOUR_PROJECT_ID>-bwg/key-visuals/bike.png'
      )
    ]);
    
    تنشئ bqOBJ.MAKE_REF المرجع من معرّف الموارد المنتظم والاتصال. OBJ.FETCH_METADATA ثم تتواصل مع خدمة Cloud Storage وتملأ حجم العنصر ونوع المحتوى والجيل. تُعدّ المكالمة الثانية أيضًا أسرع طريقة لإثبات أنّ الاتصال يعمل: إذا كانت الأذونات غير صحيحة، يعود الحقل details ويتضمّن الخطأ بدلاً من البيانات الوصفية.
  3. فحص المحتوى الذي خزّنته استبدِل طلب البحث السابق بهذا الطلب ونفِّذه:
    SELECT campaign, concept, key_visual.uri, key_visual.details
    FROM bwg.key_visuals;
    
    يحتوي الجدول على مؤشر وبياناته الوصفية، وليس على الصورة. لم تغادر وحدات البايت الحزمة مطلقًا.

تقييم كل صورة وفقًا لإرشادات العلامة التجارية

تم توجيه "المخرج المرئي" باتّباع لوحة ألوان وقاعدة إضاءة وقاعدة تركيب. ويعود تحديد ما إذا كان قد تم ذلك إلى تقديرك، وهذا هو الغرض من AI.SCORE.

  1. نفِّذ الطلب التالي (استبدِل بمنطقتك). الطلب هو مجموعة من ثلاثة عناصر: معايير التقييم وفكرة الحملة وObjectRef للصورة:
    SELECT
      campaign,
      concept,
      AI.SCORE(
        ('''You are the brand guardian reviewing a campaign key visual.
    Score the attached image from 1 to 10 on how closely it follows these house rules:
    - Palette: deep indigo and slate, with one warm accent of amber or terracotta. Nothing neon.
    - Light: one dominant low, raking source with long shadows. No flat overhead light.
    - Composition: subject off-center with generous empty negative space on the opposite side.
    - Subject: one subject only, photographic realism, shallow depth of field.
    - Never: text, logos, or watermarks anywhere in the frame.
    A 10 obeys every rule. A 1 ignores them. The campaign concept is: ''',
          concept,
          key_visual),
        connection_id => '<YOUR_REGION>.pitch-connection',
        endpoint => 'gemini-3.5-flash'
      ) AS brand_fit
    FROM bwg.key_visuals
    ORDER BY brand_fit DESC;
    
  2. تحويل النتيجة إلى حكم يمكن لفريقك اتّخاذ إجراء بشأنه:
    تعرض الدالة
    CREATE OR REPLACE TABLE bwg.brand_review AS
    SELECT
      campaign,
      concept,
      brand_fit,
      IF(brand_fit >= 7, 'on brand', 'needs another pass') AS verdict
    FROM (
      SELECT
        campaign,
        concept,
        AI.SCORE(
          ('''Score this campaign key visual from 1 to 10 on how closely it follows the house
    brand rules: deep indigo and slate palette with a single warm accent and nothing neon; one
    low raking light source with long shadows; the subject off-center with empty negative space
    opposite; a single photographic subject; and no text, logos, or watermarks in the frame.
    The campaign concept is: ''',
              concept,
              key_visual),
          connection_id => '<YOUR_REGION>.pitch-connection',
          endpoint => 'gemini-3.5-flash'
        ) AS brand_fit
      FROM bwg.key_visuals
    );
    
    SELECT * FROM bwg.brand_review ORDER BY brand_fit DESC;
    
    AI.SCORE FLOAT64 فقط، لذا يمكن ضبط الحدّ في SQL العادي بدون تعديل الطلب. يتم احتساب النتائج لكل صف، ما يعني تطبيق معايير التقييم نفسها على كل مادة عرض تنتجها الحملة، وليس فقط على مادتَي العرض اللتين أنشأتهما هنا.

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

إنشاء تقييمات

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

يتضمّن أحد الوكلاء Human in the loop، ما يعني أنّه ينتظر إدخال المعلومات. عليك السماح لـ Antigravity بإجراء تعديلات على الرمز الحالي حتى لا يعيق هذا السلوك عمليات التقييم التلقائية.

إحدى أفضل الطرق للبدء هي طرح سؤال على Antigravity CLI.

اكتب ما يلي في Cloud Shell لبدء واجهة سطر الأوامر:

cd ~/devrel-demos/codelabs/bwg-multi-agent-pitch-generator/
agy

إليك طلبًا أوّليًا:

Explore this repository (`pitch-generator` and `visual-director`), including their agent instructions, tools, and `SKILL.md` files. Design and implement a creative `agents-cli eval` evaluation suite under `tests/eval/` that stress-tests the agent's behavior.

يمكنك أيضًا العثور على مثال للتقييمات وتعليمات لتنفيذها في المستودع المستنسخ.

12. تَنظيم

لتجنُّب تحمّل رسوم في حسابك على Google Cloud مقابل الموارد المستخدَمة في هذا الدرس التطبيقي حول الترميز، يمكنك حذف المشروع (ننصحك بذلك) أو حذف الموارد الفردية التي تم إنشاؤها أثناء الدرس التطبيقي حول الترميز:

gcloud run services delete visual-director --region=<YOUR_REGION> --project=<YOUR_PROJECT_ID> --quiet
bq rm -r -f -d <YOUR_PROJECT_ID>:bwg
bq rm --connection --location=<YOUR_REGION> --project_id=<YOUR_PROJECT_ID> pitch-connection
gcloud storage rm -r gs://<YOUR_PROJECT_ID>-bwg

13. تهانينا!

في هذا الدرس التطبيقي حول الترميز، أنشأت أداة لإنشاء عروض ترويجية متعددة الوكلاء باستخدام "حزمة تطوير الوكلاء". ربطت ثلاثة وكلاء بسير عمل رسم بياني حتمي، وحزّمت إرشادات العلامة التجارية كمهارة وكيل يتم تحميلها عند الطلب، وقدّمت للوكيل أداة إنشاء صور تحفظ ناتجها كعنصر. بعد ذلك، قسّمت أداة Visual Director إلى خدمة A2A منفصلة، وأعدت تضمينها في الرسم البياني كعقدة بعيدة، ونشرتها على Cloud Run، واستدعيت سير العمل بالكامل عبر A2A من أداة تنسيق تعمل على جهازك. أخيرًا، أشرت إلى المرئيات الرئيسية التي تم إنشاؤها من BigQuery باستخدام ObjectRef واستخدمت AI.SCORE لتقييمها مقارنةً بإرشادات علامتك التجارية.