Agentic Research
首頁/學習/LangGraph 是什麼:從線性鏈到狀態機式 Agent

LangGraph 是什麼:從線性鏈到狀態機式 Agent

2026/09/2911 分鐘Bryan Chan最後更新 2026/09/29
這篇屬於學習主題LangGraphAgent狀態機Python

先講結論

LangGraph 做的事,是把 Agent 的流程從「一條直線」改寫成「一張有岔路的地鐵圖」。

打個比方。傳統的 Chain 像一條單向輸送帶:原料從這頭進去,成品從那頭出來,中間不會回頭,也不會突然換線。這在流程簡單時非常好用。

但真實世界的流程很少這麼乖。你很快就會遇到這三種情況:

  • 有些問題要繞道去查資料,有些不用
  • 查不到要回頭重試
  • 跑到一半要停下來等人審批

輸送帶做不到這些。你需要的是一張地鐵圖:有轉乘站,有迴圈線,也可以在某一站先停車等人。

LangGraph 就是幫你把「輸送帶」畫成「地鐵圖」的那套工具。你定義三樣東西:狀態(列車上載什麼)、節點(每一站做什麼)、邊(什麼條件下開往下一站)。

LangGraph 主流程圖:把判斷畫成圖上的一條岔路

一、先看一個真實的痛點

假設你在做一個客服問答機器人。它上線之後,日常會遇到四類輸入:

使用者輸入理想處理方式
「退貨政策是什麼」查知識庫,然後直接回答
「我要改收貨地址」查訂單系統,但要先驗證身分
「你們服務太差了」轉人工,不要硬答
「上一個問題再解釋一次」沿用上文,不要從頭來

把這四句話攤開看,會發現一件事:它們共用同一批工具(知識庫、訂單系統、人工客服),卻不是同一個流程。

真正決定走向的,是中間那個判斷:這句話該走哪條路?

Chain 的麻煩就在這裡。它假設你事先就知道順序,順著寫下去就好。可是「該走哪條路」這件事,往往要讀完內容才知道。

這就像餐廳的點餐流程。如果每位客人都點同一份套餐,一條輸送帶就夠了。但現實是有人要加辣、有人不吃牛、有人只想喝湯。你需要一個在旁邊看著菜單、決定往哪個廚房送的人。

那個「決定往哪送」的動作,就是 LangGraph 幫你顯式化的東西。

二、Chain 撐不住的三件事

先講清楚:不是說 Chain 不好。而是當需求長成下面三個樣子時,Chain 會開始吃力。

1. 分支

「如果問題屬於 A 類,就做 X;否則做 Y。」

你當然可以在 Chain 裡硬寫 if/else。一兩個條件還行,但當條件變成八個、又互相嵌套的時候,整條鏈就會變成一坨沒人敢改的判斷式。更麻煩的是:這些判斷藏在程式碼深處,看不到全貌。

2. 循環

「檢索回來的資料不夠,換個關鍵詞再查一次。」

這是 Agent 最常見的行為。但 Chain 沒有「回到上一步」的原生概念。你得自己包一個 while 迴圈,然後自己處理「重試幾次要放棄」的計數。

3. 中斷恢復

「這筆退款金額比較大,先送人工審批,明天再繼續。」

這一條最現實,也最致命。原型階段完全看不出來,但上線後第一個「需要人工介入」的需求就會把它打穿。因為 Chain 一旦中斷,那個跑到一半的狀態就沒有地方放,只能整條從頭再跑一次。

打個比方:Chain 像一張寫滿步驟的紙。你走到第三步被叫去開會,回來時發現那張紙已經被風吹走了,只能從第一步重做。

LangGraph 的做法是:每一步都往一塊共用的白板上記一筆。中斷了?白板還在,回頭接著走。

三、三個概念,用廚房來理解

LangGraph 的文件充滿 State、Node、Edge 這些詞。但其實它們對應的都是很日常的東西。

State 是共用的白板,Node 是工作站,Edge 是路口那個決定誰去哪裡的人

概念廚房裡的對應你要回答的問題
State共用白板每一步之間要傳什麼資料?
Node工作站這一步具體做什麼?
Edge路口的指示什麼條件下走哪條路?

有三件事值得特別說明,因為它們是新手最容易誤解的地方。

第一,節點之間不直接傳資料。 節點不呼叫節點,它們都只跟白板打交道。這樣做的好處是:任何一步都可以被單獨替換或測試,不會牽一髮動全身。

第二,節點回傳的是「增量」,不是整個狀態。 你不需要把白板上所有內容複製一份再改,只要交回你改動的那一格。合併的工作由 LangGraph 負責。

第三,判斷結果本身也該記在白板上。 例如把「決定走教學路線」記進 route 欄位。這樣事後你就能查到「當初為什麼走這條路」,對除錯和可觀測性幫助極大。

四、把那段程式碼拆開看

底下的程式碼是可直接複製運行的完整版本,在 Python 3.9.6 + langgraph 0.6.11(langchain-core 0.3.86) 實測通過。

它刻意不呼叫任何 LLM。原因很簡單:如果一開頭就拉模型進來,你會分不清哪些是「圖的機制」、哪些是「模型的功勞」。用純函式節點,機制可以被單獨隔離出來看。

先用一張圖說明這段程式碼的結構:

那段程式碼在做什麼:三個工作站,一塊白板

看懂這張圖,程式碼就只剩語法問題了。

from typing import Literal, TypedDict

from langgraph.graph import END, START, StateGraph


class State(TypedDict):
    question: str
    route: str
    answer: str


def classify(state: State) -> dict:
    """分流:示範用簡易規則,實務上這裡換成 LLM 分類即可"""
    return {"route": "tutorial" if "怎麼" in state["question"] else "direct"}


def pick(state: State) -> Literal["tutorial", "direct"]:
    """條件邊的判斷函式:回傳下一個節點的名字"""
    return state["route"]


def tutorial_node(state: State) -> dict:
    return {"answer": f"[教學路徑] 為「{state['question']}」產出分步說明"}


def direct_node(state: State) -> dict:
    return {"answer": f"[直接路徑] 為「{state['question']}」產出簡答"}


g = StateGraph(State)
g.add_node("classify", classify)
g.add_node("tutorial", tutorial_node)
g.add_node("direct", direct_node)
g.add_edge(START, "classify")
g.add_conditional_edges("classify", pick, {"tutorial": "tutorial", "direct": "direct"})
g.add_edge("tutorial", END)
g.add_edge("direct", END)

app = g.compile()
for q in ["LangGraph 怎麼用?", "LangGraph 是什麼?"]:
    out = app.invoke({"question": q})
    print(f"{q} → 路由 {out['route']}|{out['answer']}")

整段程式碼可以分成四塊,逐塊看就不難:

第一塊:定義白板上要放什麼。

class State(TypedDict):
    question: str
    route: str
    answer: str

只有三個格子。question 是進來時就有的,route 是分流時寫的,answer 是最後產出的。

第二塊:定義三個工作站。

classify 讀問題內容,決定路線;tutorial_node 和 direct_node 各自產出不同形式的回答。注意每個函式都只回傳一個 dict,也就是「我要在白板上改哪一格」。

第三塊:把線接起來。

g.add_edge(START, "classify")
g.add_conditional_edges("classify", pick, {"tutorial": "tutorial", "direct": "direct"})
g.add_edge("tutorial", END)
g.add_edge("direct", END)

這裡有兩種邊,值得分清楚:

  • add_edge 是普通邊:A 做完必定去 B,沒有懸念。
  • add_conditional_edges 是條件邊:它綁定一個判斷函式(此例是 pick),由那個函式讀白板、回傳下一個節點的名字。

換句話說,條件邊就是路口那位指揮。而指揮的依據不是憑空猜的,是白板上那個 route 欄位。

第四塊:跑起來。

app = g.compile()
out = app.invoke({"question": "LangGraph 怎麼用?"})

compile() 把圖編譯成可執行的物件,invoke() 丟進初始狀態、跑完、回傳最終狀態。

實測輸出如下(同一段程式、同一個 app,差別只在輸入句子裡有沒有「怎麼」兩個字):

同一個程式,兩種走法

LangGraph 怎麼用? → 路由 tutorial|[教學路徑] 為「LangGraph 怎麼用?」產出分步說明
LangGraph 是什麼? → 路由 direct|[直接路徑] 為「LangGraph 是什麼?」產出簡答

到這裡,你已經看完了 LangGraph 的核心。剩下的是工程細節。

五、檢查點:像遊戲存檔,而且每人一個存檔槽

前面說過,Chain 最怕中斷。LangGraph 的解法叫做檢查點(checkpoint)。

用遊戲來比喻最貼切:檢查點就是存檔點。你不需要每次重開都從第一關打起,回到上一個存檔點繼續就好。

檢查點像遊戲存檔,而且每個 thread 有獨立存檔槽

加上檢查點只需要改兩行:

from langgraph.checkpoint.memory import MemorySaver

app = g.compile(checkpointer=MemorySaver())
cfg = {"configurable": {"thread_id": "demo-1"}}

app.invoke({"question": "LangGraph 怎麼用?"}, cfg)
snap = app.get_state(cfg)

print(snap.values)   # 已保存的完整狀態
print(snap.next)     # 下一個待執行節點

實測輸出:

{'question': 'LangGraph 怎麼用?', 'route': 'tutorial', 'answer': '[教學路徑] 為「LangGraph 怎麼用?」產出分步說明'}
()

這裡有兩個新手常問的問題。

問題一:snap.next 為什麼是空的?

因為流程已經跑完了。next 代表「下一個還沒執行的節點」,跑完自然是空的。如果圖停在中間(例如正在等待人工審批),這裡就會列出待執行的節點,之後你可以把它推下去繼續跑。這就是「可恢復」的具體長相。

問題二:thread_id 是什麼?

可以理解成存檔槽。同一個 app 可以同時服務多條對話,只要給不同的 thread_id,狀態就各自獨立,不會互相污染。實測中以 demo-1 和 demo-2 分別跑不同問題,得到的 route 一個是 tutorial、一個是 direct,互不干擾。

順帶一提,MemorySaver 把狀態存在記憶體裡,適合開發測試。上生產要換成資料庫型的檢查點,否則重啟服務就全忘了。

⚠️ 一個實測踩到的小坑:get_state() 回傳的是 NamedTuple,取值要用 snap.values,不是 snap['values']。後者會直接拋 TypeError。我當初就卡在這一下。

六、什麼時候不該用 LangGraph

這是很多教學文章略過的部分,但其實很重要:加了圖就會變好,是錯的。

以下情況,用 Chain 或單次呼叫更划算:

  • 流程是一條直線,沒有分支也沒有重試
  • 只有一兩個步驟,多加一層圖只是多餘的抽象
  • 團隊還沒熟悉狀態管理,硬上圖會讓除錯變得更難
  • 你要的是單純的「問答」,不是「流程」

判斷準則只有一句:問自己「這個流程需要回頭,或需要中途停下來嗎?」

需要,就值得用圖。不需要,就別加。

七、給第一次接觸的人:三個常見誤解

誤解一:以為 LangGraph 是「更強的 Chain」。

不是。它是控制流的表達方式。Chain 描述的是「順序」,Graph 描述的是「關係」。這兩件事不一樣。

誤解二:以為一定要搭配 LLM。

不用。這篇的範例就完全沒有呼叫模型。圖只是流程的骨架,模型是放在節點裡的其中一種零件。

誤解三:以為節點之間可以直接傳值。

不行,也不該。所有資料都走白板。這個約定看起來囉嗦,但它正是「每一步可以獨立測試」的前提。

八、帶走三句話

  1. LangGraph 的價值不在「更強的 Chain」,而在把控制流顯式化:分支、循環、中斷都變成圖上看得見的東西。
  2. 節點只回傳增量,狀態由框架合併。這個約定讓複雜流程變得可推理。
  3. 檢查點是可恢復性的基礎,也是原型與生產之間最容易被低估的一道檻。

下一步

想繼續往下走,可以先補齊框架層的基礎(LangChain 完整教學 2026),再看工具如何接進 Agent(MCP 生態詳解),最後用架構視角收束(三層 Agent 協同框架)。

範例程式碼實測環境:Python 3.9.6、langgraph 0.6.11、langchain-core 0.3.86。