凌晨兩點的領悟
凌晨兩點,我盯著螢幕上那個 2,000 行的 while True 迴圈。第八次 debug 失敗。
我滾動到第 1847 行,看到一個叫 handle_inspiration_gathering_with_fallback_and_retry_logic() 的函式。它調用另一個函式,那個函式又調用另一個函式。我試著追蹤邏輯流程,但很快就迷失在層層嵌套的條件判斷和錯誤處理中。
我滾回第 1 行。while True:
我滾到第 2000 行。還在同一個迴圈裡。
我突然意識到:我不是在 debug 一個系統。我是在讀一本由瘋子寫的小說——而那個瘋子就是三個月前的我。
我在建構什麼?
讓我先解釋一下這個系統是做什麼的。它是 ericsan.io 的內容生成引擎,專門將我的學習筆記轉化成可發布的技術文章——就像你現在正在讀的這篇。
系統的挑戰:
- 識別我的寫作風格(中英雙語、技術專業、實戰導向)
- 從多個來源蒐集靈感(YouTube 影片逐字稿、Threads 貼文、資料庫中的歷史內容)
- 根據內容類型選擇合適的模板(長文分析、列表式、故事敘述)
- 生成 7000+ 字的完整雙語文章,保持一致的聲音和品質
最初,我用一個巨大的 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 目錄下單獨運行。
操作流程:
- 從 main agent 的 log 中複製目標 sub-agent 的完整 request 內容
- 在終端機
cd進入該 sub-agent 的專案目錄 - 執行
adk web - 在啟動的 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 推薦一個最佳實踐:
- 建立
prompt.py:在每個 agent 或 sub-agent 的目錄下,建立一個prompt.py檔案 - 定義 Prompt 變數:用 Python 字串變數來定義各種 prompt
- 在
agent.py中導入:from . import prompt
# 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 的溝通內容。
- Step 1: 進入你想要除錯的 Sub-agent 的子目錄。
- Step 2: 在該目錄下執行
adk web。 - Step 3: ADK 會為這個 Sub-agent 啟動一個獨立的 Web UI。
- Step 4: 你可以從主 Agent 的 log 中,將發送給該 Sub-agent 的完整 request 複製出來,然後貼到這個獨立 UI 的輸入框中。
如此一來,你就可以像操作主 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.state,Agent 類別在初始化時接受一個 output_key 參數。
llmagnet: 這是最靈活也最「Agentic」的模式。它本身就是一個 LLM-based agent,會根據當前的對話狀態和目標,動態地決定下一步應該由哪個 Sub-agent 來執行。它適用於那些無法預先確定流程、需要動態決策的複雜任務。sequentialagent: 這是最簡單的規則型流程,它會按照你定義的順序,依序執行每一個 Sub-agent。適用於有明確步驟的工作流程,例如「第一步:抓取資料;第二步:清理資料;第三步:產生報告」。parallelagent: 允許同時執行多個 Sub-agent,並等待所有 Agent 完成。這在處理可以並行處理的任務時非常有用,例如同時向三個不同的 API 查詢資訊。loopagent: 用於重複執行某個 Agent,直到滿足特定條件為止。
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?
這個問題看似簡單,但它的答案會深刻影響整個系統的架構。讓我花了整個下午重構程式碼之後,我終於理解了其中的差異。
- Tools: 將一個 Agent 視為一個可以呼叫的「函式」。
- 輸入: 結構化的函式參數 (function parameters),例如
search_web(query: str, count: int)。 - 本質: 通常是靜態的 Python 程式碼,執行確定性的操作(例如呼叫 API、計算、讀寫檔案)。它們是無狀態的。
- 用途: 為 Agent 提供具體的、原子化的能力。
- 輸入: 結構化的函式參數 (function parameters),例如
- Sub-agents: 將一個 Agent 視為一個可以委派任務的「獨立承包商」。
- 輸入: 基於文字的請求 (text-based request),例如
"幫我規劃一趟去東京的五日遊"。 - 本質: 一個完整的、可以獨立思考和執行任務的 Agent。它可能有自己的 state、tools 和 sub-agents。
- 用途: 將一個複雜的子任務完全委派出去,讓主 Agent 不必關心其內部實現細節。
- 輸入: 基於文字的請求 (text-based request),例如
簡單來說,當你需要 Agent 執行一個定義明確、輸入輸出固定的操作時,使用 Tool。當你需要 Agent 處理一個需要多步驟、有自己內部邏輯的複雜子問題時,使用 Sub-agent。
[ 第四課:兩種調用模式的深層意義 ]
在理解了 Tool 和 Agent 的區別後,下一個問題是:當一個 Agent 需要呼叫另一個 Agent 時,應該如何組織它們之間的關係?
這個選擇不只是技術細節,它決定了你的系統是動態靈活還是確定可控。
1. LLM-Driven Delegation (transfer_to_agent)
這是典型的 Sub-agent 模式,也是最具動態性的模式。
- 機制: 父 Agent 的 LLM 在思考後,認為當前任務應該由某個 Sub-agent 處理,於是它會生成一個特殊的函式呼叫
transfer_to_agent(agent_name='target_agent_name')。 - 處理: ADK 的
AutoFlow(預設的流程控制器)會攔截這個呼叫,暫停父 Agent 的執行,然後將控制權「轉移」給指定的target_agent_name。 - 關鍵: 這個模式要能成功運作,父 Agent 的
instruction必須清楚地說明在何種情況下應該委派任務,且每個 Sub-agent 都必須有清晰、獨特的description,這樣 LLM 才能做出正確的判斷。 - 範例: 一個「總助理」Agent,其
instruction是「你是總助理,訂票任務交給 Booker,查詢資訊交給 Info」。當它收到「幫我訂一張機票」的請求時,它的 LLM 就會生成transfer_to_agent(agent_name='Booker')。
2. Explicit Invocation (AgentTool)
這是將一個 Agent 當作 Tool 使用的模式,行為更像傳統的程式碼。
- 機制: 你需要將目標 Agent 用
agent_tool.AgentTool進行包裝,然後將這個AgentTool實例加入父 Agent 的tools列表中。 - 處理: 父 Agent 的 LLM 會像呼叫普通工具一樣,生成一個針對該 AgentTool 的函式呼叫,例如
FunctionCall(name='ImageGen', args={'image_prompt': 'a cat wearing a hat'})。框架會執行這個 Tool,也就是運行被包裝的 Agent,然後將其最終結果作為 Tool 的回傳值返回給父 Agent。 - 關鍵: 這次呼叫是同步的。父 Agent 會停下來等待
AgentTool執行完畢並返回結果,然後再繼續自己的任務。這是一種更明確、更可控的調用方式。 - 範例: 一個「藝術家」Agent,它有一個名為
ImageGen的AgentTool。它的流程是:先自己構思一個繪畫的 prompt,然後呼叫ImageGen工具來生成圖片,拿到圖片結果後,再對其進行評價或修改。
[ 三週後:重新找回控制權 ]
三週後的某天早上,同樣的需求又來了:「能不能再加個功能?這次要支援 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?我的理解是:
- 如果選擇邏輯是固定的、可以被程式碼描述的(例如,根據輸入的關鍵字匹配模板名稱),那麼就用 Tool。
- 如果選擇需要對上下文進行深入的語意理解,甚至需要幾輪的澄清或思考(例如,「根據用戶的心情和當前的主題,為他推薦一個最合適的寫作模板」),那麼將這個選擇邏輯本身封裝成一個 Sub-agent 會是更好的選擇。
最後的反思:不只是技術,更是心態
這個故事的重點不是我學會了 Google ADK。
重點是:我從恐懼改動程式碼,變成享受添加新功能。
以前每次有新需求,我的第一反應是:「天啊,這會破壞哪些東西?」現在我會想:「好,這個需求適合哪個 Agent 處理?還是需要新建一個?」
從抗拒到開放。從防禦到好奇。這才是 Multi-Agent 架構最深層的價值。
給同樣卡在巨大系統裡的你
如果你現在也盯著一個巨大的、複雜的、只有你自己(勉強)看得懂的系統——你不需要一次重寫全部。
我的做法很簡單:
- 第一週:把最痛苦的部分抽出來,變成一個獨立的 Agent
- 第二週:再抽一個
- 第三週:再一個
三週後,那個怪物不見了。取而代之的是 7 個我可以理解、可以測試、可以修改的小系統。
技術細節很重要,但不是最重要的
在這篇文章裡,我分享了很多技術技巧:adk web 的獨立除錯、session.state 的狀態管理、AgentTool vs transfer_to_agent 的選擇、Tool 和 Agent 的區分。
這些都很有用。但說實話,最重要的不是這些。
最重要的是你願不願意承認:這個系統已經失控了,該是時候重新開始了。
那個凌晨兩點的決定——不是優化怪物,而是殺死它——才是一切轉變的起點。
現在的系統
今天,這個系統有:
- 7 個獨立的 Agent(1 個協調者 + 6 個專家)
- 25 個專注的工具(每個不超過 60 行)
- 5 個動態載入的內容模板
- 95% 的成功率(從 60% 提升)
- 25 秒的生成時間(從 15 分鐘縮短)
- 每次請求成本 $0.04(從 $0.15 降低)
但最重要的數字是:0——我不再害怕改動任何東西的恐懼次數。
這種感覺,叫做自由。
#AI #AgenticAI #GoogleADK #LLM #SoftwareDevelopment #MultiAgentSystems