欢迎来到 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 项目,不会用到你自己的账号。
us-central1,代表你的云资源所在的大区。us-central1-a,是大区里更细的具体机房。我们全程在 Cloud Shell 里操作。Cloud Shell 是 Google Cloud 自带的一个网页版命令行终端——你不用在自己电脑上装任何东西,打开就能用,而且已经登录好了你的实验账号。
>_ 样子的按钮)。第一次打开会让你点 Continue(继续) 授权。cloudshell workspace ~
看到编辑器后,点击右上角的 Open Terminal(打开终端)按钮(下图红框处)回到终端,继续执行后面的命令:

cd ~
git clone https://github.com/salomonerobert/agent-football.git
cd agent-football
在写代码之前,我们先给项目搭好"厨房"——一个干净的 Python 环境,再让它拿到调用 Google AI 的"钥匙"。
虚拟环境(venv) 就像给这个项目单独准备的一套餐具,和你系统里其他 Python 项目互不干扰。
python3 -m venv venv
source venv/bin/activate
激活成功后,命令行开头会出现 (venv) 字样。
pip install -r LAB01/requirements.txt
.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:

改完记得保存(Ctrl+S),然后点击右上角 Open Terminal(打开终端) 回到终端,继续下一小节。
下面这几条命令,让 Cloud Shell 拿到访问 Google AI 的权限,并把默认项目 / 区域 / 可用区设好:
gcloud auth login
执行后会先出现下图这样的提示——Cloud Shell 里其实已经登录,它问你"还要继续吗"。输入 Y 并回车,随后会给出一个授权链接,点开、选择你的实验账号、点 Allow(允许) 即可:

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
gcloud services enable aiplatform.googleapis.com
这一步告诉你的项目:"我要用 Vertex AI 了,请打开这个开关。"
到这里,环境就配置完了。正常情况下,三条 gcloud config set 各回显一行 Updated property [...],services enable 显示 finished successfully,像下图这样:

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

教练和队长跑在两个不同的服务器上,它们之间靠 A2A(AI 之间的"微信")通信;而队长指挥四位专家时,用的是 AgentTool(把专家当函数直接调)。
当你喊出"全员进攻",消息是这样一层层往下传的:

教练把你的意图转交给队长,队长再把任务并行下发给四位球员专家,每位专家把自己的新状态写进一个 JSON 档案,游戏画面读取这些档案后,球员的跑位就变了。
在选做的高级环节里,球员还会"喊累":

当某个球员体力低于阈值,球员智能体会判断"是不是该下场了",然后通过 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 注释块的下方,注意与注释同级缩进):

本步说明: 这是保证"全队画风统一"的核心。我们不是每画一张图就重新找 Gemini,而是开一个持续的聊天会话,让它像同一个设计师那样,记住整队的球衣颜色和画风。
在 # TODO: Task 2 处,把创建聊天会话的代码补上:
chat = client.aio.chats.create(model="publishers/google/models/gemini-3.1-flash-image")
填好后如下图红框:

本步说明: 现在真正下单画图了。我们要生成两类图:外场球员(前锋/中场/后卫)和门将。因为它们在同一个聊天会话里先后生成,门将会自动和外场球员保持同款画风。
在 # 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: 块内,注意缩进层级):

在 # 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: 块内):

写完记得保存。
代码写好了,现在跑起来看看效果!我们会启动一个网页版"球员入职门户"。
前三个任务一直在编辑器里,现在要执行命令了——先点击右上角 Open Terminal(打开终端) 切回终端。
cd ~/agent-football/LAB01
uvicorn app:app --host 127.0.0.1 --port 8002 --reload
http://127.0.0.1:8002。

你可以确认这几个产物已生成:
LAB02/frontend/public/player_state/ 下出现了每名球员的 *.json 档案。做完 LAB01,来检验一下理解。先自己选一个答案,再点每题下方的查看答案核对:
Q1. 要开启一个能"记住上下文、保持画风一致"的图像生成会话,应该用哪个方法?
client.generate_content()client.aio.chats.create()client.images.new()Q2. 为什么门将的画风能和外场球员保持一致?
chat 会话,模型会参考历史消息里的球衣颜色/风格Q3. 生成的球员图需要透明背景,靠的是什么技术?
球员招募好了,接下来是本实验的重头戏:LAB02——组建教练团队。
我们会经历一个非常真实的"架构演进"过程:
这正是架构图 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,切回终端执行下面的命令:

cd ~/agent-football/LAB02
pip install -r football_agents/requirements.txt
adk web 单独把它跑起来(不用 run_lab02.sh——那个会连队长、前端一起拉起,而队长要到 Task 2、3 才写):adk web . --allow_origins='*'
8000 → 预览,打开 ADK 调试页面。在页面左上角的下拉框里选择 football_agents,然后在底部对话框里输入 everyone attack,回车。教练应该会直接回一句搞笑又提气的战术口号——能回,就说明巨石版教练通了。像下图这样,你输入 everyone attack,教练回了句 "Push forward, boys, let's pin them in their own half and get that goal!":

本步说明: 巨石教练啥都自己干,难以扩展。现在我们把"队长"这个角色独立成一个单独的服务,让它跑在**自己的服务器(端口 8001)**上。这样教练和队长就能各司其职、分开部署——这正是 A2A 要解决的问题:让不同服务器上的 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 删掉(或注释掉)。
编辑队长服务器文件(注意它在 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
在同一个 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
找到 # 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,
)
找到教练 instruction 里的 # TODO: Task 3b。这一步做一次"开关切换":
For any other message, respond directly ...)重新注释掉(行首加回 # );# For any other message, immediately transfer control to the team_captain sub-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)分别编辑另外三个文件:
specialist_agents/midfielder.py → midfielder_agentspecialist_agents/forward.py → forward_agentspecialist_agents/goalkeeper.py → goalkeeper_agent保存所有文件。
本步说明: 现在把四位专家"交给"队长。这里用的不是 A2A(那是跨服务器用的),而是 AgentTool——因为专家和队长跑在同一个服务器里,队长可以把每位专家当成一个工具函数直接调用,而且能**同时(并行)**调用四个,效率最高。这对应架构图 A 里"③ 本地 AgentTool 调用"那几条箭头。
回到队长文件:
cloudshell edit ~/agent-football/LAB02/football_agents/captain.py
找到 # TODO: Task 5a,把它下面这段 import 取消注释(AgentTool 在文件顶部已经导入过了,这里只需导入四位专家):
from football_agents.specialist_agents import (
defender_agent,
midfielder_agent,
forward_agent,
goalkeeper_agent,
)
找到 # TODO: Task 5b。这一步要换一个更强的队长:
captain_agent = LlmAgent(...)(用 CAPTAIN_INITIAL_INSTRUCTION 的那个)注释掉;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。
编辑后卫文件(其它专家同理):
cloudshell edit ~/agent-football/LAB02/football_agents/specialist_agents/defender.py
找到 # TODO: Task 6a,把它下面这行 import 取消注释:
from .tools import make_condition_toolset, CONDITION_GUIDANCE
还在 defender_agent 定义里(就是任务四取消注释的那段),找到两处 # TODO: Task 6b,把下面两行分别取消注释:
instruction=DEFENDER_INSTRUCTION 下面的 # + CONDITION_GUIDANCE(给它加上"体力自检"的职责说明);tools=[update_profile] 下面的 # + make_condition_toolset()(把换人/报伤工具接进来)。取消注释后,这部分应该长这样:
instruction=DEFENDER_INSTRUCTION
+ CONDITION_GUIDANCE
,
tools=[update_profile]
+ make_condition_toolset()
,
编辑工具文件:
cloudshell edit ~/agent-football/LAB02/football_agents/specialist_agents/tools.py
找到 # TODO: Task 6c,把开关从 False 改成 True:
USE_REAL_MCP_SERVER = True
保存所有文件。
激动人心的时刻到了——把整套系统跑起来,亲自当一回教练!
前面几个任务都在编辑器里写代码,现在点右上角 Open Terminal(打开终端) 切回终端。
cd ~/agent-football/LAB02
pip install -r football_agents/requirements.txt
bash run_lab02.sh
http://localhost:5173 打开游戏。everyone attack
观察场上球员的跑位随你的喊话而改变。恭喜——你正在指挥一支全 AI 驱动的足球队!
同样,先自己选一个答案,再点查看答案核对:
Q1. 要把一个普通 Agent 变成能被别的服务器远程调用的 A2A 服务,应该用哪个函数?
to_a2a(captain_agent, host, port)AgentTool(captain_agent)RemoteA2aAgent(captain_agent)Q2. 队长要在同一进程里直接、并行调用四位专家,用的是什么?
AgentTool(agent),把每个专家包装成工具Q3. Task 6 里球员智能体和体力/换人工具服务之间,靠什么协议通信?
你已经从零搭出了一支完全由 AI 驱动的足球队,并亲手实现了一套分层多智能体系统。回顾一下你掌握的硬核技能:
这套"编排层级 + A2A + 工具化 + MCP"的组合,正是当下构建生产级多智能体应用的主流范式。把它用到你自己的业务场景里——把"球员"换成你的业务模块,把"喊话"换成用户请求,一样成立。