عامل‌ها در مقیاس: معماری چندعاملی با پروتکل A2A در زمان اجرای عامل و ادغام ADK

۱. مقدمه

با افزایش مسئولیت‌های عامل‌های هوش مصنوعی، نگهداری، مقیاس‌پذیری و تکامل یک عامل واحد که همه کارها را انجام می‌دهد، دشوار می‌شود. قابلیت‌های مختلف اغلب به استراتژی‌های استقرار متفاوت، چرخه‌های به‌روزرسانی یا حتی تیم‌های مختلفی که مالک آنها هستند، نیاز دارند.

  • پروتکل A2A (Agent2Agent) مشکل ارتباطات را حل می‌کند - استانداردسازی نحوه کشف قابلیت‌های یکدیگر توسط عامل‌ها و همکاری آنها در چارچوب‌ها و سازمان‌ها.
  • پلتفرم عامل سازمانی Gemini Runtime بخش استقرار را حل می‌کند - یک پلتفرم کاملاً مدیریت‌شده و بدون سرور که میزبان عامل‌های شما با پشتیبانی داخلی A2A، مقیاس‌پذیری خودکار، نقاط پایانی امن، جلسات مداوم و مدیریت زیرساخت صفر است.

آنها در کنار هم به شما امکان می‌دهند تا عامل‌های تخصصی بسازید، آنها را به عنوان سرویس‌های A2A قابل کشف مستقر کنید و آنها را در سیستم‌های چندعاملی ترکیب کنید.

آنچه خواهید ساخت

یک عامل رزرو که رزرو میز رستوران (ایجاد، بررسی و لغو) را با استفاده از وضعیت جلسه ADK که توسط Gemini Enterprise Agent Platform Sessions مدیریت می‌شود، مدیریت می‌کند. شما این عامل را در Gemini Enterprise Agent Platform Runtime مستقر می‌کنید که در آنجا از طریق کارت عامل پروتکل A2A قابل کشف می‌شود. سپس عامل دربان رستوران Foodie Finds (از codelab پیش‌نیاز ، اگر از codelab بازدید نکرده‌اید نگران نباشید - ما یک مخزن اولیه برای شما آماده کرده‌ایم) را ارتقا می‌دهید تا عامل رزرو را به عنوان یک زیرعامل A2A از راه دور مصرف کند. نتیجه: یک سیستم چندعاملی که در آن هماهنگ‌کننده، درخواست‌های منو را به MCP Toolbox و درخواست‌های رزرو را به عامل A2A از راه دور هدایت می‌کند.

۱۴۳fadef342e67a6.jpeg

آنچه یاد خواهید گرفت

  • یک عامل ADK بسازید که از سرویس جلسه مدیریت‌شده برای مدیریت داده‌های رزرو استفاده می‌کند.
  • یک مامور ADK را به عنوان سرور A2A با کارت‌ها و مهارت‌های مامور در معرض نمایش قرار دهید
  • یک عامل A2A را در Gemini Enterprise Agent Runtime مستقر کنید
  • استفاده از یک عامل A2A از راه دور از یک عامل ADK دیگر با استفاده از RemoteA2aAgent و مدیریت درخواست‌های احراز هویت شده
  • سیستم‌های چندعاملی را به صورت تدریجی آزمایش کنید: A2A محلی، A2A مستقر، ادغام جزئی، استقرار کامل

پیش‌نیازها

۲. تنظیمات محیط - ادامه از آزمایشگاه کد قبلی

روایت‌هایی که در این آزمایشگاه کد ارائه می‌دهیم در واقع ادامه‌ی این آزمایشگاه کد پیش‌نیاز است: Agentic RAG با ADK، MCP Toolbox و Cloud SQL . می‌توانید کار خود را از آزمایشگاه کد قبلی ادامه دهید.

می‌توانیم ساخت را در دایرکتوری کاری قبلی codelab شروع کنیم (دایرکتوری کاری باید build-agent-adk-toolbox-cloudsql باشد). برای جلوگیری از سردرگمی، بیایید نام دایرکتوری را به همان نام دایرکتوری که هنگام شروع جدید استفاده می‌کنیم، تغییر دهیم.

mv ~/build-agent-adk-toolbox-cloudsql ~/adk-a2a-agent-runtime-starter
cloudshell workspace ~/adk-a2a-agent-runtime-starter && cd ~/adk-a2a-agent-runtime-starter
source .env

تأیید کنید که فایل‌های کلیدی از codelab قبلی در جای خود قرار دارند:

echo "--- Restaurant Agent ---"
cat restaurant_agent/agent.py | head -5
echo ""
echo "--- Toolbox Config ---"
cat tools.yaml | head -5

شما باید فایل restaurant_agent/agent.py را به همراه فایل import مربوط LlmAgent و tools.yaml به همراه پیکربندی Toolbox خود مشاهده کنید.

در مرحله بعد، بیایید محیط پایتون خود را دوباره مقداردهی اولیه کنیم

rm -rf .venv
uv sync

همچنین، بررسی کنید که پایگاه داده آماده و آماده است:

uv run python scripts/verify_seed.py

اگر هر جزئیات آزمایش را از codelab قبلی دنبال کنید، ممکن است خروجی مانند این را ببینید

Menu Items: 16/15
Embeddings: 16/15

✗ Database not ready

اشکالی نداره! بررسی پایگاه داده، داده‌های اضافی که از بررسی مصرف داده وارد می‌کنید رو در نظر نمی‌گیره. تا زمانی که >=15 داده داشته باشید، همه چیز خوبه!

فعال‌سازی API مورد نیاز

در مرحله بعد، باید مطمئن شویم که API مورد نیاز را برای تعامل با پلتفرم Gemini Enterprise Agent فعال کرده‌ایم.

gcloud services enable \
  cloudresourcemanager.googleapis.com

شما باید از قبل فایل‌ها و اطلاعات زیر را برای ادامه به بخش بعدی داشته باشید: A2A Protocol and Gemini Enterprise Agent Runtime !

۳. تنظیمات محیط - شروع تازه با مخزن آغازین

این مرحله محیط Cloud Shell شما را آماده می‌کند، پروژه Google Cloud شما را پیکربندی می‌کند و مخزن اولیه را کلون می‌کند.

پوسته ابری را باز کنید

Cloud Shell را در مرورگر خود باز کنید. Cloud Shell یک محیط از پیش پیکربندی شده با تمام ابزارهای مورد نیاز برای این آزمایشگاه کد را فراهم می‌کند. در صورت درخواست، روی تأیید (Authorize) کلیک کنید.

سپس روی « مشاهده » -> « ترمینال » کلیک کنید تا ترمینال باز شود. رابط کاربری شما باید شبیه به این باشد.

۸۶۳۰۷fac5da2f077.png

این رابط اصلی ما خواهد بود، IDE در بالا، ترمینال در پایین

دایرکتوری کاری خود را تنظیم کنید

مخزن اولیه را کلون کنید، تمام کدهایی که در این codelab می‌نویسید اینجا قرار می‌گیرند:

rm -rf ~/adk-a2a-agent-runtime-starter
git clone https://github.com/alphinside/adk-a2a-agent-runtime-starter.git
cloudshell workspace ~/adk-a2a-agent-runtime-starter && cd ~/adk-a2a-agent-runtime-starter

فایل .env را از الگوی ارائه شده ایجاد کنید:

cp .env.example .env

برای ساده‌سازی راه‌اندازی پروژه در ترمینال خود، این اسکریپت راه‌اندازی پروژه را در دایرکتوری کاری خود دانلود کنید:

curl -sL https://raw.githubusercontent.com/alphinside/cloud-trial-project-setup/main/setup_verify_trial_project.sh -o setup_verify_trial_project.sh

اسکریپت را اجرا کنید. این اسکریپت حساب کاربری آزمایشی شما را تأیید می‌کند، یک پروژه جدید ایجاد می‌کند (یا یک پروژه موجود را تأیید می‌کند)، شناسه پروژه شما را در یک فایل .env در دایرکتوری فعلی ذخیره می‌کند و پروژه فعال را در gcloud تنظیم می‌کند.

bash setup_verify_trial_project.sh && source .env

اسکریپت:

  1. تأیید کنید که یک حساب پرداخت آزمایشی فعال دارید
  2. بررسی وجود یک پروژه موجود در .env (در صورت وجود)
  3. یک پروژه جدید ایجاد کنید یا از پروژه موجود دوباره استفاده کنید
  4. حساب پرداخت آزمایشی را به پروژه خود پیوند دهید
  5. شناسه پروژه را در .env ذخیره کنید
  6. پروژه را به عنوان پروژه فعال gcloud تنظیم کنید

با بررسی متن زرد رنگ کنار دایرکتوری کاری خود در اعلان ترمینال Cloud Shell، مطمئن شوید که پروژه به درستی تنظیم شده است. باید شناسه پروژه شما نمایش داده شود.

5c515e235ee1179f.png

فعال‌سازی API مورد نیاز

در مرحله بعد، باید مطمئن شویم که API مورد نیاز را برای تعامل با پلتفرم Gemini Enterprise Agent فعال کرده‌ایم.

gcloud services enable \
  aiplatform.googleapis.com \
  cloudresourcemanager.googleapis.com

راه‌اندازی زیرساخت اولیه

ابتدا، باید وابستگی‌های پایتون را با استفاده از uv نصب کنیم، uv یک بسته سریع پایتون و مدیر پروژه است که با زبان Rust نوشته شده است (مستندات uv). این codelab از آن برای سرعت و سادگی در نگهداری پروژه پایتون استفاده می‌کند.

uv sync

سپس، اسکریپت راه‌اندازی کامل را اجرا کنید، که نمونه Cloud SQL را ایجاد می‌کند، داده‌ها را بارگذاری می‌کند و سرویس Toolbox را مستقر می‌کند که به عنوان وضعیت اولیه عامل رستوران ما عمل خواهد کرد.

bash scripts/full_setup.sh > logs/full_setup.log 2>&1 &

۴. مفهوم: پروتکل Agent2Agent (A2A) و زمان اجرای عامل Gemini Enterprise

قبل از ساخت، بیایید لحظه‌ای کوتاه به درک دو فناوری کلیدی ارائه شده در این آزمایشگاه کد برای مقیاس‌بندی برنامه عامل‌محور خود بپردازیم.

پروتکل Agent2Agent (A2A)

پروتکل Agent2Agent (A2A) یک استاندارد باز است که برای ایجاد ارتباط و همکاری یکپارچه بین عامل‌های هوش مصنوعی طراحی شده است. در حالی که MCP (پروتکل زمینه مدل) عامل‌ها را به ابزارها و داده‌ها متصل می‌کند، A2A عامل‌ها را به سایر عامل‌ها متصل می‌کند - و آنها را قادر می‌سازد تا قابلیت‌های یکدیگر را کشف کنند، وظایف را واگذار کنند و در چارچوب‌ها و سازمان‌ها با یکدیگر همکاری کنند.

5586b67d0437d79f.png

تفاوت کلیدی بین قرار دادن یک عامل به عنوان یک ابزار (از طریق MCP) در مقابل افشای آن از طریق A2A: ابزارها بدون وضعیت هستند و عملکردهای واحدی را انجام می‌دهند، در حالی که عامل‌های A2A می‌توانند استدلال کنند، وضعیت را حفظ کنند و تعاملات چند نوبتی مانند مذاکره یا شفاف‌سازی را مدیریت کنند. عاملی که از طریق A2A در معرض نمایش قرار می‌گیرد، به جای اینکه به یک فراخوانی تابع محدود شود، تمام قابلیت‌های خود را حفظ می‌کند.

A2A سه مفهوم اصلی را تعریف می‌کند:

  1. کارت عامل - یک سند JSON که شرح می‌دهد یک عامل چه کاری انجام می‌دهد، مهارت‌هایش چیست و نقطه پایانی‌اش چیست. سایر عامل‌ها این کارت را برای کشف قابلیت‌ها دریافت می‌کنند.
  2. پیام - درخواستی از کاربر یا عامل که به یک نقطه پایانی A2A ارسال می‌شود و یک وظیفه را آغاز می‌کند.
  3. وظیفه — یک واحد کار با چرخه عمر (ارسال شده → در حال کار → تکمیل شده/ناموفق) و مصنوعاتی که حاوی نتایج هستند.

e7e3224d05b725f0.jpeg

برای بررسی عمیق‌تر، به «A2A چیست؟» مراجعه کنید.

زمان اجرای پلتفرم عامل سازمانی جمینی

Agent Runtime یک سرویس کاملاً مدیریت‌شده در Google Cloud برای استقرار، مقیاس‌بندی و مدیریت عامل‌های هوش مصنوعی در محیط عملیاتی با ویژگی‌های امنیتی سازمانی (مانند VPC Service Controls، CMEK) است. این سرویس زیرساخت را مدیریت می‌کند تا بتوانید روی منطق عامل تمرکز کنید.

8ecbfbce8f0b9557.png

زمان اجرای عامل موارد زیر را فراهم می‌کند:

  • استقرار مدیریت‌شده - استقرار عامل‌های ساخته‌شده با ADK، LangGraph یا هر چارچوب پایتون با یک فراخوانی SDK واحد
  • میزبانی A2A - استقرار عامل‌ها به عنوان نقاط پایانی سازگار با A2A با سرویس‌دهی خودکار کارت عامل و دسترسی احراز هویت شده
  • جلسات مداوم - VertexAiSessionService تاریخچه مکالمات و وضعیت درخواست‌ها را ذخیره می‌کند
  • مقیاس‌پذیری خودکار - مقیاس‌پذیری از صفر تا مدیریت ترافیک، بدون نیاز به مدیریت زیرساخت
  • قابلیت مشاهده - ردیابی، ثبت وقایع و نظارت داخلی از طریق پشته قابلیت مشاهده Google Cloud
  • و بسیاری از ویژگی‌های دیگر، برای جزئیات بیشتر به این مستندات مراجعه کنید

در این آزمایشگاه کد، شما عامل رزرو را در Agent Runtime مستقر می‌کنید. فرآیند استقرار، کد عامل شما را سریالایز (پیکِل) کرده و آن را آپلود می‌کند. Agent Runtime یک نقطه پایانی بدون سرور فراهم می‌کند که پروتکل A2A را ارائه می‌دهد - سایر عامل‌ها (یا کلاینت‌ها) از طریق فراخوانی‌های استاندارد HTTP که با اعتبارنامه‌های Google Cloud تأیید شده‌اند، با آن تعامل دارند.

۵. نماینده رزرواسیون را بسازید

این مرحله یک عامل ADK جدید ایجاد می‌کند که رزرو رستوران را با استفاده از session state مدیریت می‌کند. این عامل از سه عملیات - ایجاد، بررسی و لغو - با شماره تلفن به عنوان کلید جستجو پشتیبانی می‌کند. تمام داده‌های رزرو در session state ADK قرار دارند.

عامل را در چارچوب قرار دهید

adk create برای تولید ساختار دایرکتوری agent با مدل و پیکربندی صحیح پروژه استفاده کنید:

source .env
uv run adk create reservation_agent \
    --model gemini-2.5-flash \
    --project ${GOOGLE_CLOUD_PROJECT} \
    --region ${GOOGLE_CLOUD_LOCATION}

این دستور یک دایرکتوری reservation_agent/ با __init__.py ، agent.py و .env که از پیش برای مدل Gemini در پلتفرم Agent پیکربندی شده‌اند، ایجاد می‌کند.

adk-a2a-agent-runtime-starter/
├── reservation_agent/
│   ├── __init__.py
│   ├── agent.py
│   └── .env
├── logs
├── scripts
└── ...

در مرحله بعد، کد عامل را به‌روزرسانی می‌کنیم

کد عامل را بنویسید

فایل عامل تولید شده را باز کنید:

cloudshell edit reservation_agent/agent.py

سپس محتویات آن را با موارد زیر جایگزین کنید:

# reservation_agent/agent.py
from google.adk.agents import LlmAgent
from google.adk.tools import ToolContext

# App-scoped state prefix ensures reservations persist across all sessions.
# See https://adk.dev/sessions/state/ for state scope details.
STATE_PREFIX = "app:reservation:"


def create_reservation(
    phone_number: str,
    name: str,
    party_size: int,
    date: str,
    time: str,
    tool_context: ToolContext,
) -> dict:
    """Create a new restaurant reservation.

    Args:
        phone_number: Customer's phone number, used as the reservation ID.
        name: Name for the reservation.
        party_size: Number of guests.
        date: Reservation date (e.g., '2025-07-15' or 'this Friday').
        time: Reservation time (e.g., '7:00 PM').

    Returns:
        Confirmation of the reservation.
    """
    reservation = {
        "name": name,
        "party_size": party_size,
        "date": date,
        "time": time,
        "status": "confirmed",
    }
    tool_context.state[f"{STATE_PREFIX}{phone_number}"] = reservation
    return {
        "status": "confirmed",
        "message": f"Reservation created for {name}, party of {party_size} on {date} at {time}. Phone: {phone_number}.",
    }


def check_reservation(phone_number: str, tool_context: ToolContext) -> dict:
    """Look up an existing reservation by phone number.

    Args:
        phone_number: The phone number used when the reservation was created.
        tool_context: ADK tool context for state access.

    Returns:
        The reservation details, or a message if not found.
    """
    reservation = tool_context.state.get(f"{STATE_PREFIX}{phone_number}")
    if reservation:
        return {"found": True, "reservation": reservation}
    return {"found": False, "message": f"No reservation found for {phone_number}."}


def cancel_reservation(phone_number: str, tool_context: ToolContext) -> dict:
    """Cancel an existing reservation by phone number.

    Args:
        phone_number: The phone number used when the reservation was created.
        tool_context: ADK tool context for state access.

    Returns:
        Confirmation of cancellation, or a message if not found.
    """
    key = f"{STATE_PREFIX}{phone_number}"
    reservation = tool_context.state.get(key)
    if not reservation:
        return {"success": False, "message": f"No reservation found for {phone_number}."}
    if reservation.get("status") == "cancelled":
        return {"success": False, "message": f"Reservation for {phone_number} is already cancelled."}
    reservation["status"] = "cancelled"
    tool_context.state[key] = reservation
    return {"success": True, "message": f"Reservation for {reservation['name']} ({phone_number}) has been cancelled."}


root_agent = LlmAgent(
    name="reservation_agent",
    model="gemini-2.5-flash",
    instruction="""You are a friendly reservation assistant for "Foodie Finds" restaurant.
You help diners create, check, and cancel table reservations.

When a diner wants to make a reservation, collect these details:
- Name for the reservation
- Phone number (used as the reservation ID)
- Party size (number of guests)
- Date
- Time

Always confirm the details before creating the reservation.
When checking or cancelling, ask for the phone number if not provided.
Be concise and professional.""",
    tools=[create_reservation, check_reservation, cancel_reservation],
)

۶. پیکربندی سرور A2A را آماده کنید

کارت عامل A2A را تعریف کنید

کارت نماینده، شرح ساختاریافته‌ای از قابلیت‌های نماینده شما است - سایر نمایندگان و مشتریان از آن برای کشف کارهایی که نماینده شما انجام می‌دهد استفاده می‌کنند. پیکربندی کارت را ایجاد کنید:

cloudshell edit reservation_agent/a2a_config.py

موارد زیر را در reservation_agent/a2a_config.py کپی کنید:

# reservation_agent/a2a_config.py
from a2a.types import AgentSkill
from vertexai.preview.reasoning_engines.templates.a2a import create_agent_card

reservation_skill = AgentSkill(
    id="manage_reservations",
    name="Restaurant Reservations",
    description="Create, check, and cancel table reservations at Foodie Finds restaurant",
    tags=["reservations", "restaurant", "booking"],
    examples=[
        "Book a table for 4 on Friday at 7pm",
        "Check reservation for 555-0101",
        "Cancel my reservation, phone number 555-0101",
    ],
    input_modes=["text/plain"],
    output_modes=["text/plain"],
)

agent_card = create_agent_card(
    agent_name="Reservation Agent",
    description="Handles restaurant table reservations — create, check, and cancel bookings for Foodie Finds restaurant.",
    skills=[reservation_skill],
)

ایجاد مجری A2A

مجری، پروتکل A2A و ​​عامل ADK را به هم متصل می‌کند. درخواست‌های A2A را دریافت می‌کند، آنها را از طریق عامل ADK اجرا می‌کند و نتایج را به عنوان وظایف A2A برمی‌گرداند:

cloudshell edit reservation_agent/executor.py

کد زیر را در reservation_agent/executor.py کپی کنید:

# reservation_agent/executor.py
import os
from typing import NoReturn

import vertexai
from a2a.server.agent_execution import AgentExecutor, RequestContext
from a2a.server.events import EventQueue
from a2a.server.tasks import TaskUpdater
from a2a.types import TaskState, TextPart, UnsupportedOperationError
from a2a.utils import new_agent_text_message
from a2a.utils.errors import ServerError
from google.adk.artifacts import InMemoryArtifactService
from google.adk.memory.in_memory_memory_service import InMemoryMemoryService
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService, VertexAiSessionService
from google.genai import types

from reservation_agent.agent import root_agent as reservation_agent


class ReservationAgentExecutor(AgentExecutor):
    """Bridge between the A2A protocol and the ADK reservation agent.

    Uses InMemorySessionService for local testing, VertexAiSessionService
    when deployed to Agent Runtime (detected via GOOGLE_CLOUD_AGENT_ENGINE_ID).
    """

    def __init__(self) -> None:
        self.agent = None
        self.runner = None

    def _init_agent(self) -> None:
        if self.agent is not None:
            return

        self.agent = reservation_agent
        engine_id = os.environ.get("GOOGLE_CLOUD_AGENT_ENGINE_ID")

        if engine_id:
            project = os.environ.get("GOOGLE_CLOUD_PROJECT")
            location = os.environ.get("GOOGLE_CLOUD_LOCATION", "us-central1")
            vertexai.init(project=project, location=location)
            session_service = VertexAiSessionService(
                project=project, location=location, agent_engine_id=engine_id,
            )
            app_name = engine_id
        else:
            session_service = InMemorySessionService()
            app_name = self.agent.name

        self.runner = Runner(
            app_name=app_name,
            agent=self.agent,
            artifact_service=InMemoryArtifactService(),
            session_service=session_service,
            memory_service=InMemoryMemoryService(),
        )

    async def execute(self, context: RequestContext, event_queue: EventQueue) -> None:
        if self.agent is None:
            self._init_agent()

        query = context.get_user_input()
        updater = TaskUpdater(event_queue, context.task_id, context.context_id)
        user_id = context.message.metadata.get("user_id", "a2a-user") if context.message.metadata else "a2a-user"

        if not context.current_task:
            await updater.submit()
        await updater.start_work()

        try:
            session = await self._get_or_create_session(context.context_id, user_id)
            content = types.Content(role="user", parts=[types.Part(text=query)])

            async for event in self.runner.run_async(
                session_id=session.id, user_id=user_id, new_message=content,
            ):
                if event.is_final_response():
                    parts = event.content.parts
                    answer = " ".join(p.text for p in parts if p.text) or "No response."
                    await updater.add_artifact([TextPart(text=answer)], name="answer")
                    await updater.complete()
                    break
        except Exception as e:
            await updater.update_status(
                TaskState.failed, message=new_agent_text_message(f"Error: {e!s}"),
            )
            raise

    async def _get_or_create_session(self, context_id: str, user_id: str):
        app_name = self.runner.app_name
        if context_id:
            session = await self.runner.session_service.get_session(
                app_name=app_name, session_id=context_id, user_id=user_id,
            )
            if session:
                return session
        session = await self.runner.session_service.create_session(
            app_name=app_name, user_id=user_id, session_id=context_id,
        )
        return session

    async def cancel(self, context: RequestContext, event_queue: EventQueue) -> NoReturn:
        raise ServerError(error=UnsupportedOperationError())

اجراکننده به طور خودکار محیط خود را تشخیص می‌دهد: وقتی GOOGLE_CLOUD_AGENT_ENGINE_ID تنظیم شده باشد (Agent Runtime این را در زمان استقرار تزریق می‌کند)، از VertexAiSessionService برای جلسات مداوم استفاده می‌کند. به صورت محلی، به InMemorySessionService برمی‌گردد.

دایرکتوری reservation_agent شما اکنون باید شامل موارد زیر باشد:

reservation_agent/
├── __init__.py
├── agent.py
├── a2a_config.py
├── executor.py
└── .env

۷. آماده‌سازی عامل A2A با استفاده از SDK پلتفرم عامل و تست محلی

این مرحله، عامل رزرو را به عنوان یک عامل سازگار با A2A با استفاده از کلاس A2aAgent متعلق به SDK پلتفرم Agent (نام SDK هنوز از اصطلاح vertex برای سازگاری با نسخه‌های قبلی استفاده می‌کند) پوشش می‌دهد، سپس جریان کامل پروتکل A2A را به صورت محلی آزمایش می‌کند - بازیابی کارت عامل، ارسال پیام و بازیابی وظیفه. این همان شیء A2aAgent است که در مرحله بعدی به Agent Runtime مستقر می‌کنید.

وابستگی‌ها را اضافه کنید

SDK پلتفرم Agent را با پشتیبانی از Agent Runtime و ADK به همراه SDK A2A نصب کنید:

uv add "google-cloud-aiplatform[agent_engines,adk]==1.149.0" "a2a-sdk==0.3.26"

اجزای A2A را درک کنید

بسته‌بندی یک عامل ADK برای A2A به سه جزء نیاز دارد:

  1. کارت عامل — یک "کارت ویزیت" که قابلیت‌ها، مهارت‌ها و آدرس اینترنتی (URL) عامل را شرح می‌دهد. سایر عامل‌ها از این برای کشف فعالیت‌های عامل شما استفاده می‌کنند.
  2. مجری عامل — پلی بین پروتکل A2A و ​​منطق عامل ADK شما. این عامل درخواست‌های A2A را دریافت می‌کند، آنها را از طریق عامل ADK اجرا می‌کند و نتایج را به عنوان وظایف A2A برمی‌گرداند.
  3. A2aAgent — کلاس SDK پلتفرم Agent که کارت و مجری را در یک واحد قابل استقرار ترکیب می‌کند.

اسکریپت تست را ایجاد کنید

اسکریپت زیر را برای تست محلی ایجاد کنید

cloudshell edit scripts/test_a2a_agent_local.py

کد زیر را در scripts/test_a2a_agent_local.py کپی کنید:

# scripts/test_a2a_agent_local.py
import asyncio
import json
import os
from pprint import pprint

from dotenv import load_dotenv
from starlette.requests import Request
from vertexai.preview.reasoning_engines import A2aAgent

from reservation_agent.a2a_config import agent_card
from reservation_agent.executor import ReservationAgentExecutor

load_dotenv()


# --- Helper functions for building mock requests ---

def receive_wrapper(data: dict):
    async def receive():
        byte_data = json.dumps(data).encode("utf-8")
        return {"type": "http.request", "body": byte_data, "more_body": False}
    return receive

def build_post_request(data: dict = None, path_params: dict = None) -> Request:
    scope = {"type": "http", "http_version": "1.1", "headers": [(b"content-type", b"application/json")], "app": None}
    if path_params:
        scope["path_params"] = path_params
    return Request(scope, receive_wrapper(data))

def build_get_request(path_params: dict) -> Request:
    scope = {"type": "http", "http_version": "1.1", "query_string": b"", "app": None}
    if path_params:
        scope["path_params"] = path_params
    async def receive():
        return {"type": "http.disconnect"}
    return Request(scope, receive)


# --- Helper: poll for task completion ---

async def wait_for_task(a2a_agent, task_id, max_retries=30):
    """Poll on_get_task until the task reaches a terminal state."""
    for _ in range(max_retries):
        request = build_get_request({"id": task_id})
        result = await a2a_agent.on_get_task(request=request, context=None)
        state = result.get("status", {}).get("state", "")
        if state in ["completed", "failed"]:
            return result
        await asyncio.sleep(1)
    return result


def print_task_answer(result):
    """Extract and print the answer from task artifacts."""
    print(f"Status: {result.get('status', {}).get('state')}")
    for artifact in result.get("artifacts", []):
        if artifact.get("parts") and "text" in artifact["parts"][0]:
            print(f"Answer: {artifact['parts'][0]['text']}")


# --- Local test ---

async def main():
    # Create and set up the A2A agent locally
    a2a_agent = A2aAgent(agent_card=agent_card, agent_executor_builder=ReservationAgentExecutor)
    a2a_agent.set_up()

    # 1. Get agent card
    print("=" * 50)
    print("1. Retrieving agent card...")
    print("=" * 50)
    request = build_get_request(None)
    card_response = await a2a_agent.handle_authenticated_agent_card(request=request, context=None)
    print(f"Agent: {card_response.get('name')}")
    print(f"Skills: {[s.get('name') for s in card_response.get('skills', [])]}")

    # 2. Create a reservation
    print("\n" + "=" * 50)
    print("2. Creating a reservation...")
    print("=" * 50)
    message_data = {
        "message": {
            "messageId": f"msg-{os.urandom(4).hex()}",
            "content": [{"text": "Book a table for 2 on Saturday at 6pm. Name: Bob, Phone: 555-0202"}],
            "role": "ROLE_USER",
        },
    }
    request = build_post_request(message_data)
    response = await a2a_agent.on_message_send(request=request, context=None)
    task_id = response["task"]["id"]
    context_id = response["task"].get("contextId")
    print(f"Task ID: {task_id}")

    # 3. Wait for result
    print("\n" + "=" * 50)
    print("3. Waiting for task result...")
    print("=" * 50)
    result = await wait_for_task(a2a_agent, task_id)
    print_task_answer(result)

    # 4. Check the reservation (same context for session continuity)
    print("\n" + "=" * 50)
    print("4. Checking the reservation...")
    print("=" * 50)
    check_data = {
        "message": {
            "messageId": f"msg-{os.urandom(4).hex()}",
            "content": [{"text": "Check the reservation for 555-0202"}],
            "role": "ROLE_USER",
            "contextId": context_id,
        },
    }
    request = build_post_request(check_data)
    check_response = await a2a_agent.on_message_send(request=request, context=None)
    check_result = await wait_for_task(a2a_agent, check_response["task"]["id"])
    print_task_answer(check_result)

    # 5. Cancel the reservation
    print("\n" + "=" * 50)
    print("5. Cancelling the reservation...")
    print("=" * 50)
    cancel_data = {
        "message": {
            "messageId": f"msg-{os.urandom(4).hex()}",
            "content": [{"text": "Cancel the reservation for 555-0202"}],
            "role": "ROLE_USER",
            "contextId": context_id,
        },
    }
    request = build_post_request(cancel_data)
    cancel_response = await a2a_agent.on_message_send(request=request, context=None)
    cancel_result = await wait_for_task(a2a_agent, cancel_response["task"]["id"])
    print_task_answer(cancel_result)

    print("\n" + "=" * 50)
    print("All tests passed!")
    print("=" * 50)


if __name__ == "__main__":
    asyncio.run(main())

اسکریپت آزمایشی، کارت عامل و اجراکننده‌ای را که در مرحله قبل ایجاد کردید، وارد می‌کند - بدون تکرار. این اسکریپت یک A2aAgent محلی ایجاد می‌کند، فراخوانی‌های پروتکل A2A را از طریق درخواست‌های HTTP ساختگی شبیه‌سازی می‌کند و هر سه عملیات رزرو را تأیید می‌کند.

از آنجایی که هیچ GOOGLE_CLOUD_AGENT_ENGINE_ID به صورت محلی تنظیم نشده است، اجراکننده InMemorySessionService استفاده می‌کند. هنگامی که در Agent Runtime مستقر می‌شود، همان اجراکننده برای جلسات مداوم به طور خودکار به VertexAiSessionService سوئیچ می‌کند.

آزمون را اجرا کنید

PYTHONPATH=. uv run python scripts/test_a2a_agent_local.py

خروجی پنج مرحله را طی می‌کند:

  1. کارت مامور - قابلیت‌ها و مهارت‌های مامور را بازیابی می‌کند
  2. ایجاد رزرو - یک میز رزرو می‌کند و یک وظیفه را با تأیید برمی‌گرداند
  3. دریافت نتیجه وظیفه - وظیفه تکمیل شده را به همراه پاسخ آن بازیابی می‌کند.
  4. بررسی رزرو - جستجوی رزرو بر اساس شماره تلفن
  5. لغو رزرو — رزرو را لغو و تأیید می‌کند

نمونه خروجی مانند زیر نشان داده شده است

==================================================
1. Retrieving agent card...
==================================================
Agent: Reservation Agent
Skills: ['Restaurant Reservations']

==================================================
2. Creating a reservation...
==================================================
Task ID: f7f7004d-cfea-49c2-b57d-5bca9959e193

==================================================
3. Waiting for task result...
==================================================
Status: TASK_STATE_COMPLETED
Answer: Your reservation for Bob, party of 2, on Saturday at 6:00 PM has been confirmed. The phone number associated is 555-0202.

==================================================
4. Checking the reservation...
==================================================
Status: TASK_STATE_COMPLETED
Answer: I found a reservation for Bob, party of 2, on Saturday at 6:00 PM. The reservation status is confirmed.

==================================================
5. Cancelling the reservation...
==================================================
Status: TASK_STATE_COMPLETED
Answer: Your reservation for Bob (555-0202) has been cancelled.

==================================================
All tests passed!
==================================================

در این مرحله شما تأیید کرده‌اید: کارت عامل A2A مهارت‌های صحیح را توصیف می‌کند، هر سه عملیات رزرو از طریق جریان پیام/وظیفه پروتکل A2A انجام می‌شوند، و وضعیت در پیام‌های درون یک زمینه ثابت می‌ماند.

۸. عامل رزرو را در Agent Runtime مستقر کنید

این مرحله، عامل رزرو را در Gemini Enterprise Agent Platform Runtime مستقر می‌کند - یک پلتفرم کاملاً مدیریت‌شده و بدون سرور که میزبان عامل شما است و آن را به عنوان یک نقطه پایانی امن A2A در معرض نمایش قرار می‌دهد. پس از استقرار، هر کلاینت مجاز می‌تواند از طریق نقاط پایانی استاندارد A2A HTTP، عامل را کشف و با آن تعامل کند.

سطل مرحله‌بندی را ایجاد کنید

یک مخزن ذخیره‌سازی ابری برای مرحله‌بندی Agent Runtime ایجاد کنید. Agent Runtime از این مخزن برای آپلود کد و وابستگی‌های agent شما در حین استقرار استفاده می‌کند:

STAGING_BUCKET="${GOOGLE_CLOUD_PROJECT}-adk-a2a-agent-runtime"
gsutil mb -l $REGION -p $GOOGLE_CLOUD_PROJECT gs://$STAGING_BUCKET 2>/dev/null || echo "Bucket already exists"
echo "STAGING_BUCKET=$STAGING_BUCKET" >> .env
source .env

اسکریپت استقرار را ایجاد کنید

در مرحله بعد، باید اسکریپت استقرار را آماده کنیم.

cloudshell edit scripts/deploy_a2a_agent_runtime.py

کد زیر را در scripts/deploy_a2a_agent_runtime.py کپی کنید:

# scripts/deploy_a2a_agent_runtime.py
import os
from pathlib import Path

import vertexai
from dotenv import load_dotenv
from google.genai import types
from vertexai.preview.reasoning_engines import A2aAgent

from reservation_agent.a2a_config import agent_card
from reservation_agent.executor import ReservationAgentExecutor

load_dotenv()

PROJECT_ID = os.environ["GOOGLE_CLOUD_PROJECT"]
REGION = os.environ["REGION"]
STAGING_BUCKET = os.environ.get("STAGING_BUCKET", f"{PROJECT_ID}-adk-a2a-agent-runtime")
BUCKET_URI = f"gs://{STAGING_BUCKET}"

a2a_agent = A2aAgent(
    agent_card=agent_card,
    agent_executor_builder=ReservationAgentExecutor,
)


def main():
    vertexai.init(project=PROJECT_ID, location=REGION, staging_bucket=BUCKET_URI)
    client = vertexai.Client(
        project=PROJECT_ID,
        location=REGION,
        http_options=types.HttpOptions(api_version="v1beta1"),
    )

    print("Deploying Reservation Agent to Agent Runtime...")
    print("This may take 3-5 minutes.")

    remote_agent = client.agent_engines.create(
        agent=a2a_agent,
        config={
            "display_name": agent_card.name,
            "description": agent_card.description,
            "requirements": [
                "google-cloud-aiplatform[agent_engines,adk]==1.149.0",
                "a2a-sdk==0.3.26",
                "google-adk==1.29.0",
                "cloudpickle",
                "pydantic"
            ],
            "extra_packages": [
                "./reservation_agent",
            ],
            "http_options": {
                "api_version": "v1beta1",
            },
            "staging_bucket": BUCKET_URI,
        },
    )

    resource_name = remote_agent.api_resource.name
    print(f"\nDeployment complete!")
    print(f"Resource name: {resource_name}")

    env_path = Path(".env")
    lines = env_path.read_text().splitlines() if env_path.exists() else []
    lines = [l for l in lines if not l.startswith("RESERVATION_AGENT_RESOURCE_NAME=")]
    lines.append(f"RESERVATION_AGENT_RESOURCE_NAME={resource_name}")
    env_path.write_text("\n".join(lines) + "\n")
    print("Written RESERVATION_AGENT_RESOURCE_NAME to .env")


if __name__ == "__main__":
    main()

اسکریپت deploy همان agent_card و ReservationAgentExecutor مورد استفاده در تست محلی را وارد می‌کند - بدون تکرار کد. Agent Runtime شیء A2aAgent را به همراه وابستگی‌های آن برای استقرار، سریالایز (pickles) می‌کند. در پایان اسکریپت deployment، مقدار RESERVATION_AGENT_RESOURCE_NAME را در فایل .env می‌نویسد.

استقرار در زمان اجرای عامل

اسکریپت استقرار را اجرا کنید:

PYTHONPATH=. uv run python scripts/deploy_a2a_agent_runtime.py

استقرار ۳ تا ۵ دقیقه طول می‌کشد. اسکریپت یک نقطه پایانی بدون سرور را در Agent Runtime فراهم می‌کند که میزبان عامل رزرو است. پس از استقرار موفقیت‌آمیز، خروجی مشابه زیر را مشاهده خواهید کرد.

Deploying Reservation Agent to Agent Runtime...
This may take 3-5 minutes.

Deployment complete!
Resource name: projects/your-project-number/locations/us-central1/reasoningEngines/your-agent-deployment-unique-id
Written RESERVATION_AGENT_RESOURCE_NAME to .env

می‌توانید عامل مستقر شده را در کنسول ابری مشاهده کنید. در نوار جستجوی کنسول، Agent Platform را جستجو کنید.

af3751f461e4708c.png

سپس، در برگه سمت چپ، نشانگر ماوس را روی Agents نگه دارید و Deployments انتخاب کنید.

8a9c7fd127e60aca.png

همانطور که در زیر نشان داده شده است، Reservation Agent در لیست استقرار مشاهده خواهید کرد.

a38b46bcb6c8e4db.png

عامل مستقر شده را آزمایش کنید

اکنون، ما آماده آزمایش عامل مستقر شده هستیم، یک اسکریپت آزمایشی برای عامل مستقر شده ایجاد می‌کنیم:

cloudshell edit scripts/test_a2a_agent_runtime.py

کد زیر را در scripts/test_a2a_agent_runtime.py کپی کنید:

# scripts/test_a2a_agent_runtime.py
import asyncio
import os
import time

import vertexai
from a2a.types import TaskState
from dotenv import load_dotenv
from google.genai import types

load_dotenv()

PROJECT_ID = os.environ["GOOGLE_CLOUD_PROJECT"]
REGION = os.environ["REGION"]
RESOURCE_NAME = os.environ["RESERVATION_AGENT_RESOURCE_NAME"]


async def main():
    vertexai.init(project=PROJECT_ID, location=REGION)
    client = vertexai.Client(
        project=PROJECT_ID, location=REGION,
        http_options=types.HttpOptions(api_version="v1beta1"),
    )

    agent = client.agent_engines.get(name=RESOURCE_NAME)

    # 1. Get agent card
    print("=" * 50)
    print("1. Retrieving agent card...")
    print("=" * 50)
    card = await agent.handle_authenticated_agent_card()
    print(f"Agent: {card.name}")
    print(f"URL: {card.url}")
    print(f"Skills: {[s.name for s in card.skills]}")

    # 2. Send a reservation request
    print("\n" + "=" * 50)
    print("2. Sending reservation request...")
    print("=" * 50)
    message_data = {
        "messageId": "msg-remote-001",
        "role": "user",
        "parts": [{"kind": "text", "text": "Book a table for 3 on Sunday at noon. Name: Carol, Phone: 555-0303"}],
    }
    response = await agent.on_message_send(**message_data)

    task_object = None
    for chunk in response:
        if isinstance(chunk, tuple) and len(chunk) > 0 and hasattr(chunk[0], "id"):
            task_object = chunk[0]
            break

    task_id = task_object.id
    print(f"Task ID: {task_id}")
    print(f"Status: {task_object.status.state}")

    # 3. Poll for result
    print("\n" + "=" * 50)
    print("3. Waiting for result...")
    print("=" * 50)
    result = None
    for _ in range(30):
        try:
            result = await agent.on_get_task(id=task_id)
            if result.status.state in [TaskState.completed, TaskState.failed]:
                break
        except Exception:
            pass
        time.sleep(1)

    print(f"Final status: {result.status.state}")
    if result.artifacts:
        for artifact in result.artifacts:
            if artifact.parts and hasattr(artifact.parts[0], "root") and hasattr(artifact.parts[0].root, "text"):
                print(f"Answer: {artifact.parts[0].root.text}")

    print("\n" + "=" * 50)
    print("Remote agent test passed!")
    print("=" * 50)


if __name__ == "__main__":
    asyncio.run(main())

سپس، بیایید تست را اجرا کنیم

source .env
uv run python scripts/test_a2a_agent_runtime.py

خروجی، کارت مامور با مهارت «رزرو رستوران» را نشان می‌دهد و پس از آن، وظیفه‌ای که با تأیید رزرو تکمیل شده است، نمایش داده می‌شود.

==================================================
1. Retrieving agent card...
==================================================
Agent: Reservation Agent
URL: https://us-central1-aiplatform.googleapis.com/v1beta1/projects/your-project-id/locations/us-central1/reasoningEngines/your-agent-unique-id/a2a
Skills: ['Restaurant Reservations']

==================================================
2. Sending reservation request...
==================================================
Task ID: b34585d0-5f03-4cb0-85a3-40710a0d224d
Status: TaskState.completed

==================================================
3. Waiting for result...
==================================================
Final status: TaskState.completed
Answer: Your reservation for Carol, party of 3 on Sunday at noon with phone number 555-0303 is confirmed.

==================================================
Remote agent test passed!
==================================================

عامل رزرو اکنون با موفقیت به عنوان یک نقطه پایانی مدیریت شده A2A در Agent Runtime اجرا می‌شود.

۹. ادغام نماینده رزرو A2A با نماینده رستوران Root

این مرحله، عامل رستوران را ارتقا می‌دهد تا از عامل رزرو مستقر شده به عنوان یک زیرعامل A2A از راه دور استفاده کند. هماهنگ‌کننده به صورت محلی اجرا می‌شود در حالی که عامل رزرو روی Agent Runtime اجرا می‌شود - یک ادغام جزئی که اتصال A2A را قبل از استقرار کامل تأیید می‌کند.

آدرس اینترنتی کارت عامل A2A را حل کنید

RemoteA2aAgent برای کشف قابلیت‌های خود به آدرس اینترنتی کارت عامل رزرو مستقر شده نیاز دارد. اسکریپتی ایجاد کنید که این آدرس اینترنتی را از Agent Runtime دریافت کرده و آن را در فایل .env عامل رستوران بنویسد:

cloudshell edit scripts/resolve_agent_card_url.py

کد زیر را در scripts/resolve_agent_card_url.py کپی کنید:

# scripts/resolve_agent_card_url.py
import asyncio
import os
from pathlib import Path

import vertexai
from dotenv import load_dotenv
from google.genai import types

load_dotenv()

PROJECT_ID = os.environ["GOOGLE_CLOUD_PROJECT"]
REGION = os.environ["REGION"]
RESOURCE_NAME = os.environ["RESERVATION_AGENT_RESOURCE_NAME"]


async def main():
    vertexai.init(project=PROJECT_ID, location=REGION)
    client = vertexai.Client(
        project=PROJECT_ID, location=REGION,
        http_options=types.HttpOptions(api_version="v1beta1"),
    )

    agent = client.agent_engines.get(name=RESOURCE_NAME)
    card = await agent.handle_authenticated_agent_card()
    card_url = f"{card.url}/v1/card"

    print(f"Agent: {card.name}")
    print(f"Card URL: {card_url}")

    # Write to restaurant_agent/.env
    # Write to both restaurant_agent/.env (for adk web) and root .env (for Cloud Run deploy)
    for env_path in [Path("restaurant_agent/.env"), Path(".env")]:
        lines = env_path.read_text().splitlines() if env_path.exists() else []
        lines = [l for l in lines if not l.startswith("RESERVATION_AGENT_CARD_URL=")]
        lines.append(f"RESERVATION_AGENT_CARD_URL={card_url}")
        env_path.write_text("\n".join(lines) + "\n")
        print(f"Written RESERVATION_AGENT_CARD_URL to {env_path}")


if __name__ == "__main__":
    asyncio.run(main())

اسکریپت را اجرا کنید تا فایل .env را با آدرس اینترنتی کارت عامل پر کنید

uv run python scripts/resolve_agent_card_url.py
source .env

به‌روزرسانی نماینده رستوران

فایل نماینده رستوران را باز کنید:

cloudshell edit restaurant_agent/agent.py

سپس، محتویات را با نسخه به‌روز شده‌ای که شامل عامل رزرو از راه دور به عنوان یک عامل فرعی است، جایگزین کنید:

# restaurant_agent/agent.py
import os

import httpx
from google.adk.agents import LlmAgent
from google.adk.agents.remote_a2a_agent import RemoteA2aAgent
from google.auth import default
from google.auth.transport.requests import Request as AuthRequest
from toolbox_adk import ToolboxToolset

TOOLBOX_URL = os.environ.get("TOOLBOX_URL", "http://127.0.0.1:5000")
RESERVATION_AGENT_CARD_URL = os.environ.get("RESERVATION_AGENT_CARD_URL", "")

toolbox = ToolboxToolset(TOOLBOX_URL)


class GoogleCloudAuth(httpx.Auth):
    """Auto-refreshing Google Cloud authentication for httpx.

    Refreshes the access token before each request if expired,
    so long-running agents never hit 401 errors.
    """

    def __init__(self):
        self.credentials, _ = default(
            scopes=["https://www.googleapis.com/auth/cloud-platform"]
        )

    def auth_flow(self, request):
        # Refresh the token if it is expired or missing
        if not self.credentials.valid:
            self.credentials.refresh(AuthRequest())
            
        request.headers["Authorization"] = f"Bearer {self.credentials.token}"
        yield request


reservation_remote_agent = RemoteA2aAgent(
    name="reservation_agent",
    description="Handles restaurant table reservations — create, check, and cancel bookings. Delegate to this agent when the user wants to book a table, check a reservation, or cancel a reservation.",
    agent_card=RESERVATION_AGENT_CARD_URL,
    httpx_client=httpx.AsyncClient(auth=GoogleCloudAuth(), timeout=60),
)

root_agent = LlmAgent(
    name="restaurant_agent",
    model="gemini-2.5-flash",
    instruction="""You are a friendly and knowledgeable concierge at "Foodie Finds," a restaurant. Your job:
- Help diners browse the menu by category or cuisine type.
- Provide full details about specific dishes, including ingredients, price, and dietary information.
- Recommend dishes based on natural language descriptions of what the diner is craving.
- Add new menu items when asked.
- For reservation requests (booking, checking, or cancelling tables), delegate to the reservation_agent.

When a diner asks about a specific dish by name or cuisine, use the get-item-details tool.
When a diner asks for a specific category or cuisine type, use the search-menu tool.
When a diner describes what kind of food they want — by flavor, texture, dietary needs, or cravings — use the search-menu-by-description tool for semantic search.

When in doubt between search-menu and search-menu-by-description, prefer search-menu-by-description — it searches dish descriptions and finds more relevant matches.
If a dish is not available (available is false), let the diner know and suggest similar alternatives from the search results.
Be conversational, knowledgeable, and concise.""",
    tools=[toolbox],
    sub_agents=[reservation_remote_agent],
)

تغییرات کلیدی نسبت به نسخه قبلی:

  • GoogleCloudAuth — یک هندلر httpx.Auth سفارشی که توکن دسترسی Google Cloud را قبل از هر درخواست به‌روزرسانی می‌کند. Agent Runtime به فراخوانی‌های A2A احراز هویت شده نیاز دارد و توکن‌ها پس از مدتی منقضی می‌شوند.
  • RemoteA2aAgent RESERVATION_AGENT_CARD_URL از فایل .env (که توسط اسکریپت resolve نوشته شده است) می‌خواند و از httpx_client احراز هویت شده استفاده می‌کند.
  • به عنوان یک نماینده فرعی ثبت شده است - هماهنگ کننده ADK به طور خودکار درخواست های رزرو را به آن واگذار می کند
  • دستورالعمل به‌روزرسانی‌شده برای ذکر نمایندگی رزرو

تست عامل یکپارچه به صورت محلی

عامل شروع‌کننده نیاز به ادغام با جعبه ابزار MCP داشت، فایل مورد نیاز باید از قبل از codelab قبلی یا از مخزن شروع‌کننده ارائه شده باشد. ما فقط باید مطمئن شویم که فرآیند جعبه ابزار به درستی اجرا می‌شود.

اگر TOOLBOX_URL در .env شما از قبل به یک سرویس Cloud Run اشاره می‌کند (از codelab قبلی یا شاید از full_setup.sh مخزن اولیه)، می‌توانید از این مرحله صرف نظر کنید - عامل به Toolbox مستقر شده متصل خواهد شد.

اگر به جای آن به یک جعبه ابزار محلی نیاز دارید، قبل از شروع یک نمونه جدید، بررسی کنید که آیا یکی از آنها از قبل در حال اجرا است یا خیر:

if curl -s http://127.0.0.1:5000/api/toolsets > /dev/null 2>&1; then
  echo "Toolbox already running on port 5000"
else
  set -a; source .env; set +a
  ./toolbox --config=tools.yaml > logs/toolbox.log 2>&1 &
  echo "Toolbox started"
fi

سپس، می‌توانیم از طریق رابط کاربری توسعه وب ADK با نماینده رستوران تعامل داشته باشیم.

uv run adk web --allow_origins "regex:https://.*\.cloudshell\.dev" --port 8080

رابط کاربری وب ADK را با استفاده از Cloud Shell Web Preview باز کنید (روی دکمه Web Preview کلیک کنید، پورت را به ۸۰۸۰ تغییر دهید) و سپس restaurant_agent انتخاب کنید.

65a055b70ab52aa8.png

یک مکالمه ترکیبی را امتحان کنید:

پرس و جو منو

What Italian dishes do you have?

درخواست رزرو

I want to create reservation under name Bob, phone number 123456

رزرو را بررسی کنید

ایجاد جلسه جدید (شروع مکالمه جدید):

Check the reservation for 123456

92cef3bc7671129a.png

16bfd60f202dcaa7.png

c5326bbf6fa778e2.png

فرآیند adk web را با دو بار Ctrl+C متوقف کنید. سپس بیایید سیستم را با استقرار کامل عامل تکمیل کنیم.

۱۰. عامل رستوران به‌روزرسانی‌شده را روی Cloud Run مستقر کنید

این مرحله، عامل رستوران را با ادغام A2A به Cloud Run منتقل می‌کند و سیستم چندعاملی کاملاً مستقر را تکمیل می‌کند.

اعطای مجوز برای دسترسی به Agent Runtime

حساب کاربری سرویس Cloud Run برای فراخوانی Agent Runtime به مجوز نیاز دارد. نقش roles/aiplatform.user را به حساب کاربری پیش‌فرض سرویس Compute Engine اعطا کنید:

PROJECT_NUMBER=$(gcloud projects describe $GOOGLE_CLOUD_PROJECT --format='value(projectNumber)')
gcloud projects add-iam-policy-binding $GOOGLE_CLOUD_PROJECT \
  --member="serviceAccount:${PROJECT_NUMBER}-compute@developer.gserviceaccount.com" \
  --role="roles/aiplatform.user"

استقرار در Cloud Run

در این تنظیمات، فرض می‌کنیم که سرویس نماینده رستوران از قبل از طریق codelab قبلی یا با اجرای scripts/full_setup.sh در صورت شروع مجدد، وجود دارد. این با کد به‌روز شده (ادغام جدید RemoteA2aAgent ) مجدداً مستقر می‌شود و URL کارت نماینده رزرو را به عنوان یک متغیر env جدید اضافه می‌کند - متغیرهای env موجود ( TOOLBOX_URL ، GOOGLE_CLOUD_PROJECT و غیره) حفظ می‌شوند:

gcloud run deploy restaurant-agent \
  --source . \
  --region=$REGION \
  --allow-unauthenticated \
  --update-env-vars="RESERVATION_AGENT_CARD_URL=$RESERVATION_AGENT_CARD_URL" \
  --min-instances=0 \
  --max-instances=1 \
  --memory=1Gi \
  --port=8080

سیستم کاملاً مستقر را آزمایش کنید

آدرس اینترنتی سرویس مستقر شده را دریافت کنید:

AGENT_URL=$(gcloud run services describe restaurant-agent --region=$REGION --format='value(status.url)')
echo "Agent URL: $AGENT_URL"

URL را در مرورگر خود باز کنید. رابط کاربری وب ADK بارگذاری می‌شود - این همان رابط کاربری است که به صورت محلی استفاده می‌کردید، اکنون روی Cloud Run اجرا می‌شود.

با خیال راحت با نماینده چت کنید

پرس و جو منو

What spicy dishes do you have?

درخواست رزرو

Book a table for 4 on Friday at 7pm. Name: Eve, Phone: 555-0505

رزرو را بررسی کنید

ایجاد جلسه جدید (شروع مکالمه جدید):

Check reservation for 555-0505

69ae9a7c35255fc.png

۵۵۱۴۵۸۴۱۳۳۸ec9b3.png

سیستم چندعاملی به طور کامل مستقر شده است. عامل رستوران در Cloud Run بین دو سرویس backend هماهنگی ایجاد می‌کند: MCP Toolbox برای عملیات منو و عامل رزرو A2A در Agent Runtime.

۱۱. تبریک می‌گویم!

شما یک سیستم چندعاملی را با استفاده از پروتکل A2A در Google Cloud ساخته و مستقر کرده‌اید.

آنچه آموخته‌اید

  • یک عامل ADK ساخت که از session state ( ToolContext ) برای مدیریت داده‌های رزرو بدون پایگاه داده استفاده می‌کند.
  • با استفاده از SDK پلتفرم Agent، یک عامل A2A را در Agent Runtime مستقر کردیم.
  • یک عامل A2A از راه دور را از یک عامل ADK دیگر با استفاده از RemoteA2aAgent به عنوان زیرعامل مصرف کرد.
  • سیستم را به صورت تدریجی آزمایش کردم: A2A محلی → A2A مستقر شده → ادغام جزئی → استقرار کامل

تمیز کردن

برای جلوگیری از تحمیل هزینه به حساب Google Cloud خود، منابع ایجاد شده در این codelab را حذف کنید.

gcloud projects delete $GOOGLE_CLOUD_PROJECT

گزینه ۲: حذف منابع تکی

# Delete the Agent Runtime deployment
uv run python -c "
import vertexai
from google.genai import types
vertexai.init(project='$GOOGLE_CLOUD_PROJECT', location='$REGION')
client = vertexai.Client(
    project='$GOOGLE_CLOUD_PROJECT', location='$REGION',
    http_options=types.HttpOptions(api_version='v1beta1'),
)
agent = client.agent_engines.get(name='$RESERVATION_AGENT_RESOURCE_NAME')
agent.delete(force=True)
print('Agent Runtime deployment deleted.')
"

# Delete Cloud Run services
gcloud run services delete restaurant-agent --region=$REGION --quiet
gcloud run services delete toolbox-service --region=$REGION --quiet

# Delete Cloud SQL instance
gcloud sql instances delete $DB_INSTANCE --quiet

# Delete GCS staging bucket
gsutil rm -r gs://$STAGING_BUCKET