ADK と A2A を使用してマルチエージェント フォーミュラ E トラック プランナーを構築する

1. 1. 概要

この Codelab では、フォーミュラ E のストリート サーキットを設計し、簡素化された FIA ルールに照らして承認を得るマルチエージェント システムを構築します。

agents-cli を使用して 2 つの標準的な Agent Development Kit(ADK)プロジェクトを生成し、重要な部分(エージェント スキル、3 つのエージェント、評価)を記述します。それ以外(プロジェクト レイアウト、サーバー、依存関係、デプロイ構成)はすべて CLI から取得されます。

フォーミュラ E を初めてご覧になる方へ

モータースポーツに詳しくなくても大丈夫。次の 5 つの用語で十分です。

  • フォーミュラ E: FIA の完全電気自動車レース選手権。ほとんどのレースは、都市中心部の仮設ストリート サーキットで開催されます。
  • ホモロゲーション: サーキットが安全にレースを行えることを示す FIA の公式承認。ホモロゲーションがなければ、レースはできません。
  • 回生ブレーキ: 車がブレーキをかけると、モーターがバッテリーを充電します。ブレーキを強くかけるほど、より多くのエネルギーを回収できるため、この Codelab のルールでは、少なくとも 1 つの急ブレーキ ゾーンを設ける必要があります。
  • CAD レイヤ: レーシング ライン、ピットレーン、バリアなど、サーキットの設計図に名前が付けられたレイヤ。FIA は、必須のものが存在することを確認します。
  • アタック モード: ドライバーは、レーシング ラインの外に配置されたアクティビティ エリアを通過することで、一時的にパワーをブーストできます。

エージェントは両方の役割を担います。1 つはサーキットを設計し、2 つは FIA のためにサーキットをチェックします。

作業内容

アーキテクチャ: track-planner プロジェクトで、Cloud Shell で実行されている track_planner エージェントは、get_track_topology、fia_assessor、remote_gatekeeper を使用します。remote_gatekeeper は、A2A 経由で gatekeeper エージェントを呼び出します。Agent Runtime で実行されている gatekeeper プロジェクトでは、gatekeeper エージェントが verify_infrastructure_checklist を使用します。

緑色のボックスはエージェント、点線のボックスはツールです。これは ADK プレイグラウンドでの描画と同じです。2 つのツールは他のエージェントをラップします。fia_assessor は FIA コンプライアンス スキルを持つ FIA 評価者エージェントで、remote_gatekeeper は A2A 経由で他のプロジェクトの Gatekeeper エージェントを呼び出します。

エージェント

プロジェクト

ジョブ

thinking_level

リード アーキテクト

track-planner

会場を読み取り、評価者に質問し、問題を修正し、ゲートキーパーに承認を求める

HIGH

FIA 評価者

track-planner

FIA スキルとスコアリング ツールを使用してレイアウトを確認する

MEDIUM

デジタルツイン ゲートキーパー

gatekeeper

最終的なホモロゲーション チェックリストを実行する

LOW

学習内容

  • agents-cli scaffold create が ADK プロジェクトをレイアウトする方法と、編集するファイル。
  • ドメイン知識をエージェント スキル(SKILL.md、references/、scripts/)としてパッケージ化し、ADK の SkillToolset で読み込む方法。
  • thinking_level を各エージェントのジョブに一致させる方法。
  • agents-cli eval run を使用してエージェントを評価する方法。
  • Agent Runtime にエージェントをデプロイする方法、エージェント ID とサービス アカウントの違い、A2A プロトコルを介してデプロイされたエージェントを呼び出す方法。

2. 2. 環境を設定する

始める前に

課金が有効になっている Google Cloud プロジェクトが必要です。イベントでこの Codelab を実施する場合は、提供されたアカウントとプロジェクトを使用してください。

Cloud Shell を開いてプロジェクトを設定する

Cloud Shell を開き、次のコマンドを実行します。

# Cloud Shell sometimes opens without a project set. If so, this sets it to
# the first project on your account.
if [ -z "$(gcloud config get-value project 2>/dev/null)" ]; then
  gcloud config set project "$(gcloud projects list --format='value(projectId)' --limit=1)"
fi

export GOOGLE_CLOUD_PROJECT="$(gcloud config get-value project)"
export REGION="us-central1"
export ASSETS="$HOME/devrel-demos/codelabs/formula-e-track-planner"
echo "Project: $GOOGLE_CLOUD_PROJECT  Region: $REGION"

エージェントの認証情報を作成する

gcloud はすでにログインしていますが、エージェントが実行する Python コードはアプリケーションのデフォルト認証情報(ADC)を使用するため、別途ログインする必要があります。

gcloud auth application-default login

出力されたリンクを開き、同じアカウントでログインしてコードをコピーし、Cloud Shell に貼り付けます。次に、割り当ての請求先となるプロジェクトを設定します。

gcloud auth application-default set-quota-project "$GOOGLE_CLOUD_PROJECT"

API を有効にする

gcloud services enable \
    aiplatform.googleapis.com \
    cloudbuild.googleapis.com \
    artifactregistry.googleapis.com \
    storage.googleapis.com \
    cloudresourcemanager.googleapis.com \
    serviceusage.googleapis.com \
    compute.googleapis.com \
    iam.googleapis.com \
    logging.googleapis.com \
    cloudtrace.googleapis.com

各 API の用途:

API

用途

aiplatform

Gemini モデルと Agent Runtime

cloudresourcemanager、serviceusage、iam

gcloud と agents-cli deploy が実行するプロジェクトと権限のチェック

compute

ステップ 7 のデプロイで使用する Compute Engine のデフォルトのサービス アカウントを作成します。

storage、logging、cloudtrace

ストレージ、ログ、トレース(Agent Runtime 設定ガイドで推奨されているとおり)

cloudbuild、artifactregistry

コンテナのビルドとイメージ(ステップ 7 のデプロイの予防措置として含まれています)

多くのプロジェクトでは、これらの機能の一部がすでに有効になっています。すでに有効になっている API を有効にしても、何も変更されません。

uv と agents-cli をインストールする

curl -LsSf https://astral.sh/uv/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"
uv tool install google-agents-cli==1.10.0
agents-cli --version

この Codelab は agents-cli 1.10.0 でテスト済みです。同じ生成ファイルを取得するには、このバージョンをインストールしてください。

3. 3. プロジェクトをスキャフォールディングしてアセットを追加する

両方のプロジェクトを生成する

agents-cli scaffold create は、デプロイ可能な完全な ADK プロジェクトを生成します。エージェント システムごとに 1 回ずつ、2 回実行します。

まず、トラック プランナーです。

cd ~
agents-cli scaffold create track-planner \
  --agent adk \
  --deployment-target agent_runtime \
  --region "$REGION" \
  --prototype \
  --yes

受付担当者の対応:

cd ~
agents-cli scaffold create gatekeeper \
  --agent adk \
  --deployment-target agent_runtime \
  --region "$REGION" \
  --prototype \
  --yes

フラグ

意味

--agent adk

Python ADK テンプレートから始める

--deployment-target agent_runtime

Agent Runtime のサーバーと構成を生成する

--prototype

CI/CD をスキップする。プロジェクトを最小限に抑える

--yes

プロンプトを表示せずにデフォルトを受け入れる

レイアウトを確認する

生成されたプロジェクトは次のようになります。編集するのは ✍️ マークが付いたファイルのみです。

track-planner/
├── agents-cli-manifest.yaml   # Records the template, deployment target and root agent name
├── pyproject.toml             # Dependencies (google-adk, a2a-sdk, ...)
├── .env                       # Your project and model location, filled in for you
├── Dockerfile                 # Used by `agents-cli deploy`; copies app/ into the image
├── app/
│   ├── agent.py               # ✍️ Your agents
│   ├── fast_api_app.py        # Generated server: ADK API, A2A routes, Agent Runtime routes
│   └── app_utils/             # Generated helpers (A2A, sessions)
└── tests/
    ├── unit/                  # Fast tests with no model calls
    ├── integration/           # Generated server tests
    └── eval/                  # ✍️ eval_config.yaml and datasets for `agents-cli eval`

生成された各ファイルの説明については、Agents CLI ガイドのプロジェクト構造をご覧ください。

生成されたエージェントを試す

変更を加える前に、テンプレート エージェントを実行して、スキャフォールドで何が得られるかを確認します。依存関係をインストールし、ポート 8080 でローカル プレイグラウンドを起動します。

cd ~/track-planner
agents-cli install
agents-cli playground --port 8080

これを開くには、Cloud Shell ツールバーの [ウェブでプレビュー] ボタン(右上の目のアイコン)をクリックし、[ポート 8080 でプレビュー] を選択します。

プレイグラウンドで、テンプレート エージェントに、ツールが回答できる質問をします。例:

What's the weather in San Francisco?

サイドパネルには、get_weather ツール呼び出しとその結果を含む各イベントが表示されます。このビューは、後で自分のエージェントをフォローする際にも使用します。

完了したら、ターミナルに戻り、Ctrl+C キーを押して Playground を停止します。

Formula E のアセットを追加する

会場データ、FIA 規則、物理スクリプト、ツール、評価データセットはレッスンではないため、入力するのではなく GitHub からコピーします。

cd ~
git clone --depth 1 --filter=blob:none --sparse \
  --branch formula-e-lab-v1 \
  https://github.com/tdsymonds/devrel-demos.git
git -C devrel-demos sparse-checkout set codelabs/formula-e-track-planner

cp -r "$ASSETS/track-planner/." ~/track-planner/
cp -r "$ASSETS/gatekeeper/." ~/gatekeeper/

formula-e-lab-v1 は固定のリリースラベルであるため、すべてのユーザーが同じファイルを取得します。$ASSETS はステップ 2 で設定したもので、クローンを作成したフォルダを指しています。

これにより、次のファイルが追加されます。

track-planner/
├── app/
│   ├── tools.py                                  # get_track_topology, evaluate_track_layout
│   ├── auth.py                                   # A2A client with per-request token refresh
│   ├── data/circuits.json                        # Five candidate venues
│   └── skills/fia-compliance-skill/
│       ├── references/track_regulations.md       # The FIA rules
│       └── scripts/calculate_regen_zones.py      # Braking-energy calculator
└── tests/
    ├── unit/test_tools.py
    └── eval/datasets/track_eval.json, track_eval_quick.json
gatekeeper/
├── app/tools.py                                  # verify_infrastructure_checklist
└── tests/unit/test_tools.py

インストールしてテストする

(cd ~/track-planner && agents-cli install && uv run pytest tests/unit)
(cd ~/gatekeeper && agents-cli install && uv run pytest tests/unit)

agents-cli install は uv sync を実行して、プロジェクトごとに仮想環境を作成します。すべての単体テストに合格する必要があります。

4. 4. FIA コンプライアンス スキルを記述する

エージェント スキルは、指示、参照ドキュメント、スクリプトを 1 つのフォルダにまとめたものです。ADK の SkillToolset は、3 つのレベルで読み込みを行うため、エージェントは必要な情報のみを読み取ります。

レベル

ファイル

エージェントがそれを見ると

1

SKILL.md front matter(name、description)

常に(数十トークン)

2

SKILL.md 本文

load_skill を呼び出す場合

3

references/、scripts/

load_skill_resource または run_skill_script を呼び出すとき

生成された Dockerfile は app/ のみをデプロイされたイメージにコピーするため、スキルは app/skills/ に存在します。

レベル 3 のファイルを確認する

ステップ 3 で、スキルのレベル 3 のファイルをコピーしました。

  • references/track_regulations.md には、査定者がチェックするルール(長さと幅の制限、各ブレーキゾーンに必要なバリア、必須の CAD レイヤー(病院ルートの医療レイヤーを含む)、回生目標、アタック モードのルール)が保持されます。
  • scripts/calculate_regen_zones.py は計算ツールです。ブレーキゾーンの進入速度、退出速度、ブレーキ距離を指定すると、回収されるエネルギーとコーナーに必要なバリアが計算されるため、エージェントが物理法則を推測する必要はありません。

ロンドンのターン 10 のスクリプトを実行します。ここでは、車は 82 メートルにわたって 215 km/h から 55 km/h に減速します。

cd ~/track-planner
uv run python app/skills/fia-compliance-skill/scripts/calculate_regen_zones.py \
  --entry-kph 215 --exit-kph 55 --brake-dist-m 82

この車両は、目標の 250 kW を上回る約 508 kW を回収しています。2G を超えると減速するため、コーナーには Tecpro バリア(F1_TEC3)が必要です。

{"entry_kph": 215.0, "exit_kph": 55.0, "brake_dist_m": 82.0, "deceleration_g": 2.07, "estimated_regen_kw": 507.7, "meets_250kw_target": true, "required_barrier_layer": "F1_TEC3"}

SKILL.md を書き込む

スキルをまとめるファイルを作成します。

cloudshell edit app/skills/fia-compliance-skill/SKILL.md

次の内容を追加します。

---
name: fia-compliance-skill
description: >
  FIA Formula E street circuit rules: length and width limits, mandatory CAD
  layers, barrier selection, and Gen3 regenerative braking estimates. Use when
  designing, checking or fixing a Formula E circuit layout.
---

# FIA Formula E Compliance

Follow these steps for every circuit you check.

1. **Read the rules.** Call `load_skill_resource` with
   `file_path="references/track_regulations.md"`.
2. **Estimate regeneration for every heavy braking zone.** Call
   `run_skill_script` with `file_path="scripts/calculate_regen_zones.py"` and
   `args={"entry-kph": <entry>, "exit-kph": <exit>, "brake-dist-m": <distance>}`.
   Never guess kW values. Use the highest `estimated_regen_kw` as the
   circuit's peak regeneration.
3. **Check barriers.** Any zone whose `required_barrier_layer` is `F1_TEC3`
   needs the `F1_TEC3` CAD layer.
4. **Report.** List every rule the layout fails and a concrete fix for each
   one.

description は、モデルがスキルを使用するタイミングを判断するために読み取るものです。そのため、スキルがカバーする内容と、スキルを使用するタイミングの両方が記述されています。

スキルの接続方法を確認する

Cloud Shell エディタで app/tools.py を開きます。

cloudshell edit app/tools.py

build_fia_skill_toolset 関数を見つけます。

def build_fia_skill_toolset() -> SkillToolset:
    return SkillToolset(
        skills=[load_skill_from_dir(SKILL_DIR)],
        code_executor=UnsafeLocalCodeExecutor(),
    )
  • load_skill_from_dir は、スキルフォルダ SKILL.md、references/、scripts/ を読み取ります。
  • SkillToolset は、エージェントが呼び出すことができるツール(list_skills、load_skill、load_skill_resource、run_skill_script)に変換します。
  • UnsafeLocalCodeExecutor を使用すると、run_skill_script はこのマシンで Python スクリプトを実行できます。ラボでは問題ありませんが、本番環境ではサンドボックス化されたエグゼキュータを使用してください。

このツールセットは、次のステップで Assessor にアタッチします。

5. 5. エージェントを記述する

Gemini 3 モデルは、トークン予算ではなく thinking_level(LOW、MEDIUM、HIGH)を使用して推論を制御します。ADK では、types.ThinkingConfig(thinking_level=types.ThinkingLevel.HIGH) を使用して設定します。各エージェントのジョブに合わせます。思考を増やすと、計画は改善されますが、レイテンシとコストが増加します。

ゲートキーパー(LOW)

Gatekeeper は決定論的なチェックリストのみを実行するため、推論はほとんど必要ありません。テンプレート エージェントを置き換えます。

cloudshell edit ~/gatekeeper/app/agent.py
"""FIA Digital Twin Gatekeeper agent.

This agent runs in its own project and is deployed to Agent Runtime. The track
planner calls it over the A2A protocol.
"""

from google.adk.agents import Agent
from google.adk.apps import App
from google.adk.models import Gemini
from google.genai import types

from app.tools import verify_infrastructure_checklist

# Tested with Gemini 3.8 Flash. To try Gemini 4 Argon, change this to
# "gemini-4-argon", delete the `temperature` settings below (Argon ignores
# them) and run `uv add "google-genai>=2.30.1"` in this project first.
MODEL = "gemini-3.8-flash"

root_agent = Agent(
    # Must match `root_agent_name` in agents-cli-manifest.yaml.
    name="gatekeeper",
    model=Gemini(
        model=MODEL,
        retry_options=types.HttpRetryOptions(attempts=3),
    ),
    description=(
        "FIA Digital Twin Gatekeeper. Runs the final homologation checklist "
        "for a Formula E circuit blueprint."
    ),
    instruction=(
        "You are the FIA Digital Twin Gatekeeper. For every circuit blueprint "
        "you receive, call `verify_infrastructure_checklist` with its circuit "
        "ID, CAD layers, minimum width and peak regeneration. Reply with the "
        "status and any missing CAD layers. Never approve a blueprint the "
        "checklist rejects."
    ),
    # A deterministic checklist needs little reasoning: keep it fast and cheap.
    generate_content_config=types.GenerateContentConfig(
        temperature=0.0,
        thinking_config=types.ThinkingConfig(
            thinking_level=types.ThinkingLevel.LOW,
        ),
    ),
    tools=[verify_infrastructure_checklist],
)

app = App(
    root_agent=root_agent,
    name="app",
)

Gatekeeper をローカルで実行する

2 つ目の Cloud Shell タブを開き、ポート 8001 で Gatekeeper の生成されたサーバーを起動します。

cd ~/gatekeeper
APP_URL=http://localhost:8001 uv run uvicorn app.fast_api_app:app --port 8001

最初のタブに戻り、ゲートキーパーの A2A エージェント カードを確認します。他のエージェントは、次のようにして Gatekeeper の概要と機能を検出します。

curl -s http://localhost:8001/a2a/app/.well-known/agent-card.json | jq .

次のような JSON が表示されます(短縮版)。

{
  "name": "gatekeeper",
  "description": "FIA Digital Twin Gatekeeper. Runs the final homologation checklist for a Formula E circuit blueprint.",
  "supportedInterfaces": [
    {"url": "http://localhost:8001/a2a/app", "protocolBinding": "JSONRPC", "protocolVersion": "1.0"}
  ],
  "skills": [
    {"id": "gatekeeper", "name": "model", "description": "FIA Digital Twin Gatekeeper. ..."},
    {"id": "gatekeeper-verify_infrastructure_checklist", "name": "verify_infrastructure_checklist", "description": "Runs the final homologation checklist ..."}
  ]
}
  • name と description は、Agent(...) 定義から直接取得されます。
  • skills には、エージェントが実行できることが一覧表示されます。ADK は、ツールの docstring を使用して verify_infrastructure_checklist を自動的に追加しました。
  • supportedInterfaces は、A2A リクエストの送信先を呼び出し元に指示します。そのため、APP_URL=http://localhost:8001 を使用してサーバーを起動しました。呼び出し元はまずカードを読み取り、カードがアドバタイズする url にリクエストを送信します。

ここで手動で記述されたものは何もありません。スキャフォールディングされたサーバーがエージェントからカードをビルドします。トラック プランナーは、このカードを読み取ってゲートキーパーを見つけ、呼び出します。

評価者(MEDIUM)とリード アーキテクト(HIGH)

トラック プランナーには、次の 2 つのエージェントがあります。

  • Assessor は複数のルールを評価するため、MEDIUM を取得します。これは AgentTool でラップされているため、リード アーキテクトは関数のように呼び出して判定結果を取得します。
  • リード アーキテクトは、計画、修正、調整を行い、HIGH を獲得します。app/auth.py の create_remote_gatekeeper_agent() によって作成された RemoteA2aAgent を介して Gatekeeper に到達し、AgentTool でラップします。

両方のヘルパーをツールとしてラップする理由AgentTool は呼び出し元に回答を返すため、リード アーキテクトが引き続き担当し、最終的な設計図を作成します。Gatekeeper が sub_agent の場合、Lead Architect は会話を Gatekeeper に引き渡し、Gatekeeper の短いステータスが最終的な回答になります。

トラック プランナーの app/agent.py ファイルを開きます。

cloudshell edit ~/track-planner/app/agent.py

次のコードをコピーして、~/track-planner/app/agent.py の内容全体を置き換えます。

"""Formula E track planner: Lead Architect, FIA Assessor and remote Gatekeeper.

- The Lead Architect (root agent) designs the circuit and stays in charge.
- The FIA Assessor checks the layout and is called as a tool (`AgentTool`).
- The Gatekeeper runs in a separate project. It is also called as a tool, over
  A2A, so its verdict comes back to the Lead Architect.
"""

from google.adk.agents import Agent
from google.adk.apps import App
from google.adk.models import Gemini
from google.adk.tools.agent_tool import AgentTool
from google.genai import types

from app.auth import create_remote_gatekeeper_agent
from app.tools import build_fia_skill_toolset
from app.tools import evaluate_track_layout
from app.tools import get_track_topology

# Tested with Gemini 3.8 Flash. To try Gemini 4 Argon, change this to
# "gemini-4-argon", delete the `temperature` settings below (Argon ignores
# them) and run `uv add "google-genai>=2.30.1"` in this project first.
MODEL = "gemini-3.8-flash"

fia_skill_toolset = build_fia_skill_toolset()

# 1. FIA Assessor: MEDIUM thinking to weigh several rules without much latency.
assessor_agent = Agent(
    name="fia_assessor",
    model=Gemini(
        model=MODEL,
        retry_options=types.HttpRetryOptions(attempts=3),
    ),
    description=(
        "FIA safety and energy assessor. Audits a proposed circuit against "
        "the geometry, regeneration and CAD layer rules."
    ),
    instruction=(
        "You are the FIA Assessor. Load the `fia-compliance-skill` and follow "
        "it. Estimate regeneration for each braking zone with "
        "`calculate_regen_zones.py`, then call `evaluate_track_layout` with "
        "the highest value. Reply with the verdict, the score and every "
        "remediation note."
    ),
    generate_content_config=types.GenerateContentConfig(
        temperature=0.1,
        thinking_config=types.ThinkingConfig(
            thinking_level=types.ThinkingLevel.MEDIUM,
        ),
    ),
    tools=[fia_skill_toolset, evaluate_track_layout],
)

# 2. Remote Gatekeeper, reached over A2A (see app/auth.py).
remote_gatekeeper = create_remote_gatekeeper_agent()

# 3. Lead Architect: HIGH thinking for multi-step planning and trade-offs.
root_agent = Agent(
    # Must match `root_agent_name` in agents-cli-manifest.yaml.
    name="track_planner",
    model=Gemini(
        model=MODEL,
        retry_options=types.HttpRetryOptions(attempts=3),
    ),
    description=(
        "Lead Formula E circuit architect. Designs FIA-compliant street "
        "circuits and gets them homologated."
    ),
    instruction=(
        "You are the Lead Formula E Circuit Architect. For every request:\n"
        "1. Call `get_track_topology` for the venue.\n"
        "2. Call the `fia_assessor` tool with the full topology.\n"
        "3. If the Assessor rejects the layout, propose concrete fixes (for "
        "example widen a sector to 10.0m or add missing CAD layers) and "
        "re-assess the fixed layout.\n"
        "4. Once the Assessor passes it, call the `remote_gatekeeper` tool "
        "with the circuit ID, CAD layers, minimum width and peak "
        "regeneration for final approval.\n"
        "5. Finish with a short blueprint: circuit ID, length, minimum width, "
        "peak regeneration, every CAD layer by name, and the Gatekeeper's "
        "status."
    ),
    generate_content_config=types.GenerateContentConfig(
        temperature=0.2,
        thinking_config=types.ThinkingConfig(
            thinking_level=types.ThinkingLevel.HIGH,
        ),
    ),
    # Both helpers are tools, so their answers come back to the Lead Architect
    # and it writes the final blueprint.
    tools=[
        get_track_topology,
        AgentTool(agent=assessor_agent),
        AgentTool(agent=remote_gatekeeper),
    ],
)

app = App(
    root_agent=root_agent,
    name="app",
)

プレイグラウンドでシステム全体を実行する

2 つ目のタブで Gatekeeper が実行されていることを確認します。最初のタブで、トラック プランナーのプレイグラウンドを起動します。

cd ~/track-planner
agents-cli playground --port 8080

手順 3 と同様に、[ウェブでプレビュー] > [ポート 8080 でプレビュー] をクリックします。

ルールを満たす会場のサーキットをリクエストします。

Design and homologate a Formula E circuit at Tokyo Bay.

エージェントが作業している間、イベントパネルを監視します。次の順序で表示されます。

  1. get_track_topology: リード アーキテクトが東京湾のデータを読み込みます。
  2. fia_assessor: リード アーキテクトがレイアウトを評価者に渡します。評価者は load_skill、load_skill_resource、run_skill_script(ブレーキゾーンごとに 1 回)、evaluate_track_layout を呼び出します。
  3. remote_gatekeeper: リード アーキテクトが A2A 経由でゲートキーパーを呼び出します。2 つ目のタブにリクエストが記録されていることがわかります。
  4. リード アーキテクトが作成した最終的な設計図。すべての CAD レイヤと、ゲートキーパーの APPROVED ステータスが記載されています。

イベントをクリックすると、その正確な入力と出力が表示されます。

次に、新しいセッションを開始して、ルールに違反する会場を試します。

Can the Monaco Harbour layout be homologated? If not, fix it and try again.

今回は、評価者が 6.8 メートルのシケインを却下し、リード アーキテクトがより広いシケインを提案します。修正されたレイアウトは、ゲートキーパーに届く前に評価者に戻されます。

完了したら、最初のタブで Ctrl+C キーを押して、プレイグラウンドを停止します。2 つ目のタブで Gatekeeper を実行したままにします。これは次のステップで必要になります。

6. 6. エージェントを評価する

agents-cli eval run は、データセットに対してエージェントを実行し、すべてのターンとツール呼び出しを記録して、結果を評価します。tests/eval/eval_config.yaml で測定する対象を決定します。

データセットを確認する

Cloud Shell エディタでデータセットを開きます。

cd ~/track-planner
cloudshell edit tests/eval/datasets/track_eval.json

このファイルには eval_cases のリストが格納されます。各ケースには、eval_case_id、prompt(ユーザーの質問)、reference(適切な回答の例)があります。LLM 判定モデルは、エージェントの回答を基準と比較します。

eval_case_id

テスト内容

fe_001_tokyo_compliant

すべてのルールに合格して承認された会場

fe_002_monaco_narrow

6.8 m のシケインは幅のルールに違反

fe_003_berlin_too_long

4,350 m の回路は長さのルールに違反している

fe_004_missing_layers

必須の CAD レイヤが欠落しているレイアウト

fe_005_low_regen

目標の 250 kW を下回るブレーキゾーン

fe_006_sao_paulo_medical

医療用 CAD レイヤを必要とする病院アクセス

fe_007_attack_mode

レーシング ライン上のアタックモード ゾーン

fe_008_multiturn_remediation

マルチターンの会話: モナコが不承認となり、ユーザーが修正を求め、修正されたレイアウトが承認される

最後のケースでは、prompt ではなく agent_data を使用しています。会話の前のターンを再生するため、単一の質問だけでなく、エージェントがフォローアップ リクエストをどのように処理するかをテストできます。

同じフォルダ内の track_eval_quick.json には、ケース 1(1 つの質問)とケース 8(会話)の 2 つのケースのみが含まれています。このクエリは、より高速で、使用する割り当てが少ないため、こちらを実行します。

指標を書き込む

データセットには、エージェントに何を尋ねるかが記載されています。eval_config.yaml には、回答を採点する方法が記載されています。agents-cli eval run は、エージェントがすべてのケースに回答した後にこれを読み取り、各回答に対して各指標を実行します。

スキャフォールドによって生成されたファイルを開きます。

cloudshell edit tests/eval/eval_config.yaml

これには、一般的な LLM 判定(custom_response_quality)と、ターンのみをカウントする agent_turn_count 指標がすでに含まれています。これはエージェントの開始としては問題ありませんが、フォーミュラ E については何も言及していません。ジャッジはそのままにして、このドメインに固有のルールをチェックする指標を追加します。

通常、適切な評価には次の 2 種類の指標が混在しています。

種類

スコアの算出方法

用途

トレードオフ

LLM-as-a-judge

モデルが回答とケースの reference を読み取り、1 ~ 5 のスコアと説明を返します。

ファジー品質: 回答は正確、完全、明確か?

ケースごとにモデル呼び出しの費用が発生。実行ごとにスコアが若干異なる場合がある

コード指標

小さな Python 関数が回答をチェックする

正確で検証可能な事実: 回答に、必ず含まれているべき情報が含まれていましたか。

コードで表現できることのみをチェックする

ファイルの内容を次のように置き換えます。

metrics_to_run:
  - custom_response_quality
  - fia_cad_layer_compliance

custom_metrics:
  # LLM-as-a-judge. response_quality.py is generated by `agents-cli scaffold
  # create`; it grades the final answer against the case's `reference`.
  - name: custom_response_quality
    custom_function_file: response_quality.py

  # Deterministic code metric: an approved blueprint must name every
  # mandatory FIA CAD layer.
  - name: fia_cad_layer_compliance
    custom_function: |
      def evaluate(instance):
          response = str(instance.get("response") or "")
          reference = str(instance.get("reference") or "")
          # Only approvals must list every layer. Rejections and other
          # questions (such as Attack Mode) don't, so skip them.
          if "APPROVED" not in reference:
              return {"score": 1.0, "explanation": "Not an approval case."}
          required = [
              "F1_TRACK_CENTRELINE",
              "F1_PIT_ENTRY_SPEED_LIMIT_LANE",
              "F1_WALL_TEMP",
              "F1_TEC3",
          ]
          missing = [layer for layer in required if layer not in response]
          return {
              "score": round(1 - len(missing) / len(required), 2),
              "explanation": "Missing: " + (", ".join(missing) or "none"),
          }

各部分の役割は次のとおりです。

  • metrics_to_run には、実行する指標を名前で指定します。ここで名前を削除すると、その指標は削除されずにオフになります。
  • custom_response_quality は LLM 判定モデルです。custom_function_file は、スキャフォールドによって生成された tests/eval/response_quality.py を指しています。判定プロンプトを表示する場合は、これを開きます。
  • fia_cad_layer_compliance はコード指標で、custom_function の下にインラインで記述されます。各ケースで、instance(プロンプト、エージェントの最終的な response、reference)を受け取ります。期待される回答が承認であるケースでは、回答に 4 つの必須 CAD レイヤがすべて含まれていることを確認し、不足しているレイヤをリストします。たとえば、F1_TEC3 を除外したブループリントのスコアは 0.75 です。その他のケースは 1.0 点です。拒否や攻撃モードの回答では、すべてのレイヤを列挙する必要がないためです。

CAD レイヤを確認する理由承認済みのブループリントに必須のレイヤが欠けている場合、読みやすくても不適切であり、LLM 判定者はそれを見落とす可能性があります。コードチェックでは、実行するたびに同じルールが同じ方法で適用されます。

評価を実行する

2 つ目のタブでローカルの Gatekeeper を実行したまま、次のコマンドを実行します。

agents-cli eval run \
  --dataset tests/eval/datasets/track_eval_quick.json \
  --config tests/eval/eval_config.yaml \
  --concurrency 1
ls artifacts/grade_results/

評価には数分かかります。各ケースでは、マルチエージェント フロー全体が実行されます。HIGH の思考に関するリード アーキテクトとのモデル呼び出しが約 10 回行われるため、Step 1/2: eval generate が遅い部分です。スタックしていない。

完了したら、Cloud Shell エディタを使用して artifacts/grade_results/ で HTML レポートを開きます。fia_cad_layer_compliance スコアが低い場合は、リード アーキテクトの最終回答にレイヤが欠落していることを示しています。指示を厳密にして、評価を再度実行してみてください。

7. 7. Gatekeeper を Agent Runtime にデプロイする

Gatekeeper のみをデプロイします。トラック プランナーは Cloud Shell で実行され、それを呼び出します。

新しい Cloud Shell タブを開く

デプロイには 5 ~ 8 分かかるため、ステップ 6 の評価が完了する間に新しいタブでデプロイします。

  1. Cloud Shell ツールバーの [新しいタブを開く](+)をクリックします。
  2. 新しいタブではステップ 2 の変数が保持されないため、再度設定します。
    export GOOGLE_CLOUD_PROJECT="$(gcloud config get-value project)"
    export REGION="us-central1"
    export ASSETS="$HOME/devrel-demos/codelabs/formula-e-track-planner"
    export PATH="$HOME/.local/bin:$PATH"
    echo "Project: $GOOGLE_CLOUD_PROJECT  Region: $REGION"
    

2 つ目のタブでローカルの Gatekeeper を実行したままにします。評価では引き続き Gatekeeper が使用されます。デプロイでは Gatekeeper を停止する必要はありません。

デプロイ

新しいタブで、Gatekeeper をデプロイします。プロジェクトの Compute Engine のデフォルト サービス アカウントとして実行されます。

cd ~/gatekeeper
PROJECT_NUMBER=$(gcloud projects describe "$GOOGLE_CLOUD_PROJECT" --format="value(projectNumber)")
agents-cli deploy \
  --no-agent-identity \
  --service-account "${PROJECT_NUMBER}-compute@developer.gserviceaccount.com" \
  --project "$GOOGLE_CLOUD_PROJECT" \
  --region "$REGION" \
  --no-confirm-project

デプロイには 5 ~ 8 分ほどかかります。実行中は Cloud Shell タブを閉じないでください。ログストリームが切断された場合は、agents-cli deploy --status で進行状況を確認します。待っている間に、ステップ 6 に戻って評価レポートを読んでください。

エージェントの ID を確認する

agents-cli は、デプロイされたリソースを deployment_metadata.json に記録します。

export RUNTIME_ID=$(jq -r .remote_agent_runtime_id ~/gatekeeper/deployment_metadata.json)
echo "$RUNTIME_ID"

curl -s -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  "https://${REGION}-aiplatform.googleapis.com/v1beta1/${RUNTIME_ID}" \
  | jq -r .spec.effectiveIdentity

出力の 2 行目は、Gatekeeper が実行される ID です。

デプロイに使用:

出力例

サービス アカウント(デフォルト)

123456789012-compute@developer.gserviceaccount.com

Agent Identity

agents.global.org-.system.id.goog/resources/aiplatform/projects//locations/us-central1/reasoningEngines/

サービス アカウントは、それを使用するプロジェクト内のすべてのリソースで共有されます。エージェント ID はこの 1 つのエージェントに属しているため、必要なロールを正確に付与し、監査ログでそのアクションを個別に確認できます。

8. 8. A2A 経由でデプロイされた Gatekeeper に接続する

Agent Runtime は、認証済みの /api パスの背後で Gatekeeper が生成した A2A ルートを提供します。

ステップ 6 の評価が完了していることを確認し、2 つ目のタブでローカル Gatekeeper を停止します(Ctrl+C)。今後は、トラック プランナーがデプロイされたものを呼び出します。最初のタブで、トラック プランナーをその場所に向ける。

export RUNTIME_ID=$(jq -r .remote_agent_runtime_id ~/gatekeeper/deployment_metadata.json)
: "${RUNTIME_ID:?No deployed Gatekeeper found. Complete Step 7 first.}"
cd ~/track-planner
echo "GATEKEEPER_A2A_URL=https://${REGION}-aiplatform.googleapis.com/reasoningEngines/v1/${RUNTIME_ID}/api/a2a/app" >> .env

クライアントがトークンを更新する理由

app/auth.py を開きます。Google Cloud アクセス トークンは 1 時間後に期限切れになるため、起動時に固定の Authorization ヘッダーを設定するクライアントは、1 時間後に 401 Unauthorized で失敗し始めます。GoogleAuthRefresh は httpx.Auth のサブクラスであり、リクエストごとに認証情報を確認し、必要に応じて更新します。create_remote_gatekeeper_agent() は、https:// URL にのみ添付します。

エンドツーエンドで実行する

プレイグラウンドを再起動して、新しい GATEKEEPER_A2A_URL を取得します。

agents-cli playground --port 8080

[ウェブでプレビュー] > [ポート 8080 でプレビュー] をクリックし、新しいセッションを開始して、次のように質問します。

Assess the Monaco Harbour layout, widen the chicane to 11m, and get Gatekeeper approval.

イベントはステップ 5 と同じように見えますが、remote_gatekeeper 呼び出しは 2 番目のタブではなく、Agent Runtime の Gatekeeper に送信されます。

Agent Runtime で通話の着信を確認する

リクエストが A2A 経由でデプロイされた Gatekeeper に実際に到達したことを証明します。

  1. Cloud コンソールで「Agent Platform」を検索し、左側のメニューで [デプロイ] を開きます。
  2. [gatekeeper] を選択します。
  3. [セッション] タブを開きます。

ユーザー ID が A2A_USER_ で始まる新しいセッションが表示されます。これは、トラック プランナーが Gatekeeper の A2A サーバーを呼び出したときに作成されたものです。同じページの [トレース] タブを開いて、Gatekeeper のモデル呼び出しと、そのリクエストに対する verify_infrastructure_checklist ツール呼び出しを確認します。

省略可: トラック プランナーもデプロイする

これまでのところ、トラック プランナーは Cloud Shell で実行されているため、Gatekeeper への A2A 呼び出しではユーザーの認証情報が使用されます。プランナーもデプロイし、両方のエージェントが Agent Runtime で実行され、デプロイされた独自の ID で相互に呼び出します。

Ctrl+C キーを押して Playground を停止し、Gatekeeper と同じ方法でプランナーをデプロイします。

cd ~/track-planner
PROJECT_NUMBER=$(gcloud projects describe "$GOOGLE_CLOUD_PROJECT" --format="value(projectNumber)")
agents-cli deploy \
  --no-agent-identity \
  --service-account "${PROJECT_NUMBER}-compute@developer.gserviceaccount.com" \
  --project "$GOOGLE_CLOUD_PROJECT" \
  --region "$REGION" \
  --no-confirm-project

両方のエージェントは、同じ Compute Engine サービス アカウントとして実行されます。--service-account はそのままにします。これがないと、プランナーは Agent Runtime のデフォルトのサービス エージェントとして実行され、Gatekeeper を呼び出すことができません(ステップ 7 のこのサービス アカウントを使用する理由をご覧ください)。

デプロイされたプランナーが Gatekeeper にアクセスする方法:

  • agents-cli deploy は .env の値をデプロイにコピーするため、プランナーは上記で追加した GATEKEEPER_A2A_URL を取得します。
  • Agent Runtime 内では、app/auth.py の GoogleAuthRefresh が、ユーザーの ID ではなく、プランナーのデプロイされた ID を取得します。

デプロイには 5 ~ 8 分ほどかかります。完了したら、プランナーがどの ID で実行されているかを確認します。

export PLANNER_ID=$(jq -r .remote_agent_runtime_id deployment_metadata.json)
curl -s -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  "https://${REGION}-aiplatform.googleapis.com/v1beta1/${PLANNER_ID}" \
  | jq -r .spec.effectiveIdentity

デプロイされたシステムを試すには:

  1. Cloud コンソールで、[Agent Platform] > [デプロイ] に戻ります。
  2. [track-planner] を選択し、[Playground] タブを開きます。
  3. 次のように入力します。
    Design and homologate a Formula E circuit at Tokyo Bay.
    
  4. Gatekeeper の APPROVED ステータスを持つブループリントを待ちます。
  5. [Deployments] に戻り、[gatekeeper] を選択して [Sessions] タブを開きます。最新の A2A_USER_ セッションは、Cloud Shell ではなく、デプロイされたプランナーから取得されています。

次のステップでクリーンアップを行う際に、プランナーも削除します。

curl -X DELETE -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  "https://${REGION}-aiplatform.googleapis.com/v1beta1/${PLANNER_ID}?force=true"

9. 9. トラブルシューティングとクリーンアップ

トラブルシューティング

症状

修正

UREQ_TOS_NOT_ACCEPTED

Cloud コンソールで利用規約に同意してから、もう一度お試しください。

プロジェクトは (unset) です

ステップ 2 のプロジェクト コマンドを再実行します。

service account info is missing 'email' field

gcloud auth application-default login(ステップ 2)を実行してから、実行中のサーバーを再起動します。

FileNotFoundError(SKILL.md)

ステップ 4 を完了するか、$ASSETS/solution/track-planner/. を ~/track-planner/ にコピーします。

Connection refused から localhost:8001

ローカル Gatekeeper を起動するか(ステップ 5)、GATEKEEPER_A2A_URL をデプロイ済みの Gatekeeper に設定します(ステップ 8)。

デプロイ時の Permission 'resourcemanager.projects.setIamPolicy' denied

お使いのアカウントではエージェント ID を使用できません。ステップ 7 のクリーンアップ ボックスに沿って操作し、サービス アカウントのデプロイを使用します。

デプロイされたトラック プランナーが Gatekeeper から 403 Forbidden を報告する

プランナーの ID には、Gatekeeper を呼び出す権限がありません。ステップ 7 のように --service-account を使用して再デプロイするか、プロジェクトを所有している場合は --agent-identity を使用して再デプロイします。

429 RESOURCE_EXHAUSTED

Gemini の割り当てに達しました。しばらく待つか、--concurrency 1 --qps 2 を agents-cli eval run に追加してください。

Address already in use

他のサーバーを停止するか、別の --port を選択します。

クリーンアップ

デプロイされた Gatekeeper を削除して、実行を継続しないようにします。

export RUNTIME_ID=$(jq -r .remote_agent_runtime_id ~/gatekeeper/deployment_metadata.json)
: "${RUNTIME_ID:?No deployed Gatekeeper found.}"
curl -X DELETE -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  "https://${REGION}-aiplatform.googleapis.com/v1beta1/${RUNTIME_ID}?force=true"

この Codelab 用にプロジェクトを作成した場合は、プロジェクト全体を削除することもできます。イベントに参加している場合は、主催者から指示がない限り、割り当てられたプロジェクトをそのまま使用してください。

学習した内容

  • agents-cli scaffold create は、デプロイ可能な標準の ADK プロジェクトを提供します。エージェント、スキル、評価を記述するだけで済みます。
  • エージェント スキルは、エージェントが必要とするまで、ドメイン知識をプロンプトから除外します。
  • thinking_level を使用すると、エージェントごとに推論の深さと速度をトレードオフできます。
  • agents-cli eval run は、「機能しているか」を追跡可能なスコアに変換します。
  • エージェント ID と A2A により、独立してデプロイされたエージェントが安全に連携できます。

詳細