欢迎来到 Super AI Google Workshop!在这个实验里,你会亲手搭建一款叫 World Cup Mania(世界杯狂热) 的浏览器足球游戏。这个游戏最特别的地方是:场上的每一名球员,背后都是一个 AI 智能体(Agent)。当你在场边"喊话"(比如喊"全员进攻!"),你的喊声会经过一整条 AI 指挥链,最终改变每个球员在场上的跑位和行为。
别被"多智能体""A2A""MCP"这些词吓到。我们会用生活里的比喻把每一步讲清楚,你只要照着复制粘贴代码,就能一步步把整套系统跑起来。

这个实验分成两大部分:
概念 | 一句话比喻 |
LlmAgent | 一个"会思考的员工":给它一段职责说明(instruction),它就能按职责干活。 |
A2A(Agent-to-Agent) | AI 之间的"企业微信/Slack":让跑在不同服务器上的两个 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 ~
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 自带的编辑器打开它:
cloudshell edit .env
把文件内容改成下面这样(YOUR_PROJECT_ID 会被右上角卡片自动替换成你的真实项目 ID):
GOOGLE_GENAI_USE_VERTEXAI=true
GOOGLE_CLOUD_PROJECT=YOUR_PROJECT_ID
改完记得保存(Ctrl+S)。
下面这几条命令,让 Cloud Shell 拿到访问 Google AI 的权限,并把默认项目 / 区域 / 可用区设好:
gcloud auth login
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 了,请打开这个开关。"
在动手写代码前,我们先鸟瞰一下整个系统长什么样。理解了这张"组织架构图",后面每一步你就知道自己在拼哪一块拼图了。
整套系统跑在三个部分上,像一条流水线:

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

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

当某个球员体力低于阈值,球员智能体会判断"是不是该下场了",然后通过 MCP 调用一个后台工具服务,把换人请求写到磁盘,游戏读到后就实时换人。
一支球队,总得先有球员。LAB01 的目标,就是用 Gemini 的生图能力,批量生成一支风格统一的球队形象。
这里的关键难点是"风格一致":我们不是随便生成 11 张互不相干的图,而是要让同一支队的球员穿同款球衣、同种画风。做法很巧妙——我们和 Gemini 开一个连续的聊天会话(chat session),让它"记住"前面画过的风格,后面再画就会自动保持一致,就像同一个设计师连续给你出图。
接下来的 Task 1~3,我们都在编辑同一个文件:LAB01/app.py。请在 Cloud Shell 里打开它:
cloudshell edit LAB01/app.py
这段在干嘛: 要用 Gemini,先得创建一个"客户端(Client)"对象——你可以把它理解成拨通 Google AI 的电话。有了这个电话,后面才能给 Gemini 打电话下单。
在 app.py 里找到 # TODO: Task 1 这一行,把客户端初始化代码补上:
client = genai.Client()
这段在干嘛: 这是保证"全队画风统一"的核心。我们不是每画一张图就重新找 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"),
),
)
在 # 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"),
),
)
写完记得保存。
代码写好了,现在跑起来看看效果!我们会启动一个网页版"球员入职门户"。
cd 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 能干活"的最小闭环。
编辑教练文件:
cloudshell edit LAB02/football_agents/agent.py
在 # TODO Task 1 处,给教练写上"职责说明"(instruction)。这段说明就是教练的"岗位职责书"——告诉它:收到喊话时,直接给出战术口号并回应。
为了单独测试教练,我们先做几个准备:
cd LAB02
pip install -r football_agents/requirements.txt
football_agents/agent.py 里的第 64、65 行(这两行是后面才用到的队长相关代码,现在还没写,先临时去掉以便单测)。bash run_lab02.sh
http://127.0.0.1:8000,在左上角选择 football_agents,然后在对话框里输入:everyone attack
看看教练是否直接回了战术口号。
这段在干嘛: 巨石教练啥都自己干,难以扩展。现在我们把"队长"这个角色独立成一个单独的服务,让它跑在**自己的服务器(端口 8001)**上。这样教练和队长就能各司其职、分开部署——这正是 A2A 要解决的问题:让不同服务器上的 Agent 互相通信。
这一步分三小步。
编辑队长文件:
cloudshell edit LAB02/football_agents/captain.py
在对应位置定义队长 Agent:
captain_agent = LlmAgent(
name="TeamCaptain",
model=GeminiConstants.GEMINI_FLASH_LITE,
description="...",
instruction="""You are the team captain...""",
)
编辑队长服务器文件:
cloudshell edit LAB02/captain_server.py
在文件顶部补上这几行导入:
from google.adk.a2a.utils.agent_to_a2a import to_a2a
import uvicorn
from football_agents.captain import captain_agent
在 captain_server.py 里继续补上:
HOST = os.environ.get("CAPTAIN_HOST", "localhost")
PORT = int(os.environ.get("CAPTAIN_PORT", "8001"))
app = to_a2a(captain_agent, host=HOST, port=PORT)
if __name__ == "__main__":
uvicorn.run(app, host=HOST, port=PORT)
写完记得保存所有文件。
这段在干嘛: 队长现在是个独立服务了,教练要怎么找到它并派活?答案就是用 RemoteA2aAgent——它相当于教练手机里存的队长联系方式。教练不需要知道队长内部怎么干活,只要"拨号"给它就行。
回到教练文件:
cloudshell edit LAB02/football_agents/agent.py
在 # TODO Task 3a 处补上:
from google.adk.agents.remote_a2a_agent import RemoteA2aAgent, AGENT_CARD_WELL_KNOWN_PATH
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="...",
agent_card=CAPTAIN_A2A_URL,
)
在 # TODO Task 3b 处,定义教练 Agent,并把队长挂成它的子 Agent:
coach_agent = LlmAgent(
name="ManagerAgent",
model=GeminiConstants.GEMINI_FLASH_LITE,
description="...",
instruction="""...transfer to team_captain...""",
tools=[backup_baseline_profiles, restore_baseline_profiles],
sub_agents=[team_captain_remote],
)
保存所有文件。
这段在干嘛: 队长一个人也管不过来全队。我们给它配四位专精球员:后卫、中场、前锋、门将。每位专家只懂自己位置的战术,收到指令后更新自己的档案(比如变得更激进或更保守)。
这四个专家的代码结构几乎一模一样,只是位置不同。我们以后卫为例,其余三个照葫芦画瓢。
编辑后卫文件:
cloudshell edit LAB02/football_agents/specialist_agents/defender.py
在 # TODO Task 4a 处填入:
defender_agent = LlmAgent(
name="DefenderSpecialist",
model=GeminiConstants.GEMINI_FLASH_LITE,
description="...",
instruction="""You are a defender specialist...""",
tools=[update_profile],
output_key="defender_response",
)
然后用同样的方式,分别编辑并填好另外三个文件:
specialist_agents/midfielder.py → midfielder_agent(name=MidfielderSpecialist,output_key=midfielder_response)specialist_agents/forward.py → forward_agent(name=ForwardSpecialist,output_key=forward_response)specialist_agents/goalkeeper.py → goalkeeper_agent(name=GoalkeeperSpecialist,output_key=goalkeeper_response)保存所有文件。
这段在干嘛: 现在把四位专家"交给"队长。这里用的不是 A2A(那是跨服务器用的),而是 AgentTool——因为专家和队长跑在同一个服务器里,队长可以把每位专家当成一个工具函数直接调用,而且能**同时(并行)**调用四个,效率最高。这对应架构图 A 里"③ 本地 AgentTool 调用"那几条箭头。
回到队长文件:
cloudshell edit LAB02/football_agents/captain.py
在文件顶部补上导入:
from google.adk.tools import AgentTool
from football_agents.specialist_agents.defender import defender_agent
from football_agents.specialist_agents.midfielder import midfielder_agent
from football_agents.specialist_agents.forward import forward_agent
from football_agents.specialist_agents.goalkeeper import goalkeeper_agent
修改队长 Agent 的定义,给它加上 tools,并在 instruction 里要求它输出一份 JSON 格式的"战术会议纪要(huddle)":
captain_agent = LlmAgent(
name="TeamCaptain",
model=GeminiConstants.GEMINI_FLASH_LITE,
description="...",
instruction="""...output a JSON huddle...""",
tools=[
AgentTool(defender_agent),
AgentTool(midfielder_agent),
AgentTool(forward_agent),
AgentTool(goalkeeper_agent),
],
)
保存所有文件。
这段在干嘛: 到目前为止球员只会执行战术。这一步我们让球员能感知体力,并在太累时自动申请换人。实现方式是 MCP(Model Context Protocol)——一套让 Agent 调用外部工具服务的统一标准接口。你可以把 MCP 想成球员戴的智能手表:手表(MCP 服务)持续监测体力,数据通过标准接口回传给球员智能体,球员据此决定要不要下场。这对应架构图 C。
编辑后卫文件(其它专家同理):
cloudshell edit LAB02/football_agents/specialist_agents/defender.py
在导入区补上:
from .tools import make_condition_toolset, CONDITION_GUIDANCE
修改专家的 tools,注意要用 * 把工具集展开:
tools=[update_profile, *make_condition_toolset()]
编辑工具文件:
cloudshell edit LAB02/football_agents/specialist_agents/tools.py
把开关改为 True:
USE_REAL_MCP_SERVER = True
保存所有文件。
激动人心的时刻到了——把整套系统跑起来,亲自当一回教练!
cd 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"的组合,正是当下构建生产级多智能体应用的主流范式。把它用到你自己的业务场景里——把"球员"换成你的业务模块,把"喊话"换成用户请求,一样成立。