יצירת מתכנן מסלולי פורמולה E מרובה-סוכנים באמצעות ADK ו-A2A

1. 1. סקירה כללית

ב-Codelab הזה תבנו מערכת מרובת סוכנים שמתכננת מסלול רחוב של פורמולה E ומקבלת אישור בהתאם לכללים פשוטים של FIA.

תשתמשו ב-agents-cli כדי ליצור שני פרויקטים רגילים של ערכה לפיתוח סוכנים (ADK), ואז תכתבו את החלקים החשובים: מיומנות של סוכן, שלושה סוכנים והערכה. כל השאר (פריסת הפרויקט, השרת, יחסי התלות, הגדרת הפריסה) מגיע מה-CLI.

חדשים בפורמולה E?

אתם לא צריכים להכיר את עולם הספורט המוטורי. חמשת המונחים האלה מספיקים:

  • ‫פורמולה E: אליפות המרוצים של FIA למכוניות חשמליות. רוב המרוצים מתקיימים במסלולי רחוב זמניים במרכזי ערים.
  • אישור רשמי: האישור הרשמי של ה-FIA לכך שמסלול מסוים בטוח למרוצים. בלי הומולוגציה, אין מרוץ.
  • בלימה רגנרטיבית: כשהרכב בולם, המנועים שלו טוענים את הסוללה. בלימה חזקה יותר מחזירה יותר אנרגיה, ולכן הכללים של ה-codelab הזה דורשים לפחות אזור אחד של בלימה חזקה.
  • שכבות CAD: שכבות עם שמות בשרטוטי התכנון של המסלול, כמו קו המרוץ, רחבת הטיפולים והמחסומים. ה-FIA בודק שהשדות הנדרשים קיימים.
  • מצב התקפה: הנהגים מקבלים תוספת כוח זמנית כשהם עוברים באזור הפעלה שנמצא מחוץ למסלול המרוצים.

הסוכנים שלכם משחקים בשני הצדדים: אחד מתכנן את המסלול, ושניים בודקים אותו עבור ה-FIA.

מה תפַתחו

ארכיטקטורה: בפרויקט track-planner, שפועל ב-Cloud Shell, הסוכן track_planner משתמש ב-get_track_topology, ב-fia_assessor וב-remote_gatekeeper. הסוכן remote_gatekeeper קורא לסוכן gatekeeper דרך A2A. בפרויקט של שומר הסף, שפועל ב-Agent Runtime, הסוכן של שומר הסף משתמש ב-verify_infrastructure_checklist.

התיבות הירוקות הן נציגים והתיבות המקווקוות הן הכלים שלהם, באותו אופן שבו הם מוצגים ב-ADK Playground. שניים מהכלים כוללים סוכנים אחרים: fia_assessor הוא סוכן FIA Assessor עם מיומנות הציות ל-FIA, ו-remote_gatekeeper קורא לסוכן Gatekeeper בפרויקט השני דרך A2A.

סוכן

פרויקט

משרה

thinking_level

Lead Architect

track-planner

קורא את המקום, שואל את המעריך, פותר בעיות, מבקש אישור מהשומר

HIGH

מעריך FIA

track-planner

בודק את הפריסה באמצעות המיומנות של FIA וכלי ניקוד

MEDIUM

שומר הסף של התאום הדיגיטלי

gatekeeper

מריץ את רשימת המשימות הסופית לאישור

LOW

מה תלמדו

  • איך agents-cli scaffold create מסדר פרויקט ADK, ואילו קבצים אתם עורכים.
  • איך לארוז ידע בתחום מסוים בתור מיומנות של סוכן (SKILL.md, references/, scripts/) ולטעון אותו באמצעות SkillToolset של ADK.
  • איך להתאים את thinking_level לתפקיד של כל נציג.
  • איך מעריכים סוכנים באמצעות agents-cli eval run.
  • איך פורסים נציג ב-Agent Runtime, מה ההבדל בין זהות הנציג לחשבון שירות ואיך קוראים לנציג שנפרס באמצעות פרוטוקול 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 שהנציגים שלך מריצים משתמש בApplication Default Credentials ‏ (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

יוצר את חשבון השירות שמוגדר כברירת מחדל ב-Compute Engine, שבו משתמשים לפריסה בשלב 7.

storage,‏ logging,‏ cloudtrace

אחסון, יומנים ומעקבים, כמו שמומלץ במדריך להגדרת Agent Runtime

cloudbuild, artifactregistry

קובצי אימג' ו-builds של קונטיינרים, שנכללים כאמצעי זהירות לפריסה בשלב 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 מלא שאפשר לפרוס. מריצים אותו פעמיים, פעם אחת לכל מערכת של נציג.

קודם כל, מתכנן המסלולים:

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

כדי לפתוח אותו, לוחצים על הלחצן Web Preview בסרגל הכלים של Cloud Shell (סמל העין בפינה הימנית העליונה) ובוחרים באפשרות Preview on port 8080.

ב-Playground, שואלים את הסוכן שאלה שהכלים שלו יכולים לענות עליה, לדוגמה:

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

מיומנות של Agent כוללת הוראות, מסמכי עזר וסקריפטים בתיקייה אחת. ה-SkillToolset של ADK טוען את הנתונים בשלוש רמות, כך שהסוכן קורא רק את מה שהוא צריך:

רמה

קובץ

מתי נציג התמיכה רואה את זה

1

SKILL.md עמודי פתיחה (name, description)

תמיד (כמה עשרות טוקנים)

2

גוף SKILL.md

כשהוא מתקשר אל load_skill

3

references/, scripts/

כשהוא מתקשר אל load_skill_resource או run_skill_script

היכולת נמצאת ב-app/skills/ כי ה-Dockerfile שנוצר מעתיק רק את app/ לתמונה שנפרסה.

עיון בקבצים ברמה 3

העתקתם את הקבצים של רמה 3 של הסקיל בשלב 3:

  • ב-references/track_regulations.md נמצאים הכללים שה-Assessor בודק: מגבלות האורך והרוחב, המחסום שנדרש בכל אזור בלימה, שכבות ה-CAD שחובה להוסיף (בנוסף לשכבות הרפואיות של מסלולי בתי החולים), יעד ההתחדשות וכללי מצב ההתקפה.
  • ‫scripts/calculate_regen_zones.py הוא מחשבון. הוא מקבל את מהירות הכניסה, מהירות היציאה ומרחק הבלימה של אזור הבלימה, ומחשב את האנרגיה שמוחזרת ואת המחסום שהפנייה צריכה, כך שהסוכן אף פעם לא צריך לנחש את הפיזיקה.

מריצים את הסקריפט לפנייה 10 בלונדון, שבה מכוניות בולמות מ-215 ל-55 קמ"ש לאורך 82 מטרים:

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

הרכב מחזיר כאן כ-508 קילוואט, מעל היעד של 250 קילוואט. הוא מאט ביותר מ-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 הוא מה שהמודל קורא כדי להחליט מתי להשתמש במיומנות, אז הוא אומר גם מה המיומנות מכסה וגם מתי להשתמש בה.

איך המיומנות מחוברת

פותחים את app/tools.py ב-Cloud Shell Editor:

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 במכונה הזו. זה בסדר למעבדה, אבל בסביבת ייצור צריך להשתמש ב-executor בארגז חול.

בשלב הבא תצרפו את ערכת הכלים הזו ל-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 באופן מקומי

פותחים כרטיסייה שנייה ב-Cloud Shell ומפעילים את השרת שנוצר על ידי Gatekeeper ביציאה 8001:

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

חוזרים לכרטיסייה הראשונה ומסתכלים על כרטיס הסוכן של Gatekeeper. ככה סוכנים אחרים מגלים מהו ה-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 הוסיף את verify_infrastructure_checklist באופן אוטומטי, באמצעות ה-docstring של הכלי.
  • supportedInterfaces מציין למתקשרים לאן לשלוח בקשות A2A. לכן הפעלתם את השרת עם APP_URL=http://localhost:8001: המתקשר קורא קודם את הכרטיס, ואז שולח את הבקשות שלו לכל url שהכרטיס מפרסם.

שום דבר כאן לא נכתב ידנית: השרת שנוצר מהתבנית בונה את הכרטיס מהסוכן שלכם. המתכנן של המסלול יקרא את הקלף הזה כדי למצוא את השומר ולקרוא לו.

המעריך (MEDIUM) והאדריכל הראשי (HIGH)

לכלי לתכנון מסלולים יש שני סוכנים:

  • המעריך שוקל כמה כללים, ולכן הוא מקבל MEDIUM. היא עטופה ב-AgentTool, כך שהאדריכל הראשי קורא לה כמו פונקציה ומקבל את התוצאה.
  • האדריכל הראשי מתכנן, מתקן ומתאם, כדי שהפרויקט יקבל HIGH. הוא מגיע אל Gatekeeper דרך RemoteA2aAgent, שנוצר על ידי create_remote_gatekeeper_agent() ב-app/auth.py, וגם עוטף אותו ב-AgentTool.

למה לעטוף את שני הכלים? AgentTool מחזיר את התשובה שלו לפונקציית הקריאה החוזרת (caller), כך שהאדריכל הראשי נשאר אחראי וכותב את התוכנית הסופית. אם ה-Gatekeeper היה sub_agent, ה-Lead Architect היה מעביר אליו את השיחה, והסטטוס הקצר של ה-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",
)

הפעלת המערכת כולה ב-Playground

מוודאים שה-Gatekeeper עדיין פועל בכרטיסייה השנייה. בכרטיסייה הראשונה, מפעילים את סביבת הניסויים של מתכנן המסלולים:

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

לוחצים על Web Preview ‏ > Preview on port 8080, כמו שעשיתם בשלב 3.

בקשו לולאת השראה במקום שעומד בכללים:

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 (פעם אחת לכל אזור בלימה), ואז ל-evaluate_track_layout.
  3. ‫remote_gatekeeper: האדריכל הראשי מתקשר למסנן השיחה באמצעות A2A. הבקשה תופיע בכרטיסייה השנייה.
  4. תוכנית סופית מהאדריכל הראשי, שבה מפורטות כל שכבות ה-CAD וסטטוס APPROVED (מאושר) של Gatekeeper.

לוחצים על אירוע כלשהו כדי לראות את הקלט והפלט המדויקים שלו.

עכשיו מתחילים סשן חדש ומנסים מקום שמפר את הכללים:

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

הפעם המעריך דוחה את העיקול של 6.8 מטרים, האדריכל הראשי מציע עיקול רחב יותר, והפריסה המתוקנת חוזרת למעריך לפני שהיא מגיעה לשומר הסף.

בסיום, לוחצים על Ctrl+C בכרטיסייה הראשונה כדי לעצור את ה-playground. משאירים את Gatekeeper פועל בכרטיסייה השנייה: תצטרכו אותו בשלב הבא.

6. 6. הערכת הסוכנים

‫agents-cli eval run מריץ את הסוכן על קבוצת נתונים, מתעד כל פנייה וקריאה לכלי ומדרג את התוצאות. אתם מחליטים מה למדוד ב-tests/eval/eval_config.yaml.

עיון במערך הנתונים

פותחים את מערך הנתונים ב-Cloud Shell Editor:

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 מטרים לא עומד בכלל הרוחב

fe_003_berlin_too_long

המסלול באורך 4,350 מטרים לא עומד בכלל האורך

fe_004_missing_layers

בפריסה חסרות שכבות CAD חובה

fe_005_low_regen

אזור בלימה מתחת ליעד של 250 קילוואט

fe_006_sao_paulo_medical

גישה לבית חולים שדורשת שכבות CAD רפואיות

fe_007_attack_mode

אזור Attack Mode במסלול המרוצים

fe_008_multiturn_remediation

שיחה מרובת שלבים: מונקו נדחית, המשתמש מבקש תיקון, והפריסה המתוקנת צריכה לקבל אישור

במקרה האחרון משתמשים ב-agent_data במקום ב-prompt. הוא מפעיל מחדש את החלקים הקודמים של השיחה, כדי שתוכלו לבדוק איך הסוכן מטפל בבקשת המשך, ולא רק בשאלה אחת.

הקובץ track_eval_quick.json באותה תיקייה מכיל רק שני מקרים: מקרה 1 (שאלה אחת) ומקרה 8 (שיחה). תריצו את השאילתה הזו כי היא מהירה יותר ומשתמשת בפחות מכסה.

כתיבת המדדים

מערך הנתונים מציין מה לשאול את הסוכן. eval_config.yaml מסביר איך לתת ציון לתשובות. ‫agents-cli eval run קורא אותו אחרי שהנציג ענה על כל מקרה, ומריץ כל מדד על כל תשובה.

פותחים את הקובץ שנוצר על ידי הפיגום:

cloudshell edit tests/eval/eval_config.yaml

הוא כבר מכיל שופט LLM כללי (custom_response_quality) ומדד agent_turn_count שסופר רק את מספר הפעמים שהמשתמש והמודל הגיבו. זו התחלה טובה לכל סוכן, אבל היא לא אומרת שום דבר על פורמולה E. אתם תשאירו את השופט ותוסיפו מדד שבודק כלל ספציפי לדומיין הזה.

הערכות טובות בדרך כלל משלבות שני סוגים של מדדים:

סוג

איך הציון נקבע

למה היעד מתאים

פשרה

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). במקרים שבהם התשובה הצפויה היא אישור, הוא בודק אם התשובה מציינת את כל ארבע שכבות ה-CAD הנדרשות ומפרט את השכבות החסרות. לדוגמה, תוכנית שלא כוללת את F1_TEC3 תקבל ציון של 0.75. במקרים אחרים הציון הוא 1.0, כי בתשובה של דחייה או של מצב התקפה לא צריך לפרט כל שכבה.

למה כדאי לבדוק את שכבות ה-CAD? תוכנית מאושרת שחסרה בה שכבה חיונית היא שגויה, גם אם היא נראית טוב, ושופט LLM עלול לפספס את זה. בדיקת קוד מחילה את אותו כלל באותו אופן בכל הפעלה.

הרצת ההערכה

משאירים את 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, כך ש-Step 1/2: eval generate הוא החלק האיטי. הוא לא תקוע.

בסיום, פותחים את דוח ה-HTML ב-artifacts/grade_results/ באמצעות Cloud Shell Editor. ציוני fia_cad_layer_compliance נמוכים מצביעים על כך שהתשובה הסופית של האדריכל הראשי לא כוללת שכבות. כדאי לנסות לנסח את ההוראה בצורה מדויקת יותר ולהפעיל את ההערכה שוב.

7. 7. פריסת Gatekeeper ב-Agent Runtime

אתם פורסים רק את Gatekeeper. הכלי לתכנון המסלול ממשיך לפעול ב-Cloud Shell וקורא לו.

פותחים כרטיסייה חדשה ב-Cloud Shell.

הפריסה נמשכת 5 עד 8 דקות, אז כדאי לבצע אותה בכרטיסייה חדשה בזמן שההערכה משלב 6 מסתיימת:

  1. בסרגל הכלים של Cloud Shell, לוחצים על Open a new tab (+).
  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"
    

משאירים את 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 וקוראים את דוח ההערכה.

בדיקת הזהות של הסוכן

‫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

השורה השנייה של הפלט היא הזהות שבה פועל Gatekeeper:

נפרס באמצעות

פלט לדוגמה

חשבון השירות (ברירת המחדל)

123456789012-compute@developer.gserviceaccount.com

Agent Identity

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

חשבון השירות משותף לכל מה שמשתמש בו בפרויקט. לכל סוכן יש זהות משלו, כך שאתם יכולים להקצות לו בדיוק את התפקידים שהוא צריך ולראות את הפעולות שלו בנפרד ביומני הביקורת.

8. 8. התחברות ל-Gatekeeper שנפרס דרך A2A

‫Agent Runtime מציג את נתיבי ה-A2A שנוצרו על ידי Gatekeeper מאחורי נתיב /api מאומת.

מוודאים שההערכה משלב 6 הסתיימה, ואז עוצרים את 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 פג אחרי שעה, ולכן לקוח שמגדיר כותרת Authorization קבועה בזמן ההפעלה מתחיל להיכשל עם 401 Unauthorized אחרי שעה. GoogleAuthRefresh יוצר מחלקת משנה של httpx.Auth ובודק את פרטי הכניסה בכל בקשה, ומרענן אותם לפי הצורך. הפרמטר create_remote_gatekeeper_agent() מצרף אותו רק לכתובות URL של https://.

הפעלה מקצה לקצה

מפעילים שוב את סביבת ה-Playground כדי שהיא תזהה את ה-GATEKEEPER_A2A_URL החדש:

agents-cli playground --port 8080

לוחצים על Web Preview (תצוגה מקדימה באינטרנט) > Preview on port 8080 (תצוגה מקדימה ביציאה 8080), מתחילים סשן חדש ושואלים:

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

האירועים נראים כמו בשלב 5, אבל הקריאה ל-remote_gatekeeper עוברת עכשיו ל-Gatekeeper ב-Agent Runtime במקום לכרטיסייה השנייה.

איך רואים את השיחה ב-Agent Runtime

מוכיחים שהבקשה באמת הגיעה ל-Gatekeeper שנפרס דרך A2A:

  1. במסוף Cloud, מחפשים את Agent Platform ופותחים את Deployments בתפריט שמימין.
  2. בוחרים באפשרות gatekeeper.
  3. פותחים את הכרטיסייה סשנים.

יופיע סשן חדש שבו מזהה המשתמש מתחיל ב-A2A_USER_. שרת ה-A2A של Gatekeeper יצר אותו כשמתכנן המסלולים שלכם קרא לו. פותחים את הכרטיסייה Traces באותו דף כדי לראות את הקריאה למודל של Gatekeeper ואת הקריאה לכלי verify_infrastructure_checklist של אותה בקשה.

אופציונלי: פריסה גם של מתכנן המסלולים

עד עכשיו, מתכנן המסלולים פעל ב-Cloud Shell, ולכן הקריאות שלו מ-A2A ל-Gatekeeper השתמשו בפרטי הכניסה שלכם. פורסים גם את הסוכן לתכנון, ושני הסוכנים פועלים ב-Agent Runtime וקוראים אחד לשני באמצעות הזהויות שנפרסו.

מקישים על 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).

איך המתכנן שנפרס מגיע אל שומר הסף:

  • ‫agents-cli deploy מעתיק את הערכים ב-.env לפריסה, כך שהמתכנן מקבל את ה-GATEKEEPER_A2A_URL שהוספתם למעלה.
  • ב-Agent Runtime, ‏GoogleAuthRefresh ב-app/auth.py מקבל את הזהות של המתכנן שנפרס במקום את הזהות שלכם.

הפריסה נמשכת כ-5 עד 8 דקות. בסיום, בודקים איזו זהות מפעילה את המתכנן:

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 ‏ > Deployments.
  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 בפריסה

אי אפשר להשתמש ב-Agent Identity בחשבון שלך. פועלים לפי ההוראות לניקוי בשלב 7, ואז משתמשים בחשבון השירות לפריסה.

הכלי לתכנון מסלולים שנפרס מדווח על 403 Forbidden מ-Gatekeeper

לזהות של המתכנן אין הרשאה להתקשר ל-Gatekeeper. פורסים אותו מחדש באמצעות --service-account כמו בשלב 7, או באמצעות --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 הופך את השאלה "האם זה עובד?" לציונים שאפשר לעקוב אחריהם.
  • התכונות Agent Identity ו-A2A מאפשרות לסוכנים שנפרסו בנפרד לעבוד יחד בצורה מאובטחת.

מידע נוסף