ADK 2 自動化調度管理:圖表、協作和動態工作流程

1. 總覽

ADK 2 的主打功能是三種自動化調度管理模式。本程式碼研究室會逐步建構「馬拉松賽事教練」應用程式,同時說明這三種概念。每個層級都會回答單一問題、新增一個想法,並自行運作。

課程內容

  • 圖形工作流程 (第 1 支柱) - 您可以在輸入內容抵達前繪製流程
  • 協作代理程式 (第 2 支柱) - 當您知道團隊,但要求會挑選子集時,以及所有三種協作模式 (chat / task / single_turn),都會即時執行。
  • 動態工作流程 (第 3 支柱) - 工作本身的形狀取決於輸入內容。
  • 如何選擇:單一問題的決策樹,以及模式的組成方式。

貫穿的主題

已知結構 → 已知團隊 / 變數子集 → 不明形狀 → 選擇正確的形狀

學習藍圖

建構項目

一個應用程式 (馬拉松賽事教練) 一次組裝一個可執行的層級。每個層級都是您從終端機執行的純 Python 模組;到了 L5,您就能擁有下列所有片段。

圖片是從執行的程式碼繪製:每條實線都是從 Workflow.graph.edges 讀取。這就是第一課的內容:可預先繪製的內容正是支柱 1,而無法預先繪製的內容則說明瞭支柱 2 和 3 的存在意義。

整個應用程式,以及圖表無法顯示的內容

軟硬體需求

  • Google 帳戶 (適用於 Colab) - 無須進行本機設定
  • 約 50 分鐘 (L4 級別的兩項作業耗時較長,請預留時間)。
  • 存取 Gemini 模型的方式有兩種,選擇路徑:您會執行一個設定步驟,並略過其他步驟:

🎓 研討會

🏠 居家

參與者

你正在參加現場研討會,講師提供抵免學分連結

其他人 (包括研討會參與者)

需求條件

聲明連結,以及可建立雲端專案的 Google 帳戶

免費的 AI Studio API 金鑰

執行

Vertex AI,專案費用會從研討會抵免額扣除

Google AI Studio

費用

抵免額適用範圍

免費方案

設定步驟

工作坊設定 (下一步)

在家設定 (後續步驟)

無論使用哪種方式,從序章開始的所有內容都相同,通道只會決定筆記本與哪個模型端點通訊。

兩種跟著做的方式

下列每個步驟都會對應至 Colab 筆記本中的一個儲存格,以及 GitHub 存放區中的一個資料夾。請選擇下列其中一項:

  • ▶ Colab (建議): 開啟筆記本 → 由上到下執行儲存格。
  • 💻 本機: git clone 存放區./setup_venv.sh,然後將每個層級做為模組 (python -m ...) 執行,或使用 ./run.sh (adk web) 瀏覽所有層級。

2. 研討會設定 · 申請抵免額並切換至 Vertex AI

工作坊會提供 Google Cloud 抵免額。您將聲明擁有該專案,並建立專案,然後將筆記本指向 Vertex AI,而非 AI Studio。一個儲存格會處理聲明後的所有事項。

1 · 領取抵免額 (約 1 分鐘)

  1. 開啟老師分享的兌換連結。網址看起來就像這樣:https://me.developers.google.com/benefits/claim/your-workshop-name
  2. 登入並按照頁面上的指示接受抵免額。
  3. 記下當時使用的 Google 帳戶。以下每個步驟都必須以該帳戶執行。

2. 開啟筆記本並安裝 ADK 2 (~1 分鐘)

按一下「在 Colab 中開啟」,然後執行第一個程式碼儲存格。這會固定本程式碼研究室驗證的確切 ADK 2 版本,並列印 ✓ installed

3 · 執行「Workshop setup」儲存格 (約 3 分鐘)

也就是標題為「🎓 路徑 A - 研討會」的儲存格。運作執行這項程式碼,Colab 會要求您授權,請選擇您剛才用來兌換抵免額的 Google 帳戶,然後允許存取。

這項作業會執行四項工作:在您的抵免額中建立名為 adk-2-tutorial-XXXX 的專案、啟用專案中的 Vertex AI API、設定後續每個儲存格讀取的四個環境變數,然後對 Vertex 進行測試呼叫並等待回應,因此設定作業會完成或告知您原因,而不是在關卡中稍後失敗。

預期的輸出內容 - 最後一行才是重點:

Signed in as: you@example.com
...
Successfully created GCP project 'adk-2-tutorial-4817'.
Successfully linked 'adk-2-tutorial-4817' to billing account '01ABCD-...'.
   waiting for Vertex AI to come up on the new project... (10s)
   waiting for Vertex AI to come up on the new project... (20s)

 Vertex AI on adk-2-tutorial-4817 · us-central1 · gemini-2.5-flash  answered a test call

4 · 略過「帶回家設定」步驟

請勿執行 AI Studio 金鑰儲存格,否則筆記本會切換回 AI Studio,並還原您剛才執行的操作。(儲存格會防範這種情況,並拒絕執行,但更整齊的做法是直接略過)。直接從這裡前往「共用建構區塊」儲存格。

5 · 執行「Shared building blocks」儲存格

執行一次。這會定義 Pydantic 結構定義 + 罐頭馬拉松情境,L2 以上的每個層級都會重複使用。你會看到 ✓ schemas + scenarios ready

研討會結束後

抵免額和抵免額建立的專案都有期限。如要在研討會結束後繼續免費重新執行這些層級,請改為執行「Take-home setup」步驟,取得免費的 AI Studio 金鑰,不必使用雲端專案,也不會產生任何費用。只有該儲存格會變更。

如要提早清理,請開啟 Cloud 控制台,選取 adk-2-tutorial-XXXX,然後刪除。本程式碼研究室中的其他項目不會建立計費資源。

3. 居家設定 · AI Studio API 金鑰

這條路徑上的所有內容都會使用免費的 Google AI Studio API 金鑰執行,不需要 Google Cloud 雲端專案、帳單資訊或在本機安裝。這個步驟大約需要 3 分鐘。

1. 開啟筆記本

按一下「在 Colab 中開啟」圖示 。系統會將您帶往筆記本,其中包含 Markdown 簡介,以及每個等級各一個可執行的儲存格。您會從上到下執行儲存格,每個儲存格都會在下方列印自己的輸出內容。

2. 安裝 ADK 2 (約 1 分鐘)

執行第一個程式碼儲存格。這會固定本程式碼研究室驗證的確切版本:

%pip install -q "google-adk==2.3.0" python-dotenv pydantic nest_asyncio

等待作業完成,畫面會顯示 ✓ installed。(第一次安裝時約需 30 到 60 秒,之後就會快取。)

3 · 從 AI Studio 取得 Gemini API 金鑰 (約 1 分鐘)

  1. 在新瀏覽器分頁中開啟 aistudio.google.com/app/apikey
  2. 使用 Google 帳戶登入。
  3. 按一下右上方的「建立 API 金鑰」
  4. 選擇現有的 Google 專案,或讓系統建立專案。
  5. 複製金鑰,開頭為 AIza...,長度約 40 個字元。

4 · 將金鑰新增至 Colab (約 1 分鐘)

選項 A - Colab Secrets (建議使用;金鑰會保持隱藏):

  1. 按一下 Colab 左側邊欄中的鑰匙圖示 🔑
  2. 按一下「+ 新增密碼」
  3. 將「Name」設為 GOOGLE_API_KEY
  4. 將金鑰貼到「值」
  5. 將「筆記本存取權」切換為「開啟」

選項 B - 系統提示時貼上 (快速):略過密碼;執行下一個儲存格時,系統會顯示隱藏的提示 🔑 Enter your Google AI Studio API key: - 貼上並按下 Enter 鍵。

5. 執行主要儲存格

讀取密碼 (或改用貼上的提示),然後將 ADK 指向 AI Studio (而非 Vertex AI):

import os

# 🏠 TAKE-HOME ONLY — if you ran the Workshop setup cell, skip this one.
if os.environ.get("GOOGLE_GENAI_USE_VERTEXAI") == "True":
    raise SystemExit("✋ You're set up on the workshop path (Vertex AI). Skip this cell.")

# Google AI Studio API key — add GOOGLE_API_KEY in the 🔑 Secrets panel (or paste when prompted).
try:
    from google.colab import userdata
    key = userdata.get("GOOGLE_API_KEY")
except Exception:
    import getpass
    key = getpass.getpass("Enter your Google AI Studio API key: ")

os.environ["GOOGLE_API_KEY"] = "".join(key.split())    # drop any stray whitespace/newlines
os.environ["GOOGLE_GENAI_USE_VERTEXAI"] = "False"      # use AI Studio, not Vertex AI
print("✅ API key set — using Google AI Studio.")

預期的輸出內容: ✅ API key set — using Google AI Studio.

6 · 執行「Shared building blocks」儲存格

執行「Shared building blocks」(共用建構區塊) 儲存格一次。這會定義 Pydantic 結構定義 + 罐頭馬拉松情境,L2 以上的每個層級都會重複使用。你會看到 ✓ schemas + scenarios ready

設定完成!🎽 在 L0 (大家首先建構的版本) 之前,我們來快速瞭解一下。

4. 序言 · 為什麼不使用一個大型提示?

⚡ 執行前,請鎖定要觀察的「一件事」:每個特定數字的來源。這就是整個練習,其他都是裝飾。

在階梯之前,請先執行階梯要取代的項目:一個提示可完成所有工作的代理程式,包括擷取天氣資訊、分析課程、讀取訓練記錄、依條件設定路徑,以及輸出計畫。

你會看到:自信、具體、格式正確的策略,但其中的數字是捏造的。在一次即時運作執行中,它以「我已擷取今天的氣象指標」開頭,並回報 52°F、風速 9 英里/小時,以及從未見過的訓練記錄數據分析。這裡沒有天氣 API、沒有課程資料,也沒有記錄,只有一個不透明的模型呼叫,不是捏造輸入內容,就是將輸入內容變成無用資訊。

這就是疾病,有四種值得命名的症狀:

  1. 不可信:這些資料是 AI 流暢地編造出來的。
  2. 您無法測試 - 步驟 4 的路徑位於散文內,沒有 if 可進行單元測試。
  3. 無法交換步驟:沒有可插入實際天氣 API 的接縫。
  4. 每次都必須支付所有費用:五個步驟、一次大型呼叫,且不會快取決定性部分。

請保持這種感覺。接下來的九個層級會逐步從提示中移除這些步驟:函式擷取 (L1 至 L2a)、if 陳述式路徑 (L2b)、專家分工 (L3a 至 L3b),以及程式碼界定形狀 (L4a 至 L4b)。

超級提示教練:充滿自信,圖表後方沒有任何內容

💻 當地商家: python -m shared.prologue

5. L0 · 您的第一個 ADK 2 代理

藍圖 - 您目前的位置:L0

⚡ 簡而言之:代理程式 = 模型 + 指令 + 可呼叫的工具Runner 則會執行代理程式。這個層級之後的所有內容都只是更多代理程式,以更完善的形狀排列。

問題:模型能否回答問題,並在需要算術運算時實際編寫程式碼

一個概念,三個部分:

  • Agent:會推理的項目 (Gemini 模型 + 指令)。
  • Runner:在工作階段中執行代理程式並串流事件的項目。
  • 工具:模型決定呼叫的簡單 Python 函式 (pace_splits)。ADK 會讀取簽章和說明字串,並將宣告傳遞給模型,無須編寫結構定義。

序言之後的第一項修復作業:LLM 會在腦中進行配速算術,但很可能會出錯。pace_splits 是確定性的 Python,因此答案中的數字是計算出來的,而非即興

Colab:執行 L0 儲存格 · 📁 GitHub:L0_first_agent/ · 💻 本機:python -m L0_first_agent.agent

L0 流程

def pace_splits(target_finish: str) -> dict:
    """Convert a goal time like '3:30:00' into exact per-mile / per-km paces."""
    ...                                  # deterministic Python — no LLM

pace_coach = Agent(
    name="pace_coach", model=MODEL,
    tools=[pace_splits],                 # the model may call it; ADK reads the signature
    instruction="You are a friendly, concise marathon coach. ... If the runner "
                "mentions a goal time, call pace_splits — never do arithmetic yourself.",
)
runner = Runner(node=pace_coach, session_service=InMemorySessionService(), auto_create_session=True)
async for event in runner.run_async(user_id="u1", session_id="s1", new_message=msg):
    ...  # events carry the model's text

🔍 標記: Agent(...) · tools=[pace_splits] · Runner(...)。在輸出內容中,🔧 行是模型決定在回覆期間呼叫程式碼。

您會看到:

   🔧 model called tool  pace_splits({'target_finish': '3:30:00'})
   🔧 tool returned      {'per_mile': '8:00', 'per_km': '4:58', ...}
🧠 Coach: To finish in 3:30:00, you need an average pace of 8:00 per mile...

「🔧」行是課程:在回答期間,模型選擇呼叫函式,而回覆中的確切 8:00/mile 來自您的程式碼,而非來自權杖統計資料。

你可能會想知道: 模型是否一律會呼叫工具?不會,系統會根據每個問題決定是否顯示。如果詢問的內容不含數字,🔧 符號就會消失 (Playground 會要求您嘗試這麼做)。

👀 閱讀: pace_splits (一般函式) 和 tools=[pace_splits] 行。· ▶ 執行。· ✏️ 變更:詢問一般問題 (沒有目標時間) - 請注意 🔧 行消失:模型會決定何時值得呼叫工具。然後重新編寫 instruction 並重新執行,指令就是程式的其餘部分。

6. L1 · 您的第一個工作流程

路線圖 - 您目前的位置:L1

⚡ 簡而言之:一般函式和 LLM 代理是同一種節點。可預測的工作 → 函式 (0 LLM,確定性);推論 → 代理。

問題:如何在一個流程中混合使用純程式碼和 LLM,但只針對程式碼部分支付模型呼叫費用?

一個概念:Workflow 中,一般 Python 函式和 LLM 代理程式都只是同一個 edges 清單中的節點

START ──► fetch_conditions (function, 0 LLM) ──► advise (agent, 1 LLM)

Colab:執行 L1 儲存格 · 📁 GitHub:L1_graph_basics/ · 💻 本機:python -m L1_graph_basics.workflow

L1 流程

函式節點會列印產生的資料 (沒有模型呼叫),然後代理程式會根據收到的實際溫度和風速提供建議:

def fetch_conditions(node_input):                # function node — 0 LLM
    return Event(output=Conditions(temp_f=78, wind_mph=12, conditions="sunny").model_dump())

advise = Agent(name="advise", model=MODEL, mode="single_turn",
               input_schema=Conditions, instruction="...give pacing + gear advice...")

workflow = Workflow(edges=[(START, fetch_conditions, advise)])

🔍 標記:一個邊緣元組 ((START, fetch_conditions, advise)),中間是裸露的 Python 函式,以及驗證交接的 input_schema=

L0 的新功能: Workflow(edges=[...])START (輸入內容進入的位置)、傳回 Event(output=...) 的函式節點,以及 input_schema=Conditions,因此函式的輸出內容會先根據該架構驗證,再提供給代理程式 (以 JSON 文字形式 - input_schema 會驗證邊界,不會將 Python 物件交給代理程式)。

您可能會想: 是否必須先呼叫函式,再呼叫代理程式?不需要,任何順序、任何組合、任何數量都可以。advise 僅次於 fetch_conditions 執行,因為需要 fetch_conditions 的資料。重點在於貴族制度,而非順序。

👀 閱讀:fetch_conditions 會傳回沒有模型呼叫的資料;advise 則有 input_schema=Conditions。· ▶ 執行。· ✏️ 變更:在函式中設定 temp_f=30 並重新執行,建議就會翻轉,且函式仍會耗用 0 次 LLM 呼叫。

7. L2a · 平行扇出 + JoinNode (Pillar 1a)

藍圖 - 您目前的位置:L2a

⚡ 摘要:平行分散 (免費)、等待「所有」結果、打包,然後將完整資訊交給一個代理程式。

問題:你可以在輸入內容抵達前繪製流程。從架構開始:平行收集資料、將資料打包,然後交給一個代理程式。

形狀:

START ──► fetch_weather ──┐
START ──► analyze_course ─┼─► JoinNode ─► strategy (1 agent)
START ──► pull_fitness ───┘   (bundles)

Colab:執行 L2a 儲存格 · 📁 GitHub:L2a_parallel_join/ · 💻 本機:python -m L2a_parallel_join.workflow

L2a 流程

🔍 標記:三個邊緣都從 START 開始,這扇出,而 JoinNode 是會合點。

  • 這三項擷取作業是函式,會平行執行,且不會呼叫 LLM。
  • JoinNode 會等待這三項作業完成,然後將其彙整為一個以函式名稱做為鍵的型別酬載 (BundledRunData)。
  • 一個 strategy 代理會讀取套件並撰寫 RaceStrategy

顯示內容:每次擷取都會列印 started / finished 時間戳記。這三項作業都從 0.0 秒開始,並在 2.0 秒結束 (最慢的擷取作業,而非總和為 4.5 秒 的時間長度)。重疊部分就是平行處理量。(最後印出的 total 實際時間為約 8 秒,因為當中也包含策略代理程式的 LLM 呼叫,請讀取平行聲明的擷取時間戳記,而非總時間。)

💡 序言回呼:巨型提示發明了天氣。這裡的溫度來自擷取 函式,是實際的程式碼和接縫。將罐頭字典換成實際的天氣 API,其他都不會變更。

你可能會想知道:

JoinNode

我需要瞭解哪些概念?簡單來說,這個函式會等待每個平行分支完成,將輸出內容封裝到以串流函式名稱做為鍵的字典中,本身不會計算任何內容。L2b 的路由器可以寫入 node_input["fetch_weather"]["temp_f"],就是因為這個字典。

👀 讀取:三條邊緣從 START 扇出;JoinNode 將這些邊緣綁定至一個代理。· ▶ 執行並讀取時間戳記,而非總和。· ✏️ 變更:進行一次擷取休眠 3.0 - 先預測新的擴散傳遞功能結束時間,然後驗證。

8. L2b · 新增確定性路由器 (第 1b 支柱)

藍圖 - 您目前的位置:L2b

⚡ 簡而言之:L2a 不會受到影響,而單純的 if 則會決定要執行哪一個代理。分支,不需詢問模型。

問題:熱天和冷天的計畫應有所不同。如何分支,而不是要求模型決定?

形狀 (L2a + 路由器):

... JoinNode ─► route_by_weather ─► hot_strategy
               (if-statement)   ─► normal_strategy
                                ─► cold_strategy

Colab:執行 L2b 儲存格 - 嘗試 run("NORMAL") / run("COLD") · 📁 GitHub:L2b_router/ · 💻 本機:python -m L2b_router.workflow COLD

L2b 流程

def route_by_weather(node_input):                        # an if-statement, 0 LLM
    temp = node_input["fetch_weather"]["temp_f"]
    route = "HOT" if temp >= 70 else "COLD" if temp <= 40 else "NORMAL"
    return Event(output=node_input, route=route)

(route_by_weather, {"HOT": hot_strategy, "NORMAL": normal_strategy, "COLD": cold_strategy})

🔍 標記: Event(output=..., route=...) - 命名路徑的函式節點,以及將名稱對應至節點的 dict 邊緣 {"HOT": ..., "NORMAL": ..., "COLD": ...}

重點:三種工作,三種住家:

  • 可預測的工作 → 函式 (3 個平行擷取作業)
  • 明確規則 → 明確轉送 (route_by_weatherif 陳述式,而非模型決策)
  • 推論 → 模型 (執行個策略代理)

顯示內容: temp=78F -> route=HOT,然後是結構化 RaceStrategy淨成本:1 次 LLM 呼叫。

⚠️ 如果新增第四個分支,請一併為 route-dict 提供 DEFAULT_ROUTE 項目。字典不相符的路徑並非錯誤,而是分支會結束,且程式會在沒有輸出內容的情況下結束 0,這會造成令人困惑的偵錯死胡同。

你可能會想知道: 所以 L2b 實際上就是 L2a 加上路由器?沒錯,擷取和聯結作業不會受到影響,而且仍然是1 次 LLM 呼叫。變更內容:「一律是同一位服務專員」改為「由資料選出三位服務專員之一」。

👀 閱讀: route_by_weather路由器是 if 陳述式,不是代理程式。· ▶ 執行 run("COLD")。· ✏️ 變更:新增具有第四個代理程式的 WINDY 分支,並在執行操作閱讀上方的 DEFAULT_ROUTE 警告。

9. L3a · 協作代理:一面旗幟,兩個世界 - 支柱 2

藍圖 - 您目前的位置:L3a

⚡ TL;DR:同一支隊伍,一面旗幟。chat整段對話交給一位專家,然後就此結束;single_turn 將每位專家變成工具,並行子集、自動回覆、一次合成。

問題:您知道團隊,但要求會決定哪些成員應回覆。如何讓 LLM 挑選子集,並同時執行?

形狀:一位協調員負責六位專家 (醫療、天氣、配速、裝備、營養、心理)。這個層級會讓同一組成員執行兩次,也就是使用相同的協調員提示和六位專家。唯一的差別在於子代理程式上的一個旗標。對比就是重點。

Colab:執行 L3a 儲存格 · 📁 GitHub:L3a_collaborative/ · 💻 本機:python -m L3a_collaborative.concierge --mode chat "What about fueling?"

L3a 流程

🔍 標記: 工廠中的 mode="single_turn",以及輸出中的 TRANSFER → (節拍 1),與共用一個時間戳記的 DISPATCH → 行爆發 (節拍 2)。

第 1 拍:先執行預設值,並觀察作業失敗

未撰寫 mode= → 子代理預設為 chat。你會看到:

TRANSFER  nutrition_specialist   (transfer_to_agent  the only tool chat subagents provide)
Final speaker: nutrition_specialist

協調員沒有委派工具,即時通訊子代理只會將整個對話依序轉交給一位專員。transfer_to_agent該專員會直接回覆使用者,並結束執行程序。不可同時出貨。恕不退貨。沒有合成內容。如果問的問題範圍很廣,情況會更糟:六位專員,一次轉接。

這不是錯誤,而是對話模式的正常運作方式。會話群組屬於持有者,直到有人明確轉移為止。適合開放式助理,但不適合管道步驟。

Beat 2:一個旗標,兩個世界

唯一的差別是每位專家都有 mode="single_turn"。相同問題,再次執行:

[t= 7.8s] DISPATCH  medical_specialist       same timestamp =
[t= 7.8s] DISPATCH  weather_specialist         one turn, many calls
[t=14.5s]    medical_specialist replied      replies land inside
[t=14.5s]    weather_specialist replied        one short window
🧠 Concierge (synthesized): <one answer>

現在 ADK 會為每位專家注入一個委派工具,並以子代理命名,由 description= 描述 (協調員選擇子集時會讀取該文字;略過該文字,您就能只根據名稱進行轉送)。協調器會在一個回合中發出多項呼叫,ADK 會並行執行這些呼叫,每個呼叫都會自動傳回結果,協調器則會合成結果。

問題

會開除員工的專家

「那加油呢?」

僅限營養

「My knee hurts at mile 18」(第 18 英里時膝蓋疼痛)

僅限醫療

「我今天應該參加比賽嗎?」

醫療 + 天氣 + 配速

「有什麼需要注意的地方嗎?」

全部 6

為何每位專家都會收到完整簡報:每個 single_turn 子代理都會在各自獨立的會話分支中執行,無法查看對話或同儕。沒有任何環境:協調器必須將整個 SpecialistInput (問題 + 策略 + 執行器資料) 分別轉送至每個平行呼叫。

💡 ADK 2 會直接提供此功能:LLM 會挑選每個要求的子集,並平行執行這些子集,透過 sub_agents + mode="single_turn" 宣告。您可以在 1.x 中將每個專家包裝在 AgentTool 中,組裝成相同的形狀;不同的是,現在這是宣告,而不是管道。(ParallelAgent 一律為 all,而 transfer_to_agent 則為 serial)。

⚠️ 兩項誠實的注意事項:(1) 模型會挑選子集,因此比 L2 的硬式編碼路由器較不具確定性,確切的子集可能會因執行而異。(2) 有時您會看到某位專員的 Error validating input: ... 訊息,這幾乎不會是專家的輸出內容,因為 output_schema 會在伺服器端強制執行這項操作。這是輸入:協調器必須為每個平行呼叫逐字重現整個巢狀 SpecialistInput,有時會出錯。ADK 會將錯誤當做該工具的結果傳回,協調器會復原,合成作業仍會完成。

您可能會好奇:

chat

一次只能委派給一個代理?基本上是:這是 1.x 的預設行為,現在有了名稱。與 single_turn 的差距是三維的:協調員持有的內容 (每個專家一個 transfer_to_agent 與一個工具 );可運作的數量 (一個,擁有對話與 N 個平行對話);控制權是否會回歸 (永不回歸與自動回歸,並附上結果)。程式碼相關資訊:工廠的 if mode == 分支版本「只」存在,因此一個團隊可以透過這兩種方式建構,以進行對比:實際應用程式會將一種模式硬式編碼,而 if 會消失。

👀 讀取:_specialist 工廠 - mode 參數是整個層級。· ▶ 執行兩個節拍。· ✏️ 變更:詢問「我在第 18 英里時膝蓋會痛」先預測子集,然後檢查 DISPATCH 行。

# The factory's mode parameter is THE variable this level teaches:
def _specialist(name, domain, focus, mode):
    kwargs = {}
    if mode == "single_turn":   # the structured contract only makes sense for a TOOL
        kwargs = dict(mode="single_turn",
                      input_schema=SpecialistInput, output_schema=SpecialistResponse)
    return Agent(name=name, model=MODEL,
                 description=f"Marathon {domain} specialist. Consult for: {focus}.",
                 instruction=..., **kwargs)

race_concierge = Agent(name="race_concierge", model=MODEL,
                       sub_agents=[...six specialists...],   # NOTE: no `mode` on the coordinator
                       instruction="...DECIDE which specialists are relevant... call them IN PARALLEL... SYNTHESIZE...")

10. L3b · 任務模式:有終點的對話 - 支柱 2

藍圖 - 您目前的位置:L3b

⚡ 摘要:中間模式 - 與使用者對話直到收集欄位為止,然後自動傳回經過驗證的物件

問題:L3a 留下間隙。chat 擁有整段對話,single_turn 絕不會與使用者交談。但實際的擷取工作介於兩者之間:「與使用者交談,直到收集到 X 為止,然後再返回並提供經過驗證的物件。」這是哪種模式?

形狀:

race_desk (coordinator)
  └─ gear_fitter (mode="task", output_schema=GearOrder)

Colab:執行 L3b 儲存格 · 📁 GitHub:L3b_task_desk/ · 💻 本機:python -m L3b_task_desk.desk

L3b 流程

gear_fitter 保持工作開啟狀態,工作已暫停,但未停止運作

🔍 標記: mode="task" + output_schema= 位於同一代理程式上,且輸出內容包含 ⏸ 暫停和 finish_task 呼叫。

您會看到:

━━ TURN 1 ━━  user: 'I need shoes for the marathon.'
  race_desk  delegate: gear_fitter
  gear_fitter: What is your shoe size?
    The run ENDED  but nothing failed. This is a PAUSED task.

━━ TURN 2 ━━  user: 'Size 9, wide.'   (same session  resumes the task)
  gear_fitter  finish_task   (payload validates as GearOrder)
  race_desk: Your order ... in size 9 Wide has been confirmed.

發生了三件事,而 L3a 模式無法做到:

  1. 執行作業確實在工作期間停止:工作暫停,而非停止回應或失敗。代理提出釐清問題,並暫緩處理工作。(在 adk web 中,您只要輸入答案即可;安全帶會將答案編寫為同一工作階段的第二則訊息)。
  2. 下一則訊息會繼續由「同一位」工作代理程式處理,不會重新轉送或委派。工作階段會知道誰在等待。
  3. finish_task已結束:ADK 因為 mode="task" 而插入的工具。服務專員必須呼叫此函式才能完成,且其酬載必須根據 output_schema 驗證。輸入完成線的對話,控制權會自動返回給協調員,並附上結果。

選擇模式的單一問題規則

💡 「使用者是否需要與其對話?需要對話到何時?」對話 = 無期限 · 工作 = 直到收集完欄位 · 單次對話 = 永不。

模式

人機迴圈

平行?

返回上層

chat (subagent default) - support assistant, open-ended copilot

完整對話

手動 (透過轉移)

task:接案、預訂、疑難排解

僅限釐清問題

自動 (透過 finish_task,並使用經過驗證的物件)

single_turn - 分類、擷取、判斷、生成

yes

自動 (附上結果)

mode 只能在子代理程式上執行,絕不能在協調器上執行。工作流程節點預設為 single_turn (這就是 L1 至 L2b 從未撰寫的原因),而子代理程式預設為 chat (這就是 L3a 必須撰寫的原因)。

⚠️ 在開始建構之前,請先注意以下兩點:(1) task

做為靜態圖形節點時,會因版本而異:在 2.0.0b1 至 2.3.0 (本程式碼研究室的釘選) 中,Workflow(...) 會在建構時引發;請使用這個層級的確切內容 (具有工作子代理程式的聊天協調器),或透過 ctx.run_node 傳送。已在 2.5.0 版中移除。(2) 「工作代理必須是葉節點代理」 (沒有自己的子代理) 是 ADK 的記錄限制,但合約並非執行階段防護措施:2.3.0 和 2.5.0 都不會阻止您。請勿將沒有錯誤視為權限。

💡 深入瞭解:task 嵌入圖形工作流程的代理 (2.5.0 以上版本),具有可將對話迴圈返回以重試的路由:隨附的存放區 22_agent_in_workflow · 完整模式指南:docs/agent-modes.md

你可能會想:

task

買到其他兩款無法提供的產品?三件事:自動回覆 (對話會轉移到其他地方);輸入的終止行 (finish_task 的酬載必須根據結構定義驗證,您會收到資料,而不是轉錄稿);暫停/繼續 (⏸ 是等待人類的保留工作,而不是停止)。

👀 閱讀: gear_fittermode="task" + output_schema 是整個合約。· ▶ 執行。· ✏️ 變更: run_desk("I need a hydration vest", "2 liters, medium") - 釐清問題會調整,但終點線仍會保留。

11. L4a · 執行階段大小的平行擴散傳遞功能 (支柱 3a)

藍圖 - 您目前的位置:L4a

⚡ 簡而言之:骨架仍是三個靜態步驟,動態部分隱藏在中間步驟,寬度由執行階段的資料決定。

⚠️ 注意:這是階梯中最陡峭的一步。先前的層級為 44 行,這個層級則約為 120 行,包含三個代理程式和兩個工作流程節點,且沒有任何填補。請預留約 15 分鐘,並在最後一行讀取/執行/變更:您不需要在第一次傳遞時吸收每一行。

問題:作品的形狀取決於輸入內容。你無法事先繪製圖表。首先是執行階段寬度:讓大型語言模型決定要提出多少子問題。

形狀 (深一層):

START ─► decompose ─► research_topic (parallel_worker) ─► synthesize
                                 
                             └──┴──┴─ (flat: no children yet)

開放式問題會分解成 N 個子問題 (N 是由 LLM 在執行階段選擇,範圍為 3 到 7),每個子問題都會平行研究,然後綜合成一份簡報。

Colab:執行 L4a 儲存格 · 📁 GitHub:L4a_flat_research/ · 💻 本機:python -m L4a_flat_research.deep_research

L4a 流程

🔍 標記 - 沒有

dynamic=True

切換。動態是撰寫方式,不是設定。只有兩個標記:@node(parallel_worker=True) (採用執行階段大小的清單,每個項目執行一個 worker) 和 ctx.run_node(...) (直接排定程式碼節點)。看到任一畫面 → 你已進入動態模式。

你會看到:分解器會列印 5 個子問題,並同時進行研究,然後綜合整理成簡報。每次執行時,數字都會不同,這是固定圖表無法做到的。

工作站上有兩個值得瞭解的標記:

  • 在呼叫 ctx.run_node 的任何節點上,rerun_on_resume=True 為必要項目,否則 ADK 會引發 ValueError。恢復執行時,必須重新執行分派節點,重建所產生的子項,因為這些子項不在靜態圖表中。
  • retry_config= bounds how this FAILS. 平行工作站會取消所有同層級項目,並在其中一個子項失敗時重新引發例外狀況,因此如果沒有重試機制,即使只有一個暫時性的 429 錯誤,也會導致整個執行程序遭到捨棄,包括所有已付費的呼叫。重試會落在內部每個項目的節點上,因此每個分支都會獨立重試。

您可能會想: ADK 是如何「得知」這是動態?不需要,因為任何地方都沒有宣告任何內容。分解器會在執行階段產生清單;平行工作站會根據收到的內容調整大小。動態性是您編寫的資料流程屬性,而非您開啟的模式。

👀 閱讀:research_topic 上的兩個旗標 - parallel_workerrerun_on_resume。· ▶ 執行。· ✏️ 變更:換成您自己的開放式問題 - N 會變更,因為輸入決定了寬度。

12. L4b · Add recursive spawning (Pillar 3b)

藍圖 - 您目前的位置:L4b

⚡ 簡而言之:遞迴是「編寫」而非「提供」,工作人員會透過 ctx.run_node (一般 Python)「呼叫自己」,因此也必須編寫煞車。即 MAX_DEPTH

問題:有時一項研究發現會帶出值得深入探討的狹隘子主題。如何讓分支產生更多平行工作,並保持在界線內?

形狀 (現在是遞迴):

START ─► decompose ─► research_topic (parallel_worker, recursive) ─► synthesize
                                 
                                 └─ research(q3) ─► maybe spawn children
                               └─── research(q2) ─► maybe spawn children
                             └────── research(q1) ─► maybe spawn children

Colab:執行 L4b 儲存格 · 📁 GitHub:L4b_recursion/ · 💻 本機:python -m L4b_recursion.deep_research

L4b 流程

@node(parallel_worker=True, rerun_on_resume=True)
async def research_topic(ctx, node_input):
    finding = coerce(await ctx.run_node(research_agent, node_input=...), ResearchFinding)
    if finding.needs_deeper and finding.deeper_questions and depth < MAX_DEPTH:   # boundary in CODE
        children = await ctx.run_node(research_topic, node_input=deeper)          # recursive fan-out
    yield Event(output={..., "children": children})

🔍 標記:ctx.run_node(research_topic, ...) 本身 (即遞迴) 內的自我參照 is,以及上方一行中的防護措施 depth < MAX_DEPTHresearch_topic

您會看到:研究節點列印 spawning N deeper - 遞迴即時發生 - 然後是執行階段樹狀結構形狀 (例如 5 top-level + 10 recursive children)。每次執行時,樹狀結構都會有所不同。

⚠️ 調高旋鈕前:上限會快速增加,最糟的情況是從約 30 次呼叫增加到約 93 次。MAX_DEPTH=3在執行作業的最後,您可能會看到 cancelling N leftover tasks 記錄行:這是 ADK 在結果完成後,拆除平行工作群組。無害,而且視記錄設定而定,您可能永遠不會看到這項訊息。

你可能會想: 動態遞迴不是預設為啟用嗎?否,L4a 完全是動態的,且沒有遞迴。動態只會提供一般的 Python 控制流程;L4b 選擇使用它編寫遞迴。由於遞迴是「您」撰寫的,因此「您」必須撰寫遞迴的邊界,這時「讓 LLM 塑造工作,將邊界保留在程式碼中」就不再只是口號。

👀 閱讀:警衛:if finding.needs_deeper and depth < MAX_DEPTH。· ▶ 執行。· ✏️ 變更:設定 MAX_DEPTH = 1 並重新執行,樹狀結構就會扁平化 (執行費用也會降低)。邊界是「您」的,以程式碼表示。

13. L5 · Which pattern should you use?

藍圖 - 您目前的位置:L5

⚡ 簡而言之:一個軸向決定一切,也就是由誰選擇下一個步驟:您繪製的圖表、LLM 或您的程式碼。

您已建立所有三個子網路。這就是模式的實用之處:將模式與問題的形狀相符。

軸:誰決定接下來要執行什麼?

支柱

誰決定下一個要執行的項目

內建

1 · 繪製圖表

您繪製的圖表

L2a / L2b

2. 協作

LLM

L3a / L3b

3. 動態

執行階段的 Python 程式碼

L4a / L4b

步驟 0:您是否需要圖表?

ADK 隨附預先建構的工作流程代理,包括 SequentialAgentParallelAgentLoopAgent。如果是簡單的代理程式鏈,這些就是最便宜的正確答案,而且不需要組裝圖表。需要明確路徑 (L2b 的路由器)、加入 (L2a 的 JoinNode) 或非代理程式的節點 (一般函式,零次 LLM 呼叫) 時,請略過這些節點,最後一個通常是原因。

Would a prebuilt SequentialAgent / ParallelAgent / LoopAgent do?

├─ YES ──────────────────────────────► use it; stop here

└─ NO  I need routing, a join, or non-agent nodes
   
   Can you draw the workflow before the input arrives?
   
   ├─ YES ───────────────────────────► Pillar 1 · Graph workflow    (L2a/L2b)
   
   └─ NO
      ├─ Known team, request picks the subset? ─► Pillar 2 · Collaborative  (L3a/L3b)
      └─ Does the shape depend on the input?  ──► Pillar 3 · Dynamic        (L4a/L4b)

L5 · which pattern

1.x 與 2 的真實比較

並非「2.0 可以做到 1.x 無法做到的事」— 1.x 可以建構所有內容。2.0 版會為每個形狀提供更直接的歸屬位置,因此已知的控制流程會離開提示,成為可查看及測試的結構。

模式

1.x 版的費用

ADK 2 首頁

圖形

一般建構作業中會進行 4 次 LLM 呼叫;提示詞中隱藏了路徑

function + agent 節點做為同層級節點 → 1 次呼叫,if 陳述式路由器

協作

可透過 AgentTool 管道建構;ParallelAgent 一律為 all,transfer_to_agent 序號

已宣告的團隊:sub_agents + mode="single_turn"

動態

遞迴會將您從架構中移除

架構內有 parallel_worker + 遞迴 ctx.run_node

整個應用程式,以及圖表無法顯示的內容

您現在已建構下列所有項目。Workflow 會在 graph.edges 露出結構,因此這張圖片是從程式碼產生,而非手繪,而內省找到的內容就是本實驗室的摘要:

支柱

graph.edges 包含的內容

原因

1 · Graph (L2b)

10 個邊緣、路線等

您在收到任何輸入內容前繪製了該筆劃

2 · 協作 (L3a)

0 邊緣 - 僅限 sub_agents + mode

LLM 會根據要求選取子集

3 · Dynamic (L4a/L4b)

3 個邊緣 - 兩者相同

遞迴是以 Python 編寫,而非在圖表中連線

最後一列是 L4b 回答問題的證明:L4a 和 L4b 具有相同的圖表,且只有其中一個會遞迴。

現在可以建構的內容

您剛執行的每個模式都是實際的產品形狀:

你練習了

在實際情況中,這表示

開始於

圖表 + 路由器 (L2a/L2b)

文件管道、ETL (含 LLM 步驟)、審查/核准鏈、評估架構

這個存放區的 L2b

統籌專員 + single_turn 團隊 (L3a)

支援 Copilot,可與專家團隊、分流服務台、多鏡頭審查功能搭配使用

馬拉松示範模式 2

task 代理 (L3b)

填寫表單、預訂流程、新手上路、瞭解你的客戶 (KYC) - 任何「收集資訊後採取行動」的

22_agent_in_workflow

動態寬度/深度 (L4a/L4b)

研究專員、報表產生器、對大小未知的輸入內容進行稽核掃描

馬拉松示範模式 3

他們撰寫

這三種模式並非互斥,圖形節點可以呼叫協作協調器,專家則可啟動動態工作流程。針對問題的每個部分選擇合適的模式,這樣才能避免將每個代理程式系統變成一個巨大的提示。

整個應用程式,以及圖表無法顯示的內容

💡 在自己的工作流程中試試看:繪製這個圖表的指令碼是 scripts/graph_dump.py。只要對準任何 Workflow,即可列印出實際邊緣,免費取得所建構任何物品的結構圖。

14. 恭喜

九位特工、一根接力棒,井然有序地完成任務

您已建構馬拉松賽事當天教練,並在此過程中瞭解所有三種 ADK 2 的協調模式。

您學到的內容

  • 序言自創天氣的巨型提示,說明結構的意義。
  • L0-L1 - AgentRunner、模型選擇呼叫的實際工具,以及您的第一個 Workflow (函式節點 + 代理程式節點做為同層級)。
  • L2a / L2b - 圖形工作流程:平行擴散傳遞功能 + JoinNode,然後確定性路由 - 一次 LLM 呼叫。
  • L3a - 協作代理:同一團隊在 chat (擱置) 和 single_turn (平行子集 + 合成) 中執行 - 一個標記,兩個世界。
  • L3b - task 模式:暫停的釐清問題、腳本式繼續、finish_task 傳回經過驗證的物件。
  • L4a / L4b - 動態工作流程:執行階段寬度 (擴散傳遞功能),然後是執行階段深度 (遞迴),並在程式碼中設有界線。
  • L5:決策樹,以及模式的組成方式。

值得保留的對話

函式會準備環境。邊緣會定義工作流程。路由器會選擇路徑。模型會撰寫答案。

讓 LLM 決定工作內容,但請在程式碼中設定界線。

根據問題的形狀,找出相符的模式。

後續步驟

  • 執行這些層級的完整應用程式 (即 Marathon Race Day Coach),這是以 FastAPI + SSE 建構的應用程式,並提供瀏覽器 UI,可即時顯示所有三種模式:github.com/cuppibla/adk-2-marathon-demo
  • 深入瞭解:adk-workflows-compared - 所有 23 個官方 ADK 2 工作流程範例,每個範例都有 1.x 移植版本和使用時機指南。請先從 docs/three-pillars.md 開始,然後是本程式碼研究室略過的內容:07_loop17_request_input22_agent_in_workflow
  • 自行移植問題:哪些部分是已知結構 (L2)、已知團隊 (L3a/L3b)、未知形狀 (L4)?
  • 瀏覽程式碼:github.com/cuppibla/adk2-tutorial
  • 參加過工作坊嗎?你的抵免額和抵免額建立的專案不會永久存在。如要繼續免費重新執行這些關卡,請改為執行「居家設定」步驟:免費 AI Studio 金鑰,無需雲端專案,也不會產生費用。唯一變更就是交換該儲存格。