文章目錄
- LangGraph入門從0到1
- 0?? 安裝 & 確認環境
- 1?? 把 LangGraph 想象成「自動化的做菜流水線」
- 2?? 最小可運行例子:一句話復讀機
- 3?? 加一個小節點:把用戶輸入變大寫
- 4?? 條件邊:如果用戶說 quit 就結束,否則復讀
- 5?? 加內存:讓機器人記住「總共復讀過幾次」
- 6?? 真實 LLM 節點:讓 GPT 來回答
- 7?? 把節點拆文件:項目怎么組織
- 8?? 一張圖總結所有概念
LangGraph入門從0到1
下面用「小白視角」帶你從零到一拆解 LangGraph:
我會把官方那套黑話翻譯成“大白話 + 生活例子”,每句話都配可運行的極短代碼,復制即見效果。整篇不需要任何 LangChain 基礎,只要會 Python print 就行。
0?? 安裝 & 確認環境
我這里使用的是uv來管理的項目以及包的安裝,你們可以使用pip進行依賴導入。
uv add langgraph langchain_openai
1?? 把 LangGraph 想象成「自動化的做菜流水線」
官方詞 | 大白話 | 廚房例子 |
---|---|---|
Graph(圖) | 一張流程圖 | 做菜步驟圖:切菜→炒菜→裝盤 |
Node(節點) | 流程圖里的一個小步驟 | “切菜”動作 |
Edge(邊) | 箭頭,決定下一步去哪 | 切完菜后“箭頭”指向炒鍋 |
State(狀態) | 一塊共享砧板,所有人都能放/拿食材 | 砧板上現有:切好的洋蔥、生牛肉 |
Checkpointer | 照相機,隨時給砧板拍照存檔,斷電也能恢復 | 每做完一步拍照,廚房停電后來電繼續 |
2?? 最小可運行例子:一句話復讀機
功能:用戶說什么,機器人就復讀什么。
先別看復雜代碼,先跑起來!
# 文件:repeat_bot.py
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END# 1. 定義“砧板”上有什么
class State(TypedDict):user_text: str # 用戶說的話bot_text: str # 機器人回答# 2. 定義節點:復讀機節點
def repeat_node(state: State):return {"bot_text": f"復讀:{state['user_text']}"}# 3. 搭流程圖
builder = StateGraph(State)
builder.add_node("repeat", repeat_node) # 把節點叫“repeat”
builder.add_edge(START, "repeat") # 從起點→repeat
builder.add_edge("repeat", END) # repeat→終點# 4. 生成可執行對象
graph = builder.compile()# 5. 運行!
result = graph.invoke({"user_text": "你好 LangGraph"})
print(result["bot_text"])
運行:
# 輸出:復讀:你好 LangGraph
恭喜!你已經跑完第一個 LangGraph。
在 LangGraph 里:
END
是一個內置常量,代表整張流程圖的“廚房大門”——也就是終點,那么START
也就是起點。- 只要箭頭指向
END
,整個圖就會立即停在這一步,不再繼續。
3?? 加一個小節點:把用戶輸入變大寫
現在流程圖是:START → upper → repeat → END
(upper 節點負責變大寫,repeat 節點負責復讀)
# 文件:repeat_bot.py
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END# 1. 定義“砧板”上有什么
class State(TypedDict):user_text: str # 用戶說的話bot_text: str # 機器人回答# 2. 定義節點:復讀機節點
def repeat_node(state: State):return {"bot_text": f"復讀:{state['user_text']}"}def upper_node(state: State):return {"user_text": state["user_text"].upper()}builder = StateGraph(State)
builder.add_node("upper", upper_node)builder.add_node("repeat", repeat_node)builder.add_edge(START, "upper")
builder.add_edge("upper", "repeat")
builder.add_edge("repeat", END)graph = builder.compile()
print(graph.invoke({"user_text": "hello"})["bot_text"])
# 輸出:復讀:HELLO
# 5. 運行!
result = graph.invoke({"user_text": "你好 LangGraph"})
print(result["bot_text"])
就這么簡單,兩個節點串起來了。
4?? 條件邊:如果用戶說 quit 就結束,否則復讀
條件邊 = 帶“紅綠燈”的箭頭,紅燈停(END),綠燈繼續(upper)。
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END# 1. 定義“砧板”上有什么
class State(TypedDict):user_text: str # 用戶說的話bot_text: str # 機器人回答# 2. 定義節點:復讀機節點
def repeat_node(state: State):return {"bot_text": f"復讀:{state['user_text']}"}def should_continue(state: State) -> str:if state["user_text"].strip().lower() == "quit":return "end_chat"return "go_repeat"def upper_node(state: State):return {"user_text": state["user_text"].upper()}builder = StateGraph(State)
builder.add_node("repeat", repeat_node)
builder.add_node("upper", upper_node)
builder.add_edge(START, "repeat")
# 條件邊:repeat 之后聽紅綠燈
builder.add_conditional_edges("repeat",should_continue,{"go_repeat": "upper","end_chat": END})
builder.add_edge("upper",END)
graph = builder.compile()# 測試
print(graph.invoke({"user_text": "upper"})["user_text"])
print(graph.invoke({"user_text": "quit"})["user_text"])
運行:
#輸出
UPPER
quit
那么到這里就有一點問題了,這個add_conditional_edges
中{"go_repeat": "upper","end_chat": END}
明明就是一個字典,該怎么理解呢?
可以這么理解:
should_continue
只負責**“舉箭頭牌子”,它返回的字符串必須是字典的某一把鑰匙**;
字典 {"go_repeat": "repeat", "end_chat": END}
是**“鑰匙→真正目的地”**的對照表。
LangGraph 內部做兩步:
- 先調用
should_continue(state)
拿到一把鑰匙(例如"go_repeat"
)。 - 再用這把鑰匙去對照表里找:
"go_repeat"
對應"upper"
→ 就把箭頭畫向名叫"upper"
的節點;
如果拿到"end_chat"
→ 對應END
→ 畫向終點。
所以字典不是給 should_continue
用的,而是給 LangGraph 自己“查地圖”用的。
5?? 加內存:讓機器人記住「總共復讀過幾次」
(引入真正的 LangGraph State 累加用法)
from typing import Annotated
import operator
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, ENDclass State(TypedDict):user_text: strbot_text: strcount: Annotated[int, operator.add] # 每次節點返回 +1 就自動累加def repeat_node(state: State):return {"bot_text": f"復讀{state['count']+1}次:{state['user_text']}","count": 1} # 只要寫增量
def should_continue(state: State) -> str:if state["user_text"].strip().lower() == "quit":return "end_chat"return "go_repeat"builder = StateGraph(State)
builder.add_node("repeat", repeat_node)
builder.add_edge(START, "repeat")
builder.add_conditional_edges("repeat", should_continue,{"go_repeat": END, "end_chat": END})# 加照相機(內存)版本
from langgraph.checkpoint.memory import MemorySaver
graph_with_memory = builder.compile(checkpointer=MemorySaver())config = {"configurable": {"thread_id": "user007"}}
# 第一次
print(graph_with_memory.invoke({"user_text": "hi", "count": 0}, config)["bot_text"])
# 第二次
print(graph_with_memory.invoke({"user_text": "hi", "count": 0}, config)["bot_text"])
輸出:
復讀1次:hi
復讀2次:hi
注意:我這里不管需不需要重復我都選擇了END因為,你箭頭若還是指向了repeat
就會成一個死循環,為了程序能運行,所以我設置成了END。還有第二次我們沒給 count=2,而是 count=0,但 LangGraph 自動幫我們加到了 2。這就是 Annotated[..., operator.add]
的魔法。
6?? 真實 LLM 節點:讓 GPT 來回答
我們現在引入LLM讓我們的ai來回答我們的問題:
from typing_extensions import TypedDict
from typing import Annotated
import operator
class State(TypedDict):user_text: strbot_text: strcount: Annotated[int, operator.add]from langchain_openai import ChatOpenAIllm = ChatOpenAI(openai_api_key="*************",base_url="https://api.siliconflow.cn/v1",model="Qwen/Qwen2.5-7B-Instruct"
)def llm_node(state: State) -> State:ai = llm.invoke([{"role": "user", "content": state["user_text"]}])# print(ai.content)return {"bot_text": ai.content, "count": 1,"user_text":"quit"}from langgraph.graph import StateGraph, START, ENDdef should_continue(state: State):return "end" if state["user_text"].strip().lower() == "quit" else "again"builder = StateGraph(State)
builder.add_node("chat", llm_node)
builder.add_edge(START, "chat")
builder.add_conditional_edges("chat", should_continue,{"again": "chat", "end": END})
graph = builder.compile()result = graph.invoke({"user_text": "給我講個笑話", "count": 0})
print(result["bot_text"])
你看我直接返回了一個{"bot_text": ai.content, "count": 1,"user_text":"quit"}
,我直接返回的是這兩個的值,那為什么循環還是結束了?之后的判斷邊它怎么知道"user_text":"quit"了。
用幼兒園砧板比喻:
- 你(節點)從砧板拿走一張舊紙條,讀完隨手 丟回一張新紙條。
- 新紙條上寫了哪些字段,LangGraph 就 照著名字往砧板上“貼”:
- 名字已存在 → 直接蓋掉舊值
- 名字不存在 → 自動貼一張新紙條
- 也就是你返回的東西都會留在砧板上
所以
return {"bot_text": ai.content, "count": 1, "user_text": "quit"}
bot_text
舊值被覆蓋count
舊值被+1
覆蓋(前面用了Annotated[int, operator.add]
會再疊一次)user_text
舊值被強行改成"quit"
,下次節點拿到就是"quit"
一句話:返回的字典就是“增量補丁”,State 里沒有的 key 就新增,有的 key 就原地更新。
7?? 把節點拆文件:項目怎么組織
我們在創建項目的時候方便管理,最好是模塊來寫代碼管理代碼就比如如下方式:
my_bot/
├─ state.py # 只放 State TypedDict
├─ nodes.py # 所有節點函數
├─ graph.py # 搭圖、compile
└─ main.py # 運行入口
8?? 一張圖總結所有概念
所有節點共享同一塊砧板(State),LangGraph 幫你端菜、拍照、指路。
你已經完成 0→1 通關!