在前幾篇文章中,我們已經學會如何使用 LangGraph 建立單一 Agent,並讓它能夠依循 ReAct 模式進行推理與行動。不過在更複雜的應用場景裡,一個 Agent 往往無法包辦所有工作。我們需要一種方式,讓多個具備不同專長的 Agent 協作,像是團隊成員各司其職,來完成更龐大或多樣化的任務。
今天,我們要學習如何建立 多代理系統(Multi-Agent Systems),示範如何用 LangGraph 將多個 Agent 串接起來,打造一個具協作能力的 AI 架構。
在應用開發的早期階段,我們通常只會建立一個 Agent,並把所有任務交給它處理。但隨著功能需求逐漸擴張,單一 Agent 很快就會遇到瓶頸,常見的挑戰包括:
為了解決這些問題,我們可以將應用拆解為多個專職 Agent,讓它們各自處理特定任務,再透過流程協作整合結果,形成一個 多代理系統(Multi-Agent Systems)。這些 Agent 的形式可能很簡單,只是一段提示搭配一次 LLM 呼叫,也可以是更複雜的架構,例如完整的 ReAct Agent。
多代理系統帶來的好處包括:
換句話說,當單一 Agent 難以有效應付龐大或多樣化的需求時,將任務拆分並交由多個專職 Agent 分工合作,往往是更靈活且穩健的解決方案。
在多代理系統中,核心挑戰之一是如何安排不同 Agent 之間的溝通與協作。不同的架構會影響整體的彈性、可擴充性與可維護性。常見的架構模式包括:
不同架構各有優缺點,選擇時需要根據應用場景來取捨:若需要高度彈性可考慮網路型;若追求集中控制則監督型更合適;而針對明確業務流程則建議自訂化設計。
當我們決定把單一 Agent 拆分成多個專職 Agent 後,下一個挑戰就是:這些 Agent 要怎麼彼此協作?
在 LangGraph 中,我們可以將 Agent 視為圖上的 節點(Node),彼此透過 邊(Edge) 傳遞任務與狀態。這樣的設計不僅能清楚定義流程,也能讓系統更有彈性。
在著手實現多代理系統之前,必須先理解 Agent 之間的交棒機制。在這類系統中,每個 Agent 通常只專注於特定的任務;當某個 Agent 完成工作後,流程應該如何推進?有些情況下可以直接結束,但更多時候則需要將控制權交給另一個更合適的 Agent。這種「從一個 Agent 切換到另一個 Agent」的過程,就稱為 Handoff。
一個完善的 handoff 機制能確保流程不中斷,同時避免資訊遺失。它的核心包含兩個要素:
若缺少這些設計,系統很容易出現任務中斷、結果不完整,甚至讓多代理協作失去意義。
在 LangGraph 中,交棒可以透過 Command
物件實現。當一個 Agent 執行完成後,可以回傳一個 Command
,其中利用 goto
指定下一個要執行的 Agent,並透過 update
傳遞或更新共享狀態。這樣的設計能確保無論流程是單純還是複雜,每次交棒都能攜帶正確的上下文繼續執行,維持整個多代理系統的穩定與可控性。
以下是一個簡單範例:
const agent = (state: typeof StateAnnotation.State) => {
const goto = getNextAgent(...);
return new Command({
goto: goto,
update: { foo: 'bar' },
});
};
在更複雜的架構中,每個 Agent 本身可能是一個 子圖(subgraph)。若需要從子圖中的某個節點直接交棒給另一個獨立的 Agent,就必須讓交接訊號「跳出」子圖,回到父層圖(Parent Graph)進行重新分配。這時可以使用 graph: Command.PARENT
:
const some_node_inside_alice = (state) => {
return new Command({
goto: 'bob',
update: { foo: 'bar' },
graph: Command.PARENT,
});
};
透過這樣的設計,不論系統是單層的簡單流程,還是由多層子圖組成的複雜架構,都能建立一套清晰且可控的交棒機制,確保多代理系統能順暢運作並具備可擴展性。
在 網路型(Network) 架構中,所有 Agent 彼此之間都是對等的,任何 Agent 都可以把控制權交給其他 Agent,形成多對多的連線方式。這種設計擁有最高的彈性,因為流程不必遵循固定路徑,可以根據當下的需求自由切換。
它的優點是靈活度極高,特別適合需要探索式嘗試或多方向並行思考的場景。然而,缺點也相當明顯:由於缺乏明確的控制中心,系統容易陷入無窮迴圈,或因為 Agent 之間過於頻繁的互動而造成流程混亂。
以下是一個概念性範例,說明如何在 LangGraph 中表達 Network 架構:
import { StateGraph, MessagesAnnotation, Command, START, END } from '@langchain/langgraph';
import { ChatOpenAI } from '@langchain/openai';
// 建立 LLM
const llm = new ChatOpenAI({
model: 'gpt-4o-mini',
});
// 節點實作範例:每個 Agent 都會回傳訊息,並決定下一個要交棒的 Agent
const agentA = async (state: typeof MessagesAnnotation.State) => {
const response = await llm.withStructuredOutput(...).invoke(...);
return new Command({
update: { messages: [response.content] },
goto: response.next_agent, // 動態決定下一步
});
};
const agentB = async (state: typeof MessagesAnnotation.State) => {
const response = await llm.withStructuredOutput(...).invoke(...);
return new Command({
update: { messages: [response.content] },
goto: response.next_agent,
});
};
const agentC = async (state: typeof MessagesAnnotation.State) => {
const response = await llm.withStructuredOutput(...).invoke(...);
return new Command({
update: { messages: [response.content] },
goto: response.next_agent,
});
};
const agentD = async (state: typeof MessagesAnnotation.State) => {
const response = await llm.withStructuredOutput(...).invoke(...);
return new Command({
update: { messages: [response.content] },
goto: response.next_agent,
});
};
// 定義四個 Agent 節點,並允許它們彼此互相呼叫
const graph = new StateGraph(MessagesAnnotation)
.addNode('agentA', agentA, {
ends: ['agentB', 'agentC', 'agentD', END],
})
.addNode('agentB', agentB, {
ends: ['agentA', 'agentC', 'agentD', END],
})
.addNode('agentC', agentC, {
ends: ['agentA', 'agentB', 'agentD', END],
})
.addNode('agentD', agentD, {
ends: ['agentA', 'agentB', 'agentC', END],
})
.addEdge(START, 'agentA') // 從 Agent A 開始
.compile();
在這個架構裡,任務的流向是 動態決定。每個 Agent 根據當前的狀態與輸入,決定下一個要呼叫的 Agent,直到流程進入 END
為止。這樣的彈性雖然能讓系統更靈活,但也可能導致流程變得複雜,因此通常會搭配額外的規則或控制條件,以避免無限循環或不必要的跳轉。
監督型(Supervisor) 架構透過引入一個 Supervisor Agent 來集中調度。所有工作 Agent 在完成任務後,會把結果交回 Supervisor,由它判斷並決定接下來要交給誰執行。這種模式讓系統結構更清晰,特別適合任務明確、角色分工清楚的應用。
相較於 Network 架構,Supervisor 的好處在於能夠集中管理,讓流程更具可預測性,也更容易除錯與維護。不過,它也存在潛在的瓶頸問題:一旦 Supervisor Agent 本身設計不佳,就可能成為整個系統的拖累,甚至演變為單點故障的來源。
以下是一個概念性範例,說明如何在 LangGraph 中表達 Supervisor 架構:
import { StateGraph, MessagesAnnotation, Command, START, END } from '@langchain/langgraph';
import { ChatOpenAI } from '@langchain/openai';
const llm = new ChatOpenAI({
model: 'gpt-4o-mini',
});
// Supervisor 負責判斷下一個要交給誰
const supervisor = async (state: typeof MessagesAnnotation.State) => {
const response = await llm.withStructuredOutput(...).invoke(...);
return new Command({
goto: response.next_agent, // 由 Supervisor 決定下一個 Agent
});
};
// 一般 Agent 完成工作後,將結果交回 Supervisor
const agentA = async (state: typeof MessagesAnnotation.State) => {
const response = await llm.invoke(...);
return new Command({
goto: 'supervisor',
update: { messages: [response] },
});
};
const agentB = async (state: typeof MessagesAnnotation.State) => {
const response = await llm.invoke(...);
return new Command({
goto: 'supervisor',
update: { messages: [response] },
});
};
const agentC = async (state: typeof MessagesAnnotation.State) => {
const response = await llm.invoke(...);
return new Command({
goto: 'supervisor',
update: { messages: [response] },
});
};
const agentD = async (state: typeof MessagesAnnotation.State) => {
const response = await llm.invoke(...);
return new Command({
goto: 'supervisor',
update: { messages: [response] },
});
};
// 建立圖形流程:由 Supervisor 統一協調四個 Agent
const graph = new StateGraph(MessagesAnnotation)
.addNode('supervisor', supervisor, {
ends: ['agentA', 'agentB', 'agentC', 'agentD', END],
})
.addNode('agentA', agentA, { ends: ['supervisor'] })
.addNode('agentB', agentB, { ends: ['supervisor'] })
.addNode('agentC', agentC, { ends: ['supervisor'] })
.addNode('agentD', agentD, { ends: ['supervisor'] })
.addEdge(START, 'supervisor')
.compile();
在這個架構中,流程的核心掌握在 Supervisor 手中。每個 Agent 完成任務後,不會直接交棒給其他 Agent,而是統一回到 Supervisor,由它來決定下一步的方向。這種集中式調度方式能讓系統更有秩序,但同時也需要確保 Supervisor 本身足夠穩健,否則會成為整個流程的瓶頸。
在 自訂流程(Custom Workflow) 架構中,開發者會事先定義 Agent 之間的執行順序,流程幾乎完全由程式控制,而不是交給模型決定。這種模式可視為最工程化的做法,因為每一步的執行對象都被明確規範。
它的最大優點是流程可預測性極高,設計簡單且穩定,適合處理高度結構化的需求。不過,缺點是缺乏彈性,無法因應任務中臨時出現的變化,對於需要動態決策的情境而言限制較多。
以下是一個概念性範例,說明如何在 LangGraph 中表達固定順序與條件分支的工作流程:
import { StateGraph, MessagesAnnotation, Command, START, END } from '@langchain/langgraph';
import { ChatOpenAI } from '@langchain/openai';
const llm = new ChatOpenAI({
model: 'gpt-4o-mini',
});
const agentA = async (state: typeof MessagesAnnotation.State) => {
const response = await llm.invoke(...);
return { messages: [response] };
};
const agentB = async (state: typeof MessagesAnnotation.State) => {
const response = await llm.withStructuredOutput(...).invoke(...);
// 這裡示範動態控制:B 可以選擇直接交給 D,或先交給 C 再回到 D
return new Command({
update: { messages: [response.content] },
goto: response.next_agent, // next_agent 可能是 "agentC" 或 "agentD"
});
};
const agentC = async (state: typeof MessagesAnnotation.State) => {
const response = await llm.invoke(...);
return new Command({
update: { messages: [response] },
goto: 'agentD', // C 完成後一定交給 D
});
};
const agentD = async (state: typeof MessagesAnnotation.State) => {
const response = await llm.invoke(...);
return { messages: [response] };
};
// 定義固定與條件流程:A → B → (C) → D
const graph = new StateGraph(MessagesAnnotation)
.addNode('agentA', agentA, { ends: ['agentB'] })
.addNode('agentB', agentB, { ends: ['agentC', 'agentD'] })
.addNode('agentC', agentC, { ends: ['agentD'] })
.addNode('agentD', agentD, { ends: [END] })
.addEdge(START, 'agentA')
.compile();
在這個例子中,流程會從 agentA
開始,接著交給 agentB
。B 根據邏輯決定是直接交給 agentD
,還是先走 agentC
再回到 D,最後流程才會結束。這種設計在需要清楚掌控執行路徑的情境下非常實用,例如資料處理管線或多步驟任務。
在多 Agent 系統中,最重要的設計課題之一就是 Agent 如何彼此溝通。畢竟,單一 Agent 只能解決局部問題,若要形成完整解決方案,必須建立起有效的訊息交換機制。一般來說,有兩種常見的設計思路:透過 共享狀態 傳遞資料,或是使用 共享訊息列表 進行交流。
在 LangGraph 裡,每個 Agent 可以被建模成圖上的一個 節點(Node) 或 子圖(Subgraph)。當流程被執行時,Agent 節點會接收到當前的 狀態(State),執行相應的邏輯,然後將更新後的狀態傳遞給下一個節點。
這種方式的前提是假設大多數 Agent 共用同一份 狀態結構(State Schema)。但在實務上,不同的 Agent 可能需要不同的狀態格式,例如搜尋型 Agent 可能只需要追蹤查詢字串與檢索結果,而不必關心整個對話內容。此時有兩種做法:
這些方法能夠讓不同角色的 Agent 各自處理最相關的資訊,同時仍能融入系統流程中。
另一種常見的方式是讓所有 Agent 共用一個 訊息列表(Message List),就像多人在同一個聊天室裡協作。這個訊息列表可以被視為一個公共頻道,所有 Agent 都能在上面讀取或新增訊息。這樣的做法特別適合基於對話的 Multi-Agent 系統。
在採用訊息列表時,有一個關鍵設計問題:Agent 是否要 共享完整歷程,還是僅 共享最終結果?
如果系統目標是提升推理能力與決策品質,完整歷程共享會更有利;若更重視效率與資源控管,則只共享最終結果會更合適。實務上,開發者常常需要依應用情境混搭這些策略,才能在準確度與效能之間取得平衡。
今天我們探討了如何透過 多代理系統(Multi-Agent Systems),讓多個具備不同專長的 Agent 協作,進而打造更靈活、可控且可擴展的 AI 架構:
Command
物件實現,確保交棒時能正確指定下一個 Agent 並傳遞必要上下文。總體而言,多代理系統就像一支由專家組成的團隊,透過分工合作與有效的交棒機制,讓 AI 從單兵作戰進化為團隊協作,在複雜應用中展現更高的彈性與擴展性。