本文 AI 產出,尚未審核

LangChain 多代理系統(Multi‑Agent Systems)

主題:角色分工與任務調度


簡介

在 LLM(大型語言模型)與 LangChain 的生態系統中,單一的 Agent 常常只能處理相對簡單或單一的任務。當需求變得複雜,例如需要同時查詢資料庫、產生報表、與使用者互動以及執行外部 API 呼叫時,多代理系統(Multi‑Agent System, MAS)就成為最佳解法。

透過角色分工(Role Assignment)與任務調度(Task Scheduling),我們可以把大型問題切分成多個子任務,交給最適合的 Agent 處理,從而提升系統的 可擴展性、可維護性執行效率。本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,帶領讀者一步步建構自己的多代理系統。


核心概念

1️⃣ 代理(Agent)的基本概念

在 LangChain 中,Agent 是一個「能夠自行決策、呼叫工具(Tool)或其他 Agent」的程式單元。最常見的實作是 LLM‑Driven Agent:它接收使用者的自然語言指令,分析後決定要使用哪個工具(如搜尋、計算、資料庫查詢),然後回傳結果。

重點:每個 Agent 內部都有一個 Prompt、一個 LLM(或多個)以及一組 Tool。透過這三者的組合,我們可以讓 Agent 完成特定工作。

2️⃣ 角色分工模型

多代理系統的第一步是 定義角色(Role),每個角色負責一類問題或工具集合。常見的角色包括:

角色 負責的任務 典型工具
資訊檢索者 在網路或文件中搜尋答案 SearchToolWikipediaAPI
計算專家 數學、統計或程式碼執行 PythonREPLCalculator
資料庫管理員 讀寫 SQL/NoSQL SQLToolMongoDBTool
報表產生器 整理資訊、產出 PDF/HTML ReportGeneratorTemplateRenderer
協調者(Scheduler) 分配子任務、追蹤進度 自訂 TaskScheduler

每個角色的 Prompt 會明確說明它的職責,並限制只能呼叫特定工具,這樣可以避免 Agent「跑題」或濫用資源。

3️⃣ 任務調度策略

在多代理系統中,任務調度 負責把使用者的原始需求拆解成子任務、指派給適當的角色、並在所有子任務完成後彙總回應。常見的調度策略有:

策略 說明 適用情境
順序執行(Sequential) 依序呼叫 Agent,前一個結果作為下一個的輸入 任務間有明顯依賴,例如「先搜尋再計算」
平行執行(Parallel) 同時觸發多個 Agent,等待全部完成 多筆獨立查詢或大量資料抓取
條件分支(Conditional) 根據前置結果決定呼叫哪個 Agent 「若搜尋結果包含數字則計算,否則回報未找到」
回饋迴圈(Feedback Loop) Agent 完成後再次檢查是否需要補充任務 需要多輪驗證或修正的情境

LangChain 提供 AgentExecutorConcurrentExecutorRouterChain 等工具,讓我們可以快速組合上述策略。

4️⃣ 程式碼範例

以下範例採用 LangChainJS(JavaScript/TypeScript)撰寫,示範如何建立三個角色(資訊檢索者、計算專家、協調者)並以 條件分支 + 平行 的方式完成「查詢股票價格並計算報酬率」的任務。

⚠️ 範例僅示意,實務上應根據 API 金鑰、錯誤處理等細節自行補齊。

4.1 安裝必要套件

npm install langchain openai axios

4.2 建立共用 LLM 與工具

// llm.js
import { OpenAI } from "langchain/llms/openai";
import { SerpAPI } from "langchain/tools/serpapi";
import { Calculator } from "langchain/tools/calculator";

// 使用 OpenAI 的 gpt-4 模型
export const llm = new OpenAI({
  temperature: 0,
  modelName: "gpt-4",
  openAIApiKey: process.env.OPENAI_API_KEY,
});

// 工具:網頁搜尋(SerpAPI)與計算機
export const searchTool = new SerpAPI({
  apiKey: process.env.SERPAPI_KEY,
  // 只允許搜尋股票代號相關資訊
  serpApiParams: { hl: "en", gl: "us", safe: "active" },
});

export const calculator = new Calculator();

4.3 定義三個角色的 Prompt 與 Agent

// agents.js
import { initializeAgentExecutorWithOptions } from "langchain/agents";
import { llm, searchTool, calculator } from "./llm";

// 1️⃣ 資訊檢索者(只允許使用 searchTool)
export const infoAgent = await initializeAgentExecutorWithOptions(
  [searchTool],
  llm,
  {
    agentType: "zero-shot-react-description",
    // 角色說明,限制只能搜尋股票資訊
    prefix: `
你是一位專業的股票資訊檢索者。請根據使用者提供的股票代號,使用搜尋工具取得最新的收盤價、成交量與公司的簡要描述。`,
    // 嚴格禁止使用其他工具
    allowedTools: ["SerpAPI"],
  }
);

// 2️⃣ 計算專家(只允許使用 calculator)
export const computeAgent = await initializeAgentExecutorWithOptions(
  [calculator],
  llm,
  {
    agentType: "zero-shot-react-description",
    prefix: `
你是一位財務計算專家。請根據提供的兩個價格(舊價與新價),計算報酬率(%),並回傳四捨五入至兩位小數的結果。`,
    allowedTools: ["Calculator"],
  }
);

// 3️⃣ 協調者(Scheduler)負責分派與彙總
export const schedulerAgent = async (stockSymbol) => {
  // 步驟 1:資訊檢索
  const infoResult = await infoAgent.run(
    `取得 ${stockSymbol} 最近的收盤價與成交量。`
  );

  // 從搜尋結果中抽出價格(簡化示範,實務上需正規表達式解析)
  const priceMatch = infoResult.match(/收盤價[:\s]*([\d.]+)/);
  const price = priceMatch ? priceMatch[1] : null;

  if (!price) {
    return `抱歉,未能取得 ${stockSymbol} 的收盤價。`;
  }

  // 步驟 2:平行計算(假設舊價為 30,使用計算專家)
  const computePromise = computeAgent.run(
    `舊價 30, 新價 ${price}, 計算報酬率。`
  );

  // 若未來有其他平行任務,可加入更多 Promise
  const [computeResult] = await Promise.all([computePromise]);

  // 步驟 3:彙總回傳
  return `股票 ${stockSymbol} 的最新收盤價為 ${price},報酬率為 ${computeResult}%。`;
};

4.4 呼叫協調者取得最終答案

// index.js
import { schedulerAgent } from "./agents";

(async () => {
  const symbol = "AAPL"; // 蘋果公司
  const answer = await schedulerAgent(symbol);
  console.log(answer);
})();

說明

  1. infoAgent 只會使用 SerpAPI,因此不會誤呼叫計算工具。
  2. computeAgent 僅能呼叫 Calculator,避免產生不相關的搜尋。
  3. schedulerAgent 先取得資訊,若成功才交給計算專家,最後彙整回傳。
  4. 透過 Promise.all,未來若加入更多平行子任務,只要把 Promise 加入陣列即可。

4.5 其他實用範例

範例 目的 關鍵程式碼
多輪對話協調 讓 Agent 在回合間保持上下文 使用 ConversationChain + Memory
動態角色切換 根據使用者需求自動選擇最適角色 RouterChain 搭配 LLMRouter
錯誤重試機制 任務失敗時自動重試或切換備援 Agent retry 包裝器或 async-retry 套件
資源限制 防止單一 Agent 用盡 API 配額 Tool.useLimit 或自行實作計數器
// 動態路由示例(RouterChain)
import { RouterChain, LLMRouter } from "langchain/chains/router";

const router = new RouterChain({
  llm: llm,
  routes: {
    search: infoAgent,
    compute: computeAgent,
  },
  llmRouter: new LLMRouter({
    // LLM 判斷使用者需求屬於搜尋還是計算
    llm,
    prompt: `判斷以下需求屬於「搜尋」還是「計算」任務,僅回傳「search」或「compute」:\n{{input}}`,
  }),
});

常見陷阱與最佳實踐

陷阱 說明 最佳實踐
角色工具不對稱 Agent 被賦予過多工具,導致「跑題」或不必要的 API 呼叫。 allowedTools嚴格列出 必要工具;使用 Prompt 明確限定職責。
子任務依賴未處理 平行執行時忽略前置結果,造成錯誤或無效計算。 使用 Promise.allSettled 捕捉失敗;在調度器內檢查前置結果是否為 null
回饋迴圈無止境 協調者不斷重新分派相同任務,形成死循環。 為每個子任務設定 最大重試次數,或在 LLM 回答中加入「已完成」的明確標誌。
資源配額耗盡 多 Agent 同時呼叫外部 API,快速觸發配額上限。 實作 Rate Limiter(如 bottleneck)並在工具層面加入 使用次數上限
錯誤訊息傳遞不透明 使用者只看到最終結果,無法得知哪個子任務失敗。 在協調者回傳時附加 子任務日誌(log),例如 [{role: "search", status: "error", message: "..."}]

其他實用建議

  1. 使用 Memory:若子任務需要跨回合的上下文,給每個 Agent 配置 ConversationBufferMemory
  2. 分層抽象:將「工具」與「角色」分離,工具只負責 I/O,角色則負責決策與 Prompt 設計。
  3. 測試驅動:為每個 Agent 寫單元測試,模擬工具回傳,確保角色不會誤用工具。
  4. 日誌與監控:利用 winstonpino 記錄每次工具呼叫、耗時與回傳結果,方便日後排查。

實際應用場景

場景 角色配置 任務調度方式 為何適合多代理
金融投資顧問 資訊檢索者 → 計算專家 → 法規檢查員 → 報表產生器 條件分支 + 平行(同時檢索多支股票) 需要同時取得市場資訊、計算風險指標、符合合規要求,且每個子任務都有專精工具。
客服自動化 意圖辨識 Agent → 知識庫檢索 Agent → 交易執行 Agent → 回饋收集 Agent 順序執行 + 回饋迴圈 客戶問題先辨識,再從文件搜尋答案,若涉及交易則呼叫交易 API,最後收集滿意度。
內容生成與校對 主題產生 Agent → 文字生成 Agent → 文法校正 Agent → 風格檢查 Agent 平行(生成多段)→ 順序(校正) 大量內容需要同時產出多篇草稿,之後統一校對與風格調整。
IoT 裝置協調 裝置狀態監控 Agent → 故障診斷 Agent → 指令下發 Agent 條件分支(若故障則診斷)+ 平行(多裝置) 每個裝置都有不同的 API,故障時需要快速定位與下指令。

總結

  • 多代理系統 讓我們可以把複雜的 LLM 任務拆解成「專精」的子任務,提升效能與可維護性。
  • 角色分工 必須透過 PromptTool 限制 以及 Memory 來明確界定,避免 Agent 混用工具。
  • 任務調度 依需求選擇 順序、平行、條件分支或回饋迴圈,並在程式碼層面加入 錯誤重試、資源限流與日誌,才能在實務環境中穩定運行。
  • 使用 LangChain 提供的 AgentExecutor、RouterChain、ConcurrentExecutor 等高階抽象,可大幅降低開發門檻,讓開發者專注於業務邏輯而非底層協調細節。

透過本文的概念說明與實作範例,你已掌握 角色分工與任務調度 的核心技巧,接下來可以依照自己的業務需求,組合出更具彈性與擴充性的多代理系統。祝開發順利,創造出更多有價值的 AI 應用!