使用 ADK 构建 AI 智能体:编排图工作流

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 开发者计划赠金,以便在没有信用卡的情况下预配结算账号。

领取活动赠金

  1. 在无痕窗口中打开研讨会赠金链接,然后使用您的 @gmail.com 账号登录。
  2. 在领取福利页面上,点击领取赠金:

Google Builders Hub“领取福利”卡片,其中显示了“领取赠金”按钮

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

Google 开发者计划“请扫描此二维码,以验证您是真人”二维码对话框

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

Google Builders Hub 页面,显示“已成功领取活动赠金”和“打开结算账号”按钮

🔍 检查您的工作

结算账号开设最多需要 3 分钟:

  1. 打开 Google Cloud Billing(或将网址复制到无痕式窗口中),然后选择您的结算账号:
https://console.cloud.google.com/billing
  1. 刷新,直到您的 GDP Credit 账号显示状态为有效:

Google Cloud Billing 账号管理页面,其中显示了一个有效的 GDP 赠金结算账号

3. 创建 Google Cloud 项目(可选)

创建一个专用的 Google Cloud 项目,以便将此 Codelab 中的所有资源隔离开来,并便于清理。

  1. 打开创建项目(或将网址复制到无痕式窗口中):
https://console.cloud.google.com/projectcreate
  1. 输入项目详细信息:
    • 项目名称:例如 google-cloud-workshop
    • 结算账号(如果显示):选择 GDP Credit(或您的有效结算账号)
    • 父级资源:无组织(或根据需要选择您的组织)
  2. 记下项目名称下方生成的项目 ID,然后点击创建:

“创建 Google Cloud 项目”界面,其中显示了“项目名称”“项目 ID”“父级资源”和“创建”按钮

🔍 检查您的工作

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

Google Cloud 控制台“通知”菜单,其中显示“创建项目”和“选择项目”链接

4. 设置您的环境

配置 Google Cloud Shell,启用 Agent Platform API,并使用 uv 创建一个隔离的 Python 3.12 虚拟环境。

配置 Cloud Shell

  1. 点击 Google Cloud 控制台工具栏中的激活 Cloud Shell (>_),或直接打开 Google Cloud Shell(或将网址复制到无痕式窗口中):
https://shell.cloud.google.com/?show=ide,terminal

Google Cloud 控制台工具栏中的“激活 Cloud Shell”按钮

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

“为 Cloud Shell 提供授权”对话框

  1. 从终端的编号菜单中选择有效的 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 提示中以黄色突出显示:

Cloud Shell 终端,其中所选项目 ID 以黄色突出显示

启用必需的 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) 前缀显示在终端提示符的开头:

Cloud Shell 终端,显示活跃 Python 虚拟环境上的 .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 键):

  1. Choose a model for the root agent(为根智能体选择模型):输入 2 以选择 gemini-3.8-flash(选项 1 会选择 gemini-3.5-flash),然后按 Enter 键。
  2. 选择后端:输入 2 以选择 Google Cloud Agent Platform 后端(下方 CLI 菜单中的选项 2),然后按 Enter。
  3. Enter Google Cloud project ID:按 Enter 键,接受括号中检测到的项目 ID。
  4. 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,然后点击更改并预览:

Cloud Shell 网页预览端口配置对话框,设置为 8000

🔍 检查您的工作

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

ADK 开发网页界面,显示基本的单智能体对话

您应该会在对话窗格中看到单轮对话回复(在事件中会看到单次 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 保存的匹配会话状态键。

架构图,显示 travel_workflow(工作流)将 START 连接到 user_profiler_agent 和 itinerary_synthesizer_agent

创建初始专家

确保 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

ADK 2.14.0 网页界面,显示从 START 到 user_profiler_agent 再到 itinerary_synthesizer_agent 的图执行过程,以及生成的京都行程

点击事件中的任意事件,然后点击图表图标(事件详情工具栏中的 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,而不是触发两次合成。

架构图,展示了 travel_workflow 从 user_profiler_agent 通过 node_input 并行扇出到 activity_finder_agent 和 restaurant_finder_agent,在 research_join (JoinNode) 处合并,并将 node_input (dict) 传递给 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) 提交到会话状态。

架构图,显示 travel_workflow,其中 itinerary_critic_agent 提供 route_critique,后者将“revise”路由到 itinerary_refiner_agent(循环回到 itinerary_critic_agent),或将“approved”路由到 finalize_itinerary

附加批评者和优化者专家

确认 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 调用

后续步骤