1. 概览
为了满足多约束条件的旅行请求(例如,规划一个 100 美元的京都一日游,包含历史寺庙、当地拉面,且不包含现代艺术博物馆),需要提取结构化偏好、同时研究地点、合成详细草稿,并在回复前审核总费用。在 ADK 1.x 中,编排这些阶段需要嵌套 SequentialAgent、ParallelAgent 和 LoopAgent 封装容器,将每个中间输出保存到会话状态中,并调用额外的 LLM 工具调用,以便退出优化循环。
在此 Codelab 中,您将使用智能体开发套件 (ADK) 2 图工作流 (Workflow) 在智能体平台上构建一个多智能体旅行规划工具。在三个渐进阶段中,您将按顺序边缘链 (node_input) 连接专业 Agent 节点(由 gemini-3.8-flash 提供支持),扇出与 JoinNode 同步的并发 google_search 研究员,并使用确定性 Python 路由器 (Event(route=...)) 强制执行预算和排除规则,只要初始草稿通过审核,该路由器就会跳过优化。
前提条件
学习内容
- 如何在 ADK 2 中使用
Workflow边缘元组声明多智能体流水线 - ADK 2 如何将输出作为
node_input直接沿边传递,并将会话状态键绑定为函数参数 - 如何使用
JoinNode扇出并发研究分支并合并其输出 - 如何使用
Event(route=...)在 Python 中确定性地路由有界限的 maker-checker 循环 - 如何在 ADK Web 界面中检查已编译的工作流图、并行执行时间轴和状态增量
所需条件
- Google 账号(如果需要领取研讨会赠金,请使用个人
@gmail.com账号) - 讲师提供的研讨会赠金链接(以及用于扫描二维码的手机摄像头)或有效的 Cloud Billing 账号
- Google Chrome(建议使用无痕式窗口,以免混淆个人账号和工作账号)
2. 领取 Google Cloud 赠金(可选)
如果您参加的研讨会提供活动赠金链接,请领取 Google 开发者计划赠金,以便在没有信用卡的情况下预配结算账号。
领取活动赠金
- 在无痕窗口中打开研讨会赠金链接,然后使用您的
@gmail.com账号登录。 - 在领取福利页面上,点击领取赠金:

- 如果系统提示您创建 Google 开发者计划个人资料,请填写表单。
- 当系统显示扫描以验证您是真人时,请使用手机扫描二维码并完成验证(如果未显示二维码,请刷新并再次点击领取赠金):

- 确认显示已成功领取活动赠金!,然后点击打开结算账号:

🔍 检查您的工作
结算账号开设最多需要 3 分钟:
- 打开 Google Cloud Billing(或将网址复制到无痕式窗口中),然后选择您的结算账号:
https://console.cloud.google.com/billing
- 刷新,直到您的
GDP Credit账号显示状态为有效:

3. 创建 Google Cloud 项目(可选)
创建一个专用的 Google Cloud 项目,以便将此 Codelab 中的所有资源隔离开来,并便于清理。
- 打开创建项目(或将网址复制到无痕式窗口中):
https://console.cloud.google.com/projectcreate
- 输入项目详细信息:
- 项目名称:例如
google-cloud-workshop - 结算账号(如果显示):选择
GDP Credit(或您的有效结算账号) - 父级资源:无组织(或根据需要选择您的组织)
- 项目名称:例如
- 记下项目名称下方生成的项目 ID,然后点击创建:

🔍 检查您的工作
点击右上角工具栏中的通知,然后在项目创建完成后点击选择项目:

4. 设置您的环境
配置 Google Cloud Shell,启用 Agent Platform API,并使用 uv 创建一个隔离的 Python 3.12 虚拟环境。
配置 Cloud Shell
- 点击 Google Cloud 控制台工具栏中的激活 Cloud Shell (
>_),或直接打开 Google Cloud Shell(或将网址复制到无痕式窗口中):
https://shell.cloud.google.com/?show=ide,terminal

- 如果出现提示,请点击授权:

- 从终端的编号菜单中选择有效的 Google Cloud 项目 ID:
unset CLOUDSDK_CORE_PROJECT
PS3="Select your project (e.g. 1): "
select PROJECT_ID in $(gcloud projects list --format="value(projectId)"); do
[ -n "$PROJECT_ID" ] && gcloud config set project "$PROJECT_ID" && break
done
您应该会看到如下所示的输出(未标记的项目也会输出信息性 [environment: untagged] 行):
1) google-cloud-workshop-4210 2) my-cloud-project-8812 Select your project (e.g. 1): 1 Updated property [core/project].
验证您选择的项目 ID 是否在 Cloud Shell 提示中以黄色突出显示:

启用必需的 API
启用 Agent Platform API,以便 ADK 智能体可以调用 Gemini 模型:
gcloud services enable aiplatform.googleapis.com
您看到的输出结果应该类似于以下内容:
Operation "operations/acat.p2-..." finished successfully.
创建 Python 虚拟环境并安装 ADK
创建 ~/adk-graph-agents 工作区并激活 Python 3.12 虚拟环境:
cd ~
mkdir -p adk-graph-agents && cd ~/adk-graph-agents
which uv >/dev/null 2>&1 || curl -LsSf https://astral.sh/uv/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"
uv venv .venv --python 3.12
source .venv/bin/activate
确认 (.venv) 前缀显示在终端提示符的开头:

在活跃的虚拟环境中安装 ADK 2:
uv pip install --no-cache "google-adk==2.14.0"
🔍 检查您的工作
验证已安装的 adk CLI 版本:
adk --version
您看到的输出结果应该类似于以下内容:
adk, version 2.14.0
5. 搭建旅行规划工具的框架
使用 adk create 生成入门级 travel_planner 软件包,并在 adk web 中测试默认的单智能体基准。
生成入门软件包
从 ~/adk-graph-agents 运行 adk create:
cd ~/adk-graph-agents
adk create travel_planner
在系统提示时选择以下选项(如果系统在首次运行时提示 Enable telemetry? [Y/n]:,请按 Enter 键或输入 n 并按 Enter 键):
- Choose a model for the root agent(为根智能体选择模型):输入
2以选择gemini-3.8-flash(选项1会选择gemini-3.5-flash),然后按 Enter 键。 - 选择后端:输入
2以选择 Google Cloud Agent Platform 后端(下方 CLI 菜单中的选项2),然后按 Enter。 - Enter Google Cloud project ID:按 Enter 键,接受括号中检测到的项目 ID。
- Enter Google Cloud region(输入 Google Cloud 区域):输入
global(以使用全球 Gemini 端点),然后按 Enter 键。
adk create 会将 GOOGLE_GENAI_USE_ENTERPRISE=1、您的项目 ID 和 GOOGLE_CLOUD_LOCATION=global 写入 travel_planner/.env,这样,当您稍后替换 agent.py 时,您的凭证会保持不变。
您看到的输出结果应该类似于以下内容:
Choose a model for the root agent: 1. gemini-3.5-flash 2. gemini-3.8-flash 3. Other models (fill later) Choose model (1, 2, 3): 2 1. Google AI 2. Vertex AI 3. Login with Google Choose a backend (1, 2, 3): 2 You need an existing Google Cloud account and project, check out this link for details: https://google.github.io/adk-docs/get-started/quickstart/#gemini---google-cloud-vertex-ai Enter Google Cloud project ID [google-cloud-workshop-4210]: Enter Google Cloud region [us-central1]: global Agent created in /home/your_username/adk-graph-agents/travel_planner: - .env - .gitignore - __init__.py - agent.py ⚠️ WARNING: Secrets (like GOOGLE_API_KEY) are stored in .env. Next steps: - Chat with your agent in the terminal: adk run travel_planner - Or in the browser: adk web . - Have your coding agent evaluate and deploy it: uvx google-agents-cli setup (other install options: https://adk.dev/get-started/agents-cli/)
在 Cloud Shell 代码编辑器中打开 ~/adk-graph-agents:
which cloudshell >/dev/null 2>&1 && cloudshell workspace ~/adk-graph-agents || true
在重新加载的终端中重新激活虚拟环境:
cd ~/adk-graph-agents
source .venv/bin/activate
测试起始单智能体
启动 ADK Web 界面,观察单个未经编排的入门级智能体如何回答旅行提示:
cd ~/adk-graph-agents
adk web --allow_origins "*"
点击 Cloud Shell 工具栏右上角的网页预览图标,选择更改端口,输入 8000,然后点击更改并预览:

🔍 检查您的工作
在 adk web 左上角的下拉菜单中选择 travel_planner,然后发送 "Help me plan a trip to Japan":

您应该会在对话窗格中看到单轮对话回复(在事件中会看到单次 root_agent 调用):
root_agent -> responds in one pass without structured preference extraction, live search, or budget verification
在 Cloud Shell 终端中按 Ctrl + C 停止 adk web,然后再在下一步中修改文件。
6. 构建线性图工作流
构建您的首个 ADK 2 Workflow:一个将预留的 "START" 入口点连接到 user_profiler_agent 和 itinerary_synthesizer_agent 的依序链(任何没有传出边的节点都充当终端节点)。每个边缘 (a, b) 都会在隔离的 single_turn 模式下自动运行 Agent(LlmAgent 的别名)节点,并将节点 a 的输出作为 node_input(传入的用户消息)传递给节点 b,而指令字符串中的 {user_preferences} 会插入通过 output_key 保存的匹配会话状态键。

创建初始专家
确保 adk web 已停止 (Ctrl + C),然后打开一个名为 travel_planner/specialists.py 的新文件:
cloudshell edit ~/adk-graph-agents/travel_planner/specialists.py
将以下代码粘贴到 travel_planner/specialists.py 中,以定义 user_profiler_agent 和 itinerary_synthesizer_agent(提前导入 google_search,以便在下一步中使用):
from google.adk import Agent
from google.adk.tools import google_search
MODEL = "gemini-3.8-flash"
# --- Specialist 1: The User Profiler ---
user_profiler_agent = Agent(
model=MODEL,
name="user_profiler_agent",
description="Extracts travel preferences and constraints from user input.",
instruction="""
Extract destination, duration (default 1 day), budget, interests, and exclusions.
Return a single JSON object with keys: "destination", "duration", "budget", "interests", "exclusions".
""",
output_key="user_preferences", # Saved for multi-hop constraints downstream
)
# --- Specialist 2: The Synthesizer ---
itinerary_synthesizer_agent = Agent(
model=MODEL,
name="itinerary_synthesizer_agent",
description="Compiles preferences and research into a final Markdown itinerary.",
instruction="""
You are an elite Travel Concierge. Using the incoming input and
User Preferences ({user_preferences}), write a Markdown itinerary
with an itemized USD and local currency cost breakdown.
""",
output_key="final_itinerary",
)
定义顺序边缘链
打开一个名为 travel_planner/workflows.py 的新文件:
cloudshell edit ~/adk-graph-agents/travel_planner/workflows.py
粘贴以下 Workflow 定义(其中包含 3 个或更多节点的元组会编译为 "START" 中的顺序链):
from google.adk import Workflow
from .specialists import (
itinerary_synthesizer_agent,
user_profiler_agent,
)
# 1. SEQUENTIAL GRAPH: START -> user_profiler_agent -> itinerary_synthesizer_agent
travel_workflow = Workflow(
name="travel_workflow",
description="Orchestrates the end-to-end travel planning process as a graph workflow.",
edges=[
("START", user_profiler_agent, itinerary_synthesizer_agent),
],
)
将工作流公开为 root_agent
打开 travel_planner/agent.py:
cloudshell edit ~/adk-graph-agents/travel_planner/agent.py
将 travel_planner/agent.py 的全部内容替换为以下两行:
from .workflows import travel_workflow
root_agent = travel_workflow
🔍 检查您的工作
从 ~/adk-graph-agents 启动 adk web:
cd ~/adk-graph-agents
adk web --allow_origins "*"
在端口 8000 上打开 Web 预览,点击 + 新会话,然后发送:
Help me plan a trip to Kyoto
在事件视图中,您应该会看到如下所示的事件序列:
1. user_profiler_agent -> outputs JSON {"destination": "Kyoto", "duration": "1 day", ...} and writes state["user_preferences"]
2. itinerary_synthesizer_agent -> receives preferences via node_input and outputs the Markdown itinerary

点击事件中的任意事件,然后点击图表图标(事件详情工具栏中的 account_tree)以检查已编译的 START -> user_profiler_agent -> itinerary_synthesizer_agent 拓扑,然后查看状态标签页,验证 user_preferences 和 final_itinerary 是否已填充。
在终端中按 Ctrl + C 停止 adk web,然后再继续。
7. 使用 JoinNode 进行扇出和联接
通过添加两个并发 google_search 专家(activity_finder_agent 和 restaurant_finder_agent)并将其与 JoinNode 同步,将行程建立在实时网络数据的基础上:
- 扇出元组:将
(activity_finder_agent, restaurant_finder_agent)放入边缘元组中,可同时将user_profiler_agent的输出作为node_input传递给两位研究人员。 - 扇入屏障 (
JoinNode):将JoinNode(name="research_join")放在元组之后,会等待每个传入分支完成,并将一个合并的dict({"activity_finder_agent": ..., "restaurant_finder_agent": ...}) 作为node_input传递给itinerary_synthesizer_agent,而不是触发两次合成。

附加研究专家
确认 adk web 已停止 (Ctrl + C),然后打开 travel_planner/specialists.py:
cloudshell edit ~/adk-graph-agents/travel_planner/specialists.py
将 activity_finder_agent 和 restaurant_finder_agent 附加到 travel_planner/specialists.py 的底部(两者都不需要 output_key,因为 research_join 会通过 node_input 直接转发这两个输出):
# --- Specialist 3: The Activity Finder ---
activity_finder_agent = Agent(
model=MODEL,
name="activity_finder_agent",
description="Finds 3 attractions matching the user's destination and interests.",
instruction="""
Using the travel preferences in the input, call google_search to find 3 specific attractions
(including budget-friendly options).
Return a JSON list with keys: "attraction_name", "estimated_cost_local", "estimated_cost_usd", "notes".
""",
tools=[google_search],
)
# --- Specialist 4: The Restaurant Finder ---
restaurant_finder_agent = Agent(
model=MODEL,
name="restaurant_finder_agent",
description="Finds 3 restaurants matching the destination and budget.",
instruction="""
Using the travel preferences in the input, call google_search to find 3 highly-rated restaurants
across price points.
Return a JSON list with keys: "restaurant_name", "cuisine", "estimated_cost_local", "estimated_cost_usd".
""",
tools=[google_search],
)
向 workflows.py 添加扇出和 JoinNode
打开 travel_planner/workflows.py:
cloudshell edit ~/adk-graph-agents/travel_planner/workflows.py
将 travel_planner/workflows.py 的全部内容替换为扇出和 JoinNode 图:
from google.adk import Workflow
from google.adk.workflow import JoinNode
from .specialists import (
activity_finder_agent,
itinerary_synthesizer_agent,
restaurant_finder_agent,
user_profiler_agent,
)
research_join = JoinNode(name="research_join")
# 2. FAN-OUT / FAN-IN GRAPH: Profile -> (Activities || Restaurants) -> Join -> Synthesize
travel_workflow = Workflow(
name="travel_workflow",
description="Orchestrates the end-to-end travel planning process as a graph workflow.",
edges=[
(
"START",
user_profiler_agent,
(activity_finder_agent, restaurant_finder_agent),
research_join,
itinerary_synthesizer_agent,
),
],
)
🔍 检查您的工作
启动 adk web 以测试并发搜索和 JoinNode 同步:
cd ~/adk-graph-agents
adk web --allow_origins "*"
在端口 8000 上打开 Web 预览,点击 + 新会话,然后发送:
Help me plan a trip to Kyoto focused on historic temples and ramen
在事件视图中,您应该会看到如下所示的事件序列:
1. user_profiler_agent -> emits JSON preferences via node_input
2. activity_finder_agent & restaurant_finder_agent -> invoke google_search concurrently
3. research_join -> merges both researcher outputs into one dict node_input
4. itinerary_synthesizer_agent -> drafts final_itinerary from the merged dict + {user_preferences}
在终端中按 Ctrl + C 停止 adk web,然后再继续。
8. 路线修订周期
通过添加一个 itinerary_critic_agent、一个 itinerary_refiner_agent 和两个返回 ADK Event 对象的确定性 Python 函数节点(route_critique 和 finalize_itinerary),强制执行预算和负面限制:
route:选择字典边缘 ({"revise": ..., "approved": ...}) 中的哪个命名分支键接下来运行。output:沿边缘将类型化 Python 值或字符串作为node_input传递给下一个节点,而不呈现聊天消息。message和state:message会在对话记录中输出用户可见的文本,而state会将键值对更新 (stateDelta) 提交到会话状态。

附加批评者和优化者专家
确认 adk web 已停止 (Ctrl + C),然后打开 travel_planner/specialists.py:
cloudshell edit ~/adk-graph-agents/travel_planner/specialists.py
将 itinerary_critic_agent 和 itinerary_refiner_agent 附加到 travel_planner/specialists.py 的底部(优化器不需要 google_search 或 exit_loop 工具,因为它会交换已在 {final_itinerary} 中收集的内容,并且仅在 route_critique 选择 "revise" 时运行):
# --- Specialist 5: The Budget & Constraint Critic ---
itinerary_critic_agent = Agent(
model=MODEL,
name="itinerary_critic_agent",
description="Audits the itinerary draft against budget and negative constraints.",
instruction="""
Audit the itinerary in the input against User Preferences ({user_preferences}).
Verify that the USD total stays within the user's budget and no excluded venue types appear.
If all checks pass, respond EXACTLY with the single word "APPROVED".
Otherwise, output 1-2 concise revision instructions explaining what to swap or adjust.
""",
)
# --- Specialist 6: The Itinerary Refiner ---
itinerary_refiner_agent = Agent(
model=MODEL,
name="itinerary_refiner_agent",
description="Revises the itinerary draft using the critic's feedback.",
instruction="""
Revise Current Itinerary ({final_itinerary}) using the auditor critique in the input
so all USD budget and exclusion rules in {user_preferences} pass.
Output ONLY the updated Markdown itinerary.
""",
output_key="final_itinerary",
)
添加 route_critique 和周期
打开 travel_planner/workflows.py:
cloudshell edit ~/adk-graph-agents/travel_planner/workflows.py
将 travel_planner/workflows.py 的全部内容替换为完整的图表工作流:
from google.adk import Event, Workflow
from google.adk.workflow import JoinNode
from .specialists import (
activity_finder_agent, itinerary_critic_agent, itinerary_refiner_agent,
itinerary_synthesizer_agent, restaurant_finder_agent, user_profiler_agent,
)
MAX_REVISIONS = 2
research_join = JoinNode(name="research_join")
def route_critique(node_input: str, revision_count: int = 0):
"""Routes to 'approved' when constraints pass, or 'revise' up to MAX_REVISIONS."""
approved = node_input.strip().rstrip(".").upper() == "APPROVED"
if approved or revision_count >= MAX_REVISIONS:
return Event(route="approved", output=approved)
return Event(
route="revise", output=node_input, state={"revision_count": revision_count + 1}
)
def finalize_itinerary(node_input: bool, final_itinerary: str, revision_count: int = 0):
"""Emits the verified itinerary and resets revision_count for the next turn."""
status = (
f"Itinerary approved after {revision_count} revision(s)."
if node_input
else f"Max revisions ({revision_count}) reached; returning best-effort draft."
)
return Event(
message=f"{status}\n\n{final_itinerary}",
output=final_itinerary,
state={"revision_count": 0},
)
travel_workflow = Workflow(
name="travel_workflow",
description="Orchestrates the end-to-end travel planning process as a graph workflow.",
edges=[
(
"START",
user_profiler_agent,
(activity_finder_agent, restaurant_finder_agent),
research_join,
itinerary_synthesizer_agent,
itinerary_critic_agent,
route_critique,
),
(route_critique, {"revise": itinerary_refiner_agent, "approved": finalize_itinerary}),
(itinerary_refiner_agent, itinerary_critic_agent),
],
)
🔍 检查您的工作
验证 travel_planner/workflows.py 是否能顺利编译并输出有向图的边:
cd ~/adk-graph-agents && source .venv/bin/activate
python3 -c "from travel_planner.agent import root_agent; print('\n'.join(f'{e.from_node.name} -> {e.to_node.name} (route={e.route})' for e in root_agent.graph.edges))"
您看到的输出结果应该类似于以下内容:
__START__ -> user_profiler_agent (route=None) user_profiler_agent -> activity_finder_agent (route=None) user_profiler_agent -> restaurant_finder_agent (route=None) activity_finder_agent -> research_join (route=None) restaurant_finder_agent -> research_join (route=None) research_join -> itinerary_synthesizer_agent (route=None) itinerary_synthesizer_agent -> itinerary_critic_agent (route=None) itinerary_critic_agent -> route_critique (route=None) route_critique -> itinerary_refiner_agent (route=revise) route_critique -> finalize_itinerary (route=approved) itinerary_refiner_agent -> itinerary_critic_agent (route=None)
接下来,启动 adk web 以测试即时审批和预算触发的修订:
adk web --allow_origins "*"
在端口 8000 上打开 Web 预览,点击 + 新会话,然后发送包含多个限制条件的京都提示:
Design a full-day itinerary for Kyoto with a total budget of $100. My main interests are historical landmarks, temples, and authentic local cuisine. Please do not include any modern art museums or galleries.
如需观察 route="revise" 循环的实际运行情况,请发送一个预算紧张($15)的后续提示:
Now design a full-day Kyoto itinerary with paid temple admissions, lunch, and dinner on a strict total budget of $15 USD.
在事件视图中,您应该会看到如下所示的执行序列:
1. user_profiler_agent -> writes JSON constraints to state["user_preferences"] 2. (activity_finder_agent, restaurant_finder_agent) -> run google_search concurrently -> research_join 3. itinerary_synthesizer_agent -> writes initial draft to state["final_itinerary"] 4. itinerary_critic_agent -> outputs "APPROVED" (or revision instructions when over budget) 5. route_critique -> emits route="approved" (skipping refiner) or route="revise" -> itinerary_refiner_agent -> itinerary_critic_agent 6. finalize_itinerary -> emits "Itinerary approved after N revision(s)." (or max-revisions notice) and resets revision_count=0
在终端中按 Ctrl + C 即可停止 adk web。
9. 问题排查
adk web 中的代理下拉菜单为空
- 原因:
adk web是在travel_planner/(而非父级~/adk-graph-agents目录)中启动的,或者 Cloud Shell 会话已重启。 - 解决方法:按
Ctrl + C,返回父级目录,然后使用cd ~/adk-graph-agents && source .venv/bin/activate && adk web --allow_origins "*"重新启动。
404 NOT_FOUND 个模型错误
- 原因:您在
adk create期间按了 Enter 键以接受默认的us-central1区域,而不是输入global。 - 解决方法:打开
~/adk-graph-agents/travel_planner/.env,并确保已设置GOOGLE_CLOUD_LOCATION=global。
ERROR: [Errno 98] address already in use(端口:8000)
- 原因:之前的
adk web服务器仍在 Cloud Shell 中运行。 - 解决方法:在活跃终端中按
Ctrl + C,或使用pkill -f "adk web"停止现有进程,然后再启动adk web。
10. 清理
移除本地工作区,并视需要停用 API 或删除专用研讨会项目。
删除本地项目文件
移除 ~/adk-graph-agents 目录和虚拟环境:
cd ~
rm -rf ~/adk-graph-agents
停用 Agent Platform API(可选)
如果您在此项目中不再需要 Agent Platform API,请将其停用:
gcloud services disable aiplatform.googleapis.com --quiet
🔍 检查您的工作
确认 ~/adk-graph-agents 已被移除:
test ! -d ~/adk-graph-agents && echo "Workspace ~/adk-graph-agents removed"
您看到的输出结果应该类似于以下内容:
Workspace ~/adk-graph-agents removed
11. 恭喜
您使用 ADK 2 Workflow 构建了一个多智能体旅行规划工具,在单个声明式边缘图中整合了顺序链、使用 JoinNode 的并发 google_search 扇出以及条件式创建者-检查者循环。
您完成的任务
- 使用 ADK 2 声明了多智能体流水线
Workflow(edges=[...]) - 通过
node_input和状态绑定参数在Agent、JoinNode和 Python 函数节点之间直接传递数据,同时尽可能减少会话状态 - 扇出并发
google_search分支,并将其与JoinNode同步 - 将
LoopAgent和基于工具的循环上报替换为确定性Event(route=...)路由器,该路由器会跳过对已批准草稿的不必要 LLM 调用
后续步骤
- 探索 ADK 中的多智能体系统
- 加入 GEAR(Gemini Enterprise Agent Ready)计划和“获得认证”计划,参加实操研讨会并学习相关课程