Mục lục
Mục tiêu bài học
Sau bài này bạn sẽ:
- Hiểu rõ những tình huống LCEL xử lý không tốt khi agent phức tạp dần.
- Biết LangGraph là gì, khác gì LangChain, và chúng coexist thế nào.
- Nhận ra mô hình mental của Graph (node + edge + state).
- Biết khi nào nên dùng LangGraph, khi nào LCEL hoặc SDK thuần là đủ.
- Nhận biết được một số pitfall phổ biến trước khi đào sâu vào syntax.
Bài này là intro module — syntax và implementation chi tiết sẽ ở bài 27–31.
Giới hạn của LangChain LCEL với agent phức tạp
LCEL được thiết kế để compose các Runnable thành pipeline tuyến tính (hoặc parallel tĩnh). Điều này đủ dùng cho phần lớn use case RAG và chain đơn giản, nhưng khi agent cần nhiều bước hơn thì một số vấn đề xuất hiện:
Vòng lặp đa bước
Bài 25 đã minh họa vòng lặp agent thủ công: gọi LLM → nếu có tool_calls thì execute rồi gọi lại, lặp đến khi không còn tool call. Vòng lặp đó phải tự viết Python thuần (for + điều kiện break), nằm ngoài LCEL chain. LCEL không có khái niệm loop với điều kiện dừng — chain là tuyến tính theo thiết kế.
Branching nhiều cấp
LCEL có RunnableBranch để route theo điều kiện, nhưng chỉ phù hợp cho 1 điểm phân nhánh đơn giản. Nếu workflow có nhiều nhánh lồng nhau, mỗi nhánh lại có state khác nhau, code nhanh chóng trở thành nested lambda khó đọc và khó test.
State implicit và dễ lẫn key
LCEL truyền dữ liệu giữa các bước qua Python dict thông thường. Không có định nghĩa schema rõ ràng cho toàn bộ pipeline — key nào có ở bước nào, key nào có thể None, key nào được thêm bởi bước nào. Với chain 3–4 bước thì ổn, nhưng với workflow 10+ bước và nhiều nhánh thì rất khó trace bug "key bị overwrite từ đâu".
Human-in-the-loop
Một số workflow cần dừng lại, chờ con người xem xét rồi mới tiếp tục — ví dụ agent soạn email xong cần human approve trước khi gửi, hoặc agent tạo SQL query cần DBA review trước khi chạy trên production. LCEL không có khái niệm pause và resume — pipeline chạy thẳng từ đầu đến cuối.
Checkpoint và replay
Nếu agent đang chạy đến bước 8/12 thì lỗi network, toàn bộ workflow phải chạy lại từ đầu khi dùng LCEL. Không có cơ chế persist state giữa các bước để resume từ điểm thất bại. Với agent gọi LLM nhiều lần, điều này tốn cả token lẫn thời gian.
Multi-agent
Khi cần nhiều agent chạy song song hoặc tuần tự, giao tiếp qua shared state — ví dụ một planner agent tạo kế hoạch, nhiều executor agent thực hiện từng task, rồi một reviewer agent tổng hợp — LCEL không có abstraction phù hợp để mô hình hóa flow này.
LangGraph là gì
LangGraph là framework để xây dựng stateful workflow dưới dạng graph, được phát triển bởi LangChain Inc. — cùng team viết LangChain và LCEL. Package riêng biệt:
pip install langgraph
# phiên bản hiện tại: 0.2.x (tính đến 2025)
Hai khái niệm cốt lõi:
- Node — function Python nhận state, trả về state update.
- Edge — transition từ node này sang node khác; có thể là unconditional hoặc conditional (dựa trên state).
State không phải dict thông thường mà là TypedDict được khai báo tường minh. Mọi node đều nhìn thấy và có thể update cùng một object state đó.
Built-in features của LangGraph 0.2.x:
- Checkpointer — persist state sau mỗi node, cho phép replay/resume.
- Interrupt — dừng graph tại node chỉ định, chờ input bên ngoài rồi tiếp tục (
interrupt_before,interrupt_after). - Parallel branches — nhiều node chạy song song, kết quả merge lại.
- Streaming — stream từng event trong quá trình graph chạy (mỗi node update state là 1 event).
- Cycle — graph có thể quay lại node cũ (khác DAG thuần).
LangGraph integrate hoàn toàn với mọi LangChain Runnable: mỗi node bên trong có thể là một LCEL chain, một ChatModel, một Retriever hoặc bất kỳ callable nào.
Mô hình mental: Graph có cycle
Một graph LangGraph gồm:
- State — TypedDict định nghĩa toàn bộ data workflow cần. Shared giữa tất cả nodes.
- Nodes — function Python (hoặc callable) nhận
state: State, trả vềdictchứa các key muốn update. - Edges — khai báo luồng đi giữa nodes. Có thể cố định (
add_edge) hoặc có điều kiện (add_conditional_edges). - START / END — hai node đặc biệt; START là điểm vào, END là điểm kết thúc.
Điểm quan trọng nhất: cycle được phép. Một node có thể có edge trỏ ngược về node trước đó, tạo vòng lặp. Đây chính là điều LCEL chain không hỗ trợ và là nền tảng để implement agent loop (LLM gọi tool → nhận kết quả → LLM quyết định gọi tiếp hay dừng → ...).
Sơ đồ đơn giản một ReAct agent:
START
│
▼
[llm_node] ← ─────────────────────┐
│ │
├─ (có tool_calls?) ─── YES ──► [tool_node]
│
└─ NO ──► END
Node llm_node gọi LLM. Nếu LLM trả về tool call, edge conditional route sang tool_node để execute, sau đó quay lại llm_node. Nếu LLM trả về câu trả lời cuối, route sang END. Đây là cycle — graph không phải DAG thuần.
Mỗi lần node chạy, LangGraph gọi checkpointer persist state. Nếu workflow fail ở tool_node, có thể resume từ đúng điểm đó thay vì chạy lại từ llm_node đầu tiên.
So sánh LCEL và LangGraph
| Tiêu chí | LangChain LCEL | LangGraph |
|---|---|---|
| Mô hình | Chain tuyến tính (Runnable pipe) | Graph có cycle (node + edge) |
| State | Implicit dict, pass qua từng bước | Explicit TypedDict, shared toàn graph |
| Branching | RunnableBranch — đơn giản, 1 cấp |
add_conditional_edges — đầy đủ, lồng được |
| Loop | Không có native — phải tự viết Python | Native — edge có thể trỏ về node trước |
| Human-in-the-loop | Không có khái niệm pause/resume | interrupt_before / interrupt_after |
| Checkpoint | Không có | MemorySaver, SqliteSaver, PostgresSaver, RedisSaver |
| Use case phù hợp | Pipeline ngắn, RAG, chain 1–3 bước | Agent stateful, multi-agent, workflow phức tạp |
LangGraph không thay thế LCEL. Chúng coexist: node bên trong LangGraph graph có thể là một LCEL chain. LangChain cung cấp component (ChatModel, Tool, Retriever), LangGraph cung cấp orchestration (state, loop, checkpoint).
Hello world LangGraph
Ví dụ tối giản — graph 2 node, không có LLM, chỉ để thấy cấu trúc:
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
# 1. Khai báo State — TypedDict là schema của toàn bộ graph
class State(TypedDict):
messages: list[str]
# 2. Định nghĩa nodes — function nhận state, trả dict update
def greet(state: State) -> dict:
return {"messages": state["messages"] + ["Hello!"]}
def farewell(state: State) -> dict:
return {"messages": state["messages"] + ["Goodbye!"]}
# 3. Build graph
builder = StateGraph(State)
builder.add_node("greet", greet)
builder.add_node("farewell", farewell)
builder.add_edge(START, "greet")
builder.add_edge("greet", "farewell")
builder.add_edge("farewell", END)
graph = builder.compile()
# 4. Invoke — truyền initial state
result = graph.invoke({"messages": []})
print(result)
# {"messages": ["Hello!", "Goodbye!"]}
Quan sát:
Statelà TypedDict — schema rõ ràng, IDE có thể type-check.- Mỗi node trả
dictchứa các key muốn update — không phải toàn bộ state. LangGraph merge update vào state. STARTvàENDlà constant import từlanggraph.graph.- Gọi
builder.compile()mới tạo ra graph chạy được — bước này cũng là lúc validate cấu trúc (thiếu END, node không được kết nối...).
Bài 28 sẽ xây agent thực sự (LLM + tools) với conditional edge và tool execution loop. Bài này chỉ minh họa cấu trúc cơ bản.
Patterns và helpers có sẵn
LangGraph 0.2.x cung cấp sẵn một số abstraction để không phải viết lại từ đầu:
create_react_agent
Factory tạo ReAct agent từ LLM và danh sách tools trong 1 dòng. Không cần tự khai báo StateGraph, node, edge:
from langgraph.prebuilt import create_react_agent
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
@tool
def add(a: int, b: int) -> int:
"""Cộng 2 số nguyên."""
return a + b
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
agent = create_react_agent(llm, tools=[add])
result = agent.invoke({"messages": [{"role": "user", "content": "5 + 7 bằng bao nhiêu?"}]})
# result["messages"] chứa toàn bộ conversation kể cả tool call và kết quả
create_react_agent vẫn trả về một CompiledStateGraph — vẫn có thể thêm checkpointer, gọi .stream(), dùng interrupt. Bài 28 sẽ đào chi tiết.
MessagesState
TypedDict có sẵn dành cho chatbot pattern — state chỉ cần 1 key messages: list[BaseMessage]:
from langgraph.graph import MessagesState
# Thay vì tự khai báo:
# class State(TypedDict):
# messages: Annotated[list, add_messages]
#
# Dùng sẵn:
builder = StateGraph(MessagesState)
add_messages reducer
Mặc định khi node trả {"messages": new_list}, LangGraph overwrite key đó (replace). Dùng add_messages reducer để append thay vì replace:
from typing import Annotated
from langgraph.graph.message import add_messages
from langchain_core.messages import BaseMessage
class State(TypedDict):
# Annotated với add_messages → append thay vì overwrite
messages: Annotated[list[BaseMessage], add_messages]
Đây là một pitfall phổ biến — xem mục 11.
Checkpointers
Pass vào compile() để persist state:
from langgraph.checkpoint.memory import MemorySaver
# hoặc
from langgraph.checkpoint.sqlite import SqliteSaver
# hoặc (cần cài thêm package)
# from langgraph.checkpoint.postgres import PostgresSaver
# from langgraph.checkpoint.redis import RedisSaver
memory = MemorySaver()
graph = builder.compile(checkpointer=memory)
# Mỗi invoke với thread_id riêng = session riêng
config = {"configurable": {"thread_id": "user-123"}}
result = graph.invoke({"messages": [...]}, config=config)
Tooling và ecosystem
Ba công cụ thường đi kèm khi dùng LangGraph:
LangGraph Studio
Desktop app (macOS, Windows) visualize graph runtime. Hiển thị graph dưới dạng sơ đồ tương tác, cho phép step qua từng node, xem state tại từng bước, edit state rồi replay. Hữu ích khi debug agent chạy sai nhánh hoặc loop vô hạn.
LangSmith
Tracing và observability platform. LangGraph tích hợp tracing vào LangSmith theo mặc định nếu set LANGCHAIN_TRACING_V2=true. Mỗi graph run tạo ra một trace gồm tất cả node calls, state update, LLM calls bên trong. Không cần instrumentation code thủ công.
LangGraph Platform
Managed deployment service của LangChain Inc. — deploy graph như API, tự động handle scaling, checkpointing, streaming. Là tùy chọn paid; không bắt buộc để dùng LangGraph (vẫn có thể tự deploy qua FastAPI + bất kỳ infrastructure nào).
Khi nào dùng LangGraph, khi nào không
Nên dùng LangGraph khi
- Agent có loop — ReAct pattern, plan-and-execute, bất kỳ workflow nào cần LLM → tool → LLM → ... lặp không cố định số lần.
- Branching phức tạp nhiều cấp — routing khác nhau dựa trên giá trị state, có thể lồng nhau.
- Human-in-the-loop — cần pause để con người xem xét (approve, edit, reject) trước khi tiếp tục.
- Checkpoint / debug — muốn replay từ giữa workflow khi agent fail, hoặc cần so sánh kết quả khi thay đổi một node.
- Multi-agent — 2+ agent cần collaborate qua shared state (planner + executor, writer + reviewer...).
- Long-running workflow — workflow chạy nhiều phút, cần persist state giữa các LLM call.
Không cần LangGraph khi
- Chain đơn giản: prompt → LLM → parser — LCEL gọn hơn đáng kể.
- RAG chuẩn: retrieve → rerank → 1 LLM call — LCEL.
- 1-shot LLM call không cần tool, không cần lịch sử — gọi SDK provider trực tiếp.
- Batch inference offline — không cần state management, chỉ cần pipeline xử lý data.
LangGraph có learning curve và thêm dependency. Với bài toán đơn giản, overhead của nó không đem lại lợi ích tương xứng.
Alternatives
LangGraph không phải lựa chọn duy nhất cho orchestration agent phức tạp:
| Framework | Model | Điểm khác biệt |
|---|---|---|
| LangGraph 0.2 | Graph (node + edge + state) | Control flow rõ ràng, coexist với LangChain, checkpoint sẵn |
| CrewAI | Role-based agent | Abstraction cao hơn — khai báo Agent, Task, Crew; ít code boilerplate hơn với multi-agent đơn giản |
| AutoGen (Microsoft) | Conversational agent | Agent giao tiếp qua message như người dùng; phù hợp multi-agent chat-style |
| Code thuần | State machine + asyncio | Không dependency, full control; phù hợp khi requirement đơn giản và team muốn tự kiểm soát |
Với lộ trình này, Module 5 tập trung vào LangGraph vì nó integrate trực tiếp với LangChain ecosystem (Module 4), có checkpoint và human-in-the-loop built-in, và kiến trúc graph rõ ràng dễ debug hơn so với agent message-based.
Pitfalls thường gặp
1. Hiểu nhầm LangGraph là LangChain v2
LangGraph là package riêng biệt (langgraph), không phải version mới của LangChain. Cả hai cùng tồn tại — LangGraph sử dụng LangChain components bên trong node, không thay thế chúng. Import từ langgraph.graph và langchain_core/langchain_openai đều cần thiết.
2. Lạm dụng LangGraph cho chain đơn giản
Không cần StateGraph cho prompt → LLM → parser. LCEL chain = prompt | llm | parser ngắn hơn, dễ test hơn, không có overhead graph. Dùng LangGraph khi thực sự cần loop hoặc checkpoint.
3. State update bị overwrite thay vì append
Khi node trả {"messages": [new_message]}, mặc định LangGraph replace key messages bằng list mới — toàn bộ lịch sử bị mất. Để append, cần dùng Annotated[list[BaseMessage], add_messages] hoặc dùng MessagesState:
# Sai — mỗi node call xóa messages cũ
class BadState(TypedDict):
messages: list[str]
# Đúng — messages được append
from typing import Annotated
from langgraph.graph.message import add_messages
from langchain_core.messages import BaseMessage
class GoodState(TypedDict):
messages: Annotated[list[BaseMessage], add_messages]
4. Quên kết nối node với END
Nếu không có edge từ node cuối đến END, graph sẽ raise lỗi khi compile hoặc chạy vô hạn. builder.compile() validate cấu trúc — đọc kỹ error message nếu nó fail.
5. Conditional edge return sai giá trị
Function trong add_conditional_edges phải return một string khớp với key trong mapping. Nếu return giá trị không có trong mapping, graph raise ValueError lúc runtime (không phải lúc compile). Bài 29 sẽ trình bày chi tiết pattern này.
Tóm tắt
- LCEL chain tuyến tính — phù hợp pipeline ngắn, RAG, chain không cần loop hay checkpoint.
- LangGraph giải quyết 5 vấn đề LCEL không có: loop, branching nhiều cấp, state explicit, human-in-the-loop, checkpoint/replay.
- LangGraph không thay thế LangChain — node bên trong vẫn dùng LangChain component (ChatModel, Tool, Retriever).
- Mô hình mental: node (function) + edge (transition) + state (TypedDict shared). Cycle được phép — đây là nền tảng của agent loop.
- Built-in:
create_react_agent,MessagesState,add_messagesreducer,MemorySaver/SqliteSaver/PostgresSaver. - Pitfall quan trọng nhất: state update mặc định là overwrite, không phải append — dùng reducer
add_messageskhi cần giữ lịch sử messages. - Nếu workflow đơn giản, LCEL hoặc SDK thuần vẫn là lựa chọn gọn hơn.
Bài tiếp theo
Bài 27: State, Node, Edge — 3 khái niệm cốt lõi — đào sâu vào từng khái niệm: cách định nghĩa State với reducer, cách viết Node đúng chuẩn, các loại Edge và cách compile graph.
