欢迎来到 Super AI Google Workshop!在这个实验里,你会亲手搭建一款叫 World Cup Mania(世界杯狂热) 的浏览器足球游戏。这个游戏最特别的地方是:场上的每一名球员,背后都是一个 AI 智能体(Agent)。当你在场边"喊话"(比如喊"全员进攻!"),你的喊声会经过一整条 AI 指挥链,最终改变每个球员在场上的跑位和行为。

别被"多智能体""A2A""MCP"这些词吓到。我们会用生活里的比喻把每一步讲清楚,你只要照着复制粘贴代码,就能一步步把整套系统跑起来。

实验封面

你会造出什么

这个实验分成两大部分:

你会学到的核心概念(先混个脸熟)

概念

一句话比喻

LlmAgent

一个"会思考的员工":给它一段职责说明(instruction),它就能按职责干活。

A2A(Agent-to-Agent)

AI 之间的"微信":让跑在不同服务器上的两个 Agent 能互相发消息、派活。

AgentTool

把一个 Agent"打包成一个工具",让另一个 Agent 可以像调用函数一样直接调用它。

MCP(Model Context Protocol)

AI 的"USB 接口/智能手表数据线":让 Agent 用统一标准去调用外部的工具服务(比如查体力、申请换人)。

你需要准备

本实验使用讲师提供的实验平台(Qwiklabs / Google Skills)分配的临时学员账号和临时 GCP 项目,不会用到你自己的账号。

  1. 在实验页面左上角点击 Start Lab(开始实验)。计时器开始倒计时,倒计时代表云资源对你可用的时长。
  2. 左侧出现 Lab Setup(实验详情) 面板。这次除了 Project ID(项目 ID),你还会看到两个新东西:
    • Lab Region(实验区域):例如 us-central1,代表你的云资源所在的大区。
    • Lab Zone(实验可用区):例如 us-central1-a,是大区里更细的具体机房。
  1. 右键点击 Open Google Cloud console,选择 在无痕窗口中打开链接(Open Link in Incognito Window),用面板里的 Username / Password 登录。

我们全程在 Cloud Shell 里操作。Cloud Shell 是 Google Cloud 自带的一个网页版命令行终端——你不用在自己电脑上装任何东西,打开就能用,而且已经登录好了你的实验账号。

  1. 在 Google Cloud 控制台右上角,点击 Activate Cloud Shell(激活 Cloud Shell) 图标(一个 >_ 样子的按钮)。第一次打开会让你点 Continue(继续) 授权。
  2. Cloud Shell 打开后,先把 Cloud Shell 编辑器的工作区设为主目录:
cloudshell workspace ~

看到编辑器后,点击右上角的 Open Terminal(打开终端)按钮(下图红框处)回到终端,继续执行后面的命令:

编辑器右上角的 Open Terminal 按钮

  1. 从 GitHub 下载本实验的全部代码:
cd ~
git clone https://github.com/salomonerobert/agent-football.git
  1. 进入项目文件夹:
cd agent-football

在写代码之前,我们先给项目搭好"厨房"——一个干净的 Python 环境,再让它拿到调用 Google AI 的"钥匙"。

1. 创建并激活虚拟环境

虚拟环境(venv) 就像给这个项目单独准备的一套餐具,和你系统里其他 Python 项目互不干扰。

python3 -m venv venv
source venv/bin/activate

激活成功后,命令行开头会出现 (venv) 字样。

2. 安装 LAB01 依赖

pip install -r LAB01/requirements.txt

3. 准备 .env 配置文件

.env 文件用来存放项目的"环境变量"——这里我们要告诉代码:用 Vertex AI 来跑 Gemini,以及用哪个 GCP 项目。

先从模板复制一份:

cp .env.example .env

然后用 Cloud Shell 自带的编辑器打开它(页面会切到编辑器界面并打开 .env 文件):

cloudshell edit ~/agent-football/.env

确保文件里这两行如下(YOUR_PROJECT_ID 会被右上角卡片自动替换成你的真实项目 ID),其余注释行不用动:

GOOGLE_GENAI_USE_VERTEXAI=true
GOOGLE_CLOUD_PROJECT=YOUR_PROJECT_ID

改好后应该像下图红框这样——GOOGLE_CLOUD_PROJECT= 后面是你的实验项目 ID:

在编辑器中修改 .env 的 GOOGLE_CLOUD_PROJECT 行

改完记得保存(Ctrl+S),然后点击右上角 Open Terminal(打开终端) 回到终端,继续下一小节。

4. 登录并绑定项目、区域

下面这几条命令,让 Cloud Shell 拿到访问 Google AI 的权限,并把默认项目 / 区域 / 可用区设好:

gcloud auth login

执行后会先出现下图这样的提示——Cloud Shell 里其实已经登录,它问你"还要继续吗"。输入 Y 并回车,随后会给出一个授权链接,点开、选择你的实验账号、点 Allow(允许) 即可:

gcloud auth login 的确认提示,输入 Y 继续

gcloud auth application-default login

这条同样会给出授权链接,照上面的方式再授权一次(这次是给代码用的应用默认凭据)。

gcloud config set project YOUR_PROJECT_ID
gcloud config set compute/region YOUR_REGION
gcloud config set compute/zone YOUR_ZONE

5. 开启 Vertex AI 接口

gcloud services enable aiplatform.googleapis.com

这一步告诉你的项目:"我要用 Vertex AI 了,请打开这个开关。"

到这里,环境就配置完了。正常情况下,三条 gcloud config set 各回显一行 Updated property [...],services enable 显示 finished successfully,像下图这样:

gcloud config set 与 services enable 的成功输出

在动手写代码前,我们先鸟瞰一下整个系统长什么样。理解了这张"组织架构图",后面每一步你就知道自己在拼哪一块拼图了。

架构图 A:三台服务器的分工

整套系统跑在三个部分上,像一条流水线:

架构图 A:服务器拓扑

教练和队长跑在两个不同的服务器上,它们之间靠 A2A(AI 之间的"微信")通信;而队长指挥四位专家时,用的是 AgentTool(把专家当函数直接调)。

架构图 B:喊话指令的流动路径

当你喊出"全员进攻",消息是这样一层层往下传的:

架构图 B:喊话流转

教练把你的意图转交给队长,队长再把任务并行下发给四位球员专家,每位专家把自己的新状态写进一个 JSON 档案,游戏画面读取这些档案后,球员的跑位就变了。

架构图 C:自动换人是怎么发生的(选做部分)

在选做的高级环节里,球员还会"喊累":

架构图 C:换人循环

当某个球员体力低于阈值,球员智能体会判断"是不是该下场了",然后通过 MCP 调用一个后台工具服务,把换人请求写到磁盘,游戏读到后就实时换人。

一支球队,总得先有球员。LAB01 的目标,就是用 Gemini 的生图能力,批量生成一支风格统一的球队形象。

这里的关键难点是"风格一致":我们不是随便生成 11 张互不相干的图,而是要让同一支队的球员穿同款球衣、同种画风。做法很巧妙——我们和 Gemini 开一个连续的聊天会话(chat session),让它"记住"前面画过的风格,后面再画就会自动保持一致,就像同一个设计师连续给你出图。

接下来的 Task 1~3,我们都在编辑同一个文件:LAB01/app.py。请在 Cloud Shell 里打开它:

cloudshell edit ~/agent-football/LAB01/app.py

本步说明: 要用 Gemini,先得创建一个"客户端(Client)"对象——你可以把它理解成拨通 Google AI 的电话。有了这个电话,后面才能给 Gemini 打电话下单。

在 app.py 里找到 # TODO: Task 1 这一行,把客户端初始化代码补上:

client = genai.Client()

填好后应该像下图这样(在 TODO 注释块的下方,注意与注释同级缩进):

Task 1 填写后的样子

本步说明: 这是保证"全队画风统一"的核心。我们不是每画一张图就重新找 Gemini,而是开一个持续的聊天会话,让它像同一个设计师那样,记住整队的球衣颜色和画风。

在 # TODO: Task 2 处,把创建聊天会话的代码补上:

chat = client.aio.chats.create(model="publishers/google/models/gemini-3.1-flash-image")

填好后如下图红框:

Task 2 填写后的样子

本步说明: 现在真正下单画图了。我们要生成两类图:外场球员(前锋/中场/后卫)和门将。因为它们在同一个聊天会话里先后生成,门将会自动和外场球员保持同款画风。

任务 3a:生成外场球员精灵图

在 # TODO: Task 3a 处填入:

response = await chat.send_message(
    player_prompt,
    config=types.GenerateContentConfig(
        response_modalities=["IMAGE"],
        image_config=types.ImageConfig(aspect_ratio="16:9"),
    ),
)

填好后如下图红框(在 try: 块内,注意缩进层级):

Task 3a 填写后的样子

任务 3b:生成风格一致的门将精灵图

在 # TODO: Task 3b 处填入(注意这次用的是 gk_prompt):

response = await chat.send_message(
    gk_prompt,
    config=types.GenerateContentConfig(
        response_modalities=["IMAGE"],
        image_config=types.ImageConfig(aspect_ratio="16:9"),
    ),
)

填好后如下图红框(同样在对应的 try: 块内):

Task 3b 填写后的样子

写完记得保存。

代码写好了,现在跑起来看看效果!我们会启动一个网页版"球员入职门户"。

前三个任务一直在编辑器里,现在要执行命令了——先点击右上角 Open Terminal(打开终端) 切回终端。

  1. 进入 LAB01 目录并启动服务:
cd ~/agent-football/LAB01
uvicorn app:app --host 127.0.0.1 --port 8002 --reload
  1. 服务启动后,在 Cloud Shell 里点击 Web Preview(网页预览) 图标 → 选择 Change port(更改端口) → 输入 8002 → 打开。或直接访问 http://127.0.0.1:8002。
  2. 在打开的入职门户里:
    • 点击 Generate Avatars(生成头像),等待 Gemini 为蓝队和红队生成球员精灵图。
    • 生成完成后,你会看到两队的球员形象和门将形象。
    LAB01 入职门户:生成的球队资产
  3. 接着点击 Configure Player Profiles(配置球员档案),给每名球员调节速度、侵略性等属性滑块,配置好后点击 Save(保存)。LAB01 配置球员档案
  1. 确认生成无误后,回到 Cloud Shell,按 Ctrl + C 停止这个服务。

小验证

你可以确认这几个产物已生成:

做完 LAB01,来检验一下理解。先自己选一个答案,再点每题下方的查看答案核对:

Q1. 要开启一个能"记住上下文、保持画风一致"的图像生成会话,应该用哪个方法?

Q2. 为什么门将的画风能和外场球员保持一致?

Q3. 生成的球员图需要透明背景,靠的是什么技术?

球员招募好了,接下来是本实验的重头戏:LAB02——组建教练团队。

我们会经历一个非常真实的"架构演进"过程:

  1. 先做一个"巨石版"教练(Task 1):一个 Agent 大包大揽,啥都自己干。简单,但不好扩展。
  2. 把队长独立出去(Task 2、3):让队长跑在自己的服务器上,教练通过 A2A 远程指挥它。这就像公司变大后,老板不再事必躬亲,而是设一个"队长"岗位分担。
  3. 给队长配四位位置专家(Task 4、5):后卫、中场、前锋、门将各有专精,队长用 AgentTool 并行调度他们。
  4. (选做)接入 MCP 工具(Task 6):让球员能查体力、自动申请换人。

这正是架构图 A / B 画的那套分层结构。走完这几步,你就亲手把它搭出来了。

本步说明: 我们先用最朴素的方式实现:一个教练 Agent 独自处理所有喊话。它收到"全员进攻"后,自己直接喊出战术口号(TACTICAL SHOUTS)。这样你能先看到"一个 Agent 能干活"的最小闭环。

编辑教练文件(在终端里执行下面的命令,页面会自动切到编辑器打开 agent.py):

cloudshell edit ~/agent-football/LAB02/football_agents/agent.py

这一步只需要"取消注释"一行——英文职责说明模板里已经写好了,你不用自己动手写。

在文件里找到 # TODO: Task 1 这一段(它在教练 coach_agent 的 instruction 字符串里,TACTICAL SHOUTS: 标题的正下方)。你会看到最后一句被 # 注释掉了:

    TACTICAL SHOUTS:
    # TODO: Task 1 - Direct touchline shouts
    # Copy or uncomment the instruction below to make the Coach respond directly to tactical shouts:
    # For any other message, respond directly as a passionate coach with a funny, encouraging one sentence shout! Do NOT call any subagents yet.

把最后一行开头的

#

(井号加一个空格)删掉,缩进保留,让这句话变成教练职责说明的正式内容:

    TACTICAL SHOUTS:
    # TODO: Task 1 - Direct touchline shouts
    # Copy or uncomment the instruction below to make the Coach respond directly to tactical shouts:
    For any other message, respond directly as a passionate coach with a funny, encouraging one sentence shout! Do NOT call any subagents yet.

改完按 Ctrl + S 保存。Task 1 到此就完成了。

临时测试一下这个巨石教练

改完保存后,点编辑器右上角的 Open Terminal,切回终端执行下面的命令:

在编辑器右上角点 Open Terminal 切回终端

  1. 进入 LAB02 目录,安装依赖:
cd ~/agent-football/LAB02
pip install -r football_agents/requirements.txt
  1. 启动 ADK 自带的调试界面。现在只有教练一个 Agent,我们直接用 adk web 单独把它跑起来(不用 run_lab02.sh——那个会连队长、前端一起拉起,而队长要到 Task 2、3 才写):
adk web . --allow_origins='*'
  1. 点 Cloud Shell 窗口右上角的 Web Preview(网页预览)图标 → 更改端口 → 填 8000 → 预览,打开 ADK 调试页面。在页面左上角的下拉框里选择 football_agents,然后在底部对话框里输入 everyone attack,回车。

教练应该会直接回一句搞笑又提气的战术口号——能回,就说明巨石版教练通了。像下图这样,你输入 everyone attack,教练回了句 "Push forward, boys, let's pin them in their own half and get that goal!":

在 ADK 调试界面输入 everyone attack,教练直接回一句战术口号

  1. 测试完毕,回到终端按 Ctrl + C 停止服务。

本步说明: 巨石教练啥都自己干,难以扩展。现在我们把"队长"这个角色独立成一个单独的服务,让它跑在**自己的服务器(端口 8001)**上。这样教练和队长就能各司其职、分开部署——这正是 A2A 要解决的问题:让不同服务器上的 Agent 互相通信。

这一步分三小步。

任务 2a:定义队长 Agent

编辑队长文件(在终端执行下面的命令,页面会切回编辑器打开该文件):

cloudshell edit ~/agent-football/LAB02/football_agents/captain.py

找到 # TODO: Task 2a,把它下面这段被注释的队长定义取消注释(逐行删掉行首 # ):

captain_agent = LlmAgent(
    name="TeamCaptain",
    model=GeminiConstants.GEMINI_FLASH_LITE,
    description="The team captain who relays coach shouts to the outfield players.",
    instruction=CAPTAIN_INITIAL_INSTRUCTION
)

然后把紧接着下面那行 captain_agent = None 删掉(或注释掉)。

任务 2b:给队长写一个"对外服务器"

编辑队长服务器文件(注意它在 football_agents/ 目录里):

cloudshell edit ~/agent-football/LAB02/football_agents/captain_server.py

找到 # TODO: Task 2b,把它下面这 3 行 import 取消注释:

from google.adk.a2a.utils.agent_to_a2a import to_a2a
import uvicorn
from football_agents.captain import captain_agent

任务 2c:把队长"变成"一个 A2A 网络服务

在同一个 captain_server.py 里找到 # TODO: Task 2c,把它下面这段取消注释:

app = to_a2a(captain_agent, host=HOST, port=PORT)

if __name__ == "__main__":
    print(f"Serving Team Captain over A2A at http://{HOST}:{PORT}")
    print(f"Agent card: http://{HOST}:{PORT}/.well-known/agent-card.json")
    uvicorn.run(app, host=HOST, port=PORT)

写完记得保存所有文件(Ctrl+S)。

本步说明: 队长现在是个独立服务了,教练要怎么找到它并派活?答案就是用 RemoteA2aAgent——它相当于教练手机里存的队长联系方式。教练不需要知道队长内部怎么干活,只要"拨号"给它就行。这一步都在教练文件里改,分三小步(3a / 3b / 3c)。

回到教练文件:

cloudshell edit ~/agent-football/LAB02/football_agents/agent.py

任务 3a:登记队长的"联系方式"

找到 # TODO: Task 3a。先把那行 import 取消注释:

from google.adk.agents.remote_a2a_agent import RemoteA2aAgent, AGENT_CARD_WELL_KNOWN_PATH

再把下面这段 team_captain_remote 定义取消注释,并把紧接着的 team_captain_remote = None 删掉:

CAPTAIN_A2A_URL = os.environ.get("CAPTAIN_A2A_URL", f"http://localhost:8001{AGENT_CARD_WELL_KNOWN_PATH}")
team_captain_remote = RemoteA2aAgent(
    name="team_captain",
    description="The team captain, reachable over the A2A protocol.",
    agent_card=CAPTAIN_A2A_URL,
)

任务 3b:让教练把喊话"转交"给队长

找到教练 instruction 里的 # TODO: Task 3b。这一步做一次"开关切换":

  1. 把 Task 1 那句(你在任务一里取消注释的 For any other message, respond directly ...)重新注释掉(行首加回 # );
  2. 把 Task 3b 那句(# For any other message, immediately transfer control to the team_captain sub-agent ...)取消注释。

这样教练收到喊话后,不再自己喊,而是转交给队长处理。

任务 3c:把队长登记为教练的子 Agent

找到教练 LlmAgent(...) 结尾处、带 # TODO: Task 3c 注释的 sub_agents=[],把它改成:

    sub_agents=[team_captain_remote],

保存所有文件。

本步说明: 队长一个人也管不过来全队。我们给它配四位专精球员:后卫、中场、前锋、门将。每位专家只懂自己位置的战术,收到指令后更新自己的档案(比如变得更激进或更保守)。

这四个专家的模板结构几乎一模一样,都是"取消注释 + 删掉 = None"。我们以后卫为例,其余三个照做。

编辑后卫文件:

cloudshell edit ~/agent-football/LAB02/football_agents/specialist_agents/defender.py

找到 # TODO: Task 4a,把它下面这段 defender_agent = LlmAgent(...) 取消注释(它用的 DEFENDER_INSTRUCTION 是文件里已写好的变量),再把下面那行 defender_agent = None 删掉。取消注释后大致长这样:

defender_agent = LlmAgent(
    name="DefenderSpecialist",
    model=GeminiConstants.GEMINI_FLASH_LITE,
    description="Handles tactical instructions and attribute updates for the DEFENDER role.",
    instruction=DEFENDER_INSTRUCTION
    ,
    tools=[update_profile]
    ,
    output_key="defender_response"
)

然后用同样的方式(取消注释 X_agent = LlmAgent(...) + 删掉 X_agent = None)分别编辑另外三个文件:

保存所有文件。

本步说明: 现在把四位专家"交给"队长。这里用的不是 A2A(那是跨服务器用的),而是 AgentTool——因为专家和队长跑在同一个服务器里,队长可以把每位专家当成一个工具函数直接调用,而且能**同时(并行)**调用四个,效率最高。这对应架构图 A 里"③ 本地 AgentTool 调用"那几条箭头。

回到队长文件:

cloudshell edit ~/agent-football/LAB02/football_agents/captain.py

任务 5a:导入四位专家

找到 # TODO: Task 5a,把它下面这段 import 取消注释(AgentTool 在文件顶部已经导入过了,这里只需导入四位专家):

from football_agents.specialist_agents import (
    defender_agent,
    midfielder_agent,
    forward_agent,
    goalkeeper_agent,
)

任务 5b:把队长升级成"调度版"

找到 # TODO: Task 5b。这一步要换一个更强的队长:

  1. 把任务 2a 里那个简单版 captain_agent = LlmAgent(...)(用 CAPTAIN_INITIAL_INSTRUCTION 的那个)注释掉;
  2. 把 Task 5b 下面这段升级版 captain_agent(用 CAPTAIN_ORCHESTRATOR_INSTRUCTION、带四个 AgentTool 的那个)取消注释:
captain_agent = LlmAgent(
    name="TeamCaptain",
    model=GeminiConstants.GEMINI_FLASH_LITE,
    description="Team captain who relays the coach's tactics to the individual players and reports back the huddle.",
    instruction=CAPTAIN_ORCHESTRATOR_INSTRUCTION,
    tools=[
        AgentTool(defender_agent),
        AgentTool(midfielder_agent),
        AgentTool(forward_agent),
        AgentTool(goalkeeper_agent),
    ],
)

保存所有文件。

本步说明: 到目前为止球员只会执行战术。这一步我们让球员能感知体力,并在太累时自动申请换人。实现方式是 MCP(Model Context Protocol)——一套让 Agent 调用外部工具服务的统一标准接口。你可以把 MCP 想成球员戴的智能手表:手表(MCP 服务)持续监测体力,数据通过标准接口回传给球员智能体,球员据此决定要不要下场。这对应架构图 C。

任务 6a:导入体力工具集

编辑后卫文件(其它专家同理):

cloudshell edit ~/agent-football/LAB02/football_agents/specialist_agents/defender.py

找到 # TODO: Task 6a,把它下面这行 import 取消注释:

from .tools import make_condition_toolset, CONDITION_GUIDANCE

任务 6b:把体力能力加进专家

还在 defender_agent 定义里(就是任务四取消注释的那段),找到两处 # TODO: Task 6b,把下面两行分别取消注释:

取消注释后,这部分应该长这样:

    instruction=DEFENDER_INSTRUCTION
    + CONDITION_GUIDANCE
    ,
    tools=[update_profile]
    + make_condition_toolset()
    ,

任务 6c:打开"真实 MCP 服务器"开关

编辑工具文件:

cloudshell edit ~/agent-football/LAB02/football_agents/specialist_agents/tools.py

找到 # TODO: Task 6c,把开关从 False 改成 True:

USE_REAL_MCP_SERVER = True

保存所有文件。

激动人心的时刻到了——把整套系统跑起来,亲自当一回教练!

前面几个任务都在编辑器里写代码,现在点右上角 Open Terminal(打开终端) 切回终端。

  1. 确保你在 LAB02 目录并装好依赖:
cd ~/agent-football/LAB02
pip install -r football_agents/requirements.txt
  1. 一键启动整套服务(它会同时拉起 游戏前端 5173、队长服务 8001、教练服务 8000):
bash run_lab02.sh
  1. 通过 Cloud Shell 的 Web Preview(端口填 5173),或直接访问 http://localhost:5173 打开游戏。
  2. 点击 Kick Off!(开球),然后在喊话框里试着输入各种战术口号,例如:
everyone attack

观察场上球员的跑位随你的喊话而改变。恭喜——你正在指挥一支全 AI 驱动的足球队!

  1. 玩够了,回到 Cloud Shell 按 Ctrl + C 停止所有服务。

同样,先自己选一个答案,再点查看答案核对:

Q1. 要把一个普通 Agent 变成能被别的服务器远程调用的 A2A 服务,应该用哪个函数?

Q2. 队长要在同一进程里直接、并行调用四位专家,用的是什么?

Q3. Task 6 里球员智能体和体力/换人工具服务之间,靠什么协议通信?

你已经从零搭出了一支完全由 AI 驱动的足球队,并亲手实现了一套分层多智能体系统。回顾一下你掌握的硬核技能:

这套"编排层级 + A2A + 工具化 + MCP"的组合,正是当下构建生产级多智能体应用的主流范式。把它用到你自己的业务场景里——把"球员"换成你的业务模块,把"喊话"换成用户请求,一样成立。

延伸学习