从线性链路到复杂工作流,构建企业级 AI 应用架构
LangGraph 是 LangChain 官方推出的「有状态、可循环」的工作流编排框架。它把 AI 应用建模为一张「图」(Graph),图上的节点是处理步骤,边是流转规则,状态在节点之间自动传递。
虽然 LangChain 的 LCEL 支持分支逻辑(RunnableBranch)和条件路由,但在处理复杂的有状态工作流时仍有局限:
LangGraph 正是为解决这些问题而生,它提供了统一的图结构编排、自动状态管理、内置循环/重试、人工介入点等企业级特性。
输入 → 节点A → RunnableBranch
├→ 分支B → 输出
└→ 分支C → 输出
✅ 支持分支和条件路由
⚠️ 复杂循环和状态管理需手动编排
┌→ 节点B → 节点D ─┐
输入 → 节点A ─┤ ├→ 输出
└→ 节点C ←─────┘(循环)
✅ 内置循环、重试、并行、人工介入
✅ 自动状态管理、可视化调试
# 安装 LangGraph(它会自动安装 langchain-core 依赖)
pip install langgraph
# 如果使用通义千问模型
pip install dashscope langchain-community整个 LangGraph 只需要掌握以下几个核心类,就能构建大部分工作流:
| 类 / 函数 | 作用 | 来源 |
|---|---|---|
StateGraph |
创建一张带状态的图,是一切的起点 | langgraph.graph |
END |
特殊常量,表示图的终点 | langgraph.graph |
START |
特殊常量,表示图的起点(新版) | langgraph.graph |
MemorySaver |
内存级别的状态持久化(检查点) | langgraph.checkpoint.memory |
Annotated |
配合 reducer 定义状态字段的合并策略 | typing |
create_react_agent |
快速创建一个 ReAct 模式的智能体(⚠️ 最新版本) | langgraph.prebuilt |
✅ 最新推荐(V1.0+):from langchain.agents import create_agent
⚠️ 即将废弃:from langgraph.prebuilt import create_react_agent(V1.0 已移至 langchain.agents)
❌ 已废弃:from langchain.agents import create_react_agent
❌ 已废弃:from langchain_classic.agents import create_react_agent
"""
LangGraph V1.0+ Agent 创建完整示例
使用 create_agent 构建智能体
"""
import os
from langchain.agents import create_agent
from langgraph.checkpoint.memory import MemorySaver
from langchain_core.tools import tool
from langchain_community.chat_models import ChatTongyi
from langchain_core.messages import SystemMessage
# ============ 1. 定义工具 ============
@tool
def search_database(query: str) -> str:
"""在数据库中搜索信息"""
# 模拟数据库搜索
db = {
"LangChain": "LangChain 是一个用于构建 LLM 应用的框架",
"LangGraph": "LangGraph 是 LangChain 的工作流编排框架"
}
return db.get(query, f"未找到关于 {query} 的信息")
@tool
def calculate(expression: str) -> str:
"""计算数学表达式"""
try:
result = eval(expression)
return f"计算结果: {result}"
except Exception as e:
return f"计算错误: {str(e)}"
@tool
def get_user_info(user_id: str) -> str:
"""获取用户信息"""
users = {
"001": "张三,VIP用户,余额: 1000元",
"002": "李四,普通用户,余额: 500元"
}
return users.get(user_id, "用户不存在")
# ============ 2. 创建模型 ============
api_key = os.getenv("DASHSCOPE_API_KEY", "your-api-key")
model = ChatTongyi(
model_name="qwen-turbo",
dashscope_api_key=api_key,
temperature=0
)
# ============ 3. 创建 Agent(展示新特性)============
# ✨ 新特性: checkpointer - 自动保存会话状态
memory = MemorySaver()
# ✨ 创建 Agent(V1.0+ 最简化 API)
# 注意:create_agent 当前版本只支持 model、tools、checkpointer 三个核心参数
agent = create_agent(
model=model, # 模型
tools=[search_database, calculate, get_user_info], # 工具列表
checkpointer=memory, # ✨ 检查点(会话持久化)
)
# 💡 如果需要自定义系统提示,可以在调用时通过 messages 参数传入
# 例如:
# from langchain_core.messages import SystemMessage
# result = agent.invoke({
# "messages": [
# SystemMessage(content="你是一个专业的AI助手"),
# ("user", "你的问题")
# ]
# }, config=config)
# ============ 4. 使用 Agent(展示新优势)============
if __name__ == "__main__":
# ✨ 优势1: 多轮对话 + 会话持久化
config = {"configurable": {"thread_id": "conversation-001"}}
print("=== 第1轮对话 ===")
result1 = agent.invoke(
{"messages": [("user", "搜索 LangGraph 的信息")]},
config=config
)
print(result1["messages"][-1].content)
print("\n=== 第2轮对话(记住上下文)===")
result2 = agent.invoke(
{"messages": [("user", "帮我计算 123 * 456")]},
config=config # 同一个 thread_id,自动记住历史
)
print(result2["messages"][-1].content)
print("\n=== 第3轮对话(多步骤推理)===")
result3 = agent.invoke(
{"messages": [("user", "查询用户001的信息,然后计算他的余额打8折是多少")]},
config=config
)
print(result3["messages"][-1].content)
# ✨ 优势2: 流式输出(实时观察推理过程)
print("\n=== 流式输出(观察推理过程)===")
for chunk in agent.stream(
{"messages": [("user", "搜索 LangChain,然后告诉我它是什么")]},
config={"configurable": {"thread_id": "conversation-002"}}
):
if "agent" in chunk:
print(chunk["agent"]["messages"][-1].content)
if "tools" in chunk:
print(f"🔧 工具调用结果: {chunk['tools']['messages'][-1].content}")
print("""
🎯 create_agent (V1.0+) 的核心优势:
1️⃣ API 极简化
- 旧版: create_react_agent(llm, tools, prompt) + AgentExecutor
- V1.0+: create_agent(model, tools, checkpointer) 直接返回可执行 graph
2️⃣ 内置会话持久化
- checkpointer: 自动保存会话历史,支持多轮对话
- thread_id: 通过配置隔离不同会话
3️⃣ 原生支持流式输出
- stream() 方法实时观察 Agent 的推理过程
- 可以看到每一步的工具调用和思考
4️⃣ 更好的可观测性
- 返回的是 LangGraph,可以可视化工作流
- 自动记录每个节点的状态变化
5️⃣ 无需 AgentExecutor
- 旧版需要: executor = AgentExecutor(agent=agent, tools=tools)
- V1.0+: 直接 agent.invoke() 或 agent.stream()
6️⃣ 灵活的系统提示
- 通过 messages 参数在调用时动态传入 SystemMessage
- 每次调用可以使用不同的系统提示
""")| ❌ 旧版 (已废弃) | ⚠️ 过渡版 (即将废弃) | ✅ V1.0+ (最新) |
|---|---|---|
langchain.agents |
langgraph.prebuilt |
langchain.agents |
(llm, tools, prompt) |
(model, tools) |
(model, tools) |
| 需要 AgentExecutor | 直接返回 graph | 直接返回 graph |
| 手动管理状态 | ❌ 不支持修改器 | 通过 messages 参数传入 |
| 无持久化 | ✨ checkpointer | ✨ checkpointer |
| 流式需配置 | ✨ 原生 stream() | ✨ 原生 stream() |
langgraph.prebuilt.create_react_agent 在 V1.0 中已移至 langchain.agents.create_agentcreate_agent 当前只支持 3 个核心参数:model、tools、checkpointerinvoke() 的 messages 参数传入,不支持 state_modifier 或 messages_modifiercreate_agent 以避免未来版本不兼容
LangGraph 的所有工作流都由三个要素组成,理解它们就掌握了 LangGraph 的 80%。
┌──────────────────────────────────────────────────┐ │ LangGraph 工作流结构 │ │ │ │ State(状态)= 一个在所有节点间共享的字典 │ │ │ │ ┌─────────┐ Edge ┌─────────┐ │ │ │ Node A │ ─────────→ │ Node B │ │ │ │ (函数) │ │ (函数) │ │ │ └─────────┘ └────┬────┘ │ │ │ 条件Edge │ │ ┌────┴────┐ │ │ │ Node C │ │ │ │ (函数) │ │ │ └─────────┘ │ │ │ │ 每个 Node 接收 State,返回要更新的字段 │ │ Edge 决定下一个执行哪个 Node │ └──────────────────────────────────────────────────┘
State 是一个 TypedDict(类型化字典),它就像一个"共享内存",在整个工作流的所有节点之间传递和共享数据。
from typing import TypedDict, Annotated, List
import operator
# ========== 方式1:最简单的 State(覆盖模式)==========
class SimpleState(TypedDict):
"""每个字段都是覆盖模式:新值直接替换旧值"""
user_name: str # 用户名
age: int # 年龄
score: float # 分数
# 示例:覆盖模式的行为
# 初始: state = {"user_name": "张三", "age": 25, "score": 80.0}
# 节点返回: {"age": 26}
# 结果: state = {"user_name": "张三", "age": 26, "score": 80.0}
# ↑ 只有 age 被更新
# ========== 方式2:带 Reducer 的 State(累加模式)==========
class ChatState(TypedDict):
"""使用 Annotated 定义字段的合并策略"""
# operator.add:新值追加到列表末尾(不覆盖)
messages: Annotated[List[str], operator.add]
# 无 Annotated:普通覆盖模式
current_speaker: str
turn_count: int
# 示例:Reducer 的行为
# 初始: state = {
# "messages": ["用户: 你好"],
# "current_speaker": "user",
# "turn_count": 1
# }
# 节点返回: {
# "messages": ["AI: 你好,我是助手"],
# "current_speaker": "bot",
# "turn_count": 2
# }
# 结果: state = {
# "messages": ["用户: 你好", "AI: 你好,我是助手"], # ← 追加(operator.add)
# "current_speaker": "bot", # ← 覆盖
# "turn_count": 2 # ← 覆盖
# }
# ========== 方式3:自定义 Reducer 函数 ==========
def merge_metadata(old: dict, new: dict) -> dict:
"""自定义合并逻辑:合并两个字典"""
return {**old, **new}
class AdvancedState(TypedDict):
messages: Annotated[List[str], operator.add]
# 自定义 reducer:合并元数据字典
metadata: Annotated[dict, merge_metadata]
status: str
# 示例:自定义 Reducer 的行为
# 初始: state = {
# "messages": ["消息1"],
# "metadata": {"source": "web", "user_id": "001"},
# "status": "processing"
# }
# 节点返回: {
# "metadata": {"timestamp": "2024-03-21", "version": "1.0"}
# }
# 结果: state = {
# "messages": ["消息1"],
# "metadata": {"source": "web", "user_id": "001", "timestamp": "2024-03-21", "version": "1.0"},
# "status": "processing"
# }
问题:在对话系统中,如果不用 operator.add,会发生什么?
# ❌ 错误示例:不使用 Reducer
class BadState(TypedDict):
messages: List[str] # 没有 Annotated
# 节点1返回: {"messages": ["用户: 你好"]}
# 节点2返回: {"messages": ["AI: 你好"]}
# 结果: state = {"messages": ["AI: 你好"]} # ❌ 用户消息丢失了!# ✅ 正确示例:使用 Reducer
class GoodState(TypedDict):
messages: Annotated[List[str], operator.add]
# 节点1返回: {"messages": ["用户: 你好"]}
# 节点2返回: {"messages": ["AI: 你好"]}
# 结果: state = {"messages": ["用户: 你好", "AI: 你好"]} # ✅ 消息都保留了!Node(节点)就是一个普通的 Python 函数,它是工作流中的"处理单元",负责执行具体的业务逻辑。
# ========== 模式1:简单处理节点 ==========
def greet_node(state: ChatState) -> dict:
"""读取 state,返回要更新的字段"""
user_name = state.get("current_speaker", "朋友")
return {
"messages": [f"你好 {user_name},欢迎使用 LangGraph!"],
"turn_count": state.get("turn_count", 0) + 1
}
# ========== 模式2:调用 LLM 的节点 ==========
def llm_node(state: ChatState) -> dict:
"""调用大语言模型处理用户消息"""
from langchain_community.chat_models import ChatTongyi
llm = ChatTongyi(model_name="qwen-turbo")
user_message = state["messages"][-1] # 获取最后一条消息
# 调用 LLM
response = llm.invoke(user_message)
return {
"messages": [f"AI: {response.content}"],
"current_speaker": "bot"
}
# ========== 模式3:条件判断节点 ==========
def check_sentiment_node(state: ChatState) -> dict:
"""分析用户情绪"""
last_message = state["messages"][-1]
# 简单的情绪判断逻辑
if "谢谢" in last_message or "感谢" in last_message:
sentiment = "positive"
elif "问题" in last_message or "错误" in last_message:
sentiment = "negative"
else:
sentiment = "neutral"
return {
"metadata": {"sentiment": sentiment},
"messages": [f"[系统] 检测到情绪: {sentiment}"]
}
# ========== 模式4:工具调用节点 ==========
def search_database_node(state: ChatState) -> dict:
"""查询数据库的节点"""
query = state["messages"][-1]
# 模拟数据库查询
db_results = {
"LangChain": "LangChain 是一个用于构建 LLM 应用的框架",
"LangGraph": "LangGraph 是 LangChain 的工作流编排框架"
}
result = db_results.get(query, "未找到相关信息")
return {
"messages": [f"数据库查询结果: {result}"],
"metadata": {"source": "database"}
}
# ========== 关键点:节点的返回值 ==========
# ✅ 正确:只返回要更新的字段
def good_node(state: ChatState) -> dict:
return {"messages": ["新消息"]} # ✅ 只返回需要更新的字段
# ❌ 错误:返回完整的 state(不推荐,但也能工作)
def bad_node(state: ChatState) -> dict:
state["messages"].append("新消息") # ❌ 直接修改 state(不推荐)
return state # ❌ 返回完整 state(冗余)def node(state: MyState) -> dict)Edge(边)定义了节点之间的连接关系,决定工作流的执行顺序。它就像"路由规则",告诉 LangGraph:"执行完节点 A 后,下一步该执行哪个节点?"
用法:add_edge("A", "B")
含义:A 执行完后,一定执行 B
场景:固定的线性流程
START → A → B → C → END
用法:add_conditional_edges("A", 路由函数)
含义:A 执行完后,根据 State 动态决定下一个节点
场景:需要分支逻辑的流程
┌→ B (条件1)
A ──┼→ C (条件2)
└→ D (其他)
# ========== 示例1:普通边(固定流程)==========
from langgraph.graph import StateGraph, START, END
graph = StateGraph(ChatState)
# 添加节点
graph.add_node("greet", greet_node)
graph.add_node("process", process_node)
graph.add_node("farewell", farewell_node)
# 添加普通边:固定顺序执行
graph.add_edge(START, "greet") # 开始 → greet
graph.add_edge("greet", "process") # greet → process
graph.add_edge("process", "farewell") # process → farewell
graph.add_edge("farewell", END) # farewell → 结束
# 流程图:START → greet → process → farewell → END
# ========== 示例2:条件边(动态路由)==========
# 定义路由函数
def route_by_sentiment(state: ChatState) -> str:
"""根据情绪决定下一个节点"""
sentiment = state.get("metadata", {}).get("sentiment", "neutral")
if sentiment == "positive":
return "thank_you_node" # 积极情绪 → 感谢节点
elif sentiment == "negative":
return "help_node" # 消极情绪 → 帮助节点
else:
return "continue_node" # 中性情绪 → 继续对话节点
graph = StateGraph(ChatState)
# 添加节点
graph.add_node("check_sentiment", check_sentiment_node)
graph.add_node("thank_you_node", thank_you_node)
graph.add_node("help_node", help_node)
graph.add_node("continue_node", continue_node)
# 添加条件边
graph.add_edge(START, "check_sentiment")
graph.add_conditional_edges(
"check_sentiment", # 从哪个节点出发
route_by_sentiment, # 路由函数(返回下一个节点的名称)
{
"thank_you_node": "thank_you_node", # 映射:函数返回值 → 实际节点名
"help_node": "help_node",
"continue_node": "continue_node"
}
)
# 所有分支最终都到 END
graph.add_edge("thank_you_node", END)
graph.add_edge("help_node", END)
graph.add_edge("continue_node", END)
# 流程图:
# ┌→ thank_you_node → END (positive)
# START → check ──┼→ help_node → END (negative)
# └→ continue_node → END (neutral)
# ========== 示例3:复杂条件边(多条件判断)==========
def complex_router(state: ChatState) -> str:
"""复杂的路由逻辑"""
last_message = state["messages"][-1]
turn_count = state.get("turn_count", 0)
# 多条件判断
if turn_count > 10:
return END # 超过10轮对话,直接结束
elif "再见" in last_message or "拜拜" in last_message:
return "farewell_node" # 用户说再见
elif "帮助" in last_message or "?" in last_message:
return "help_node" # 用户需要帮助
else:
return "llm_node" # 正常对话
graph.add_conditional_edges(
"process",
complex_router,
{
"farewell_node": "farewell_node",
"help_node": "help_node",
"llm_node": "llm_node",
END: END # 特殊:直接结束
}
)
路由函数必须返回节点名称(字符串)或 END:
return "node_name" - 跳转到指定节点return END - 直接结束工作流return None - 会报错我们用一个最简单的例子把三要素串起来:构建一个「问候→处理→告别」的工作流。
START → greet → process → farewell → END
from typing import TypedDict, Annotated, List
import operator
from langgraph.graph import StateGraph, START, END
# ===== 第1步:定义 State =====
class MyState(TypedDict):
messages: Annotated[List[str], operator.add] # 消息会累加
user_name: str
# ===== 第2步:定义 Node(普通 Python 函数)=====
def greet(state: MyState):
return {"messages": [f"👋 你好 {state['user_name']}!"]}
def process(state: MyState):
return {"messages": ["⚙️ 正在处理你的请求..."]}
def farewell(state: MyState):
return {"messages": ["👋 处理完成,再见!"]}
# ===== 第3步:构建图 =====
graph = StateGraph(MyState)
# 添加节点
graph.add_node("greet", greet)
graph.add_node("process", process)
graph.add_node("farewell", farewell)
# 添加边(定义执行顺序)
graph.add_edge(START, "greet") # 起点 → greet
graph.add_edge("greet", "process") # greet → process
graph.add_edge("process", "farewell")# process → farewell
graph.add_edge("farewell", END) # farewell → 终点
# ===== 第4步:编译并运行 =====
app = graph.compile()
result = app.invoke({
"messages": [],
"user_name": "小明"
})
print("最终状态:")
for msg in result["messages"]:
print(f" {msg}")
# 输出:
# 👋 你好 小明!
# ⚙️ 正在处理你的请求...
# 👋 处理完成,再见!StateGraph(MyState) — 用 State 类型创建图add_node("名字", 函数) — 注册节点add_edge("A", "B") — A 完成后执行 Bgraph.compile() — 编译成可执行的应用app.invoke(初始状态) — 传入初始状态并运行真实业务中,流程不会只有一条路。LangGraph 通过 add_conditional_edges 实现动态路由。
START
│
▼
┌──────────────┐
│ classify │ ← 意图识别节点
└──────┬───────┘
│
┌─────────┼─────────┐
▼ ▼ ▼
┌──────────┐ ┌────────┐ ┌────────┐
│ handle │ │ handle │ │ handle │
│ _query │ │ _compla│ │ _chat │
└────┬─────┘ └───┬────┘ └───┬────┘
│ │ │
└───────────┼──────────┘
▼
END
from typing import TypedDict, Annotated, List, Literal
import operator
from langgraph.graph import StateGraph, START, END
# ===== State =====
class CustomerState(TypedDict):
messages: Annotated[List[str], operator.add]
user_input: str
intent: str # 识别出的意图
# ===== 节点 =====
def classify(state: CustomerState):
"""意图识别节点"""
text = state["user_input"].lower()
if "退款" in text or "投诉" in text:
intent = "complaint"
elif "查" in text or "订单" in text:
intent = "query"
else:
intent = "chat"
return {"intent": intent, "messages": [f"🔍 识别意图: {intent}"]}
def handle_query(state: CustomerState):
return {"messages": ["📋 正在查询您的订单信息..."]}
def handle_complaint(state: CustomerState):
return {"messages": ["🔧 已记录您的投诉,客服将尽快处理"]}
def handle_chat(state: CustomerState):
return {"messages": ["💬 感谢您的咨询,还有什么可以帮您?"]}
# ===== 路由函数 =====
# 返回值必须是下一个节点的名字
def route_by_intent(state: CustomerState) -> Literal["handle_query", "handle_complaint", "handle_chat"]:
return f"handle_{state['intent']}"
# ===== 构建图 =====
graph = StateGraph(CustomerState)
graph.add_node("classify", classify)
graph.add_node("handle_query", handle_query)
graph.add_node("handle_complaint", handle_complaint)
graph.add_node("handle_chat", handle_chat)
graph.add_edge(START, "classify")
# 关键:条件边 —— classify 之后根据 route_by_intent 的返回值决定走哪条路
graph.add_conditional_edges("classify", route_by_intent)
graph.add_edge("handle_query", END)
graph.add_edge("handle_complaint", END)
graph.add_edge("handle_chat", END)
app = graph.compile()
# ===== 测试 =====
for question in ["查一下我的订单", "我要投诉!", "你好呀"]:
result = app.invoke({"messages": [], "user_input": question, "intent": ""})
print(f"\n用户: {question}")
for msg in result["messages"]:
print(f" {msg}")| 参数 | 说明 |
|---|---|
| 第1个参数 | 源节点名称(从哪个节点出发) |
| 第2个参数 | 路由函数(接收 state,返回目标节点名称) |
| 第3个参数(可选) | 映射字典 {"返回值": "节点名"},当返回值和节点名不一致时使用 |
循环是 LangGraph 相比传统 Chain 最强大的特性之一。通过条件边指回之前的节点,就实现了循环。
START → generate → check_quality ─┬─ 合格 → END
│
└─ 不合格 → generate(循环重试)
from typing import TypedDict, Annotated, List
import operator
from langgraph.graph import StateGraph, START, END
class WriterState(TypedDict):
messages: Annotated[List[str], operator.add]
draft: str
attempt: int # 第几次尝试
is_approved: bool # 是否通过审核
def generate(state: WriterState):
"""生成内容(模拟)"""
attempt = state["attempt"] + 1
# 模拟:第3次才写出合格内容
if attempt >= 3:
draft = "这是一篇高质量的文章,包含详细数据和分析。共500字。"
else:
draft = f"这是第{attempt}次草稿,内容较短。"
return {
"draft": draft,
"attempt": attempt,
"messages": [f"✍️ 第{attempt}次生成: {draft[:30]}..."]
}
def check_quality(state: WriterState):
"""质量检查"""
# 这里是“示例用”的质量规则:要能稳定判定第3次之后的草稿为合格。
# 真实项目中你会把它替换成:更严格的规则(字数/结构)或 LLM-as-a-judge。
draft = state["draft"]
is_good = (
("高质量" in draft) # mock 输出的显式标记
or ("共500字" in draft) # mock 输出的显式标记
or (len(draft) >= 20 and "数据" in draft) # 兜底:有关键要素且长度足够
)
return {
"is_approved": is_good,
"messages": [f"{'✅ 质量合格!' if is_good else '❌ 质量不达标,需要重写'}"]
}
def should_continue(state: WriterState):
"""路由:合格就结束,不合格就继续生成"""
if state["is_approved"]:
return END
if state["attempt"] >= 5: # 安全阀:最多5次
return END
return "generate"
# 构建图
graph = StateGraph(WriterState)
graph.add_node("generate", generate)
graph.add_node("check_quality", check_quality)
graph.add_edge(START, "generate")
graph.add_edge("generate", "check_quality")
# 关键:条件边指回 generate,形成循环
graph.add_conditional_edges("check_quality", should_continue)
app = graph.compile()
result = app.invoke({
"messages": [], "draft": "", "attempt": 0, "is_approved": False
})
print("执行过程:")
for msg in result["messages"]:
print(f" {msg}")
print(f"\n最终草稿: {result['draft']}")
print(f"总共尝试: {result['attempt']}次")
# 输出:
# ✍️ 第1次生成: 这是第1次草稿,内容较短。...
# ❌ 质量不达标,需要重写
# ✍️ 第2次生成: 这是第2次草稿,内容较短。...
# ❌ 质量不达标,需要重写
# ✍️ 第3次生成: 这是一篇高质量的文章,包含详细数据和分析...
# ✅ 质量合格!LangGraph 支持流式输出,让你实时看到每个节点的执行情况。这对调试和用户体验非常重要。
from typing import TypedDict, Annotated, List
import operator
import asyncio
from langgraph.graph import StateGraph, START, END
class DemoState(TypedDict):
messages: Annotated[List[str], operator.add]
step: int
def node_a(state: DemoState):
step = state["step"] + 1
return {
"step": step,
"messages": [f"🧩 A 执行完成(step={step})"]
}
def node_b(state: DemoState):
step = state["step"] + 1
return {
"step": step,
"messages": [f"🧩 B 执行完成(step={step})"]
}
# 构建最小可运行 graph
graph = StateGraph(DemoState)
graph.add_node("A", node_a)
graph.add_node("B", node_b)
graph.add_edge(START, "A")
graph.add_edge("A", "B")
graph.add_edge("B", END)
app = graph.compile()
initial_state: DemoState = {"messages": [], "step": 0}
# ===== invoke vs stream =====
# 方式1: invoke — 一次性返回最终结果
final_state = app.invoke(initial_state)
print("invoke 最终状态:", final_state)
# 方式2: stream — 逐节点返回中间状态(增量更新)
print("\nstream 逐节点事件:")
for event in app.stream(initial_state):
for node_name, updates in event.items():
print(f" 📍 节点 [{node_name}] 完成 -> updates={updates}")
# 方式3: stream + stream_mode="values" — 返回每一步的完整状态
print("\nstream(values) 完整状态快照:")
for snapshot in app.stream(initial_state, stream_mode="values"):
print(f" snapshot={snapshot}")
# 方式4: astream — 异步流式(用于 Web 服务)
async def run_async_stream():
print("\nastream 异步逐节点事件:")
async for event in app.astream(initial_state):
for node_name, updates in event.items():
print(f" 📍 节点 [{node_name}] 完成 -> updates={updates}")
asyncio.run(run_async_stream())| 方法 | 返回 | 适用场景 |
|---|---|---|
invoke() |
最终完整状态 | 简单调用,只关心结果 |
stream() |
每个节点的增量更新 | 调试、进度展示 |
astream() |
异步流式 | Web API、高并发 |
当调用 app = graph.compile() 后,得到的 app 对象具有以下方法:
| 方法 | 参数说明 | 返回值 | 适用场景 |
|---|---|---|---|
invoke(input, config=None, **kwargs)
|
input: 初始状态字典 config: 配置字典,包含 thread_id 等 示例: {"messages": ["你好"]}
|
最终完整状态字典(所有节点执行完毕后的 State) | 同步执行,一次性获取最终结果 |
stream(input, config=None, stream_mode="updates", **kwargs)
|
input: 初始状态 stream_mode: 流式模式 - "updates"(默认): 返回增量更新 - "values": 返回完整状态快照 - "debug": 返回调试信息 |
生成器,每次迭代返回一个节点执行结果{"节点名": {"字段": "更新值"}}
|
实时观察每个节点的执行,适合调试和展示进度 |
astream(input, config=None, **kwargs)
|
input: 初始状态 config: 异步配置 与 stream() 参数相同 |
异步生成器 (AsyncGenerator) 需要使用 async for 遍历
|
Web 服务、高并发场景、异步处理 |
get_state(config)
|
config: 必须包含 thread_id 示例: {"configurable": {"thread_id": "xxx"}}
|
StateSnapshot 对象,包含: - values: 当前状态值 - next: 下一个要执行的节点 - config: 配置信息 |
查看指定会话的当前状态,用于状态监控 |
get_state_history(config, filter=None)
|
config: 包含 thread_id 的配置 filter: 可选过滤器 用于筛选特定类型的状态变更 |
列表,包含所有历史状态快照 每个快照记录了该时刻的完整状态 |
查看执行历史、审计日志、时间旅行调试 |
update_state(config, values, as_node=None, **kwargs)
|
config: 包含 thread_id 的配置 values: 要更新的状态字段字典 as_node: 可选,指定以哪个节点身份写入 示例: update_state(config, {"approved": True})
|
更新后的 StateSnapshot 状态被修改并可继续执行 |
人工审批时修改状态、从指定点重新执行 |
# 定义初始状态
initial_state = {"messages": [], "step": 0}
# 执行工作流,等待所有节点完成
final_state = app.invoke(initial_state)
# 获取最终结果
print(f"最终消息: {final_state['messages']}")
print(f"最终步骤: {final_state['step']}")# 方式A: stream_mode="updates" (默认) - 返回增量更新
for event in app.stream(initial_state):
for node_name, updates in event.items():
print(f"📍 节点 [{node_name}] 执行完成")
print(f" 更新内容: {updates}")
# 方式B: stream_mode="values" - 返回完整状态
for snapshot in app.stream(initial_state, stream_mode="values"):
print(f"当前完整状态: {snapshot}")
print(f"消息数量: {len(snapshot.get('messages', []))}")# 配置必须包含 thread_id
config = {"configurable": {"thread_id": "conversation-001"}}
# 获取当前状态快照
snapshot = app.get_state(config)
# 查看状态详情
print(f"当前状态值: {snapshot.values}")
print(f"下一个执行节点: {snapshot.next}")
print(f"配置信息: {snapshot.config}")# 配置 thread_id
config = {"configurable": {"thread_id": "approval-001"}}
# 第1步: 提交申请,工作流会在审批节点前暂停
result = app.invoke(initial_state, config=config)
print(f"工作流暂停,当前状态: {result['status']}")
# 第2步: 人工审批 - 修改 approved 状态
approval_update = {"approved": True, "approver": "经理张三"}
app.update_state(config, approval_update)
print("✅ 已批准,更新状态")
# 第3步: 恢复执行(传入 None 表示从 checkpoint 继续)
final_result = app.invoke(None, config=config)
print(f"审批完成: {final_result}")LangGraph 内置了检查点机制(Checkpointer),可以随时保存和恢复工作流状态。这是实现「人工审批」「断点续传」「跨会话恢复」的核心基础。
interrupt_before/after 实现 Human-in-the-Loopthread_id 区分不同用户/会话的状态LangGraph 提供了多种 Checkpointer 实现,适用于不同的场景和需求:
MemorySaver,无需配置,快速验证逻辑SqliteSaver,轻量级且持久化PostgresSaver,支持事务和复杂查询RedisSaver,配合 PostgreSQL 做持久化备份MongoDBSaver,支持水平扩展最简单的 Checkpointer 实现,将状态保存在内存中。适合开发测试,但服务重启后数据会丢失。
from typing import TypedDict, Annotated, List
import operator
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import MemorySaver
class ChatState(TypedDict):
messages: Annotated[List[str], operator.add]
user_name: str
def chat_node(state: ChatState):
"""简单的对话节点"""
user_msg = state["messages"][-1] if state["messages"] else ""
user_name = state.get("user_name", "用户")
reply = f"你好 {user_name}!你说:'{user_msg}'"
return {"messages": [reply]}
# 构建图
graph = StateGraph(ChatState)
graph.add_node("chat", chat_node)
graph.add_edge(START, "chat")
graph.add_edge("chat", END)
# 创建检查点存储并编译
memory = MemorySaver()
app = graph.compile(checkpointer=memory)
# 配置:thread_id 用于区分不同会话
config = {"configurable": {"thread_id": "conversation_001"}}
# 第一次运行
result1 = app.invoke(
{"messages": ["你好"], "user_name": "小明"},
config=config
)
print("第1次对话:", result1["messages"])
# 第二次运行(同一个 thread_id,会自动恢复上次的状态)
result2 = app.invoke(
{"messages": ["继续之前的对话"]},
config=config
)
print("第2次对话:", result2["messages"])
# result2 的 messages 会包含之前所有的消息!通过 interrupt_before 或 interrupt_after 参数,可以让工作流在指定节点暂停,等待人工确认后继续。
START → 生成方案 → ⏸️ 暂停(等待人工审批)→ 执行方案 → END
↑
人工查看并确认后
调用 app.invoke(None, config)
恢复执行
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import MemorySaver
class ApprovalState(TypedDict):
plan: str
approved: bool
result: str
def generate_plan(state: ApprovalState):
"""生成执行方案"""
plan = "计划:购买服务器,预算 5000 元"
return {"plan": plan, "approved": False}
def execute_plan(state: ApprovalState):
"""执行方案(需要审批)"""
if state["approved"]:
result = f"已执行:{state['plan']}"
else:
result = "方案未通过审批,暂不执行"
return {"result": result}
# 构建图
graph = StateGraph(ApprovalState)
graph.add_node("generate", generate_plan)
graph.add_node("execute", execute_plan)
graph.add_edge(START, "generate")
graph.add_edge("generate", "execute")
graph.add_edge("execute", END)
# 编译时设置检查点和中断点
memory = MemorySaver()
app = graph.compile(
checkpointer=memory,
interrupt_before=["execute"] # 在 execute 节点前暂停
)
config = {"configurable": {"thread_id": "approval_001"}}
initial_state = {"plan": "", "approved": False, "result": ""}
# 第一次运行:会在 execute 节点前暂停
result = app.invoke(initial_state, config=config)
print("工作流已暂停,等待人工审批...")
print(f"当前状态: {result}")
# 模拟人工审批:修改 approved 状态
print("\n人工审批中...")
approval_input = {"approved": True} # 人工批准
# 恢复执行(传入新的状态更新)
final = app.invoke(approval_input, config=config)
print(f"审批通过,继续执行: {final}")将状态保存到 SQLite 数据库文件,支持持久化存储,适合单机部署和小型项目。
from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph.graph import StateGraph, START, END
from typing import TypedDict
class WorkflowState(TypedDict):
step: int
data: str
def process_node(state: WorkflowState):
return {"step": state["step"] + 1, "data": f"处理步骤 {state['step']}"}
# 构建图
graph = StateGraph(WorkflowState)
graph.add_node("process", process_node)
graph.add_edge(START, "process")
graph.add_edge("process", END)
# 使用 SQLite 持久化(数据保存到文件)
with SqliteSaver.from_conn_string("checkpoints.db") as checkpointer:
app = graph.compile(checkpointer=checkpointer)
config = {"configurable": {"thread_id": "workflow_001"}}
# 第一次运行
result = app.invoke({"step": 0, "data": ""}, config=config)
print(f"第1次运行: {result}")
# 服务重启后,仍然可以恢复状态
result2 = app.invoke({"step": 0, "data": ""}, config=config)
print(f"恢复后运行: {result2}") # step 会从上次的值继续使用 PostgreSQL 数据库存储状态,支持事务、并发控制和复杂查询,适合生产环境。
from langgraph.checkpoint.postgres import PostgresSaver
from psycopg import Connection
# 连接 PostgreSQL
connection_string = "postgresql://user:password@localhost:5432/langgraph_db"
with Connection.connect(connection_string) as conn:
# 创建检查点表(首次使用需要)
PostgresSaver.create_tables(conn)
# 使用 PostgreSQL 持久化
checkpointer = PostgresSaver(conn)
app = graph.compile(checkpointer=checkpointer)
config = {"configurable": {"thread_id": "prod_workflow_001"}}
result = app.invoke({"step": 0, "data": ""}, config=config)app.get_state_history(config) 获取所有历史状态app.update_state(config, values, as_node="node_name") 修改状态并重新执行thread_id 的状态完全独立,互不干扰app.get_state(config) 获取当前状态和元数据LangGraph 的真正威力在于与大语言模型结合。下面我们演示如何手动构建一个能调用工具的智能体。
from typing import TypedDict, Annotated, List
import operator
from langgraph.graph import StateGraph, START, END
from langchain_community.chat_models import ChatTongyi
from langchain_core.messages import HumanMessage, AIMessage, BaseMessage
class AgentState(TypedDict):
messages: Annotated[List[BaseMessage], operator.add]
# 初始化 LLM(通义千问)
llm = ChatTongyi(model="qwen-plus", temperature=0)
def call_model(state: AgentState):
"""调用 LLM"""
response = llm.invoke(state["messages"])
return {"messages": [response]}
def should_continue(state: AgentState):
"""判断是否需要继续(是否有工具调用)"""
last_message = state["messages"][-1]
if hasattr(last_message, "tool_calls") and last_message.tool_calls:
return "tools" # 有工具调用,去执行工具
return END # 没有工具调用,结束
# 构建 Agent 图
graph = StateGraph(AgentState)
graph.add_node("agent", call_model)
# graph.add_node("tools", tool_executor) # 工具执行节点
graph.add_edge(START, "agent")
graph.add_conditional_edges("agent", should_continue)
# graph.add_edge("tools", "agent") # 工具执行完回到 agent
app = graph.compile()
# ===== 运行示例 =====
# 简单对话(无工具调用)
result = app.invoke({
"messages": [HumanMessage(content="你好,请介绍一下自己")]
})
print("Agent 回复:")
for msg in result["messages"]:
print(f"- {type(msg).__name__}: {msg.content}")
# 输出示例:
# Agent 回复:
# - HumanMessage: 你好,请介绍一下自己
# - AIMessage: 我是通义千问大模型,可以回答问题、分析文本、协助创作等。START → agent(LLM) ──→ 需要调用工具?──→ YES → tools → agent(LLM)
│ ↑
│ (循环直到不需要工具)
└─ NO → END
综合运用以上所有知识,构建一个完整的订单处理工作流。
START
│
▼
┌──────────────┐
│ validate │ ← 验证订单(金额、库存)
└──────┬───────┘
│
┌────┴────┐
▼ ▼
有效 无效 → END(返回错误)
│
▼
┌──────────────┐
│ payment │ ← 处理支付
└──────┬───────┘
│
┌────┴────┐
▼ ▼
成功 失败 → retry_payment(最多3次)
│ │
│ ▼
│ ┌──────────┐
│ │ payment │(循环)
│ └──────────┘
▼
┌──────────────┐
│ ship │ ← 安排发货
└──────┬───────┘
│
▼
┌──────────────┐
│ notify │ ← 通知客户
└──────┬───────┘
│
▼
END
"""
智能订单处理系统 - 基于 LangGraph StateGraph
展示:条件分支 + 循环重试 + 流式输出
"""
from typing import TypedDict, Annotated, List, Optional
import operator
import random
from langgraph.graph import StateGraph, START, END
# ========== 1. 定义状态 ==========
class OrderState(TypedDict):
messages: Annotated[List[str], operator.add] # 处理日志
order_id: str
amount: float
status: str # pending/validated/paid/shipped/failed
payment_attempts: int # 支付尝试次数
tracking_number: Optional[str]
# ========== 2. 定义节点 ==========
def validate(state: OrderState):
"""验证订单"""
order_id = state["order_id"]
amount = state["amount"]
if amount <= 0:
return {
"status": "failed",
"messages": [f"❌ 订单 {order_id} 验证失败:金额无效 (¥{amount})"]
}
if amount > 100000:
return {
"status": "failed",
"messages": [f"❌ 订单 {order_id} 验证失败:超出限额 (¥{amount})"]
}
return {
"status": "validated",
"messages": [f"✅ 订单 {order_id} 验证通过,金额 ¥{amount}"]
}
def payment(state: OrderState):
"""处理支付(模拟:有概率失败)"""
attempts = state["payment_attempts"] + 1
# 模拟支付:前2次50%概率失败,第3次一定成功
success = attempts >= 3 or random.random() > 0.5
if success:
return {
"status": "paid",
"payment_attempts": attempts,
"messages": [f"💳 第{attempts}次支付成功!"]
}
return {
"payment_attempts": attempts,
"messages": [f"⚠️ 第{attempts}次支付失败,准备重试..."]
}
def ship(state: OrderState):
"""安排发货"""
tracking = f"SF{random.randint(100000, 999999)}"
return {
"status": "shipped",
"tracking_number": tracking,
"messages": [f"📦 已发货,运单号: {tracking}"]
}
def notify(state: OrderState):
"""通知客户"""
return {
"messages": [f"📧 已通知客户,订单 {state['order_id']} 处理完成"]
}
# ========== 3. 定义路由 ==========
def after_validate(state: OrderState):
"""验证后的路由"""
if state["status"] == "failed":
return END
return "payment"
def after_payment(state: OrderState):
"""支付后的路由"""
if state["status"] == "paid":
return "ship"
if state["payment_attempts"] >= 3:
return END # 超过最大重试次数
return "payment" # 重试(循环)
# ========== 4. 构建图 ==========
graph = StateGraph(OrderState)
graph.add_node("validate", validate)
graph.add_node("payment", payment)
graph.add_node("ship", ship)
graph.add_node("notify", notify)
graph.add_edge(START, "validate")
graph.add_conditional_edges("validate", after_validate)
graph.add_conditional_edges("payment", after_payment)
graph.add_edge("ship", "notify")
graph.add_edge("notify", END)
app = graph.compile()
# ========== 5. 运行(流式输出)==========
print("=" * 50)
print("🚀 订单处理系统启动")
print("=" * 50)
initial = {
"messages": [],
"order_id": "ORD-2024-001",
"amount": 5999.0,
"status": "pending",
"payment_attempts": 0,
"tracking_number": None
}
# 使用 stream 观察每个节点的执行
for event in app.stream(initial):
for node_name, updates in event.items():
print(f"\n📍 节点 [{node_name}]:")
if "messages" in updates:
for msg in updates["messages"]:
print(f" {msg}")
if "status" in updates:
print(f" 状态: {updates['status']}")
print("\n" + "=" * 50)
print("✅ 订单处理完成")| 方法 | 参数说明 | 功能说明 |
|---|---|---|
add_node(name, fn) |
name: 节点名称 fn: 接收state返回字典的函数 |
添加处理节点,是工作流的基本单元 |
add_edge(a, b) |
a: 起始节点或START b: 目标节点或END |
添加普通边,固定流转关系 |
add_conditional_edges(source, router_fn) |
source: 起始节点 router_fn: 返回目标节点名的函数 |
添加条件边,实现分支和循环逻辑 |
set_entry_point(node) |
node: 入口节点名称 | 设置图入口(替代add_edge(START, node)) |
set_finish_point(node) |
node: 结束节点名称 | 设置图结束点(替代add_edge(node, END)) |
compile(checkpointer, interrupt_before, debug) |
checkpointer: 持久化存储 interrupt_before: 暂停节点列表 debug: 调试模式 |
编译图为可执行应用,返回app对象 |
调用 app = graph.compile() 后,使用以下方法执行工作流:
| 方法 | 参数 | 功能 |
|---|---|---|
invoke(input, config) |
input: 初始状态 config: 含thread_id的配置 |
同步执行,返回最终状态 |
stream(input, config, stream_mode) |
stream_mode: updates/values/debug | 流式执行,逐节点返回更新 |
get_state(config) |
config: 含thread_id的配置 | 获取当前状态快照 |
update_state(config, values, as_node) |
values: 要更新的状态字段 as_node: 以哪个节点身份写入 |
人工修改状态,用于审批场景 |
get_state_history(config) |
config: 含thread_id的配置 | 获取历史状态列表(时间旅行) |
# ========== 步骤1: 定义状态 ==========
from typing import TypedDict, Annotated, List
import operator
class ChatState(TypedDict):
messages: Annotated[List[str], operator.add] # 累积消息
user_name: str
# ========== 步骤2: 定义节点函数 ==========
def chat_node(state: ChatState):
"""聊天节点:处理消息"""
return {"messages": [f"回复: {state['messages'][-1]}"]}
def check_node(state: ChatState):
"""检查节点:判断继续或结束"""
return {"continue": len(state["messages"]) < 4}
# ========== 步骤3: 定义路由 ==========
def router(state: ChatState):
if state.get("continue"):
return "chat" # 继续循环
return END # 结束
# ========== 步骤4: 构建图 ==========
from langgraph.graph import StateGraph, START, END
graph = StateGraph(ChatState)
graph.add_node("chat", chat_node)
graph.add_node("check", check_node)
graph.add_edge(START, "chat")
graph.add_edge("chat", "check")
graph.add_conditional_edges("check", router)
# ========== 步骤5: 编译 ==========
app = graph.compile()
# ========== 步骤6: 执行 ==========
result = app.invoke({
"messages": ["你好"],
"user_name": "小明"
})
print(result["messages"])| 写法 | 合并行为 |
|---|---|
field: str |
直接覆盖(后者覆盖前者) |
field: Annotated[List, operator.add] |
列表追加(累积式) |
field: Annotated[int, lambda a,b: a+b] |
自定义 reducer(如计数器累加) |
add_conditional_edges + 路由函数stream() 逐节点观察状态变化MemorySaver + thread_id 保存会话状态interrupt_before 暂停等待人工确认create_react_agent 快速构建工具调用智能体add_conditional_edgeshttps://langchain-ai.github.io/langgraph/