模組 05 · 第 6 課

多個智慧體一起幹活

讓一個主管智慧體拆分任務,交給三個上下文獨立的工人智慧體並行完成,再彙總結果。和單個智慧體比,詞元少了三分之一,主管的上下文只有 472 個詞元。講清楚多智慧體真正的價值和代價。

  • 約 40 分鐘
  • 難度:進階
  • 實測:2026-09-14 deepseek-flash

程式碼和執行結果保留原樣(簡體中文),與實際執行時完全一致。

一個人幹不完的活,可以分給幾個人一起幹。智慧體也一樣:讓一個"主管"把大任務拆成幾個小任務,分給幾個"工人"智慧體,各自完成後再彙總。這叫多智慧體(multi-agent)系統。

聽起來很自然,也很有吸引力,很多框架專門為此設計。但在把一個任務交給一群智慧體之前,要先搞清楚:多智慧體到底解決了什麼問題?代價是什麼?這一課用第 4 課的那個任務做一次對比。

幾種常見的組織方式

  • 主管和工人:主管負責拆分任務、分派、彙總,工人各自完成一個子任務。子任務之間互相獨立時最合適。
  • 流水線:第一個智慧體的輸出是第二個的輸入,比如"研究員查資料 → 寫手寫初稿 → 審稿人檢查"。這其實更像工作流,只是每一步由一個智慧體完成。
  • 辯論:幾個智慧體對同一個問題各自給出答案,互相質疑,最後投票或者由裁判決定。

這一課做第一種,因為它最能說明多智慧體的核心好處。

實驗:主管加三個工人

任務還是第 4 課那個:"對比 httpx 的 Client 和 AsyncClient 在三件事上的配置方式是否一樣:超時、代理、HTTP/2。列成一張表,每一格都註明文件出處。"

這個任務天然可以拆開:超時、代理、HTTP/2 三件事互不相關,可以分別去查。

主管的工作分三步:

def supervisor():
    # 1. 主管拆任务:只输出 JSON,不调用工具
    plan, usage = model([{"role": "user", "content":
                          TASK + '\n\n把这个任务拆成互相独立、可以同时进行的子任务,每个子任务只查一件事。'
                                 '输出 json:{"subtasks": ["子任务1", ...]}'}], None)
    subtasks = json.loads(plan.content[plan.content.find("{"):plan.content.rfind("}") + 1])["subtasks"]

    # 2. 工人并行执行,每个工人都从空白的上下文开始,只看得到自己的子任务
    def work(subtask):
        return run_agent(model, subtask + "\n只回答这一件事,写清楚结论和文档出处(文件名和行号),不超过 150 字。",
                         max_steps=8, verbose=False)

    with ThreadPoolExecutor(len(subtasks)) as pool:
        results = list(pool.map(work, subtasks))

    # 3. 主管汇总:只看工人交回的简短结论,看不到工人查过的原文
    reports = "\n\n".join(f"子任务:{s}\n结论:{a}" for s, (a, _) in zip(subtasks, results))
    final, usage = model([{"role": "user", "content": TASK + "\n\n下面是各子任务的调查结论,据此完成任务:\n\n" + reports}], None)
    ……

工人就是第 2 課寫的 run_agent,沒有任何改動。每個工人從一個空白的訊息列表開始,只知道自己的子任務。要求它們"不超過 150 字",是為了讓交回給主管的內容足夠精煉。

對照組是單個智慧體直接做整個任務。完整程式碼在 code/05-agents/multi_agent.py

結果

單個智慧體:

===== 单个智能体
[第 1 步] grep_docs({"keyword": "AsyncClient"}) → ……
[第 1 步] grep_docs({"keyword": "timeout"}) → ……
[第 1 步] grep_docs({"keyword": "proxy"}) → ……
[第 1 步] grep_docs({"keyword": "http2"}) → ……
[第 2 步] read_doc({"path": "advanced/timeouts.md", "start": 1, "end": 70}) → ……
[第 2 步] read_doc({"path": "advanced/proxies.md", "start": 1, "end": 50}) → ……
[第 2 步] read_doc({"path": "http2.md", "start": 20, "end": 75}) → ……
[第 3 步] read_doc({"path": "advanced/timeouts.md", "start": 70, "end": 120}) → ……
[第 3 步] grep_docs({"keyword": "AsyncClient(proxy"}) → 没有找到 AsyncClient(proxy
[第 3 步] grep_docs({"keyword": "AsyncClient(timeout"}) → 没有找到 AsyncClient(timeout
[第 3 步] read_doc({"path": "advanced/clients.md", "start": 1, "end": 40}) → ……
[第 4 步] read_doc({"path": "async.md", "start": 1, "end": 50}) → ……
[第 4 步] read_doc({"path": "api.md", "start": 30, "end": 50}) → ……
  共 5 次模型调用,21047 词元,9 秒

主管加三個工人:

===== 主管 + 工人
  主管拆出了 3 个子任务:
    - 调查 httpx.Client 和 AsyncClient 在超时(timeout)配置上的方式是否一样,并记录文档文件名和行号
    - 调查 httpx.Client 和 AsyncClient 在代理(proxy)配置上的方式是否一样,并记录文档文件名和行号
    - 调查 httpx.Client 和 AsyncClient 在 HTTP/2 配置上的方式是否一样,并记录文档文件名和行号
  工人「调查 httpx.Client 和 As…」:3 次调用,3943 词元,交回 222 字
  工人「调查 httpx.Client 和 As…」:3 次调用,4506 词元,交回 208 字
  工人「调查 httpx.Client 和 As…」:3 次调用,4094 词元,交回 253 字
  主管汇总时的输入:472 词元
  共 11 次模型调用,13720 词元,8 秒

兩份最終回答的結論基本一致:HTTP/2 在文件裡有明確依據(http2.md 第 50 行"HTTP/2 support is available on both Client and AsyncClient"),超時和代理的示例只寫了 Client,AsyncClient 用同樣的參數是推斷。

讀結果

詞元少了三分之一。多智慧體用了 11 次模型呼叫,比單個智慧體的 5 次多了一倍多,可總詞元反而更少:13720 對 21047。

原因在於上下文的增長方式。單個智慧體做到第 4 步時,它的訊息列表裡已經裝著超時、代理、HTTP/2 三個主題的所有搜尋結果和文件內容,每一步都要帶著這一大堆東西呼叫模型。而每個工人只裝著自己那一個主題的內容,上下文一直很小。三個小上下文加起來,比一個不斷膨脹的大上下文便宜。

主管的上下文很乾淨。彙總時,主管的輸入只有 472 個詞元:三份各兩百多字的結論。它看不到工人讀過的那些文件原文。這是多智慧體最核心的好處:上下文隔離。每個智慧體只看到它需要看到的東西,主管不會被細節淹沒。在更長、更復雜的任務裡,這個好處會更明顯。

時間差不多。三個工人並行執行,所以雖然總呼叫次數多,總耗時(8 秒)和單個智慧體(9 秒)差不多。如果序列執行,就會慢得多。

單個智慧體的回答又一次以英文開頭。它最終回答的第一句是 "I have enough evidence. Let me summarize.",然後才是中文。上下文裡堆滿英文文件時,模型的語言容易跑偏,第 4 課也出現過一次。多智慧體的主管上下文裡幾乎只有中文結論,就沒有這個問題。

代價

  • 細節會丟。工人只交回 150 字的結論,主管拿不到原文。如果某個工人的結論寫得含糊,或者漏了關鍵的出處,主管沒辦法發現,更沒法糾正。要求工人交回的格式越明確(結論、依據、出處、不確定的地方),這個問題越小。
  • 拆分本身可能出錯。這次的任務很容易拆,三個主題互不相關。如果子任務之間有依賴,比如"先找到出錯的函式,再看它的呼叫者",並行拆分就行不通了。
  • 呼叫次數更多,系統更復雜。每多一個智慧體,就多一份提示詞要寫、多一條執行軌跡要看。出了問題,要先判斷是拆分的問題、某個工人的問題,還是彙總的問題。

什麼時候用

  • 子任務互相獨立,而且每個都要讀大量材料。上下文隔離能省錢,也能讓每個智慧體更專注。
  • 想讓一個長任務的主線保持清爽。Claude Code 這類程式設計智慧體會把"去程式碼庫裡搜尋某樣東西"這種子任務交給子智慧體,子智慧體翻遍幾十個檔案,只把找到的結果交回來,主對話不會被塞滿搜尋的中間過程。
  • 不同的子任務需要不同的工具或者不同的提示詞。給每個工人只配它需要的工具,比給一個智慧體配一大堆工具更不容易出錯(第 3 課講過工具多了的問題)。

大多數任務,一個智慧體就夠了。先用一個,發現上下文膨脹得太厲害、或者任務天然可以並行拆分時,再考慮拆。

練習

  1. 把工人的"不超過 150 字"改成"不超過 50 字",重新執行。最終回答的出處還完整嗎?
  2. 讓工人按固定格式交回結論:结论:…… 依据原文:…… 出处:…… 不确定的地方:……。彙總後的回答質量有變化嗎?
  3. 設計一個子任務之間有依賴的問題(比如"找出 httpx 裡丟擲 ReadTimeout 的函式,再說明它會被哪些公開 API 呼叫"),讓主管去拆,看看它拆得對不對。

自測

1. 多智慧體的呼叫次數比單個智慧體多,為什麼總詞元數反而更少?

單個智慧體的上下文會不斷累積所有子主題的搜尋結果和文件,後面每一步都要帶著這些內容呼叫模型。多智慧體裡,每個工人只裝著自己子任務的內容,上下文一直很小,主管也只看到簡短的結論。幾個小上下文加起來,比一個持續膨脹的大上下文便宜。

2. 多智慧體裡"上下文隔離"指的是什麼?有什麼好處和壞處?

每個智慧體只看到它需要的資訊:工人只看自己的子任務和查到的材料,主管只看工人交回的結論。好處是每個上下文都小而專注,省錢,主管不會被細節淹沒。壞處是資訊在交接時會丟失,主管無法核對工人沒有交回的細節。

3. 什麼樣的任務不適合拆給多個工人並行完成?

子任務之間有依賴的任務。比如後一步要基於前一步找到的結果,就沒法並行,強行拆開,後面的工人會缺少前面的資訊。另外,任務本身很簡單、一個智慧體幾步就能完成時,拆分只會增加呼叫次數和複雜度。

提問與討論

這一課沒看懂的地方,在這裡問。看到別人的問題,也歡迎你來回答。

提問 +3 點,回答別人 +6 點。內容經審核後公開。

正在載入討論…