1. LangGraph 框架概述
LangGraph 是一个基于 Python 的图形化编排框架,专为构建复杂 AI 工作流而设计。它采用有向图结构来组织计算流程,支持循环、分支、并行和状态管理等高级特性。与传统的线性执行链不同,LangGraph 允许开发者以更直观的方式构建需要决策、工具调用和状态保持的智能代理系统。
在实际 AI 应用开发中,约 78% 的复杂任务需要处理非线性流程,这正是 LangGraph 的核心价值所在。例如客服机器人、数据分析流水线和自动化决策系统等场景。
1.1 核心设计理念
LangGraph 的设计遵循三个基本原则:
- 状态驱动:整个工作流围绕状态对象运转,每个节点接收状态并返回修改后的状态
- 图形化编排:通过节点(Node)和边(Edge)定义执行路径,支持条件分支和循环
- 持久化支持:内置检查点机制,确保长时间运行的任务可以中断恢复
python复制# 典型 LangGraph 工作流结构示例
from langgraph.graph import StateGraph
# 定义状态类型
class WorkflowState(TypedDict):
data: dict
step: int
result: str
# 创建图实例
workflow = StateGraph(WorkflowState)
# 添加节点
workflow.add_node("preprocess", preprocess_function)
workflow.add_node("analyze", analyze_function)
workflow.add_node("report", generate_report_function)
# 设置边关系
workflow.add_edge("preprocess", "analyze")
workflow.add_edge("analyze", "report")
# 编译执行图
graph = workflow.compile()
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心组件详解
2.1 图形 API 架构
2.1.1 节点(Node)设计
节点是 LangGraph 的基本执行单元,每个节点需要满足以下接口要求:
- 接收一个状态字典作为输入
- 对状态进行修改或添加新字段
- 返回更新后的状态字典
python复制def typical_node(state: dict) -> dict:
"""
标准节点函数模板
:param state: 输入状态字典
:return: 修改后的状态字典
"""
# 1. 从状态中读取数据
input_data = state.get("input")
# 2. 执行核心逻辑
processed_data = some_processing(input_data)
# 3. 更新状态
state["output"] = processed_data
state["last_step"] = "typical_node"
# 4. 返回新状态
return state
2.1.2 边(Edge)类型系统
LangGraph 支持多种边类型,构成工作流的控制逻辑:
| 边类型 | 描述 | 使用场景 |
|---|---|---|
| 普通边 | 无条件转移 | 线性执行步骤 |
| 条件边 | 基于状态判断 | 分支决策 |
| 循环边 | 指向之前节点 | 循环处理 |
python复制# 条件边示例
def decision_point(state):
if state["score"] > 0.8:
return "high_score_path"
else:
return "low_score_path"
workflow.add_conditional_edges(
"classifier",
decision_point,
{
"high_score_path": "premium_processing",
"low_score_path": "standard_processing"
}
)
2.2 状态管理系统
2.2.1 状态结构设计
良好的状态设计是 LangGraph 工作流的关键。推荐采用 TypedDict 明确状态结构:
python复制from typing import TypedDict, Annotated
from typing_extensions import TypedDict
class AnalysisState(TypedDict):
"""数据分析工作流状态"""
raw_data: list # 原始数据
cleaned_data: list # 清洗后数据
features: dict # 特征字典
model_result: Annotated[dict, "模型输出"] # 带注解的字段
current_stage: str # 当前阶段标识
2.2.2 状态版本控制
LangGraph 自动维护状态版本,开发者可以通过 @version 装饰器管理关键字段:
python复制from langgraph.graph import version
@version("1.0")
def process_data(state: AnalysisState) -> AnalysisState:
"""数据处理节点"""
state["cleaned_data"] = [x for x in state["raw_data"] if validate(x)]
state["current_stage"] = "processed"
return state
3. 高级特性实现
3.1 持久化检查点
3.1.1 内存检查点
python复制from langgraph.checkpoint import MemorySaver
# 初始化内存检查点
memory = MemorySaver()
# 配置工作流使用检查点
workflow = StateGraph(AnalysisState)
...
graph = workflow.compile(checkpointer=memory)
# 保存检查点
config = {"configurable": {"thread_id": "job_001"}}
graph.invoke(initial_state, config)
# 恢复检查点
checkpoints = list(memory.list(config))
last_checkpoint = checkpoints[-1]
resume_config = {
"configurable": {
"thread_id": "job_001",
"checkpoint_id": last_checkpoint.checkpoint_id
}
}
graph.invoke(None, resume_config)
3.1.2 自定义存储后端
实现 BaseCheckpointSaver 接口创建自定义存储:
python复制from langgraph.checkpoint.base import BaseCheckpointSaver
class PostgreSQLCheckpointSaver(BaseCheckpointSaver):
"""PostgreSQL 存储后端"""
def __init__(self, connection_string: str):
import psycopg2
self.conn = psycopg2.connect(connection_string)
self._init_db()
def _init_db(self):
with self.conn.cursor() as cur:
cur.execute("""
CREATE TABLE IF NOT EXISTS checkpoints (
id SERIAL PRIMARY KEY,
thread_id VARCHAR(255) NOT NULL,
checkpoint_id VARCHAR(255) NOT NULL,
data JSONB NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
self.conn.commit()
def put(self, config, checkpoint):
import json
thread_id = config["configurable"]["thread_id"]
checkpoint_id = str(uuid.uuid4())
with self.conn.cursor() as cur:
cur.execute(
"INSERT INTO checkpoints (thread_id, checkpoint_id, data) VALUES (%s, %s, %s)",
(thread_id, checkpoint_id, json.dumps(checkpoint.dict()))
)
self.conn.commit()
return {"checkpoint_id": checkpoint_id}
# 实现其他必要方法...
3.2 记忆系统集成
3.2.1 对话记忆实现
python复制from langchain.memory import ConversationBufferMemory
class ChatAgent:
def __init__(self):
self.memory = ConversationBufferMemory(
memory_key="history",
return_messages=True
)
def process_message(self, state):
# 从状态获取输入
user_input = state["message"]
# 更新记忆
self.memory.save_context(
{"input": user_input},
{"output": ""} # 将在后续填充
)
# 获取完整历史
history = self.memory.load_memory_variables({})
state["full_history"] = history["history"]
return state
3.2.2 长期记忆模式
python复制import sqlite3
from datetime import datetime
class LongTermMemory:
def __init__(self, db_path=":memory:"):
self.conn = sqlite3.connect(db_path)
self._init_db()
def _init_db(self):
cursor = self.conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS memories (
id INTEGER PRIMARY KEY,
user_id TEXT NOT NULL,
key TEXT NOT NULL,
value TEXT NOT NULL,
created_at TEXT NOT NULL,
last_accessed TEXT NOT NULL
)
""")
self.conn.commit()
def store(self, user_id: str, key: str, value: str):
now = datetime.now().isoformat()
cursor = self.conn.cursor()
# 检查是否已存在
cursor.execute(
"SELECT id FROM memories WHERE user_id=? AND key=?",
(user_id, key)
)
exists = cursor.fetchone()
if exists:
cursor.execute(
"UPDATE memories SET value=?, last_accessed=? WHERE id=?",
(value, now, exists[0])
)
else:
cursor.execute(
"INSERT INTO memories (user_id, key, value, created_at, last_accessed) VALUES (?, ?, ?, ?, ?)",
(user_id, key, value, now, now)
)
self.conn.commit()
def retrieve(self, user_id: str, key: str) -> Optional[str]:
now = datetime.now().isoformat()
cursor = self.conn.cursor()
cursor.execute(
"SELECT value FROM memories WHERE user_id=? AND key=?",
(user_id, key)
)
result = cursor.fetchone()
if result:
# 更新访问时间
cursor.execute(
"UPDATE memories SET last_accessed=? WHERE user_id=? AND key=?",
(now, user_id, key)
)
self.conn.commit()
return result[0]
return None
4. 实战:构建客服机器人
4.1 需求分析
构建一个具备以下能力的客服机器人:
- 理解用户意图(问候/咨询/投诉)
- 查询知识库获取答案
- 对于复杂问题自动转人工
- 记录对话历史
- 支持对话中断恢复
4.2 状态设计
python复制class CustomerServiceState(TypedDict):
"""客服系统状态"""
user_input: str # 用户输入
intent: str # 识别出的意图
knowledge_answer: Optional[str] # 知识库答案
requires_human: bool # 是否需要人工
conversation_id: str # 对话ID
history: Annotated[list, operator.add] # 对话历史
sentiment: float # 用户情绪分值
timestamp: str # 最后更新时间
4.3 核心节点实现
4.3.1 意图识别节点
python复制from langchain_openai import ChatOpenAI
def intent_classification(state: CustomerServiceState) -> CustomerServiceState:
"""意图分类节点"""
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
prompt = f"""分析以下用户输入的意图:
用户输入:{state['user_input']}
可选意图类型:
- greeting: 问候/打招呼
- inquiry: 业务咨询
- complaint: 投诉
- goodbye: 结束对话
请只返回意图类型,不要包含其他内容。"""
response = llm.invoke(prompt)
state['intent'] = response.content.strip().lower()
# 记录处理日志
state['history'].append({
'step': 'intent_classification',
'result': state['intent'],
'timestamp': datetime.now().isoformat()
})
return state
4.3.2 知识库查询节点
python复制from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings
class KnowledgeBase:
def __init__(self):
self.embeddings = OpenAIEmbeddings()
self.db = FAISS.load_local("kb_index", self.embeddings)
def query(self, question: str, top_k: int =3) -> list:
docs = self.db.similarity_search(question, k=top_k)
return [doc.page_content for doc in docs]
def knowledge_lookup(state: CustomerServiceState) -> CustomerServiceState:
"""知识库查询节点"""
kb = KnowledgeBase()
results = kb.query(state['user_input'])
if len(results) > 0:
state['knowledge_answer'] = "\n\n".join(results)
# 使用LLM精炼答案
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.3)
prompt = f"""基于以下知识库内容,生成对用户问题的友好回答:
用户问题:{state['user_input']}
知识库内容:
{state['knowledge_answer']}
要求:
1. 用中文回答
2. 保持专业但友好
3. 不超过100字"""
response = llm.invoke(prompt)
state['knowledge_answer'] = response.content
else:
state['knowledge_answer'] = None
return state
4.4 完整工作流组装
python复制def build_customer_service_graph():
"""构建客服工作流"""
workflow = StateGraph(CustomerServiceState)
# 添加节点
workflow.add_node("receive_input", receive_input_node)
workflow.add_node("classify_intent", intent_classification)
workflow.add_node("query_knowledge", knowledge_lookup)
workflow.add_node("generate_response", generate_response_node)
workflow.add_node("transfer_to_human", human_transfer_node)
workflow.add_node("end_conversation", end_conversation_node)
# 设置入口点
workflow.set_entry_point("receive_input")
# 添加边
workflow.add_edge("receive_input", "classify_intent")
# 根据意图路由
def route_by_intent(state):
if state['intent'] == 'goodbye':
return 'end_conversation'
elif state['intent'] == 'greeting':
return 'generate_response'
else:
return 'query_knowledge'
workflow.add_conditional_edges(
"classify_intent",
route_by_intent,
{
"end_conversation": "end_conversation",
"generate_response": "generate_response",
"query_knowledge": "query_knowledge"
}
)
# 知识库查询后处理
def after_knowledge_query(state):
if state['knowledge_answer'] is None:
return 'transfer_to_human'
else:
return 'generate_response'
workflow.add_conditional_edges(
"query_knowledge",
after_knowledge_query,
{
"transfer_to_human": "transfer_to_human",
"generate_response": "generate_response"
}
)
# 最终响应生成
workflow.add_edge("generate_response", END)
workflow.add_edge("transfer_to_human", END)
workflow.add_edge("end_conversation", END)
# 使用PostgreSQL检查点
checkpoint = PostgreSQLCheckpointSaver("postgresql://user:pass@localhost/db")
return workflow.compile(checkpointer=checkpoint)
5. 性能优化技巧
5.1 节点并行化
python复制from concurrent.futures import ThreadPoolExecutor
def parallel_nodes(state):
"""并行执行多个节点"""
nodes = [
("intent", intent_classification),
("sentiment", sentiment_analysis),
("entities", entity_extraction)
]
with ThreadPoolExecutor() as executor:
# 准备参数
params = [(state.copy(),) for _, _ in nodes]
# 并行执行
futures = [
executor.submit(func, param)
for (_, func), param in zip(nodes, params)
]
# 合并结果
for (key, _), future in zip(nodes, futures):
result = future.result()
state[key] = result[key]
return state
5.2 缓存策略
python复制from functools import lru_cache
from hashlib import md5
@lru_cache(maxsize=1000)
def cached_llm_call(prompt: str, model: str) -> str:
"""带缓存的LLM调用"""
llm = ChatOpenAI(model=model)
return llm.invoke(prompt).content
def get_cache_key(state: dict, prefix: str) -> str:
"""生成缓存键"""
data = {
"input": state.get("user_input"),
"context": state.get("context", {})
}
return f"{prefix}_{md5(str(data).encode()).hexdigest()}"
def cached_node(state: dict) -> dict:
"""使用缓存的节点"""
cache_key = get_cache_key(state, "cached_node")
if cache_key in cached_llm_call.cache:
state["response"] = cached_llm_call.cache[cache_key]
state["from_cache"] = True
else:
prompt = build_prompt(state)
state["response"] = cached_llm_call(prompt, "gpt-3.5-turbo")
state["from_cache"] = False
return state
5.3 监控与日志
python复制import logging
from datetime import datetime
class GraphLogger:
"""工作流日志记录器"""
def __init__(self):
self.logger = logging.getLogger("langgraph")
self.logger.setLevel(logging.INFO)
handler = logging.FileHandler("workflow.log")
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
self.logger.addHandler(handler)
def log_node_execution(self, node_name: str, state: dict):
"""记录节点执行"""
log_data = {
"node": node_name,
"timestamp": datetime.now().isoformat(),
"state_keys": list(state.keys()),
"execution_time": state.get("_execution_time", 0)
}
self.logger.info(json.dumps(log_data))
def timed_node(node_func):
"""执行时间统计装饰器"""
def wrapper(state):
start = time.time()
result = node_func(state)
end = time.time()
result["_execution_time"] = end - start
return result
return wrapper
# 使用示例
@timed_node
def monitored_node(state):
# 节点逻辑
time.sleep(0.5) # 模拟处理
state["processed"] = True
return state
6. 生产环境最佳实践
6.1 错误处理机制
python复制from typing import Optional
class ErrorState(TypedDict):
"""错误处理扩展状态"""
error: Optional[dict]
retry_count: int
def error_handler(state: ErrorState) -> ErrorState:
"""全局错误处理节点"""
if state.get("error"):
logging.error(f"工作流错误: {state['error']}")
# 重试逻辑
if state["retry_count"] < 3:
state["retry_count"] += 1
return "retry"
else:
return "failure"
return "success"
def safe_node(node_func):
"""错误捕获装饰器"""
def wrapper(state):
try:
return node_func(state)
except Exception as e:
state["error"] = {
"node": node_func.__name__,
"exception": str(e),
"timestamp": datetime.now().isoformat()
}
return state
return wrapper
# 使用示例
@safe_node
def risky_operation(state):
# 可能失败的操作
result = 1 / 0 # 故意引发错误
state["result"] = result
return state
6.2 版本控制策略
python复制import yaml
from pathlib import Path
class WorkflowVersioner:
"""工作流版本管理"""
def __init__(self, repo_dir: str = "./workflows"):
self.repo = Path(repo_dir)
self.repo.mkdir(exist_ok=True)
def save_version(self, workflow, version: str):
"""保存工作流版本"""
# 序列化节点和边
spec = {
"version": version,
"nodes": list(workflow.nodes.keys()),
"edges": self._extract_edges(workflow),
"timestamp": datetime.now().isoformat()
}
# 保存为YAML
version_file = self.repo / f"v{version}.yaml"
with open(version_file, "w") as f:
yaml.safe_dump(spec, f)
def _extract_edges(self, workflow):
"""提取边关系"""
# 实现取决于具体LangGraph版本
return str(workflow.edges)
def load_version(self, version: str):
"""加载特定版本"""
version_file = self.repo / f"v{version}.yaml"
with open(version_file) as f:
return yaml.safe_load(f)
# 使用示例
versioner = WorkflowVersioner()
workflow = build_customer_service_graph()
versioner.save_version(workflow, "1.0.2")
6.3 CI/CD 集成
python复制import unittest
from langgraph.graph import StateGraph
class WorkflowTest(unittest.TestCase):
"""工作流测试用例"""
def setUp(self):
self.workflow = build_customer_service_graph()
self.test_state = {
"user_input": "如何重置密码?",
"conversation_id": "test_001"
}
def test_intent_classification(self):
"""测试意图识别"""
state = self.workflow.nodes["classify_intent"](self.test_state.copy())
self.assertIn(state["intent"], ["inquiry", "complaint"])
def test_knowledge_lookup(self):
"""测试知识库查询"""
state = self.workflow.nodes["query_knowledge"](self.test_state.copy())
self.assertTrue(state.get("knowledge_answer") or state["requires_human"])
def test_full_workflow(self):
"""测试完整工作流"""
result = self.workflow.invoke(self.test_state.copy())
self.assertTrue(result.get("knowledge_answer") or result["requires_human"])
# 集成到CI流水线
if __name__ == "__main__":
unittest.main()
