Table of Contents
[TOC]
Reference
- Sample Codes @ Google Colab
- LangGraph Essentials @ LangGraph Academy
Components
使用 LangGraph 通常會包含幾個步驟:
- Define State
- Define Node
- (optional) define conditional edges
- Build the Graph (Define Edge)
- StateGraph
- Add nodes
- Add edges (static and conditional edges)
- Compile and Display
- Invoke the Graph
- define initial state
- invoke with initial state
State: Data
-
會被餵進去 Graph 中,被 Graph 更新,最後回傳給使用者
-
可以使用 Pydantic Model、TypedDict、dataclass 來定義 State
-
在定義 State 的時候,可以同時定義 reducer function 來處理 state
class State(TypedDict):# operator.add 是 Langgraph 提供的 reducer function# 以這裡來說,是說明 State 被呼叫時,它會把新的資料 append 到 nlist 中,而不是覆蓋nlist: Annotated[List[str], operator.add]State(nlist=["A"]) # nlist=["A"]State(nlist=["B"]) # nlist=["A", "B"]
State Schema:何時用哪一種?
定義 graph schema 的方式有三種,文件主推 TypedDict,其它兩種有各自的觸發條件:
| 寫法 | 何時使用 | 注意事項 |
|---|---|---|
TypedDict(預設) | 一般情況都用這個 | 沒有預設值、沒有遞迴驗證 |
dataclass | 想要在 state 裡提供預設值時改用 | 仍是輕量結構,效能接近 TypedDict |
Pydantic BaseModel | 需要遞迴的資料驗證(nested model 也要被驗證)時用 | 效能比 TypedDict / dataclass 差;且 LangChain 高階的 create_agent factory 不支援 Pydantic state schema |
[!WARNING] 如果打算用
create_agent包出 agent,state schema 限制更嚴:自 langchain 1.0 起只接受 TypedDict(且要 extendAgentState),PydanticBaseModel和 dataclass 都不支援。
StateGraph
- graph 是無狀態的(stateless)
- 定義一個 graph 時,需要先把 graph 會用到的 state 定義好,這個 state 可以被 graph 中的所有 node 所共享
- 當 graph 被 invoke 的時候,state 會被初始化
builder = StateGraph(State)
# ...
graph = builder.compile()
# ...
graph.invoke()
Reducer
operator.add是 python 內建的 reducer function- 透過 Annotated Type 的 metadata 把 reducer function 帶入
class State(TypedDict):
# operator.add 是 reducer function,用來說明當有多個 Node 對 State 進行處理的話
# 要如何處理
nlist: Annotated[list[str], operator.add]
Node: Functions
- 是一個 function,input 是 state,output 是更新後的 state
Edges: Control Flow

[!TIP] 一個重點是:Edge 可以控制 control flow,但不能控制 data(state)能被誰(哪個 node)存取;在 LangGraph 中 State 會被所有 Nodes 所共享。
[!TIP] 在圖中,我們會用「實線」來表現 Static Edges;用「虛線」來表示 Conditional Edges。
Static Edges
- Serial
- Parallel
Conditional Edges
- Conditional
- Map-Reduce
建立 Conditional Edges 的方式有兩個:
- 定義 conditional edge function 並使用
builder.add_conditional_edges() - 不額外定義 conditional edge,而是在 Node 中使用
Command方法
方法一:
# Define Conditional Edges
def conditional_edge(state: State) -> Literal["b", "c", END]:
select = state["nlist"][-1]
if select == "b":
return "b"
elif select == "c":
return "c"
elif select == "q":
return END
else:
return END
# Build the graph
builder = StateGraph(State)
# Add nodes
builder.add_node("a", node_a)
builder.add_node("b", node_b)
builder.add_node("c", node_c)
# Add edges
builder.add_edge(START, "a")
builder.add_edge("b", END)
builder.add_edge("c", END)
builder.add_conditional_edges("a", conditional_edge) # Add conditional edges
# Compile and display
graph = builder.compile()
display(Image(graph.get_graph().draw_mermaid_png()))
方法二:透過 Command 把邏輯放在 Node 中
def node_a(state: State) -> Command[Literal["b", "c", END]]:
select = state["nlist"][-1]
if select == "b":
next_node = "b"
elif select == "c":
next_node = "c"
elif select == "q":
next_node = END
else:
next_node = END
return Command (
# update state
update = State(nlist=[select]),
# specify next node
goto = next_node
)
# 不需要再額外添加 conditional edges
# builder.add_conditional_edges("a", conditional_edge) # Add conditional edges
Map-Reduce 與 Send
要在執 行期動態 fan-out 出多個平行任務(每個任務跑同一個 node、但帶不同 payload),就要在 routing function 中回傳 Send 的 list。Send(node, payload) 代表「呼叫某個 node,並指定它這次收到的 state」——這是少數會讓 node 看到的 state 不等於 OverallState 的場合。
from operator import add
from langgraph.types import Send
class OverallState(TypedDict):
tasks: list[str]
results: Annotated[list[str], add] # 👈 reduce 階段靠它把各 worker 的輸出合併
final_summary: str
def planner(state):
return {"tasks": ["task1", "task2", "task3"]}
def worker(state): # 👈 注意:state 是 {"task": t},不是 OverallState
result = do_work(state["task"])
return {"results": [result]} # 回傳 list,靠 add reducer 合併
def dispatch_tasks(state: OverallState): # 👈 routing function,回傳 Send list
return [Send("worker", {"task": t}) for t in state["tasks"]]
def final_summary(state):
return {"final_summary": summarize(state["results"])}
builder.add_node("planner", planner)
builder.add_node("worker", worker)
builder.add_node("final_summary", final_summary)
builder.add_edge(START, "planner")
builder.add_conditional_edges("planner", dispatch_tasks) # 👈 dispatch (map)
builder.add_edge("worker", "final_summary") # 👈 所有 worker 收斂到這 (reduce)
builder.add_edge("final_summary", END)
幾個容易踩到的點:
- worker 收到的 state 不是 OverallState:而是你在
Send(..., payload)裡指定的那份 dict。所以worker簽章上應該對應 payload 的形狀(這個範例是{"task": str})。 - 收斂靠 reducer,不是靠程式碼自己 append:每個平行的 worker 各自回傳
{"results": [result]},由Annotated[list[str], add]在 reduce 階段把它們合併成完整 list。沒有 reducer 就會互相覆蓋。 - fan-in 用一條普通的
add_edge:所有 worker 都連到final_summary,LangGraph 會等所有 Send 出去的 worker 都跑完才推進。
[!TIP] > 為什麼
Send要放在 routing function 裡,而不是 node 裡? 把 dispatch 寫在 conditional edge function(routing function)中,可以把「這個 node 在做什麼業務邏輯」(planner 規劃任務)和「接下來要往哪走、要 fan-out 幾份」(dispatch_tasks 拆派工作)兩件事拆開。planner只負責生出tasks;要不要平行、平行幾份、payload 長什麼樣,由 routing function 決定。這樣 node 本身保持單純、好測試,也比較容易換掉 dispatch 策略而不動到 node。
Super-step
在 LangGraph 中,一個 super-step 代表一個 "tick",是 graph 執行的最小時間單位,也是 checkpoint 記錄的基本單位。
- 一個 super-step 會被記錄成一次 checkpoint
- 平行執行的多個 node 會算在同一個 super-step 中,所以只會被記錄一次(而不是每個 node 各記錄一次)
- 序列(serial)執行的 node 則會分別屬於不同的 super-step,各自產生一次 checkpoint
序列:a → b → c 共 3 個 super-steps(3 次 checkpoint)
平行: ┌─ b ─┐
a ─┤ ├─ d 共 3 個 super-steps(b、c 平行算一次)
└─ c ─┘
CheckingPoint/Memory
[!TIP] 本節以及接下來的 Context、Store,對應官方文件 Persistence,涵蓋 checkpointer、thread、Store 等持久化機制。
由於在 LangGraph 中,所有的 Node 都會共享 State,如果我們希望能在每一個 step 結束後,記錄當下的資料狀態(類似 snapshot),則可以使用 checkpoint。
透過 checkpointer 你可以把每一步驟結束時的資料狀態保存下來。
thread 則是把一系列的 checkpoint 整理起來,如此可以回朔出經過每個步驟後,資料改變了些什麼。

使用 Memory 的好處包含:
- 能夠錯誤的狀態中復原(recover gracefully from failure)
- 能夠 rollback 回特定的時間點(time travel)
- 即時 graph 沒有在運行,也能夠把資料保存下來(persistent state)