Agent 任務分解實作課:先把大任務拆成 DAG,再讓 Agent 動手
今日主題:Agent 任務分解
2026-11-06 | 資料來源:LangChain Plan-and-Execute、柏克萊 LLMCompiler、CoA-Planner 預印本、GOAT、dev.to TaskTree
前言:ReAct 走三步沒問題,走十步就散了
ReAct 是 Agent 最經典的迴路:思考、行動、觀察,再思考、再行動。步驟少的時候它很靈活,幾乎什麼任務都能硬著頭皮跑下去。但任務一拉長,問題就浮現了:每一步都要把整段歷史重新讀一遍,上下文越滾越長;一個中途的錯誤判斷會一路帶偏後面所有步驟;而且每一步都得叫一次大型模型,速度與成本同時失控。
實務上的經驗法則是:需要三到四步以上的任務,只靠 ReAct 迴路的成功率大約只有四成。解法不是換更大的模型,而是在動手之前,先把任務拆開。這就是任務分解(Task Decomposition):讓 Agent 先把一個模糊的大目標,拆成一張有依賴關係的子任務圖,再照圖施工。
這篇文章講四件事:為什麼要先拆再做、三種分解策略、四種經典架構的取捨,以及一個可以直接拿去用的 DAG 執行器骨架。
一、為什麼要先拆再做:三個理由
第一,速度。 拆開之後,沒有依賴關係的子任務可以平行跑。研究寫作的三個搜尋任務平行執行,三秒做完的事,循序跑要九秒。柏克萊團隊的 LLMCompiler 論文實測,並行化讓整體任務加速最高可達 3.6 倍。
第二,專注。 拆成小任務後,每個執行步驟只需要看到自己那一份上下文,而不是整段對話歷史。子任務甚至可以交給較小、較便宜的模型執行,大模型只負責拆解與最後的整合判斷。
第三,可控。 計畫一旦寫成明確的結構(樹或圖),就可以在執行前先檢查:有沒有循環依賴?有沒有哪個子任務沒有驗收標準?有沒有哪一步的失敗會拖垮全部?這些問題在 ReAct 的自由迴路裡,要到出錯那一刻才會現形。
二、三種分解策略
由上而下:先分階段,再分動作
最常見的做法。模型先把大目標切成三到五個階段,每個階段再切成具體動作。例如「寫一份競品分析報告」先切成找競品、研究功能、比較價格、撰寫報告四個階段,研究功能那一階段再切成三家廠商各自的子任務。優點是結構清楚、容易檢查;缺點是如果一開始的階段切錯,後面整張圖都要重排。
由下而上:先列出能做的事,再拼成計畫
反向操作:先盤點手上有什麼工具、能拿到什麼資料,再由這些能力反推可以完成哪些子任務,最後拼成完整計畫。適合工具箱固定、任務邊界清楚的場景,例如內部系統的維運 Agent——先知道能查日誌、能重啟服務、能發通知,再決定怎麼組合。
動態分解:邊做邊拆,拆到夠細為止
前兩種都是「先拆完再執行」,動態分解則是執行中遇到太大的子任務才現場再拆。優點是計畫不會過度設計,缺點是執行邏輯變複雜,需要一個能隨時改圖的執行器。實務上常見的折衷是:第一層先由上而下拆好,執行時允許子任務再往下拆一層,但總深度不超過四層——再深就代表這個任務應該拆成多次獨立的 Agent 執行,而不是一次硬跑完。
三、四種經典架構:從循序到平行
如圖一,四種架構的差別,核心在於「計畫長什麼樣子」與「誰決定下一步」。
Plan-and-Execute(LangChain / LangGraph)。 規劃者產出一份循序的步驟清單,執行者照清單一步步做,做完一輪再由規劃者決定要收尾還是補一份後續計畫。概念源自 Plan-and-Solve 提示法與 BabyAGI。優點是簡單、好除錯;缺點是只能循序執行,而且每個步驟都要再叫一次模型。
ReWOO(史丹佛團隊,Xu 等人)。 關鍵洞見是:規劃時根本不需要等觀察結果。規劃者一次產出完整的計畫,計畫裡的步驟可以用變數引用前面步驟的結果(例如「用步驟一查到的電影 ID 去查影評」),執行者按順序填值執行,不再為每個步驟呼叫規劃模型。少了來回,速度與成本都下降。
LLMCompiler(柏克萊團隊,Kim 等人)。 把 ReWOO 的想法再推一步:規劃者產出的不是清單,而是一張 DAG(有向無環圖)。任務擷取單元(Task Fetching Unit)收到串流進來的任務後,只要依賴滿足就立刻排程執行,無關的任務平行跑;最後由 Joiner 決定要回答還是重新規劃。論文宣稱比循序執行快 3.6 倍,也快過 OpenAI 原生的平行工具呼叫。代價是實作複雜度最高。
Chain-of-Agents Planner(CoA-Planner,2026 年預印本)。 把分工再明確化:規劃 Agent 負責拆解成 DAG,工作 Agent 各自執行專長子任務,監督 Agent 在里程碑檢查進度、解決衝突。在長文件分析與程式碼審查等長上下文基準上,平均成功率 67.9%,比單一 Agent 高出 23.5 個百分點。適合任務夠大、值得為它配三種角色的場景。
| 架構 | 計畫形式 | 平行執行 | 適合場景 |
| --- | --- | --- | --- |
| Plan-and-Execute | 循序清單 | 否 | 步驟少、邏輯簡單的任務 |
| ReWOO | 帶變數的清單 | 否 | 需要省模型呼叫次數的任務 |
| LLMCompiler | DAG | 是 | 工具呼叫多、延遲敏感的任務 |
| CoA-Planner | DAG+三角色 | 是 | 長任務、多人協作式的大型分析 |
選型建議很直接:步驟少於五步,Plan-and-Execute 就夠了;工具呼叫開始變多、延遲變明顯,升級到 LLMCompiler 的 DAG 路線;任務大到需要不同專長的執行者,再考慮 CoA-Planner 的三角色分工。
四、實作骨架:一個能跑的 DAG 執行器
計畫的資料結構不需要花俏。一個子任務只要五個欄位:編號、描述、依賴清單、輸入、輸出。執行器用拓樸排序分出「波次」:每一波是所有依賴已滿足的子任務,同一波用 asyncio 平行跑,跑完再進下一波。
import asyncio
from dataclasses import dataclass, field
@dataclass
class Subtask:
id: str
description: str
depends_on: list[str] = field(default_factory=list)
done: bool = False
result: str | None = None
async def run_subtask(task: Subtask, results: dict) -> str:
"""執行單一子任務:把依賴的輸出餵進執行 Agent。"""
context = {d: results[d] for d in task.depends_on}
# 這裡換成真正的 Agent 呼叫:小模型+專屬工具即可
return await execute_with_agent(task.description, context)
async def run_dag(tasks: list[Subtask]) -> dict:
results: dict[str, str] = {}
remaining = {t.id: t for t in tasks}
while remaining:
ready = [t for t in remaining.values()
if all(d in results for d in t.depends_on)]
if not ready:
raise ValueError("依賴出現循環,計畫無效")
done = await asyncio.gather(
*(run_subtask(t, results) for t in ready))
for t, r in zip(ready, done):
results[t.id] = r
remaining.pop(t.id)
return results
這段骨架刻意保持最小:沒有重試、沒有重規劃、沒有驗證。那些是下一節的加法。先讓 DAG 跑起來,再一層一層加。
五、分解的品質檢查:五條規則
計畫寫出來之後、執行之前,先跑一次檢查。實務上最常見的失敗,都不是執行出錯,而是計畫本身寫壞了。
第一,原子性。 每個葉子任務要小到一到兩次工具呼叫就能完成。如果一個子任務的描述裡出現「然後」超過一次,它其實是兩個任務。
第二,驗收標準。 每個子任務都要寫清楚「怎樣算完成」:是回傳一個數字、一份檔案,還是一句確認?沒有驗收標準的任務,執行者只能憑感覺停下來。
第三,依賴顯式化。 依賴關係不能只存在模型腦中,要寫進結構裡。一個子任務需要前面哪個任務的哪個輸出欄位,寫出來。隱性依賴是平行執行時最常見的競態來源。
第四,深度上限四層。 超過四層的分解樹,通常代表任務本身需要拆成多次執行,或是目標還不夠清楚。硬拆只會讓計畫脆弱。
第五,失敗級聯先想好。 執行前就決定:哪個子任務失敗時,哪些後續任務要跟著取消、哪些可以降級繼續、哪些值得重試一次。這是執行器的 _cascade_failure,不是事後補救。把這條規則寫進計畫裡,失敗時才不會整張圖一起垮。
如圖二,一份合格的計畫在執行前就該通過這五項檢查,而不是等執行到一半才發現。
六、動態重規劃:計畫趕不上變化時
再好的計畫也會遇到現實:某個子任務的執行結果跟預期不同,某個工具回傳了計畫時沒想到的資料。這時候有三種反應,成本由低到高:
局部修補。 只改受影響的子任務,例如換一個查詢關鍵字、換一個資料來源。計畫結構不動。
子圖重拆。 把失敗的子任務連同它之後的任務一起拆掉,針對當下的新資訊重新分解一次。這是 LLMCompiler 裡 Joiner 的工作:看完整張圖的執行歷史,決定要回答還是把進度交回規劃者。
全圖重規劃。 目標本身需要修正時才用,例如使用者中途改了需求。成本最高,但比硬著頭皮把錯的計畫跑完便宜。
判斷用哪一種的依據很實際:看失敗影響的範圍有多大。影響一個節點就局部修補,影響一整個分支就子圖重拆,影響目標定義就全圖重規劃。實作上給重規劃設一個上限(例如最多三輪),避免 Agent 在「一直改計畫、永遠不執行」的迴圈裡燒掉預算。
七、起手清單:今天就能做的五件事
1. 先量再拆。 記錄你家 Agent 目前的任務:幾步?成功率多少?超過四步且成功率低於八成的任務,就是分解的第一批候選。
2. 從 Plan-and-Execute 起手。 不要一開始就寫 DAG 執行器。先讓規劃者與執行者分開,驗證拆解本身有沒有提升成功率。
3. 計畫用結構化輸出。 規劃者的輸出走 Structured Output(JSON Schema),不要用自由文字再解析。少一層解析,就少一類錯誤。
4. 平行之前先驗證依賴。 上線平行執行前,先跑一次拓樸檢查,確認沒有循環依賴、沒有指向不存在任務的邊。
5. 把計畫存下來。 每次執行的計畫與結果一起寫進軌跡紀錄。幾週後你會有一份「哪些任務怎麼拆最有效」的實證資料,比任何選型文章都準。
結論:計畫是 Agent 的施工圖,不是裝飾品
任務分解不是為了讓架構圖好看,而是為了解決三個具體問題:ReAct 在長任務上的成功率崩塌、循序執行的延遲浪費、以及出錯時不知道從哪裡開始修。一張寫清楚依賴與驗收標準的 DAG,同時解決了這三個問題:可以平行、可以檢查、可以局部重做。
起手不需要大改架構:先把規劃與執行拆成兩個角色,再把計畫寫成結構,最後才上平行執行。等你家的 Agent 開始接五步以上的任務時,這張施工圖會是它最划算的一次投資。
---
資料來源
- LangChain,〈Plan-and-Execute Agents〉(含 Plan-and-Execute、ReWOO、LLMCompiler 三架構說明):https://www.langchain.com/blog/planning-agents
- Kim 等人,〈An LLM Compiler for Parallel Function Calling〉(柏克萊團隊,arXiv:2312.04511):https://arxiv.org/abs/2312.04511
- Chain-of-Agents Planner 預印本(2026 年 10 月,DAG 任務圖+三角色分工,成功率 67.9%):https://www.preprints.org/manuscript/202610.3027
- GOAT:目標導向 Agent 訓練框架(ICLR 2026 審查中,任務分解與 API 依賴圖):https://openreview.net/pdf/0eae0f58e4ac75ad32cdf379b1c8db1e78eadaf8.pdf
- dev.to,〈Why Your Agent Can't Follow a Plan (And How to Fix It)〉(TaskTree 實作、深度上限四層、失敗級聯):https://dev.to/manfredmacx/why-your-agent-cant-follow-a-plan-and-how-to-fix-it-f4g
沒有留言:
張貼留言