精華筆記

· @aihub.tw

AI Agent 開發

多代理:orchestrator 與 subagent

多代理:orchestrator 與 subagent

你已經在第 3 課用 Claude Agent SDK 跑出一個真正自主的 agent——它能呼叫工具、判斷下一步、在迴圈裡跑到任務完成。但當任務變大,你會撞到三道牆。

第一道:context 視窗爆炸。讓 agent 去研究五個競品,每個競品讀十幾份資料——才到第三個,視窗快滿,agent 開始「忘記」前面的發現。第二道:速度慢成線性。五個競品一個一個研究,等於五倍時間,用戶等到放棄。第三道:職責混亂。同一個 agent 既搜資料又分析又寫報告,system prompt 變成拼盤,什麼都做等於什麼都不精。

多 agent 架構就是為了解決這三個問題而存在的:拆開 context、並行執行、分工專業化。

這堂課適合誰 適合:完成第 3 課(Claude Agent SDK 實戰)的開發者。需要基礎:能寫 Python async/await、看得懂 Claude Agent SDK 的 query() 用法、有基本的 API 呼叫經驗。前置課:第 3 課 Claude Agent SDK 實戰。

這堂學什麼

  • 何時需要多 agent:三個判斷標準——context 隔離、並行加速、專業分工
  • orchestrator-worker 架構:orchestrator 的職責、subagent 的設計原則
  • 傳話失真問題:資訊在 agent 間傳遞時為何失真,以及 fat prompt 對策
  • 成本意識:多 agent 的 token 倍增邏輯,以及 Opus + Sonnet 混搭策略
  • 實戰:用 Claude Agent SDK 搭出三 agent 研究分工系統

觀念一:何時需要多 agent

不是什麼任務都需要多 agent。Multi-agent 有固定代價:token 成本倍增、複雜度提高、出錯點增加。先搞清楚三個真正需要它的場景。

何時需要多 agent 決策樹

場景一:context 隔離

每個 LLM 呼叫都有 context 視窗上限(claude-sonnet-4-5 是 200K tokens)。聽起來很大,但一個讀大量文件的研究任務——讀 50 份 PDF、爬 30 個網頁、累積工具呼叫結果——很容易把視窗塞滿。

多 agent 的解法:讓每個 subagent 處理一個子任務,只有最終摘要回傳給 orchestrator,不是所有原始資料都塞進去。Anthropic 官方的多 agent 研究系統就是這樣設計的:subagent 讀幾十份文件,但 orchestrator 的 context 裡只有每個 subagent 的一段總結。

場景二:並行加速

如果任務能拆成獨立的子任務,並行跑比串行快得多。Anthropic 的實測結果:把原本線性執行改為並行派發 3–5 個 subagent,複雜研究任務的完成時間縮短了最多 90%

前提是子任務之間沒有依賴——A 的結果不需要等 B 才能跑。如果 A 的輸出是 B 的輸入,那是串行,沒辦法並行。

場景三:專業分工

同一個 agent 又搜資料又分析又寫報告,system prompt 就會臃腫失焦。更好的做法:researcher 只管搜集整理、analyst 只管找模式和矛盾、writer 只管輸出漂亮格式。每個 subagent 的 prompt 更短更聚焦,品質通常更高。


觀念二:orchestrator-worker 架構

多 agent 系統的標準模式是 orchestrator-worker:一個統籌者負責規劃派發,多個執行者各自完成子任務。

Orchestrator-Worker 架構圖

Orchestrator 的職責:

  1. 接收高層任務,分析並拆成子任務
  2. 決定哪些子任務可以並行、哪些必須串行
  3. 為每個 subagent 準備清晰的任務 prompt(含所有它需要的上下文)
  4. 等待 subagent 回傳,驗證結果品質
  5. 彙整所有結果,處理衝突,輸出最終答案

Subagent 的職責:

  1. 專注在一個明確定義的子任務
  2. 使用受限的工具集(只拿它真正需要的工具)
  3. 輸出摘要性結果,不是原始資料的全部

Anthropic 官方的生產級研究系統印證了這個分工:claude-opus-4-5 當 orchestrator 負責規劃和彙整,claude-sonnet-4-5 當 subagent 負責具體的搜尋和閱讀。測評結果:這個組合比純用 Opus 單 agent 效能高 90.2%,而且成本更低。品質重要的地方用強模型,量大的地方用快又便宜的模型——這是最重要的成本控制策略。

在 Claude Agent SDK 裡,AgentDefinitionmodel 參數讓你為每個 subagent 獨立指定模型:

from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition
import asyncio

async def main():
    async for msg in query(
        prompt="研究台灣 AI 新創生態,重點:估值前三名公司、主要投資人、競爭格局",
        options=ClaudeAgentOptions(
            model="claude-opus-4-5",       # orchestrator 用 Opus 做規劃
            allowed_tools=["Agent"],       # orchestrator 只負責派發,不自己搜尋
            agents={
                "researcher": AgentDefinition(
                    description="負責網路搜尋和資料蒐集。用來查詢公司資訊、新聞、財報。",
                    prompt="""你是一個資料蒐集專家。
針對指定主題搜尋相關事實和數據,整理成結構化摘要。
輸出格式:每個主題一段,包含來源說明。只要事實,不要分析。""",
                    tools=["WebSearch", "WebFetch"],
                    model="claude-sonnet-4-5",   # subagent 用 Sonnet 省成本
                ),
                "analyst": AgentDefinition(
                    description="負責分析和比較數據。用來找模式、矛盾、洞察。",
                    prompt="""你是一個商業分析師。
根據提供的資料找出規律、比較差異、提出洞察。
輸出格式:重點條列,每點附支持資料。""",
                    tools=["Read"],             # analyst 只需要讀資料,不需要上網
                    model="claude-sonnet-4-5",
                ),
            },
        ),
    ):
        if hasattr(msg, "result"):
            print(msg.result)

asyncio.run(main())

三個關鍵細節:

  • allowed_tools 裡放 "Agent" — 讓 orchestrator 呼叫 subagent 時不被權限擋住
  • 每個 AgentDefinitiondescription 是告訴 orchestrator「什麼情況下用這個 subagent」,這個欄位寫清楚,自動委派才準確
  • tools 裡只放它真正需要的工具—— analyst 不需要上網,給它 Read 就夠了,工具最小化是安全最佳實踐

觀念三:傳話失真——最容易被忽略的問題

多 agent 系統有一個隱藏的敵人:subagent 的 context 是全新的、隔離的

官方文件說得很清楚:

「父 agent 和 subagent 之間的唯一通道,就是 Agent tool 的 prompt 字串。subagent 的 context 視窗從零開始,沒有父對話的任何歷史。」

這意味著:orchestrator 知道的事情,subagent 不會自動知道。如果你沒有在派發 prompt 裡明確寫出來,subagent 就是從零開始工作。

Subagent Context 隔離示意圖

這就是傳話失真的根本原因——不是資訊在傳遞中扭曲,而是沒傳到:orchestrator 知道某個重要背景,但在派發任務時忘記告訴 subagent。

對策:寫 fat prompt,不要省字

壞的 orchestrator 派發 prompt:

幫我研究 TeraPower 這間公司。

好的 orchestrator 派發 prompt:

任務背景:使用者想分析台灣 AI 新創生態,重點是三家估值最高的公司。
你的子任務:研究 TeraPower 公司。

需要回傳的資訊:
1. 最新估值(如有)
2. 主要投資人和輪次
3. 核心產品/服務
4. 最近 6 個月的重大新聞(最多 3 則)

輸出格式:結構化 JSON,欄位名稱英文,值繁中。
注意:只要事實,不要推測;如果找不到某項資訊,填 null 並說明原因。

差別在哪?好的版本給了 subagent 任務背景(它為什麼要做這件事)、明確的輸出規格(orchestrator 才能可靠地解析結果)、邊界條件(找不到怎麼辦)。三樣都有,傳話失真的機率就大幅降低。


觀念四:成本意識不能省略

Anthropic 工程部落格寫得很直白:

多 agent 系統消耗的 token 大約是聊天的 15 倍。 單 agent 相比聊天大約是 4 倍。

換算成成本:如果一個 claude-sonnet-4-5 聊天問答花你 NT$1,一個 agent 跑同樣的任務可能要 NT$4,一個多 agent 系統可能要 NT$15 以上——而且是每次呼叫。

多 agent Token 成本倍增對比

這不是要你別用多 agent,而是要在架構設計時主動思考兩件事:

一、任務的價值值得這個成本嗎? 生成一份賣 NT$500 的客製報告?值得。幫使用者找一篇文章?直接 WebSearch 就好,不需要三個 agent。

二、Orchestrator 用強模型、subagent 用弱模型。 Anthropic 的數據:Opus 4 orchestrator + Sonnet 4 subagent,比純 Opus 4 單 agent 效能高 90.2%,同時成本更低。Opus 做規劃和判斷,Sonnet 做大量的執行工作——分工讓預算花在刀口上。

另一個成本陷阱:不必要的串行。如果五個 subagent 都能並行跑,但你的 prompt 讓 orchestrator 一個一個等,token 總量不變,但等待時間乘以五。並行不省 token,但並行省時間,這兩件事要分開想清楚。


手把手實戰:三 agent 研究分工系統

我們來實作一個三 agent 的研究系統:給定一個主題,researcher 蒐集資料,critic 找盲點和爭議,最後 orchestrator 彙整出平衡的報告。

三 agent 研究分工系統流程圖

確認環境

確認第 3 課的環境已就緒:

pip show claude-agent-sdk      # 確認已安裝,應看到版本號

# 確認 API 金鑰
echo $ANTHROPIC_API_KEY        # 應看到 sk-ant-... 開頭的值

如果沒裝,回頭看第 3 課。這堂課從已有 SDK 的環境繼續。

定義三個角色的 system prompt

新建 research_agents.py,先定義每個 subagent 的 prompt:

# research_agents.py

RESEARCHER_PROMPT = """你是一個資料蒐集和整理專家。

工作原則:
- 針對指定主題,搜尋並整理相關事實、數據、現況
- 優先找一手資料(官方報告、新聞報導、研究機構)
- 每個重要主張都要附來源(網址或出版品名稱)
- 只回報事實,不做主觀評價

輸出格式:
## 主要發現
(條列,每點一個事實+來源)

## 關鍵數據
(數字、統計資料整理)

## 相關資源
(可信來源列表)"""


CRITIC_PROMPT = """你是一個批判性思考專家。

工作原則:
- 針對指定主題,搜尋反例、爭議、被忽略的角度
- 找出主流觀點的盲點或過度簡化的地方
- 平衡呈現:不是否定,而是補充另一面
- 每個反例或爭議點都要有依據

輸出格式:
## 主要爭議點
(條列,每點含反例或質疑+依據)

## 被低估的風險/問題
(補充說明)

## 不同立場的觀點
(對比呈現)"""

兩個 prompt 的設計原則:職責不重疊、輸出格式明確。Orchestrator 才能可靠地解析兩者的結果並彙整。

組裝 orchestrator 和 subagent

繼續在 research_agents.py 加入主要邏輯:

import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition


async def run_research(topic: str) -> None:
    """
    三 agent 研究系統:
    - Orchestrator (claude-opus-4-5): 規劃、彙整
    - Researcher (claude-sonnet-4-5): 蒐集資料
    - Critic (claude-sonnet-4-5): 找反例和爭議
    """
    print(f"\n=== 開始研究:{topic} ===\n")

    async for msg in query(
        prompt=f"""請針對以下主題進行深度研究,產出一份平衡的分析報告。

主題:{topic}

研究步驟:
1. 同時派發 researcher 和 critic 兩個 subagent 並行工作——不要等一個完成再叫另一個:
   - researcher:蒐集主題的主要事實、數據、現況
   - critic:找出主流觀點的盲點、爭議、被低估的風險
2. 等兩個 subagent 都完成後,彙整成報告
3. 報告結構:執行摘要 → 主要發現 → 爭議與不同觀點 → 結論

派發給每個 subagent 的 prompt 必須包含:完整的研究主題、你希望它聚焦的面向、輸出格式。
不要假設 subagent 知道背景,它們的 context 從零開始。

如果兩個 subagent 的資料有衝突,在報告中明確標注並說明你的判斷依據。""",
        options=ClaudeAgentOptions(
            model="claude-opus-4-5",    # orchestrator 用 Opus 做規劃和彙整
            allowed_tools=["Agent"],    # 只允許呼叫 subagent,不讓 orchestrator 自己搜尋
            agents={
                "researcher": AgentDefinition(
                    description="資料蒐集專家。當需要搜尋事實、數據、現況時使用。",
                    prompt=RESEARCHER_PROMPT,
                    tools=["WebSearch", "WebFetch"],
                    model="claude-sonnet-4-5",   # subagent 用 Sonnet 省成本
                    maxTurns=20,    # 限制最大輪次,防止 subagent 無限循環
                ),
                "critic": AgentDefinition(
                    description="批判性分析專家。當需要找反例、爭議、不同立場時使用。",
                    prompt=CRITIC_PROMPT,
                    tools=["WebSearch", "WebFetch"],
                    model="claude-sonnet-4-5",
                    maxTurns=15,
                ),
            },
        ),
    ):
        # 偵測 subagent 被呼叫的時機(方便 debug)
        if hasattr(msg, "content") and msg.content:
            for block in msg.content:
                if hasattr(block, "name") and block.name in ("Agent", "Task"):
                    subagent_name = block.input.get("subagent_type", "unknown")
                    print(f"[派發 subagent: {subagent_name}]")

        # 輸出最終結果
        if hasattr(msg, "result"):
            print(msg.result)


if __name__ == "__main__":
    topic = "台灣電動車產業的現況與挑戰"
    asyncio.run(run_research(topic))

幾個設計決策值得說明:

allowed_tools=["Agent"] — orchestrator 自己不搜尋,只派發。這讓架構更清晰:orchestrator 是指揮官,不是士兵。如果你讓 orchestrator 既搜尋又派發,職責就混亂了。

maxTurns=20 — 限制 subagent 最多跑幾輪。沒有設這個的話,迷路的 subagent 可能跑幾十輪才停止,token 費用失控。

工具偵測的 block.name in ("Agent", "Task") — SDK v2.1.63 之前工具叫 "Task",之後改成 "Agent"。寫兩個名字都檢查,兼容新舊版本。

執行並觀察

python research_agents.py

正常執行時,你會看到類似這樣的流程:

=== 開始研究:台灣電動車產業的現況與挑戰 ===

[派發 subagent: researcher]
[派發 subagent: critic]
... (兩個 subagent 各自執行)

# 執行摘要
台灣電動車產業正處於關鍵轉型期...

# 主要發現
- 台灣電動機車市場佔有率...

# 爭議與不同觀點
- 政策補貼退場時間表的爭議...

# 結論
...

兩個 [派發 subagent: ...] 訊息幾乎同時出現,代表並行成功。如果一個接一個出現,代表 orchestrator 沒有正確理解「並行」——這時候把 orchestrator prompt 裡的說明強調得更清楚,例如加上:「必須同時呼叫 researcher 和 critic,在任一個完成之前不要等待。」

加入基本的錯誤處理

生產環境裡,subagent 可能因為 API 限流或其他問題提前中止。SDK v2.1.199+ 的行為:

  • 若 subagent 有產出文字:回傳部分輸出 + 說明「subagent 未完成」
  • 若 subagent 什麼都沒產出:拋出錯誤 Agent terminated early due to an API error

加入基本的重試處理:

async def run_research_with_retry(topic: str, max_retries: int = 2) -> None:
    for attempt in range(max_retries + 1):
        try:
            async for msg in query(
                prompt=f"研究主題:{topic}\n請並行派發 researcher 和 critic,再彙整報告。",
                options=ClaudeAgentOptions(
                    model="claude-opus-4-5",
                    allowed_tools=["Agent"],
                    agents={
                        "researcher": AgentDefinition(
                            description="資料蒐集專家",
                            prompt=RESEARCHER_PROMPT,
                            tools=["WebSearch", "WebFetch"],
                            model="claude-sonnet-4-5",
                            maxTurns=20,
                        ),
                        "critic": AgentDefinition(
                            description="批判性分析專家",
                            prompt=CRITIC_PROMPT,
                            tools=["WebSearch", "WebFetch"],
                            model="claude-sonnet-4-5",
                            maxTurns=15,
                        ),
                    },
                ),
            ):
                if hasattr(msg, "result"):
                    print(msg.result)
                    return  # 成功,結束

        except Exception as e:
            err = str(e)
            if "Agent terminated early due to an API error" in err:
                print(f"[第 {attempt + 1} 次] subagent 提前中止: {err}")
                if attempt < max_retries:
                    print("等待 5 秒後重試...")
                    await asyncio.sleep(5)
                else:
                    print("已達最大重試次數。")
                    raise
            else:
                raise  # 其他錯誤直接往上拋

常見坑

坑 1:Agent 沒加進 allowed_tools,orchestrator 自己回答不委派

症狀:執行後看不到任何 [派發 subagent: ...],orchestrator 直接產出結果,完全沒有用到你定義的 subagent。

原因:allowed_tools 裡缺少 "Agent",SDK 把所有 Agent tool 呼叫視為需要手動批准。自動化腳本裡通常靜默拒絕,orchestrator 就改自己處理。

# 錯誤寫法:orchestrator 想呼叫 subagent 時會被拒絕
options=ClaudeAgentOptions(
    allowed_tools=["WebSearch", "WebFetch"],   # 忘記加 "Agent"
    agents={...},
)

# 正確寫法
options=ClaudeAgentOptions(
    allowed_tools=["Agent"],   # orchestrator 不自己搜尋,只派發
    agents={...},
)

坑 2:subagent 拿到的 prompt 太模糊,輸出品質差

症狀:orchestrator 派發任務後,subagent 回傳的結果偏離主題或過於空泛。Debug 方式:在 orchestrator prompt 裡加一句「在派發給 subagent 之前,先輸出你準備傳給它的完整 prompt」,然後看那段 prompt 到底寫了什麼。

根本原因:subagent 的 context 從零開始,它不知道外面發生了什麼事。Orchestrator prompt 沒有明確告訴它任務背景、聚焦面向、輸出格式、找不到資料怎麼辦——subagent 只能靠猜。

修正原則:每次派發給 subagent 的 prompt 必須包含四樣東西:任務背景(為什麼要做)、具體要找什麼、輸出格式、邊界條件(找不到怎麼處理)。

坑 3:subagent 巢狀過深,執行時間爆增

症狀:執行時間遠超預期,token 消耗爆炸,有時 subagent 靜默停止。

原因:SDK v2.1.172 之後,subagent 可以自己再召喚 subagent,最多五層。如果你沒有限制工具,orchestrator 呼叫 researcher,researcher 又自己召喚 sub-researcher……就產生無意間的多層巢狀。

解法:在 subagent 的 AgentDefinition 裡不要把 "Agent" 放進 tools,或者用 disallowedTools 明確禁止:

AgentDefinition(
    description="資料蒐集專家",
    prompt=RESEARCHER_PROMPT,
    tools=["WebSearch", "WebFetch"],   # 不含 "Agent" → 無法召喚 subagent
    # 或者更明確:
    # disallowedTools=["Agent"],
    model="claude-sonnet-4-5",
)

坑 4:兩個 subagent 的結果有矛盾,orchestrator 兩個都塞進報告

症狀:researcher 說某數據是 A,critic 找到資料說是 B。最後報告把兩個都列出來,沒有說明矛盾,讀者一頭霧水。

這不是 bug,是 orchestrator prompt 設計遺漏。要明確告訴 orchestrator 衝突處理原則:

如果 researcher 和 critic 提供了相互矛盾的資訊:
1. 明確標注衝突:「對於 X,researcher 引用來源 A 說是○○;
   critic 引用來源 B 說是●●,兩者矛盾。」
2. 如果能判斷哪個更可信(更新、更一手的來源),說明原因並採用
3. 如果無法判斷,兩者都呈現,標注「資料來源存在分歧,建議查閱原始資料」

坑 5:把多 agent 用在不需要的場景,成本白燒

症狀:跑完了,結果沒比單 agent 好,但 token 帳單多了好幾倍。

多 agent 的固定開銷是真實的:每多一個 subagent,就多一個完整的 LLM 對話,包括 system prompt、工具定義、所有工具呼叫的紀錄。如果任務可以由一個 agent 在 20 輪內完成,硬拆成三個 agent 反而增加協調成本、傳話失真風險、還有 token 費用。

判斷標準:如果任務無法清楚地拆成獨立的子任務,或子任務之間資訊耦合非常緊密(A 需要隨時知道 B 在做什麼),就用單 agent。多 agent 的價值在「獨立、可並行、可隔離 context」——三個條件至少要符合兩個才值得上多 agent。


作業

  1. 跑起來:執行 research_agents.py,用你自己感興趣的主題。觀察兩個 subagent 的呼叫順序——是幾乎同時出現還是一個接一個?如果是後者,調整 orchestrator prompt 讓它真正並行。

  2. 加第三個 subagent:在 researcher 和 critic 之後,加一個 summarizer,職責是把前兩者的原始輸出濃縮成 300 字以內的執行摘要。注意這個 subagent 必須串行執行——orchestrator 要等 researcher 和 critic 都完成後,才把它們的輸出傳給 summarizer。思考一下你要怎麼在 orchestrator prompt 裡表達這個「先並行、再串行」的流程。

  3. 成本紀錄:到 Anthropic 後台的 Usage 頁面,記錄今天這幾次執行花了多少 token。和第 3 課的單 agent 任務對比——多 agent 的 token 倍數大約是多少?跟 Anthropic 文件說的 15 倍相比,你的實測結果接近嗎?

下一課預告

你現在已經能架出一個多 agent 系統,讓它並行研究、彙整結果。但有一天 agent 會讓你失望:它走錯方向、工具呼叫出錯、輸出格式亂掉——而你完全不知道為什麼

第 5 課「評估與除錯:agent 為什麼失敗」教你拆解 agent 的執行路徑:怎麼讀懂工具呼叫的 trace、怎麼設計評估指標、怎麼寫回歸測試讓你改完一個問題不會引發另一個。從「能跑」到「可以信任」,是這門課最重要的一跨。

#AI Agent#多代理#orchestrator#subagent#Claude Agent SDK#並行

← 回所有文章