顯示具有 任務分解 標籤的文章。 顯示所有文章
顯示具有 任務分解 標籤的文章。 顯示所有文章

Agent 任務分解實作課:先把大任務拆成 DAG,再讓 Agent 動手

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

Uploaded Image Uploaded Image