Agent2Agent ile çoklu temsilci sistemleri

1. Genel Bakış

Bu codelab'de, Agent2Agent (A2A) protokolünü kullanarak birden fazla ADK aracısının iletişim kurduğu ve işbirliği yaptığı çok aracılı bir sistem oluşturacaksınız.

Neler öğreneceksiniz?

  • Birden fazla bağımsız ADK temsilcisi oluşturma
  • Her temsilciye nasıl temsilci kartı verilir ve A2A sunucusu olarak nasıl sarmalanır?
  • Uzak aracılarınızı düzenleyen bir ana makine aracısı oluşturma
  • Uzak aracı bağlantıları oluşturma
  • Çok agentlı sistemi yerel olarak test etme

İhtiyacınız olanlar

  • Faturalandırmanın etkin olduğu bir Google Cloud projesi
  • Chrome gibi bir web tarayıcısı
  • Python 3.12 veya daha yeni sürümler

Bu codelab, Python ve Google Cloud hakkında bilgi sahibi olan orta düzey geliştiriciler içindir.

Bu codelab'in tamamlanması yaklaşık 15 dakika sürer.

Bu kod laboratuvarında oluşturulan kaynakların maliyeti 5 ABD dolarından az olmalıdır.

2. Ortamınızı ayarlama

Google Cloud projesi oluşturma

  1. Google Cloud Console'daki proje seçici sayfasında bir Google Cloud projesi seçin veya oluşturun.
  2. Cloud projeniz için faturalandırmanın etkinleştirildiğinden emin olun. Bir projede faturalandırmanın etkin olup olmadığını kontrol etmeyi öğrenin.

Cloud Shell Düzenleyici'yi başlatma

Google Cloud Console'dan Cloud Shell oturumu başlatmak için Google Cloud Console'da Cloud Shell'i etkinleştir'i tıklayın.

Bu işlem, Google Cloud Console'unuzun alt bölmesinde bir oturum başlatır.

Düzenleyiciyi başlatmak için Cloud Shell penceresinin araç çubuğunda Open Editor'ı (Düzenleyiciyi Aç) tıklayın.

Ortamınızı yapılandırma

A2A sisteminiz için proje klasörü yapısını oluşturmak üzere terminalinizde aşağıdaki komutu çalıştırarak başlayın. Bu demoda, $HOME dizininizden mutlak yol oluşturma yöntemini kullanacağız:

mkdir -p $HOME/app/src/agents $HOME/app/src/host
touch $HOME/app/.env $HOME/app/pyproject.toml

Genel mimariyi oluşturduğumuza göre şimdi ortam yapılandırmalarını dolduralım. Aşağıdaki kod segmentini yeni .env dosyasına kopyalayın:

Yeni .env dosyanızı GCP proje kimliğiniz ve GCP bölgenizle doldurun. Oluşturmak üzere olduğunuz aracıdan rapor e-postaları almak istiyorsanız MAIL_TO alanına istediğiniz bir e-posta adresini de girebilirsiniz. Ayrıca, GitHub alma aracını kolaylaştırmak için sürekli olarak bir GitHub kişisel erişim jetonu GITHUB_TOKEN ekleyebilirsiniz.

Aşağıdaki bash komutuyla yeni .env dosyanızı açın:

cloudshell edit .env

Ardından aşağıdaki dosyayı app.env konumundaki .env dosyasına kopyalayın. ÖNEMLİ NOT: KENDİ değerlerinizi girdiğinizden emin olun.

# --- Google Cloud ---
GOOGLE_GENAI_USE_VERTEXAI=TRUE
GOOGLE_CLOUD_PROJECT=<your-gcp-project-id>
GOOGLE_CLOUD_LOCATION=<your-gcp-project-region>

# --- GitHub ---
# Personal Access Token with "repo" scope
# Create one at: https://github.com/settings/tokens
# Generate new token --> Fine-grained, repo-secured --> (populate) Token name --> (scroll down) Generate token
GITHUB_TOKEN=<your-github-pat>
MCP_SERVER_HOST=https://api.githubcopilot.com/mcp/
TARGET_REPOS=["google/adk-python", "langchain-ai/langchain"]
DEFAULT_ISSUE_COUNT=5
DEFAULT_PR_COUNT=5

# --- Email (Optional) ---
# Only needed if you want the emailer agent to send reports
RESEND_API_KEY=<your-resend-API-key>
RESEND_DOMAIN=<your-domain>
MAIL_TO=<insert-email-to-receive-reports>

# --- Local Development Overrides  ---
RETRIEVAL_AGENT_URL=http://localhost:8001
EVALUATOR_AGENT_URL=http://localhost:8002
EMAILER_AGENT_URL=http://localhost:8003
ORCHESTRATOR_PORT=http://localhost:8000

Ortam değişkenlerinizi doldurduktan sonra UV ortamımızı yapılandırmamız gerekir. Aşağıdaki kod segmentini pyproject.toml dosyasına kopyalayın:

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "app"
version = "0.1.0"
description = "Multi-agent system designed to assess the newest issues and pull requests from numerous agentic development platforms"
authors = [
    { name = "Thomas Wagner" },
    { name = "Kris Overholt" }
]
license = "Apache-2.0"
requires-python = ">=3.11,<3.14"
dependencies = [
    "resend>=2.21.0",
    "uvicorn>=0.40.0",
    "a2a-sdk>=0.3.26,<0.4.0",
    "google-adk[a2a]>=1.27.0,<1.30.0",
    "google-generativeai>=0.8.4",
    "httpx>=0.28.1",
    "pydantic>=2.12.5, <3.0.0",
    "python-dotenv>=1.2.0",
    "nest-asyncio>=1.6.0",
]

[project.optional-dependencies]
test = [
    "pytest>=8.3.2",
    "pytest-asyncio>=0.23.8",
    "pytest-mock>=3.14.0",
    "respx>=0.21.0",
]

[tool.ruff]
target-version = "py313"
line-length = 80

[tool.ruff.lint]
select = ["E", "F", "I", "C", "PL", "B", "UP", "RUF"]
ignore = ["E501", "C901"]

[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401"]

[tool.ruff.lint.isort]
known-first-party = ["src"]

[tool.hatch.build.targets.wheel]
packages=["src/"]

Sanal ortamınızı oluşturma

Şimdi, yeni oluşturduğunuz app dizininden terminalinizde aşağıdaki bash komut dosyasını çalıştırın. Bu işlem, Python sanal ortamınızı ayarlar ve pyproject.toml dosyasındaki tüm gerekli bağımlılıkları yükler.

# Ensure you are in the 'app' directory before running this

# Exit immediately if a command exits with a non-zero status.
set -e

# 1. Install uv, the Python package manager used for this project
echo "Installing uv..."
if ! command -v uv &> /dev/null
then
    pip install uv
else
    echo "uv is already installed."
fi

# 2. Create and activate virtual environment
echo "Setting up virtual environment..."
# --clear ensures you start with a fresh environment
uv venv --clear

# 3. Install dependencies
echo "Installing Python dependencies..."
uv sync

echo "Installation and setup complete."

Artık çoklu aracı sistemimizi oluşturmak için her şey hazır.

3. Retriever aracısını oluşturma

Eva, yeni sorunlar ve PR'lerle güncellenen GitHub depolarından haberdar olmak isteyen bir yazılım geliştiricidir. Bu nedenle, istediği GitHub verilerini almak için bir ADK aracısı oluşturur.

Bu demoda, alıcı aracısı için gerekli dizini ve dosyayı oluşturmak üzere projenizin kökünden aşağıdaki komutu çalıştırın:

mkdir -p $HOME/app/src/agents/retriever
touch $HOME/app/src/agents/retriever/agent.py
touch $HOME/app/src/agents/retriever/__init__.py
echo "from . import agent" >> $HOME/app/src/agents/retriever/__init__.py

Aşağıdaki ADK kodu segmentini $HOME/app/src/agents/retriever/agent.py içine kopyalayın:

import logging
import os
import json
from dotenv import load_dotenv
from google.adk.agents.llm_agent import Agent
from google.adk.tools.mcp_tool.mcp_session_manager import (
    StreamableHTTPConnectionParams,
)
from google.adk.tools.mcp_tool.mcp_toolset import McpToolset

logger = logging.getLogger(__name__)
load_dotenv()

GITHUB_MCP_URL = os.getenv(
    "GITHUB_MCP_URL", "https://api.githubcopilot.com/mcp/"
)
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
if GITHUB_TOKEN is None:
    raise ValueError("GITHUB_TOKEN env is not set")
TARGET_REPOS = os.getenv("TARGET_REPOS")
DEFAULT_ISSUE_COUNT = os.getenv("DEFAULT_ISSUE_COUNT")
DEFAULT_PR_COUNT = os.getenv("DEFAULT_PR_COUNT")

# --- Prompt ---
GITHUB_RETRIEVAL_INSTRUCTIONS = f"""
    You are a specialized GitHub data retrieval agent.
    Your only purpose is to fetch data from GitHub using the available MCP tools and format it for another agent.

    **DEFAULT CONFIGURATION:**
    If the orchestrator does not specify which repositories or how many items to fetch, you MUST use the following defaults:
    - **Repositories:** {TARGET_REPOS}
    - **PR Count:** {DEFAULT_PR_COUNT} per repository
    - **Issue Count:** {DEFAULT_ISSUE_COUNT} per repository

    **Your Task:**
    1. Analyze the task. If no specific repo is mentioned, iterate through the Default Repositories list above.
    2. Use the `GitHub` MCP tool to fetch the requested data (Pull Requests and/or Issues).
    3. **CRITICAL:** After the tool has finished running, you MUST take the raw output and compile it into a single response.

    The orchestrator is waiting for this raw data to pass to the Evaluator. Do not summarize it.
"""


# --- Agent Initialization ---
root_agent = Agent(
    model="gemini-2.5-flash",
    name="retriever",
    instruction=GITHUB_RETRIEVAL_INSTRUCTIONS,
    description="""
        Connects to the GitHub MCP server to retrieve real-time development
        insights, actively reading repository issues, pull requests, and commit
        histories.
    """,
    tools=[
        McpToolset(
            connection_params=StreamableHTTPConnectionParams(
                url=GITHUB_MCP_URL,
                headers={
                    "Authorization": f"Bearer {GITHUB_TOKEN}",
                    "X-MCP-Toolsets": "repos,issues,pull_requests",
                    "X-MCP-Readonly": "true",
                },
            )
        )
    ],
)

Bu ajan bağımsız bir araç olarak çalışır. Bağımsız olarak test etmek için aşağıdaki komutları çalıştırarak **/agents/ DİZİNİNDE uv run adk web'i çalıştırın:

cd $HOME/app/src/agents

uv run adk run retriever

"uv run adk web" terminalinizde localhost bağlantısını açın ve denemek için aracıyı seçin.

4. Değerlendirici aracısını oluşturma

Richard, teknik olmayan müşterileri ve iş arkadaşları için genellikle çok fazla teknik jargonu basitleştirmesi gerektiğini düşünüyor. Aynı terimleri tanımlamaktan ve aynı projeyi özetlenmiş bir şekilde açıklamaktan bıkan Richard, teknik terminolojiyi kolayca anlaşılabilir bir metne dönüştüren bir özetleme aracısı oluşturur.

Bu demo için değerlendirici aracının dosyasını oluşturmak üzere aşağıdaki komutu çalıştırın:

mkdir -p $HOME/app/src/agents/evaluator
touch $HOME/app/src/agents/evaluator/agent.py
touch $HOME/app/src/agents/evaluator/__init__.py
echo "from . import agent" >> $HOME/app/src/agents/evaluator/__init__.py

Aşağıdaki aracı kodu segmentini $HOME/app/src/agents/evaluator/agent.py içine kopyalayın.

import json
from google.adk.agents.llm_agent import Agent

# --- Prompt ---
EVAL_AND_SUMMARIZATION_INSTRUCTIONS = """
    You are a specialized analysis and summarization agent. Your only purpose is to take raw, structured text about GitHub repositories and transform it into a concise, human-readable Markdown report.

    **Your Task:**
    1. You will receive a block of text containing raw data about pull requests and issues from an orchestrator.
    2. Analyze the provided data. For pull requests, evaluate their significance. For issues, identify key themes and problems.
    3. **CRITICAL:** You MUST generate a comprehensive summary in Markdown format based on your analysis. Your final output should be ONLY this Markdown report. Do not add any conversational text or explanations (e.g., "Here is the summary..."). The orchestrator needs to pass your clean report to another agent or directly to the user.

    **Output Format Rules:**
    - The report MUST be in Markdown.
    - Structure the report by repository.
    - For each repository, provide a concise overview of significant pull requests and important issues.
    - Conclude with overall insights.

    The orchestrator is waiting for this report. Ensure your final response consists of nothing but the complete Markdown summary.
"""

# --- Agent ---
root_agent = Agent(
    model="gemini-2.5-flash",
    name="evaluator",
    instruction=EVAL_AND_SUMMARIZATION_INSTRUCTIONS,
    description="""
        Distills and summarizes complex GitHub data, breaking down pull
        requests, code changes, and issue discussions into easily understandable
        insights.
    """,
)

Bu ajan bağımsız bir araç olarak çalışır. Bağımsız olarak test etmek için YENİ bir terminalde aşağıdaki komutları çalıştırarak uv run adk web'i **/agents/ DİZİNİNDE çalıştırın:

cd $HOME/app/src/agents

uv run adk run evaluator

Terminalinizde localhost bağlantısını açın ve denemek için değerlendirici aracıyı seçin.

5. E-posta Gönderen Aracı'yı oluşturma

İvan, kolayca erişilebilen bir metni özetleyip yeniden biçimlendirmesi gereken e-postaları yazmaktan bıktı. Bu nedenle, belirli bir metni yeniden biçimlendirip sağlanan e-posta hesabına gönderecek bir e-posta aracı oluşturdu.

Bu demoda, e-posta gönderen aracı için dosyayı oluşturmak üzere terminalinizde aşağıdaki komutu çalıştırın:

mkdir -p $HOME/app/src/agents/emailer
touch $HOME/app/src/agents/emailer/agent.py
touch $HOME/app/src/agents/emailer/__init__.py
echo "from . import agent" >> $HOME/app/src/agents/emailer/__init__.py

Aşağıdaki aracı kodu segmentini app/src/agents/emailer/agent.py içine kopyalayın.

import logging
import os
import resend

from dotenv import load_dotenv
from google.adk.agents.llm_agent import Agent

load_dotenv()
logger = logging.getLogger(__name__)

RESEND_API_KEY = os.getenv("RESEND_API_KEY")
RESEND_DOMAIN = os.getenv("RESEND_DOMAIN")
MAIL_TO = os.getenv("MAIL_TO")

if RESEND_API_KEY:
    resend.api_key = RESEND_API_KEY

# --- Tools ---
def send_report_email(recipient: str, subject: str, body: str) -> str:
    """
    Sends an email of the given subject and body to the specified recipient
    via Resend. If recipient is None, it defaults to the MAIL_TO environment variable.

    Args:
        recipient (str | None): The email address of the recipient. If None, defaults to MAIL_TO.
        subject (str): The subject of the email.
        body (str): The body of the email.

    Returns:
        str: A success or failure message.
    """
    if not recipient:
        recipient = MAIL_TO

    if not all([RESEND_API_KEY, RESEND_DOMAIN, recipient]):
        error_msg = "Error: Email tool configuration missing (API Key, Domain, or Recipient)"
        logger.error(error_msg)
        return error_msg

    try:
        html_body = f"<div style='font-family: sans-serif; white-space: pre-wrap;'>{body}</div>"

        params: resend.Emails.SendParams = {
            "from": f"Research Agent <agent@{RESEND_DOMAIN}>",
            "to": [recipient], #type: ignore
            "subject": subject,
            "text": body,
            "html": html_body,
        }

        email = resend.Emails.send(params)

        logger.info(f"Email sent successfully. ID: {email['id']}")
        return f"Email sent! ID: {email['id']}"

    except Exception as e:
        error_msg = f"Failed to send email: {e}"
        logger.error(error_msg)
        return error_msg

# --- Prompt ---
EMAIL_INSTRUCTIONS = """
    You are an emailer agent responsible for formatting and sending a research report via email.

    INPUT: You will receive a Markdown-formatted string (the report) and the email recipient.

    YOUR TASK:
    1. Take the Markdown content and format it appropriately for an email body.
       The goal is to render the Markdown effectively so it is readable and well-presented in an email client.
    2. Based on the summary, generate a concise and informative subject line for the email.
       The subject line should reflect the main themes or key insights from the report.
    3. Use the `send_report_email` tool with the extracted recipient, the generated subject line, and the formatted email body.
       If no email recipient is provided, output a message indicating that no recipient was specified and do NOT call the tool.
"""

# --- Agent ---
root_agent = Agent(
    model="gemini-2.5-flash",
    name="emailer",
    instruction=EMAIL_INSTRUCTIONS,
    description="""
        Acts as the final delivery mechanism in the pipeline, dispatching
        generated summaries and reports to designated email addresses.
    """,
    tools=[
        send_report_email
    ],
)

Bu ajan bağımsız bir araç olarak çalışır. Bağımsız olarak test etmek için YENİ bir terminalde aşağıdaki komutları çalıştırarak uv run adk web'i **/agents/ DİZİNİNDE çalıştırın:

cd $HOME/app/src/agents

uv run adk run emailer

Terminalinizde localhost bağlantısını açın ve denemek için aracıyı seçin.

6. Temsilci kartları oluşturma

Kendi benzersiz temsilcilerine sahip üç bağımsız geliştiricinin hikayesini inceledik. Bu temsilcilerin tümü yayınlanmış ve şirketlerinin Temsilci Kitaplığı'na giriş yapmıştır. Başka bir geliştirici olan Diane, bu bağımsız fikirleri tek bir çoklu ajan sisteminde bir araya getirmek istiyor.

Amacı, farklı aracı geliştirme çerçevelerindeki sorunlar ve PR'ler hakkında kendisini güncel tutan bir talep üzerine araştırma aracına sahip olmaktır. Ayrıca, ekosistemdeki tüm yeni gelişmelerin özetini e-posta olarak göndermesini istiyor. Tüm aracıların farklı ortamlarda ayrı ayrı geliştirilmesi nedeniyle A2A, mevcut aracıları bir araya getirmek için ideal bir çözümdür.

Bir dizi uzak aracı oluşturmanın ilk adımı, gerekli uzak aracıların her birine bir aracı kartı vermektir. Bunları, temsilcinizin kartvizitleri olarak düşünebilirsiniz. Bunlar, ana makine temsilcinizin temsilcileri adlarına, açıklamalarına, özelliklerine ve giriş/çıkış şemalarına göre tanımlamasını sağlamak için kullanılan JSON dosyalarıdır.

İlk olarak, cards dizinini ve aracı kartları için gerekli tüm JSON dosyalarını oluşturmak üzere proje kökünüzden aşağıdaki komutu çalıştırın:

mkdir -p $HOME/app/cards/
touch $HOME/app/cards/github_retrieval_agent_card.json
touch $HOME/app/cards/content_evaluator_agent_card.json
touch $HOME/app/cards/emailer_agent_card.json

Bu dosyalara yalnızca aşağıdaki bash komutuyla erişilebilir:

cloudshell edit $HOME/app/cards/github_retrieval_agent_card.json

Aşağıdaki JSON içeriğini app/cards/github_retrieval_agent_card.json klasörüne kopyalayın. Bu dosyaya şu şekilde erişilebilir:

{
  "name": "GitHub_Retrieval_Agent",
  "description": "Connects to the GitHub MCP server to retrieve real-time development insights, actively reading repository issues, pull requests, and commit histories.",
  "url": "http://localhost:8001",
  "capabilities": {
    "streaming": true,
    "pushNotifications": true,
    "stateTransitionHistory": false
  },
  "defaultInputModes": [
    "text",
    "text/plain"
  ],
  "defaultOutputModes": [
    "text",
    "json",
    "text/plain"
  ],
  "skills": [
    {
      "id": "read_github_repos",
      "name": "GitHub_Retriever",
      "description": "Fetches and analyzes recent content updates from GitHub repositories, including open/closed issues, pull request statuses, and detailed commit logs.",
      "tags": [
        "Find the newest pull requests from",
        "Check recent issues in",
        "Summarize commits for",
        "GitHub repository status"
      ],
      "examples": [
        "Find the 10 most recent pull requests from the langchain-ai/langgraph repository along with their commits.",
        "List all open issues tagged with 'bug' in the current repository."
      ]
    }
  ]
}

Aşağıdaki içeriği app/cards/content_evaluator_agent_card.json dosyasına kopyalayın. Bu işlem, ana makine aracısına bu aracının neler yapabileceği, hangi tür veriler verebileceğiniz ve hangi verileri döndüreceği hakkında bir açıklama sağlar.

Değerlendirici aracısı kartını düzenlemek için öncekiyle aynı komutu kullanın:

cloudshell edit $HOME/app/cards/content_evaluator_agent_card.json
{
  "name": "Content_Evaluation_Agent",
  "description": "Distills and summarizes complex GitHub data, breaking down pull requests, code changes, and issue discussions into easily understandable insights.",
  "url": "http://localhost:8002",
  "capabilities": {
    "streaming": true,
    "pushNotifications": true,
    "stateTransitionHistory": false
  },
  "defaultInputModes": [
    "text",
    "json",
    "text/plain"
  ],
  "defaultOutputModes": [
    "text",
    "text/plain"
  ],
  "skills": [
    {
      "id": "evaluate_github_content",
      "name": "Evaluate GitHub Content",
      "description": "Analyzes and synthesizes provided GitHub data—including code diffs, commit histories, issue threads, and PR comments—into clear, structured summaries.",
      "tags": [
        "summarize pull request",
        "evaluate GitHub changes",
        "break down commits",
        "distill issue discussion",
        "code review summary"
      ],
      "examples": [
        "Make a thorough summary of the code changes and commit history from this pull request data.",
        "Break down the main arguments and proposed solutions from this provided GitHub issue discussion."
      ]
    }
  ]
}

E-posta gönderen temsilcinin temsilci kartını aşağıda bulabilirsiniz. Aynı cloudshell komutunu kullanarak app/cards/emailer_agent_card.json dosyasına kopyalayın:

cloudshell edit $HOME/app/cards/emailer_agent_card.json
{
  "name": "Email_Agent",
  "description": "Acts as the final delivery mechanism in the pipeline, dispatching generated summaries and reports to designated email addresses.",
  "url": "http://localhost:8003",
  "capabilities": {
    "streaming": true,
    "pushNotifications": true,
    "stateTransitionHistory": false
  },
  "defaultInputModes": [
    "text",
    "text/plain"
  ],
  "defaultOutputModes": [
    "text",
    "text/plain"
  ],
  "skills": [
    {
      "id": "dispatch_email_report",
      "name": "Dispatch_Report_via_Email",
      "description": "Takes synthesized text data and securely emails it to a specified recipient or distribution list.",
      "tags": [
        "send email",
        "email summary",
        "dispatch report",
        "forward to inbox"
      ],
      "examples": [
        "Email me the summarized evaluations from the retrieved pull request and issue data.",
        "Take this code review breakdown and send it in an email to the dev team."
      ]
    }
  ]
}

7. Uzaktan aracı yürütücüleri oluşturma

Uzak aracıların bir ana makine aracısı tarafından "çağrılması" veya "konuşulması" için her birinin, A2A sunucusuna görünür bir aracı yürütücüsü olması ve Aracı Kartı ile tanımlanması gerekir. Yürütücü, uzak aracıyı çağıran ve ona bir görev veya mesaj sunan ya da görevi tamamen iptal eden execute() ve cancel() yöntemlerine sahip soyut bir sınıftır.

Burada, mesaj göndermeyi ve görevleri uzak temsilcilere sıralamayı kolaylaştıran soyut bir ADK temsilci yürütücüsünün uygulaması yer almaktadır. Bu temsilcilerin tümü TextPart yapılarını değiştirir. Bu nedenle, temsilciler arasında paylaşılan ortak bir temsilci yürütücüsü kullanabiliriz. Yeni bir yürütücü dosyası oluşturun:

touch $HOME/app/src/agents/executor.py

Aşağıdaki kod segmentini yeni $HOME/app/src/agents/executor.py dosyasına kopyalayın:

# $HOME/app/src/agents/executor.py
from a2a.server.agent_execution import AgentExecutor, RequestContext
from a2a.server.agent_execution.context import RequestContext
from a2a.server.events.event_queue import EventQueue
from a2a.server.tasks import TaskUpdater
from a2a.types import TaskState, TextPart, Part
from a2a.utils import new_agent_text_message, new_task

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

from google.genai import types

class BaseAgentExecutor(AgentExecutor):
    """An AgentExecutor that runs a remote ADK Agent"""
    def __init__(
            self,
            agent,
            status_message='Processing request...',
            artifact_name='response',
        ):
        """Initialize a generic ADK agent executor.

        Args:
            agent: The ADK agent instance
            status_message: Message to display while processing
            artifact_name: Name for the response artifact
        """
        self.agent = agent
        self.status_message = status_message
        self.artifact_name = artifact_name
        self.runner = Runner(
            app_name=agent.name,
            agent=agent,
            artifact_service=InMemoryArtifactService(),
            session_service=InMemorySessionService(),
            memory_service=InMemoryMemoryService(),
        )

    async def execute(
        self,
        context: RequestContext,
        event_queue: EventQueue,
    ) -> None:
        query = context.get_user_input()
        task = context.current_task or new_task(context.message) # type: ignore
        await event_queue.enqueue_event(task)

        updater = TaskUpdater(event_queue, task.id, task.context_id)
        if context.call_context:
            user_id = context.call_context.user.user_name
        else:
            user_id = "a2a_user"

        try:
            # Update status with custom message
            await updater.update_status(
                TaskState.working,
                new_agent_text_message(
                    self.status_message,
                    task.context_id,
                    task.id
                ),
            )

            # Process with ADK agent
            session = await self.runner.session_service.create_session(
                app_name=self.agent.name,
                user_id=user_id,
                state={},
                session_id=task.context_id,
            )

            content = types.Content(
                role="user", parts=[types.Part.from_text(text=query)]
            )

            response_text = ""
            async for event in self.runner.run_async(
                user_id=user_id, session_id=session.id, new_message=content
            ):
                if event.is_final_response() and event.content and event.content.parts:
                    for part in event.content.parts:
                        if hasattr(part, "text") and part.text:
                            response_text += part.text + "\n"
                        elif hasattr(part, "function_call"):
                            # Log or handle function calls if needed
                            pass  # Function calls are handled internally by ADK

            # Add response as artifact with custom name
            await updater.add_artifact(
                [Part(root=TextPart(text=response_text))],
                name=self.artifact_name,
            )

            await updater.complete()

        except Exception as e:
            await updater.update_status(
                TaskState.failed,
                new_agent_text_message(f"Error: {e!s}", task.context_id, task.id),
                final=True,
            )

    async def cancel(
            self,
            context: RequestContext,
            event_queue: EventQueue
        ) -> None:
        """Cancel the execution of a specific task."""
        raise NotImplementedError("Cancel not implemented for RetrieverAgentExecutor")

Artık bu soyut aracı yürütücüsüne sahip olduğumuza göre, yürütücüyü her aracının belirli özelliklerine ve ihtiyaçlarına göre hassas bir şekilde ayarlamak için Python'a özgü devralma özelliğini kullanabiliriz. Bizim durumumuzda, yük ile temsilciyi çağırma, yanıt bekleme ve yanıt yükünü alma işlemleri iş akışımız için evrenseldir. Bu nedenle herhangi bir değişikliğe gerek yoktur. Ancak, A2A sunucularının her birinde bir aracı yürütücü olması gerekir. Üç aracının tümü için yürütücü dosyalarını oluşturmak üzere aşağıdaki komutu çalıştırın:

touch $HOME/app/src/agents/retriever/executor.py
touch $HOME/app/src/agents/evaluator/executor.py
touch $HOME/app/src/agents/emailer/executor.py

Şimdi, her dosyaya ilgili içeriği ekleyin.

# $HOME/app/src/agents/retriever/executor.py
from ..executor import BaseAgentExecutor

class RetrieverAgentExecutor(BaseAgentExecutor):
    """
    An AgentExecutor that runs the Retriever Agent

    All agent specific implementations for execute() and cancel() can be
    overloaded here, along with any other desired funcitonality.
    """
    pass
# $HOME/app/src/agents/evaluator/executor.py
from ..executor import BaseAgentExecutor

class EvaluatorAgentExecutor(BaseAgentExecutor):
    """
    An AgentExecutor that runs the Evaluator Agent

    All agent specific implementations for execute() and cancel() can be
    overloaded here, along with any other desired funcitonality.
    """
    pass
# $HOME/app/src/agents/emailer/executor.py
from ..executor import BaseAgentExecutor

class EmailerAgentExecutor(BaseAgentExecutor):
    """
    An AgentExecutor that runs the Emailer Agent

    All agent specific implementations for execute() and cancel() can be
    overloaded here, along with any other desired funcitonality.
    """
    pass

8. Uzak aracıları A2A sunucularına sunma

Artık her aracının kendi kartviziti olduğundan, A2A sunucusu aracılığıyla bir uç noktaya maruz kaldıklarında bulunabilirler. Bu codelab'de, uzak aracı uç noktalarının her biri için localhost'ları kullanarak tüm süreci yerel olarak tutuyoruz. Ancak uygulamada bunlar istenen herhangi bir uç noktaya sunulabilir ve aracı kartlarının url alanında referans verildiği sürece bulunabilir.

Her uzak aracının bir sonraki adımı, kendi A2A sunucusuna erişmesini sağlamaktır. Üç uzak aracı için server.py dosyasını oluşturmak üzere bu komutu çalıştırın:

touch $HOME/app/src/agents/retriever/server.py
touch $HOME/app/src/agents/evaluator/server.py
touch $HOME/app/src/agents/emailer/server.py

Şimdi her aracı için sunucu kodunu ekleyeceksiniz. Alıcı aracısıyla başlayın:

# $HOME/app/src/agents/retriever/server.py
import logging
import os
import json
import uvicorn

from a2a.types import AgentCard
from a2a.server.apps import A2AStarletteApplication
from a2a.server.request_handlers import DefaultRequestHandler
from a2a.server.tasks import InMemoryTaskStore

from .agent import root_agent as retriever_agent
from .executor import RetrieverAgentExecutor

logging.basicConfig(level=logging.INFO)

with open("cards/github_retrieval_agent_card.json", "r") as f:
    card_data = json.load(f)
github_retrieval_agent_card = AgentCard(**card_data)

request_handler = DefaultRequestHandler(
    agent_executor=RetrieverAgentExecutor(
        agent=retriever_agent
    ),
    task_store=InMemoryTaskStore(),
)

server = A2AStarletteApplication(
    http_handler=request_handler,
    agent_card=github_retrieval_agent_card,
)

if __name__ == "__main__":
    port = int(os.getenv("PORT", 8001))
    print(f"Starting Retriever Agent on Port {port}...")
    uvicorn.run(server.build(), host="0.0.0.0", port=port)

Ardından, değerlendirme aracısı için sunucuyu oluşturun:

# $HOME/app/src/agents/evaluator/server.py
import logging
import os
import json
import uvicorn

from a2a.types import AgentCard
from a2a.server.apps import A2AStarletteApplication
from a2a.server.request_handlers import DefaultRequestHandler
from a2a.server.tasks import InMemoryTaskStore

from .agent import root_agent as evaluator_agent
from .executor import EvaluatorAgentExecutor

logging.basicConfig(level=logging.INFO)

with open("cards/content_evaluator_agent_card.json", "r") as f:
    card_data = json.load(f)
content_evaluator_agent_card = AgentCard(**card_data)

request_handler = DefaultRequestHandler(
    agent_executor=EvaluatorAgentExecutor(agent=evaluator_agent),
    task_store=InMemoryTaskStore(),
)

server = A2AStarletteApplication(
    http_handler=request_handler,
    agent_card=content_evaluator_agent_card,
)

if __name__ == "__main__":
    port = int(os.getenv("PORT", 8002))
    print(f"Starting Evaluator Agent on Port {port}...")
    uvicorn.run(server.build(), host="0.0.0.0", port=port)

Son olarak, e-posta gönderen aracısı için:

# $HOME/app/src/agents/emailer/server.py
import logging
import os
import json
import uvicorn

from a2a.types import AgentCard
from a2a.server.apps import A2AStarletteApplication
from a2a.server.request_handlers import DefaultRequestHandler
from a2a.server.tasks import InMemoryTaskStore

from .agent import root_agent as emailer_agent
from .executor import EmailerAgentExecutor

logging.basicConfig(level=logging.INFO)

with open("cards/emailer_agent_card.json", "r") as f:
    card_data = json.load(f)
emailer_agent_card = AgentCard(**card_data)

request_handler = DefaultRequestHandler(
    agent_executor=EmailerAgentExecutor(
        agent=emailer_agent
    ),
    task_store=InMemoryTaskStore(),
)

server = A2AStarletteApplication(
    http_handler=request_handler,
    agent_card=emailer_agent_card,
)

if __name__ == "__main__":
    port = int(os.getenv("PORT", 8003))
    print(f"Starting Emailer Agent on Port {port}...")
    uvicorn.run(server.build(), host="0.0.0.0", port=port)

9. Ana makine aracısını oluşturma

Şimdiye kadar yaptığımız şey, her bir temsilcimizi bir ana temsilcinin ping atabileceği bir API'ye dönüştürmek oldu. Uzak aracılarımız kendi A2A sunucularına maruz kaldığına göre, bunları keşfeden ve düzenleyen bir ana makine aracısı oluşturmamız gerekiyor.

Bunu gerçekleştirmek için ana makinenin birkaç farklı yönünü oluşturmamız gerekiyor. Öncelikle, her bir uzak aracımız ve sunucuları için ayrı istemciler oluşturmanın bir yolunu bulmamız gerekiyor. Bu işlem, ana makinenin aracı kartlarını keşfedebileceği, sunucuları tarafından barındırılan her bir uzak aracı uç noktasına bağlantı kuran bir A2A istemci fabrikası oluşturularak yapılır. Ana makine ile her bir uzak aracı arasındaki bağlantılar, RemoteAgentConnections nesnesiyle başlatılabilir.

touch $HOME/app/src/host/remote_agent_connection.py
touch $HOME/app/src/host/agent.py
touch $HOME/app/src/host/__init__.py
echo "from . import agent" >> $HOME/app/src/host/__init__.py

Aşağıdaki uzak aracı bağlantısı uygulamasını src/host/remote_agent_connection.py dosyasına kopyalayın:

# src/host/remote_agent_connection.py
import traceback

from a2a.client import (
    Client,
    ClientFactory,
)
from a2a.types import (
    AgentCard,
    Message,
    Task,
    TaskState,
)


class RemoteAgentConnection:
    """A class to hold the connections to the remote agents."""

    def __init__(self, client_factory: ClientFactory, agent_card: AgentCard):
        self.agent_client: Client = client_factory.create(agent_card)
        self.card: AgentCard = agent_card

    def get_agent(self) -> AgentCard:
        return self.card

    async def send_message(self, message: Message) -> Task | Message | None:
        lastTask: Task | None = None
        try:
            async for event in self.agent_client.send_message(message):
                if isinstance(event, Message):
                    return event
                if self.is_terminal_or_interrupted(event[0]):
                    return event[0]
                lastTask = event[0]
        except Exception as e:
            print('Exception found in send_message')
            traceback.print_exc()
            raise e
        return lastTask

    def is_terminal_or_interrupted(self, task: Task) -> bool:
        return task.status.state in [
            TaskState.completed,
            TaskState.canceled,
            TaskState.failed,
            TaskState.input_required,
            TaskState.unknown,
        ]

Artık belirli bir İstemci ve Temsilci Kartı aracılığıyla bir istemci ile sunucu arasında bağlantı kurabiliyoruz. Bu, ana makine aracısının uzak aracı sunucularıyla bağlantı kurmak için kullanacağı bir araçtır.

Şimdi ana makine aracısını oluşturmaya geçelim. İlk olarak aşağıdaki kod segmentini app/src/host/agent.py dosyasına kopyalayın. Bu, uzak aracı uç noktalarını ve standart bir httpx istemcisini gerektiren Pythonic sınıfının başlatılmasıdır. Bu aracı sınıfını başlatmak, kendisine sağlanan uzak adresler üzerinden bağlantı kurar.

import asyncio
import json
import os
import uuid
import httpx
from typing import Any

from a2a.client import A2ACardResolver, ClientConfig, ClientFactory
from a2a.types import (
    AgentCard,
    Message,
    Part,
    Role,
    Task,
    TaskState,
    TextPart,
    TransportProtocol,
)

from google.adk import Agent
from google.adk.agents.callback_context import CallbackContext
from google.adk.agents.readonly_context import ReadonlyContext
from google.adk.tools.tool_context import ToolContext
from dotenv import load_dotenv

from .remote_agent_connection import RemoteAgentConnection

load_dotenv()


######################################
# --- Coordinator Agent Definition ---
######################################
class CoordinatorAgent:
    """
    The Coordinator agent.
    This is the agent responsible for sending tasks to agents.
    """

    def __init__(
        self,
        remote_agent_addresses: list[str],
        http_client: httpx.AsyncClient,
    ):
        self.http_client = http_client
        self.remote_agent_addresses = remote_agent_addresses
        self.client_factory = None
        self.remote_agent_connections: dict[str, RemoteAgentConnection] = {}
        self.cards: dict[str, AgentCard] = {}
        self.agents: str = ''

Ana makine aracısının bir sonraki yönü, uzak aracılarla bağlantı kurmaktır. __init__ içinde bağlanmak (bir etkinlik döngüsü mevcut olmadan önce, modül içe aktarma sırasında çalışır) yerine, ensure_initialized yöntemi bu çalışmayı temsilci gerçekten kullanılana kadar erteler. Bağlantıların kurulup kurulmadığını kontrol eder. Kurulmamışsa A2A istemcisini oluşturur ve her uzak aracıya paralel olarak bağlanır. Bu segmenti app/src/host/agent.py içinde önceki segmentin hemen altına kopyalayın.

    #####################################
    # --- Remote Agent Initialization ---
    #####################################
    async def ensure_initialized(self):
        if not self.remote_agent_connections:
            config = ClientConfig(
                httpx_client=self.http_client,
                supported_transports=[
                    TransportProtocol.jsonrpc,
                    TransportProtocol.http_json,
                ],
            )
            self.client_factory = ClientFactory(config=config)
            async with asyncio.TaskGroup() as task_group:
                for address in self.remote_agent_addresses:
                    task_group.create_task(self.retrieve_card(address))

    async def retrieve_card(self, address: str):
        card_resolver = A2ACardResolver(self.http_client, address)
        card = await card_resolver.get_agent_card()
        card.url = address  # Use the actual address, not the hardcoded URL in the card JSON

        remote_connection = RemoteAgentConnection(self.client_factory, card)
        self.remote_agent_connections[card.name] = remote_connection
        self.cards[card.name] = card

        agent_info = []
        for card in self.cards.values():
            agent_info.append(
                json.dumps({"name": card.name, "description": card.description})
            )
        self.agents = "\n".join(agent_info)

Sırada LLM Agent uygulaması var. Bu örnekte, düzenleyicimiz için bir ADK aracısı kullanıyoruz. Bu aracının, istem talimatları, geri aramalar ve araçlar gibi tipik eklentilere ihtiyacı var. Bu bileşenlerin tümünü app/src/host/agent.py dosyasındaki ana makine aracısı sınıfının içine kopyalayın. Aşağıdaki geri çağırmayı ana makine aracısı sınıfına yerleştirin. Bu geri çağırma, aracı düzgün şekilde tetiklenmeden önce bile aracının durumunda henüz başlatılmamışsa aracı başlatır.

    ############################
    # -- Implement the ADK Agent
    ############################

    # --- Before Model Callback ---
    def before_model_callback(
        self, callback_context: CallbackContext, llm_request
    ):
        """
        A callback to set up the session state before the model processes the
        request
        """
        state = callback_context.state
        if 'session_active' not in state or not state['session_active']:
            if 'session_id' not in state:
                state['session_id'] = str(uuid.uuid4())
            state['session_active'] = True

Barındırıcı aracımızın bir sonraki bileşeni istem talimatıdır. Bu, düzenleyici aracımız olduğundan, oturumda şu anda neler olduğunu koordinatöre bildirmek için kullanabileceği uzak aracıların yanı sıra etkin olan mevcut aracı da bilmesi gerekir. Geri çağırma işlevinin altına aşağıdaki istemi ve yardımcı işlevi ekleyin.

    # --- Prompt ---
    def root_instruction(self, context: ReadonlyContext) -> str:
        current_agent = self.check_state(context)
        return f"""
            You are an expert orchestrator that can delegate user requests to the
            appropriate remote agents to generate a GitHub research report.

            **Your Goal:** To fulfill user requests for GitHub repository data, evaluate it, and optionally email a report.

            **Workflow Steps:**

            1.  **Understand the User Request**:
                -   Identify repository names (e.g., "google/adk-python").
                -   Determine if the user wants Pull Requests, Issues, or both.
                -   Extract any specified email address for sending the report (e.g., "user@example.com").
                -   Note the number of PRs/issues requested per repository. If not specified, the Retrieval Agent has defaults.

            2.  **Retrieve Data (GitHub_Retrieval_Agent)**:
                -   Use the `send_message` tool to send a message to the "GitHub_Retrieval_Agent".
                -   Your message to the Retrieval Agent should clearly state which repositories to fetch data for, and specify if you need issues, pull requests, and the respective limits.
                -   Example message to Retrieval Agent: "Fetch 5 issues and 3 pull requests for google/adk-python."

            3.  **Evaluate and Summarize Data (Content_Evaluation_Agent)**:
                -   Once you receive the raw JSON data from the "GitHub_Retrieval_Agent", use the `send_message` tool to send this data to the "Content_Evaluation_Agent".
                -   Your message to the Evaluation Agent should include the raw JSON data you received.
                -   The Evaluation Agent will return a Markdown-formatted report.

            4.  **Email Report (Email_Agent - if email provided)**:
                -   If the user's initial request included an email address, use the `send_message` tool to send the Markdown report from the "Content_Evaluation_Agent" to the "Email_Agent".
                -   Your message to the Email Agent should include the report and the recipient's email address.
                -   Example message to Email Agent: "Send this report to user@example.com: [Markdown Report Content]".

            5.  **Respond to User**:
                -   Based on the outcome of the steps above, formulate a concise and informative response to the user.
                -   If a report was generated and emailed, confirm that. If only a report was generated, provide it directly to the user.
                -   If an email address was requested but the email failed to send, inform the user.
                -   If any step failed, inform the user about the failure.

            **Available Tools:**

            -   `list_remote_agents()`: Use this to see what agents are available (though you already know their names for this workflow).
            -   `send_message(agent_name: str, message: str)`: Use this to interact with remote agents.

            **Crucial Guidelines:**

            -   **Rely on Tools**: ALWAYS use `send_message` to interact with the remote agents. Do NOT attempt to perform the tasks yourself.
            -   **No Conversational Filler**: Only communicate the essential information to the remote agents or back to the user.
            -   **Error Handling**: If a remote agent returns an error, acknowledge it and try to provide a helpful message to the user.

            Agents:
            {self.agents}

            Current agent: {current_agent['active_agent']}
        """

    def check_state(self, context: ReadonlyContext):
        state = context.state
        if (
            'context_id' in state
            and 'session_active' in state
            and state['session_active']
            and 'agent' in state
        ):
            return {'active_agent': f'{state["agent"]}'}
        return {'active_agent': 'None'}

Şimdi de ana makine aracısının en önemli kısmı olan araçlara geçelim. Ana makine, send_message() ve list_remote_agents() araçlarına erişebilir. list_remote_agent() aracı daha basittir. Yalnızca sınıfın aracı kartlarını okur ve her aracının adını ve açıklamasını içeren bir sözlük listesi döndürür. send_message() biraz daha gelişmiştir. Uzak bir aracıya, aracı adını ve ileti dizesini kullanarak mesaj veya görev gönderebilir, yayın yapabilir ya da yayın yapmadan ileti gönderebilir. Bu kod segmentini, app/src/host/agent.py içindeki istem bölümünün altına, aynı Host Agent Python sınıfının içine yerleştirin.

    # --- Agent Tools ---
    def list_remote_agents(self):
        """List the available remote agents you can use to delegate the task."""
        if not self.remote_agent_connections:
            return []

        remote_agent_info = []
        for card in self.cards.values():
            remote_agent_info.append(
                {'name': card.name, 'description': card.description}
            )
        return remote_agent_info

    async def send_message(
        self, agent_name: str, message: str, tool_context: ToolContext
    ):
        """Sends a task either streaming (if supported) or non-streaming.

        This will send a message to the remote agent named agent_name.

        Args:
          agent_name: The name of the agent to send the task to.
          message: The message to send to the agent for the task.
          tool_context: The tool context this method runs in.

        Yields:
          A dictionary of JSON data.
        """
        await self.ensure_initialized()
        if agent_name not in self.remote_agent_connections:
            raise ValueError(f'Agent {agent_name} not found')
        state = tool_context.state
        state['agent'] = agent_name

        client = self.remote_agent_connections[agent_name]
        if not client:
            raise ValueError(f'Client not available for {agent_name}')

        task_id = state.get('task_id', None)
        context_id = state.get('context_id', None)
        message_id = state.get('message_id', None)
        task: Task
        if not message_id:
            message_id = str(uuid.uuid4())

        request_message = Message(
            role=Role.user,
            parts=[Part(root=TextPart(text=message))],
            message_id=message_id,
            context_id=context_id,
            task_id=task_id,
        )
        response = await client.send_message(request_message)

        if isinstance(response, Message):
            return await convert_parts(response.parts, tool_context)

        task: Task = response # type: ignore

        # Assume completion unless a state returns that isn't complete
        state['session_active'] = not client.is_terminal_or_interrupted(task)
        if task.context_id:
            state['context_id'] = task.context_id
        state['task_id'] = task.id

        if task.status.state == TaskState.input_required:
            # Force user input back
            tool_context.actions.skip_summarization = True
            tool_context.actions.escalate = True
        elif task.status.state == TaskState.canceled:
            # Open question, should we return some info for cancellation instead
            raise ValueError(f'Agent {agent_name} task {task.id} is cancelled')
        elif task.status.state == TaskState.failed:
            # Raise error for failure
            raise ValueError(f'Agent {agent_name} task {task.id} failed')

        response = []
        if task.status.message:
            response.extend(
                await convert_parts(task.status.message.parts, tool_context)
            )

        if task.artifacts:
            for artifact in task.artifacts:
                response.extend(
                    await convert_parts(artifact.parts, tool_context)
                )
        return response

Son olarak, tüm bu bileşenleri içeren ADK aracısını uygulayabiliriz. Bu, BeforeModelCallback çalıştırıldıktan sonra kendisine verilen istemi elindeki araçlarla gerçekleştiren düzenleyici sınıfı aracısının beynidir.

Aşağıdaki kod segmentini, app/src/host/agent.py dosyasındaki araçlar bölümünün hemen altına yerleştirin:

    def create_agent(self) -> Agent:
        """Create an instance of the CoordinatorAgent."""
        return Agent(
            model='gemini-2.5-flash',
            name='host',
            instruction=self.root_instruction,
            before_model_callback=self.before_model_callback,
            description=(
                'This coordinator agent orchestrates the retriever, evaluator, and emailer agents'
            ),
            tools=[
                self.send_message,
                self.list_remote_agents,
            ],
        )

Temsilcinin, A2A bölümlerinin ham metne ve verilere dönüştürülmesi için send_message() aracında birkaç yardımcı işlevi kullanması gerekir. Bu segmenti CoordinatorAgent sınıfının DIŞINDA kopyalayın:

##########################
# --- Helper Functions ---
##########################
async def convert_part(part: Part, tool_context: ToolContext):
    """Convert a part to text. Only text parts are supported."""
    if isinstance(part.root, TextPart):
        return part.root.text
    if part.root.kind == "data":
        return part.root.data
    return f"Unknown type: {part}"

async def convert_parts(parts: list[Part], tool_context: ToolContext):
    """Convert parts to text."""
    rval = []
    for p in parts:
        rval.append(await convert_part(p, tool_context))
    return rval

uv run adk web veya adk run üzerinden yerel test ve etkileşimi kolaylaştırmak için app/src/host/agent.py dosyasının en altına root_agent başlatma işlemi ekleyin:

root_agent = CoordinatorAgent(
    remote_agent_addresses=[
        os.getenv('RETRIEVAL_AGENT_URL', 'http://localhost:8001'),
        os.getenv('EVALUATOR_AGENT_URL', 'http://localhost:8002'),
        os.getenv('EMAILER_AGENT_URL', 'http://localhost:8003'),
    ],
    http_client=httpx.AsyncClient(timeout=30),
).create_agent()

TEBRİKLER! İlk A2A ana makine aracınızı oluşturdunuz. root_agent değişkeniyle başlattığımız için, diğer uzak aracılar gibi yerel uv run adk web dağıtımı üzerinden bu değişkenle etkileşim kurabiliriz. Toplantı sahibiyle etkileşim kurmak için aşağıdaki komutları kullanın:

cd $HOME/app/src/

uv run adk run host

10. Yerel olarak test etme

Çoklu aracı sisteminin tamamını test etmek için ana makine aracınızın kullanmasını istediğiniz uzak aracı sunucularını başlatmanız gerekir. Bu işlem, tüm aracı sunucularını aynı anda başlatır.

# Make sure you have activated your virtual environment first!
# source .venv/bin/activate

#!/bin/bash

# Exit immediately if a command exits with a non-zero status.
set -e

# Function to kill all background processes
cleanup() {
    echo "Caught signal, terminating background processes..."
    # The negative PID kills the entire process group
    kill -TERM -$$
    wait
    echo "All processes terminated."
    exit
}

# Trap TERM, INT, and EXIT signals and call the cleanup function
trap cleanup TERM INT EXIT

# Activate virtual environment
uv sync

# Start the agent servers in the background
echo "Starting agent servers..."
uv run python3 -m src.agents.retriever.server --port 8001 &
uv run python3 -m src.agents.evaluator.server --port 8002 &
uv run python3 -m src.agents.emailer.server --port 8003 &

cd $HOME/app/src/
RETRIEVER_AGENT_URL=http://localhost:8001
EVALUATOR_AGENT_URL=http://localhost:8002
EMAILER_AGENT_URL=http://localhost:8003
uv run adk run host

# Wait for all background processes to finish
wait

Bu bash komutu, dört aracı sunucunun (retriever, evaluator, emailer ve host) tümünü başlatır. Terminalinizde her sunucudan günlük çıktısı görürsünüz. Sunucular çalıştıktan sonra yeni bir terminal penceresi açabilir (aynı app dizininde olduğunuzdan ve sanal ortamın etkinleştirildiğinden emin olarak) ve uv run adk web arayüzünü başlatabilirsiniz:

# In a new terminal, from the 'app' directory
source .venv/bin/activate
cd $HOME/app/src/
uv run adk web

Terminalinizde görünen localhost bağlantısını açın. Buradan, sol üstteki açılır menüden seçerek doğrudan ana makine aracısıyla etkileşim kurabilirsiniz.

11. Temizleme

Devam eden ücretleri önlemek için bu codelab sırasında oluşturulan kaynakları silin.

Çalışan tüm aracı sunucularını durdurun, ardından özellikle bu codelab için oluşturduysanız projeyi silin. Uzak aracıları başlatan terminallere gidin, Ctrl+C komutunu çalıştırın ve ardından bu Google Cloud projesini kaldırın:

gcloud projects delete ${GOOGLE_CLOUD_PROJECT}

12. Tebrikler

Agent2Agent ile başarıyla çoklu temsilci sistemi oluşturdunuz.

Öğrendikleriniz

  • Kendi araçlarına sahip bağımsız ADK temsilcileri oluşturma
  • Aracı kartlarıyla aracılara bulunabilir kimlikler verme
  • A2A sunucuları aracılığıyla aracıları kullanıma sunma
  • Uzak aracıları düzenleyen bir ana makine aracısı oluşturma
  • A2A protokolü, bağımsız olarak geliştirilen temsilciler arasında iletişimi nasıl sağlar?

Sonraki adımlar

  • Sistemi üretimde kullanmak üzere Cloud Run'a dağıtma
  • Sistemin özelliklerini genişletmek için daha fazla uzak aracı ekleyin
  • A2A'daki yayın ve push bildirimi özelliklerini keşfetme

Referans belgeleri