Geniş Ölçekte Temsilciler: Agent Runtime ve ADK Entegrasyonunda A2A Protokolü ile Çok Temsilcili Mimari

1. Giriş

Yapay zeka temsilcileri daha fazla sorumluluk üstlendikçe her şeyi yapan tek bir temsilciyi yönetmek, ölçeklendirmek ve geliştirmek zorlaşır. Farklı özellikler genellikle farklı dağıtım stratejileri, güncelleme döngüleri veya hatta farklı ekipler gerektirir.

  • A2A (Agent2Agent) Protokolü, iletişimi çözerek ajanların birbirlerinin yeteneklerini keşfetme ve çerçeveler ile kuruluşlar arasında işbirliği yapma şeklini standartlaştırır.
  • Gemini Enterprise Agent Platform Runtime, dağıtım tarafını çözer. Bu, temsilcilerinizi barındıran, tümüyle yönetilen bir sunucusuz platformdur. Yerleşik A2A desteği, otomatik ölçeklendirme, güvenli uç noktalar, kalıcı oturumlar ve sıfır altyapı yönetimi sunar.

Bu araçlar, uzmanlaşmış ajanlar oluşturmanıza, bunları bulunabilir A2A hizmetleri olarak dağıtmanıza ve çok ajanlı sistemlerde birleştirmenize olanak tanır.

Ne oluşturacaksınız?

Gemini Enterprise Agent Platform Sessions tarafından yönetilen ADK oturum durumunu kullanarak restoran masa rezervasyonlarını (oluşturma, kontrol etme ve iptal etme) yöneten bir rezervasyon ajanı. Bu ajanı, A2A protokolünün aracı kartı aracılığıyla keşfedilebilir hale geldiği Gemini Enterprise Agent Platform Runtime'a dağıtırsınız. Ardından, Foodie Finds restoran konsiyerj ajanını (ön koşul codelab'inden, codelab'i ziyaret etmediyseniz endişelenmeyin, sizin için bir başlangıç deposu hazırladık) uzaktan A2A alt ajanı olarak Rezervasyon Ajanı'nı kullanacak şekilde yükseltirsiniz. Sonuç: Düzenleyicinin menü sorgularını MCP Toolbox'a ve rezervasyon isteklerini uzaktan A2A aracısına yönlendirdiği çoklu aracı sistemi.

143fadef342e67a6.jpeg

Neler öğreneceksiniz?

  • Rezervasyon verilerini yönetmek için yönetilen oturum hizmetini kullanan bir ADK aracısı oluşturma
  • ADK aracısını, aracı kartları ve becerileriyle A2A sunucusu olarak kullanıma sunma
  • Gemini Enterprise Agent Runtime'a A2A ajanı dağıtma
  • RemoteA2aAgent kullanarak başka bir ADK aracısından uzak bir A2A aracısı tüketme ve kimliği doğrulanmış isteği işleme
  • Çoklu temsilci sistemlerini artımlı olarak test etme: yerel A2A, dağıtılmış A2A, kısmi entegrasyon, tam dağıtım

Ön koşullar

2. Ortam Kurulumu - Önceki Codelab'den devam etme

Bu codelab'de sağladığımız anlatılar, ön koşul codelab'i olan "ADK, MCP Toolbox ve Cloud SQL ile Ajan Tabanlı RAG" ile devam etmektedir . Önceki codelab'deki çalışmanıza devam edebilirsiniz.

Önceki codelab'in çalışma dizininde ( çalışma dizini build-agent-adk-toolbox-cloudsql olmalıdır) oluşturmaya başlayabiliriz. Karışıklığı önlemek için dizini, sıfırdan başladığımızda kullandığımız dizin adıyla yeniden adlandıralım.

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

Önceki codelab'deki anahtar dosyalarının yerinde olduğunu doğrulayın:

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

restaurant_agent/agent.py dosyasını LlmAgent içe aktarma işlemiyle, tools.yaml dosyasını ise araç kutusu yapılandırmanızla birlikte görmeniz gerekir.

Şimdi Python ortamımızı yeniden başlatalım.

rm -rf .venv
uv sync

Ayrıca, veritabanının başlatıldığını ve hazır olduğunu doğrulayın:

uv run python scripts/verify_seed.py

Önceki codelab'deki her test ayrıntısını uygularsanız aşağıdaki gibi bir çıktı görebilirsiniz.

Menu Items: 16/15
Embeddings: 16/15

✗ Database not ready

Sorun değil. Veritabanı kontrolünde, veri kullanımı kontrolünden girdiğiniz ek veriler dikkate alınmaz. 15'ten fazla veriniz olduğu sürece sorun yok.

Gerekli API'yi Etkinleştirme

Ardından, Gemini Enterprise Ajan Platformu ile etkileşim kurmak için gerekli API'yi etkinleştirdiğimizden emin olmamız gerekir.

gcloud services enable \
  cloudresourcemanager.googleapis.com

Bir sonraki bölüme geçmek için gerekli dosya ve altyapıya sahip olmanız gerekir: A2A Protocol and Gemini Enterprise Agent Runtime.

3. Ortam Kurulumu - Başlangıç deposuyla yeni bir başlangıç yapma

Bu adımda Cloud Shell ortamınız hazırlanır, Google Cloud projeniz yapılandırılır ve başlangıç deposu klonlanır.

Cloud Shell'i açma

Tarayıcınızda Cloud Shell'i açın. Cloud Shell, bu codelab için ihtiyacınız olan tüm araçların bulunduğu önceden yapılandırılmış bir ortam sağlar. İstendiğinde Yetkilendir'i tıklayın.

Ardından, terminali açmak için "Görünüm" -> "Terminal"i tıklayın. Arayüzünüz aşağıdaki gibi görünmelidir.

86307fac5da2f077.png

Bu, ana arayüzümüz olacak. Üstte IDE, altta terminal yer alacak.

Çalışma dizininizi ayarlama

Başlangıç deposunu klonlayın. Bu codelab'de yazdığınız tüm kodlar burada yer alır:

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

Sağlanan şablondan .env dosyasını oluşturun:

cp .env.example .env

Terminalinizde proje kurulumunu basitleştirmek için bu proje kurulum komut dosyasını çalışma dizininize indirin:

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

Komut dosyasını çalıştırın. Bu komut, deneme faturalandırma hesabınızı doğrular, yeni bir proje oluşturur (veya mevcut bir projeyi doğrular), proje kimliğinizi geçerli dizindeki bir .env dosyasına kaydeder ve gcloud'de etkin projeyi ayarlar.

bash setup_verify_trial_project.sh && source .env

Komut dosyası:

  1. Etkin bir deneme faturalandırma hesabınız olduğunu doğrulayın.
  2. .env içinde mevcut bir proje olup olmadığını kontrol edin (varsa)
  3. Yeni bir proje oluşturun veya mevcut projeyi yeniden kullanın
  4. Deneme faturalandırma hesabını projenize bağlama
  5. Proje kimliğini .env dosyasına kaydedin.
  6. Projeyi etkin gcloud projesi olarak ayarlayın

Cloud Shell terminal isteminde çalışma dizininizin yanındaki sarı metni kontrol ederek projenin doğru şekilde ayarlandığını doğrulayın. Proje kimliğiniz gösterilmelidir.

5c515e235ee1179f.png

Gerekli API'yi Etkinleştirme

Ardından, Gemini Enterprise Agent Platform ile etkileşim kurmak için gerekli API'yi etkinleştirdiğimizden emin olmamız gerekir.

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

Başlangıç Altyapı Kurulumu

Öncelikle, Rust ile yazılmış hızlı bir Python paketi ve proje yöneticisi olan uv kullanarak Python bağımlılıklarını yüklememiz gerekir ( uv belgeleri). Bu codelab, Python projesini korumada hız ve basitlik için bu aracı kullanır.

uv sync

Ardından, Cloud SQL örneğini oluşturan, verileri yerleştiren ve restoran temsilcimizin ilk durumu olarak hareket edecek Toolbox hizmetini dağıtan tam kurulum komut dosyasını çalıştırın.

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

4. Kavram: Agent2Agent (A2A) Protokolü ve Gemini Enterprise Ajan Çalışma Zamanı

Geliştirmeye başlamadan önce, temsilci tabanlı uygulamamızı ölçeklendirmek için bu codelab'de sunulan iki temel teknolojiyi anlamak üzere kısa bir süre ayıralım.

Agent2Agent (A2A) Protokolü

Agent2Agent (A2A) protokolü, yapay zeka aracıları arasında sorunsuz iletişim ve işbirliği sağlamak için tasarlanmış açık bir standarttır. MCP (Model Context Protocol) , aracıları araçlara ve verilere bağlarken A2A, aracıları diğer aracılara bağlar. Böylece, birbirlerinin özelliklerini keşfedebilir, görevleri devredebilir ve çerçeveler ile kuruluşlar arasında işbirliği yapabilirler.

5586b67d0437d79f.png

Bir temsilciyi araç olarak sarmalama (MCP aracılığıyla) ile A2A aracılığıyla kullanıma sunma arasındaki temel fark: Araçlar durum bilgisi içermez ve tek işlevleri yerine getirir. A2A temsilcileri ise akıl yürütebilir, durumu koruyabilir ve pazarlık veya açıklama gibi çok turlu etkileşimleri yönetebilir. A2A aracılığıyla kullanıma sunulan bir temsilci, işlev çağrısına indirgenmek yerine tüm özelliklerini korur.

A2A üç temel kavramı tanımlar:

  1. Aracı Kartı: Bir aracının ne yaptığını, becerilerini ve uç noktasını açıklayan bir JSON belgesi. Diğer aracılar, yetenekleri keşfetmek için bu kartı getirir.
  2. Mesaj: Bir görevi tetikleyen, A2A uç noktasına gönderilen kullanıcı veya temsilci isteği.
  3. Görev: Bir yaşam döngüsüne (gönderildi → çalışıyor → tamamlandı/başarısız oldu) sahip bir iş birimi ve sonuçları içeren yapılar.

e7e3224d05b725f0.jpeg

Daha ayrıntılı bilgi için A2A nedir? başlıklı makaleyi inceleyin.

Gemini Enterprise Agent Platform Runtime

Agent Runtime, Google Cloud'da üretimde yapay zeka aracı dağıtmak, ölçeklendirmek ve yönetmek için kullanılan, tümüyle yönetilen bir hizmettir.Bu hizmette kurumsal güvenlik özellikleri (ör. VPC Hizmet Kontrolleri, CMEK) bulunur. Altyapıyı yönettiği için aracı mantığına odaklanabilirsiniz.

8ecbfbce8f0b9557.png

Agent Runtime'ın sağladığı özellikler:

  • Yönetilen dağıtım: ADK, LangGraph veya herhangi bir Python çerçevesiyle oluşturulan aracıları tek bir SDK çağrısıyla dağıtın.
  • A2A barındırma: Temsilcileri, otomatik temsilci kartı yayını ve kimliği doğrulanmış erişim ile A2A uyumlu uç noktalar olarak dağıtın.
  • Kalıcı oturumlar: VertexAiSessionService İstekler arasında sohbet geçmişini ve durumunu saklar.
  • Otomatik ölçeklendirme: Altyapı yönetimi olmadan trafiği işlemek için sıfırdan ölçeklendirme
  • Gözlemlenebilirlik: Google Cloud'un gözlemlenebilirlik yığını aracılığıyla yerleşik izleme, günlük kaydı ve izleme
  • ve daha birçok özellik için ayrıntılı bilgiyi bu belgede bulabilirsiniz.

Bu codelab'de rezervasyon aracısını Agent Runtime'a dağıtıyorsunuz. Dağıtım sürecinde aracı kodunuz serileştirilir (pickle) ve yüklenir. Agent Runtime, A2A protokolüne hizmet veren sunucusuz bir uç nokta sağlar. Diğer aracıların (veya istemcilerin) bu uç noktayla etkileşimi, Google Cloud kimlik bilgileriyle kimliği doğrulanmış standart HTTP çağrıları üzerinden gerçekleşir.

5. Rezervasyon aracısını oluşturma

Bu adımda, oturum durumunu kullanarak restoran rezervasyonlarını işleyen yeni bir ADK aracısı oluşturulur. Temsilci, arama anahtarı olarak telefon numarasıyla üç işlemi (oluşturma, kontrol etme ve iptal etme) destekler. Tüm rezervasyon verileri ADK'nın oturum durumunda bulunur.

Temsilciyi yapılandırma

Doğru model ve proje yapılandırmasıyla aracı dizin yapısını oluşturmak için adk create aracını kullanın:

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

Bu işlem, Ajan Platformu'ndaki Gemini modeli için önceden yapılandırılmış __init__.py, agent.py ve .env ile bir reservation_agent/ dizini oluşturur.

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

Ardından, aracı kodunu güncelleyelim.

Aracı kodunu yazma

Oluşturulan temsilci dosyasını açın:

cloudshell edit reservation_agent/agent.py

Ardından içeriği aşağıdakiyle değiştirin:

# reservation_agent/agent.py
import os
from functools import cached_property
from typing import Any

from google.adk.agents import LlmAgent
from google.adk.models.google_llm import Gemini
from google.adk.tools import ToolContext
from google.genai import Client, types

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


class GeminiGlobal(Gemini):
    """Gemini with location pinned to 'global'.

    A2aAgent.set_up() on Agent Platform Runtime SDK overwrites GOOGLE_CLOUD_LOCATION with the Agent Platform Runtime
    region (e.g. us-central1), but the Gemini 3-family endpoint requires 'global'.
    """

    @cached_property
    def api_client(self) -> Client:
        project = os.getenv("GOOGLE_CLOUD_PROJECT")
        return Client(
            project=project,
            location="global",
            http_options=types.HttpOptions(
                headers=self._tracking_headers(),
                retry_options=self.retry_options,
            ),
        )


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=GeminiGlobal(model="gemini-3.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],
)

6. A2A sunucu yapılandırmasını hazırlama

A2A temsilci kartını tanımlama

Temsilci kartı, temsilcinizin özelliklerinin yapılandırılmış bir açıklamasıdır. Diğer temsilciler ve istemciler, temsilcinizin ne yaptığını öğrenmek için bu kartı kullanır. Kart yapılandırmasını oluşturun:

cloudshell edit reservation_agent/a2a_config.py

Aşağıdakileri reservation_agent/a2a_config.py içine kopyalayın:

# 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 yürütücüsünü oluşturma

Yürütücü, A2A protokolü ile ADK aracısı arasında köprü görevi görür. A2A isteklerini alır, bunları ADK aracısı üzerinden çalıştırır ve sonuçları A2A görevleri olarak döndürür:

cloudshell edit reservation_agent/executor.py

Aşağıdakileri reservation_agent/executor.py içine kopyalayın:

# 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())

Yürütücü, ortamını otomatik olarak algılar: GOOGLE_CLOUD_AGENT_ENGINE_ID ayarlandığında (Agent Runtime bunu dağıtım sırasında ekler) kalıcı oturumlar için VertexAiSessionService kullanılır. Yerel olarak InMemorySessionService'ya geri döner.

reservation_agent dizininizde artık şunlar olmalıdır:

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

7. Agent Platform SDK'yı kullanarak A2A aracısı hazırlama ve yerel olarak test etme

Bu adımda, rezervasyon aracısı, Agent Platform SDK'sının (SDK adı, geriye dönük uyumluluk için vertex terimini kullanmaya devam etmektedir) A2aAgent sınıfı kullanılarak A2A uyumlu bir aracı olarak sarmalanır. Ardından, tam A2A protokol akışı (aracı kartı alma, mesaj gönderme ve görev alma) yerel olarak test edilir. Bu, sonraki adımda Agent Runtime'a dağıttığınız A2aAgent nesnesiyle aynıdır.

Bağımlılık ekleme

Agent Platform SDK'yı Agent Runtime ve ADK desteğiyle birlikte A2A SDK'yı da yükleyin:

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

A2A bileşenlerini anlama

A2A için bir ADK aracısını sarmak üç bileşen gerektirir:

  1. Aracı kartı: Aracının yeteneklerini, becerilerini ve uç nokta URL'sini açıklayan bir"kartvizit". Diğer ajanlar, bu bilgiyi kullanarak ajanın ne yaptığını öğrenir.
  2. Agent Executor: A2A protokolü ile ADK temsilcinizin mantığı arasındaki köprüdür. A2A isteklerini alır, ADK temsilcisinde çalıştırır ve sonuçları A2A görevleri olarak döndürür.
  3. A2aAgent: Kartı ve yürütücüyü dağıtılabilir bir birimde birleştiren Agent Platform SDK sınıfı.

Test komut dosyasını oluşturma

Yerel olarak test etmek için aşağıdaki komut dosyasını oluşturun

cloudshell edit scripts/test_a2a_agent_local.py

Aşağıdakileri scripts/test_a2a_agent_local.py içine kopyalayın:

# 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())

Test komut dosyası, önceki adımda oluşturduğunuz aracı kartını ve yürütücüyü içe aktarır. Bu nedenle, herhangi bir kopya oluşturulmaz. Bu işlem, yerel bir A2aAgent oluşturur, sahte HTTP istekleri aracılığıyla A2A protokolü çağrılarını simüle eder ve üç rezervasyon işleminin tümünü doğrular.

Yerel olarak GOOGLE_CLOUD_AGENT_ENGINE_ID ayarlanmadığından yürütücü InMemorySessionService kullanır. Agent Runtime'a dağıtıldığında aynı yürütücü, kalıcı oturumlar için otomatik olarak VertexAiSessionService'e geçer.

Testi çalıştırın

PYTHONPATH=. uv run python scripts/test_a2a_agent_local.py

Çıkış beş aşamadan oluşur:

  1. Ajan kartı: Ajanın yeteneklerini ve becerilerini alır.
  2. Rezervasyon oluştur: Masa ayırtır ve onay içeren bir görev döndürür.
  3. Get task result: Yanıtla birlikte tamamlanan görevi alır.
  4. Rezervasyonu kontrol et: Telefon numarasına göre rezervasyonu arar.
  5. Rezervasyonu iptal et: Rezervasyonu iptal eder ve onaylar.

Aşağıdaki gibi bir çıktı örneği

==================================================
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!
==================================================

Bu noktada şunları doğrulamış olursunuz: A2A aracı kartı doğru becerileri açıklıyor, üç rezervasyon işlemi de A2A protokolünün mesaj/görev akışı üzerinden çalışıyor ve durum aynı bağlamdaki mesajlar arasında kalıcı oluyor.

8. Rezervasyon aracısını Agent Runtime'a dağıtma

Bu adımda, rezervasyon temsilcisi Gemini Enterprise Agent Platform Runtime'a dağıtılır. Bu, temsilcinizi barındıran ve güvenli bir A2A uç noktası olarak kullanıma sunan, tamamen yönetilen ve sunucusuz bir platformdur. Dağıtımdan sonra, yetkili tüm istemciler standart A2A HTTP uç noktaları aracılığıyla aracıyı keşfedebilir ve onunla etkileşimde bulunabilir.

Hazırlama paketi oluşturma

Agent Runtime hazırlığı için bir Cloud Storage paketi oluşturun. Agent Runtime, dağıtım sırasında aracınızın kodunu ve bağımlılarını yüklemek için bu paketi kullanır:

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

Dağıtım komut dosyasını oluşturma

Ardından, dağıtım komut dosyasını hazırlamamız gerekir.

cloudshell edit scripts/deploy_a2a_agent_runtime.py

Aşağıdakileri scripts/deploy_a2a_agent_runtime.py içine kopyalayın:

# 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()

Dağıtım komut dosyası, yerel testte kullanılan aynı agent_card ve ReservationAgentExecutor öğelerini içe aktarır. Böylece kod tekrarı olmaz. Agent Runtime, dağıtım için A2aAgent nesnesini bağımlılıklarıyla birlikte serileştirir (pickle). Dağıtım komut dosyasının sonunda, RESERVATION_AGENT_RESOURCE_NAME değerini .env dosyasına yazar.

Agent Runtime'a dağıtma

Dağıtım komut dosyasını çalıştırın:

PYTHONPATH=. uv run python scripts/deploy_a2a_agent_runtime.py

Dağıtım işlemi 3-5 dakika sürer. Komut dosyası, rezervasyon aracısını barındıran Agent Runtime üzerinde sunucusuz bir uç nokta sağlar. Başarılı dağıtımın ardından aşağıdakine benzer bir çıkış görürsünüz.

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

Dağıtılan aracıyı Cloud Console'da görüntüleyebilirsiniz. Konsol arama çubuğunda Agent Platform simgesini arayın.

af3751f461e4708c.png

Ardından, soldaki sekmede Agents simgesinin üzerine gelin ve Deployments simgesini seçin.

8a9c7fd127e60aca.png

Reservation Agent öğesinin, aşağıdaki örnekte gösterildiği gibi dağıtım listesinde yer aldığını görürsünüz.

a38b46bcb6c8e4db.png

Dağıtılan temsilciyi test etme

Şimdi de dağıtılan aracıyı test etmeye ve dağıtılan aracı için bir test komut dosyası oluşturmaya hazırız:

cloudshell edit scripts/test_a2a_agent_runtime.py

Aşağıdakileri scripts/test_a2a_agent_runtime.py içine kopyalayın:

# 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())

Ardından testi çalıştıralım.

source .env
uv run python scripts/test_a2a_agent_runtime.py

Çıkışta, "Restoran Rezervasyonları" becerisine sahip temsilci kartı ve ardından rezervasyon onayıyla tamamlanan görev gösteriliyor.

==================================================
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!
==================================================

Rezervasyon aracısı artık Agent Runtime'da yönetilen bir A2A uç noktası olarak başarıyla çalışıyor.

9. A2A Reservation Agent'ı Root Restaurant Agent ile entegre etme

Bu adımda, restoran aracısı, dağıtılan rezervasyon aracısını uzak bir A2A alt aracısı olarak kullanacak şekilde yükseltilir. Düzenleyici yerel olarak çalışırken rezervasyon aracısı, tam dağıtımdan önce A2A bağlantısını doğrulayan kısmi bir entegrasyon olan Agent Runtime üzerinde çalışır.

A2A temsilci kartı URL'sini çözme

RemoteA2aAgent, yeteneklerini keşfetmek için dağıtılan rezervasyon aracısının kart URL'sine ihtiyaç duyar. Bu URL'yi Agent Runtime'dan getiren ve restoran aracısının .env özelliğine yazan bir komut dosyası oluşturun:

cloudshell edit scripts/resolve_agent_card_url.py

Aşağıdakileri scripts/resolve_agent_card_url.py içine kopyalayın:

# 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 dosyasını aracı kartı URL'siyle doldurmak için komut dosyasını çalıştırın.

uv run python scripts/resolve_agent_card_url.py
source .env

Restoran temsilcisini güncelleme

Restoran temsilcisi dosyasını açın:

cloudshell edit restaurant_agent/agent.py

Ardından, içeriği, alt aracı olarak uzaktan rezervasyon aracısını içeren güncellenmiş sürümle değiştirin:

# 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-3.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],
)

Önceki sürüme kıyasla öne çıkan değişiklikler:

  • GoogleCloudAuth: Her istekten önce Google Cloud erişim jetonunu yenileyen özel bir httpx.Auth işleyicisi. Agent Runtime, kimliği doğrulanmış A2A çağrıları gerektirir ve jetonların süresi belirli bir süre sonra dolar.
  • RemoteA2aAgent, .env'den (çözümleme komut dosyası tarafından yazılır) RESERVATION_AGENT_CARD_URL değerini okur ve kimliği doğrulanmış httpx_client değerini kullanır.
  • Alt acente olarak kaydedilmişse: ADK'nin düzenleyicisi, rezervasyon isteklerini otomatik olarak bu acenteye devreder.
  • Rezervasyon yetkisinden bahsetmek için talimat güncellendi

Entegre aracıyı yerel olarak test etme

Başlangıç aracısının MCP Toolbox ile entegrasyonu gerekiyordu. Gerekli dosya, önceki codelab'den veya başlangıç deposundan sağlanmış olmalıdır. Yalnızca araç kutusu işleminin düzgün çalıştığından emin olmamız gerekir.

.env içindeki TOOLBOX_URL, önceki codelab'den veya başlangıç deposunun full_setup.sh'sından bir Cloud Run hizmetine yönlendiriyorsa bu adımı atlayabilirsiniz. Aracı, dağıtılan Toolbox'a bağlanır.

Bunun yerine yerel bir Toolbox'a ihtiyacınız varsa yeni bir örnek başlatmadan önce Toolbox'ın çalışıp çalışmadığını kontrol edin:

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

Ardından, ADK web geliştirme kullanıcı arayüzü üzerinden restoran temsilcisiyle etkileşim kurmayı deneyebiliriz.

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

Cloud Shell Web Önizlemesi'ni kullanarak ADK web kullanıcı arayüzünü açın (Web Önizlemesi düğmesini tıklayın, bağlantı noktasını 8080 olarak değiştirin) ve ardından restaurant_agent seçeneğini belirleyin.

65a055b70ab52aa8.png

Karışık bir görüşmeyi test etme:

Menü Sorgusu

What Italian dishes do you have?

Rezervasyon isteği

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

Rezervasyonu kontrol etme

Yeni oturum oluşturma ( yeni bir görüşme başlatma):

Check the reservation for 123456

92cef3bc7671129a.png

16bfd60f202dcaa7.png

c5326bbf6fa778e2.png

adk web işlemini Ctrl+C tuşlarına iki kez basarak durdurun. Ardından, sistemi tamamlamak için aracıyı tamamen dağıtalım.

10. Güncellenen restoran aracısını Cloud Run'a dağıtma

Bu adımda, restoran temsilcisi A2A entegrasyonuyla Cloud Run'a yeniden dağıtılır ve tamamen dağıtılmış çok temsilcili sistem tamamlanır.

Agent Runtime'a erişim izni verme

Cloud Run hizmet hesabının Agent Runtime'ı çağırma izni olması gerekir. Varsayılan Compute Engine hizmet hesabına roles/aiplatform.user rolünü verin:

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'a dağıtma

Bu kurulumda, önceki codelab'den veya sıfırdan başlıyorsanız scripts/full_setup.sh komutunu çalıştırarak restoran temsilcisi hizmetinin zaten mevcut olduğunu varsayıyoruz. Bu, güncellenmiş kodla (yeni RemoteA2aAgent entegrasyonu) yeniden dağıtılır ve rezervasyon temsilcisi kartı URL'sini yeni bir ortam değişkeni olarak ekler. Mevcut ortam değişkenleri (TOOLBOX_URL, GOOGLE_CLOUD_PROJECT vb.) korunur:

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

Tamamen dağıtılmış sistemi test etme

Dağıtılan hizmet URL'sini alma:

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

URL'yi tarayıcınızda açın. ADK web kullanıcı arayüzü yüklenir. Bu, yerel olarak kullandığınız arayüzün Cloud Run'da çalışan sürümüdür.

Temsilciyle sohbet edebilirsiniz.

Menü Sorgusu

What spicy dishes do you have?

Rezervasyon isteği

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

Rezervasyonu kontrol etme

Yeni oturum oluşturma ( yeni bir görüşme başlatma):

Check reservation for 555-0505

69ae9a7c35255fc.png

55145841338ec9b3.png

Çoklu aracı sistemi tamamen dağıtılır. Cloud Run'daki restoran aracısı, iki arka uç hizmeti arasında düzenleme yapar: menü işlemleri için MCP Toolbox ve Agent Runtime'da A2A rezervasyon aracısı.

11. Tebrikler!

Google Cloud'da A2A protokolünü kullanarak çoklu ajan sistemi oluşturup dağıttınız.

Öğrendikleriniz

  • Veritabanı olmadan rezervasyon verilerini yönetmek için oturum durumunu (ToolContext) kullanan bir ADK aracısı oluşturdu.
  • Agent Platform SDK'yı kullanarak Agent Runtime'a A2A aracısı dağıtma
  • RemoteA2aAgent alt ajan olarak kullanılarak başka bir ADK ajanından uzaktan A2A ajanı tüketildi.
  • Sistemi kademeli olarak test ettik: yerel A2A → dağıtılan A2A → kısmi entegrasyon → tam dağıtım

Temizleme

Google Cloud hesabınızın ücretlendirilmesini önlemek için bu codelab'de oluşturulan kaynakları silin.

gcloud projects delete $GOOGLE_CLOUD_PROJECT

2. seçenek: Kaynakları tek tek silme

# 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