凌晨兩點的領悟

凌晨兩點,我盯著螢幕上那個 2,000 行的 while True 迴圈。第八次 debug 失敗。

我滾動到第 1847 行,看到一個叫 handle_inspiration_gathering_with_fallback_and_retry_logic() 的函式。它調用另一個函式,那個函式又調用另一個函式。我試著追蹤邏輯流程,但很快就迷失在層層嵌套的條件判斷和錯誤處理中。

我滾回第 1 行。while True:

我滾到第 2000 行。還在同一個迴圈裡。

我突然意識到:我不是在 debug 一個系統。我是在讀一本由瘋子寫的小說——而那個瘋子就是三個月前的我。

我在建構什麼?

讓我先解釋一下這個系統是做什麼的。它是 ericsan.io 的內容生成引擎,專門將我的學習筆記轉化成可發布的技術文章——就像你現在正在讀的這篇。

系統的挑戰:

最初,我用一個巨大的 Python 腳本來處理所有這些步驟。單一的 LLM,一個永不停止的 while True 迴圈,不斷地建構 prompt、呼叫 API、解析回應、更新狀態。

一開始,它運作得很好。然後系統開始成長。

紙牌屋的崩塌

那天早上,需求很簡單:「加個功能,讓系統也能分析 YouTube 影片。」

我看著那 2,000 行程式碼,心裡盤算:加新功能就像在搖搖欲墜的紙牌屋上小心翼翼地加一張牌。

「兩天。」我說。心裡想著:如果不崩潰的話。

結果花了一個禮拜。因為我改了第 430 行,第 1200 行就莫名其妙地壞了。修好了 1200 行,第 680 行又出問題。整個系統像一張緊繃的蜘蛛網,碰觸任何一條線都會牽動其他所有的部分。

那個週末,我做了個決定:不是優化這個怪物,而是殺死它,用 Multi-Agent 架構重新開始。

為什麼選擇 Google ADK?

在接觸大型語言模型(LLM)應用的開發過程中,我們經常從單一的 orchestrator 開始。這個 orchestrator 通常是一個巨大的 prompt,透過 while-loop 或 chain-of-thought 的方式處理複雜任務。然而,隨著邏輯越來越複雜,這個單一的 prompt 變得極難維護和擴展。

Google ADK 正是為此而生的框架。它不僅僅是理論,更提供了一套具體的工具、類別和模式,讓我們能結構化地建立、測試和部署多代理系統。

過去三週,我將整個系統從單一的 2,000 行 orchestrator 遷移到 ADK 的 Multi-agent 架構。這篇文章整理了這段旅程中學到的核心技巧——從實戰中提煉出來的、真正有用的經驗。

[ 第一課:當你的 Sub-agent 變成黑盒子 ]

凌晨三點的頓悟

遷移的第一週,我把 YouTube 抓取邏輯抽出來,變成一個獨立的 inspiration_agent。看起來很棒——程式碼從 500 行縮減到 120 行。我覺得自己是個天才。

然後它開始失敗。

主 Agent 的 log 只顯示:"Calling inspiration_agent..." 接著就是 "Error occurred"。但到底哪裡出錯?Sub-agent 內部發生了什麼事?它呼叫了哪些工具?LLM 的思考過程是什麼?我完全看不到。

這就是多代理系統最頭痛的問題:子代理變成了黑盒子

1. 使用 adk web 獨立除錯 Sub-agents

在一個 multi-agent 系統中,最常見的痛點之一是 sub-agent 的行為不透明。從 main agent 的日誌檔案中,你只能看到發送給 sub-agent 的 request 字串,但無法得知 sub-agent 內部發生了什麼事。

那天凌晨三點,我在 ADK 文件的角落發現了一個不起眼的提示:adk web 可以在 sub-agent 目錄下單獨運行。

操作流程:

  1. 從 main agent 的 log 中複製目標 sub-agent 的完整 request 內容
  2. 在終端機 cd 進入該 sub-agent 的專案目錄
  3. 執行 adk web
  4. 在啟動的 Web UI 中貼上剛剛複製的 request,點擊執行

效果:你可以像在主流程中一樣,即時觀察到這個 sub-agent 的所有內部運作細節——event stream、LLM 的 function call、以及最終的 response。

這個方法讓我的 debug 時間從 45 分鐘縮短到 6 分鐘。

限制與應對

這種獨立測試模式無法傳遞 session.state。但這反而迫使我養成一個好習慣:將所有必要的上下文和狀態資訊,透過 prompt engineering 的方式,明確地包含在傳遞給 sub-agent 的 request 中。這讓 sub-agent 變得更加獨立和無狀態,有利於測試和複用。

2. 利用 prompt.py 結構化 Agent 指令

隨著 agent 邏輯變複雜,instruction prompt 也會變得冗長。將所有 prompt 字串硬編碼在 agent.py 中會導致程式碼難以閱讀和維護。ADK 推薦一個最佳實踐:

# In prompt.py
DATA_ANALYST_PROMPT = """你是一個數據分析專家..."""

# In agent.py
from . import prompt

my_agent = LlmAgent(
    name="DataAnalyst",
    instruction=prompt.DATA_ANALYST_PROMPT,
    ...
)

優點:這種模式將 agent 的「行為指令」與「核心邏輯」分離,讓 agent.py 專注於 agent 的結構(如 tools, sub-agents),而 prompt.py 則專門管理其與 LLM 的溝通內容。

如此一來,你就可以像操作主 Agent 一樣,完整地觀察這個 Sub-agent 的每一步思考、每一次 Tool call 以及最終的 response。這對於隔離問題、精準除錯至關重要。

3. 除錯時的限制:狀態(State)的處理

使用 adk web 獨立除錯 Sub-agent 時必須注意一點:這種方式不會傳遞共享的 session.state。如果你的 Sub-agent 依賴從 session.state 中讀取先前步驟的資料,直接貼上 request 會因為缺少上下文而執行失敗。解決方法是,在除錯時手動將 session.state 中需要的資訊,一併整合進你的 prompt/request 中,模擬出它在完整流程中會收到的樣子。

[ 第二課:State 管理的頓悟時刻 ]

「它們怎麼不會說話?」

第二週,我有了 3 個獨立的 Agent:Persona Agent(識別作者風格)、Inspiration Agent(蒐集內容)、Writing Agent(生成文章)。每個 Agent 都能獨立運作,測試都通過了。

問題是:它們之間如何傳遞資訊?

我最初的做法很笨拙——讓每個 Agent 都有一個 get_previous_output() 工具去查詢前一個 Agent 的結果。這樣不僅多了一次 tool call(增加成本和延遲),而且 Agent 經常「忘記」去呼叫它。

某天下午,我盯著 logs 看了半個小時,Writing Agent 一直在說「我需要作者風格資訊」,但它就是不去呼叫那個工具。我突然意識到:我在強迫 LLM 記住一個程序

直到我發現了 ADK 的狀態管理模式,一切才變得清晰。

1. 共享的 session.state

session.state 是 ADK 中一個貫穿整個執行流程的共享字典(dictionary-like object)。它扮演著一個中央狀態存儲的角色,所有在同一個會話 (session) 中執行的 agent 都可以讀取和寫入這個物件。

簡單來說:如果 Agent 是工人,那麼 session.state 就是他們之間共享的白板或工作台。

2. output_key:定義 Agent 的產出位置

為了讓 agent 的輸出能被結構化地存入 session.stateAgent 類別在初始化時接受一個 output_key 參數。

2. 狀態管理 (State Management):session.state

如果說 Agent 是工人,那麼 session.state 就是它們之間共享的白板或工作台。這是在整個會話期間持續存在的一個 dictionary,讓 Agent 之間可以傳遞資料和狀態。

3. output_key:生產者-消費者模式

ADK 提供了一個非常優雅的模式來操作 session.state。當你定義一個 Agent 時,可以指定一個 output_key

data_fetcher_agent = BaseAgent(
    name="DataFetcher",
    output_key="raw_data",
    ...
)

data_fetcher_agent 執行完畢後,它的最終輸出會被自動存入 session.state,鍵值就是你指定的 raw_data

接著,另一個 Agent(消費者)可以在它的 instruction 中直接引用這個結果:

data_cleaner_agent = LlmAgent(
    name="DataCleaner",
    instruction="Please clean the following raw data: {raw_data}",
    ...
)

ADK 的流程控制器會自動從 session.state 中取出 raw_data 的值,並填充到 prompt 中。這個 output_key{key} 的模式,讓 Agent 之間的資料流變得非常明確,也解耦了 Agent 之間的直接依賴。

[ 第三課:工具還是 Agent?一個價值百萬的問題 ]

第三週的某天下午,我面臨一個決策:YouTube 影片抓取功能應該是一個 Tool 還是一個 Agent?

這個問題看似簡單,但它的答案會深刻影響整個系統的架構。讓我花了整個下午重構程式碼之後,我終於理解了其中的差異。

簡單來說,當你需要 Agent 執行一個定義明確、輸入輸出固定的操作時,使用 Tool。當你需要 Agent 處理一個需要多步驟、有自己內部邏輯的複雜子問題時,使用 Sub-agent。

[ 第四課:兩種調用模式的深層意義 ]

在理解了 Tool 和 Agent 的區別後,下一個問題是:當一個 Agent 需要呼叫另一個 Agent 時,應該如何組織它們之間的關係?

這個選擇不只是技術細節,它決定了你的系統是動態靈活還是確定可控。

1. LLM-Driven Delegation (transfer_to_agent)

這是典型的 Sub-agent 模式,也是最具動態性的模式。

2. Explicit Invocation (AgentTool)

這是將一個 Agent 當作 Tool 使用的模式,行為更像傳統的程式碼。

[ 三週後:重新找回控制權 ]

三週後的某天早上,同樣的需求又來了:「能不能再加個功能?這次要支援 Threads 的貼文抓取。」

我打開電腦。進入 sub_agents/ 資料夾。新增一個 scraper_agent 目錄。

60 行程式碼。20 分鐘。完成。跑測試。通過。整合進主系統。沒有任何東西壞掉。

這才是 Multi-Agent 架構真正的價值。

1. 從單一 LLM 到 Multi-Agent 的架構轉變

過去的「Orchestrator」模式,核心是一個巨大的 prompt 和一個不斷解析 LLM 輸出的迴圈。ADK 讓我們轉向一個更像微服務的架構。主 Agent(Orchestrator)的職責不再是事必躬親,而是定義高層次的目標和策略,並將具體執行工作分配給專門的 Sub-agent 或 Tools。原本散落在各處的 Python 腳本或 prompt 生成邏輯,現在都可以被封裝成獨立的 Agent,使得整個系統的職責劃分更加清晰。

2. 加速開發:Cursor 開發模式

為了進一步提升效率,我們甚至可以為 ADK 建立專屬的 Cursor rule file。透過分析 ADK 官方的 sample agents,定義出標準的檔案結構和程式碼模板(例如 agent.py, prompt.py, tools.py 的基本框架),之後就可以讓 Cursor 快速生成一個新的、符合 ADK 規範的 Agent 骨架,開發者只需專注於填寫核心邏輯。

3. 實踐中的抉擇:何時用 Tool,何時用 Agent?

這是在實作中不斷會遇到的問題。例如,當我需要從多個模板中選擇一個時,應該把它做成一個 Tool,還是一個 Sub-agent?我的理解是:

最後的反思:不只是技術,更是心態

這個故事的重點不是我學會了 Google ADK。

重點是:我從恐懼改動程式碼,變成享受添加新功能。

以前每次有新需求,我的第一反應是:「天啊,這會破壞哪些東西?」現在我會想:「好,這個需求適合哪個 Agent 處理?還是需要新建一個?」

從抗拒到開放。從防禦到好奇。這才是 Multi-Agent 架構最深層的價值。

給同樣卡在巨大系統裡的你

如果你現在也盯著一個巨大的、複雜的、只有你自己(勉強)看得懂的系統——你不需要一次重寫全部。

我的做法很簡單:

三週後,那個怪物不見了。取而代之的是 7 個我可以理解、可以測試、可以修改的小系統。

技術細節很重要,但不是最重要的

在這篇文章裡,我分享了很多技術技巧:adk web 的獨立除錯、session.state 的狀態管理、AgentTool vs transfer_to_agent 的選擇、Tool 和 Agent 的區分。

這些都很有用。但說實話,最重要的不是這些。

最重要的是你願不願意承認:這個系統已經失控了,該是時候重新開始了。

那個凌晨兩點的決定——不是優化怪物,而是殺死它——才是一切轉變的起點。

現在的系統

今天,這個系統有:

但最重要的數字是:0——我不再害怕改動任何東西的恐懼次數。

這種感覺,叫做自由。

#AI #AgenticAI #GoogleADK #LLM #SoftwareDevelopment #MultiAgentSystems