LangGraph
工作流引擎
核心架构
StateGraph
状态机
State
类型安全状态
Node
纯函数节点
Edge
条件边/普通边
状态管理
Reducer
状态合并策略
配置系统
多租户支持
结构化输出
Pydantic验证
提示工程
动态模板
上下文感知
Few-shot
语义示例选择
CoT推理
显式思维链
自洽性
多路径验证
记忆机制
滑动窗口
Token管理
摘要压缩
层次化记忆
持久化
PostgreSQL/SQLite
检查点
容错恢复
生产特性
人工介入
Human-in-the-loop
时间旅行
调试与回滚
并发控制
版本向量
3.1 LangGraph基础:状态机驱动的业务流程自动化
LangGraph并非简单的"LangChain扩展",而是基于状态机(State Machine)理论的持久化工作流引擎。它解决了传统Chain无法处理的循环、分支和状态持久化问题。
3.1.1 状态管理:单一事实源架构
核心架构对比:
传统Chain(无状态)
输入
步骤1
步骤2
步骤3
输出
LangGraph(有状态)
条件A
条件B
继续
结束
输入
节点1
更新状态
State
节点2
更新状态
State
条件分支
Conditional Edge
节点3A
节点3B
更新状态
循环或结束
输出
状态(State)的技术定义:
State是不可变数据结构的累积器,每个节点接收当前状态,返回更新片段,LangGraph自动合并:
python
from typing import TypedDict, Annotated, List, Optional
from langgraph.graph import StateGraph, END
from langchain_core.messages import AnyMessage, SystemMessage, HumanMessage, AIMessage
import operator
from datetime import datetime
# 图书馆业务状态定义(类型安全)
class LibraryState(TypedDict):
# 消息历史:使用operator.add实现追加语义
messages: Annotated[List[AnyMessage], operator.add]
# 当前业务实体
current_book: Optional[str]
borrower_id: Optional[str]
# 检索结果缓存(自定义reducer:替换而非追加)
search_results: Annotated[List[dict], lambda x, y: y if y else x]
# 业务标志位
intent: Optional[str] # 识别到的意图
eligibility: Optional[bool] # 借阅资格
confirmation: Optional[bool] # 用户确认
# 元数据(用于限流和调试)
step_count: Annotated[int, operator.add] # 步骤计数
start_time: str
library_code: str # 分馆代码
# 初始化状态
initial_state = {
"messages": [SystemMessage(content="图书馆智能服务系统启动")],
"current_book": None,
"borrower_id": None,
"search_results": [],
"intent": None,
"eligibility": None,
"confirmation": None,
"step_count": 0,
"start_time": datetime.now().isoformat(),
"library_code": "MAIN"
}
# 构建状态图
builder = StateGraph(LibraryState)
节点(Node)的函数式语义:
节点是纯函数 f(state) -> update_dict,遵循不可变性原则:
python
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field
llm = ChatOpenAI(model="gpt-4", temperature=0)
# 节点1:意图识别(分类任务)
class IntentClassification(BaseModel):
intent: str = Field(description="意图类型:search/loan/return/renew/help")
confidence: float = Field(description="置信度0-1")
entities: dict = Field(description="提取的实体(书名、作者等)")
intent_parser = PydanticOutputParser(pydantic_object=IntentClassification)
def understand_request(state: LibraryState) -> dict:
"""
分析用户最后一条消息,识别意图和提取实体
返回:状态更新字典(仅包含变更的字段)
"""
last_message = state["messages"][-1]
prompt = f"""分析以下读者咨询,提取意图和关键信息:
咨询内容:{last_message.content}
格式要求:{intent_parser.get_format_instructions()}"""
response = llm.invoke(prompt)
parsed = intent_parser.parse(response.content)
# 仅返回需要更新的字段
return {
"intent": parsed.intent,
"step_count": 1, # 累加到现有值
"messages": [AIMessage(content=f"系统理解:意图={parsed.intent}")] # 追加
}
# 节点2:图书检索(条件执行)
def search_books(state: LibraryState) -> dict:
"""仅在intent为search时执行"""
if state["intent"] != "search":
return {"step_count": 0} # 空操作仍需返回dict
# 从state中提取查询参数
query = state.get("current_book", "")
# 模拟向量检索(实际应连接真实数据库)
results = [
{"title": "Python编程:从入门到实践", "call_number": "TP311.56/12", "available": True},
{"title": "流畅的Python", "call_number": "TP311.56/34", "available": False}
]
return {
"search_results": results,
"step_count": 1,
"messages": [AIMessage(content=f"检索到{len(results)}本相关图书")]
}
# 节点3:资格检查(外部API调用)
def check_eligibility(state: LibraryState) -> dict:
"""检查读者借阅资格"""
borrower_id = state.get("borrower_id")
# 模拟外部系统查询
# 实际:调用图书馆管理系统API
is_eligible = borrower_id and borrower_id.startswith("LIB")
return {
"eligibility": is_eligible,
"step_count": 1,
"messages": [AIMessage(content="资格检查完成" if is_eligible else "无有效借书证")]
}
⚠️ 注意 :看到上述节点函数,可能误以为返回
{"intent": intent},实际上LangGraph要求返回完整的更新字典,且类型必须与State定义匹配。缺失的字段不会被修改,但返回的类型必须正确。
3.1.2 Reducer:状态合并的代数结构
Reducer定义了状态更新的半群(Semigroup)操作,这是LangGraph的核心数学抽象:
Reducer代数
旧状态
old_state
合并操作
Reducer
新片段
update_dict
新状态
new_state
operator.add
列表追加
累加语义
messages
lambda x,y: y
替换
覆盖语义
search_results
max
取最大
极值语义
优先级
Reducer类型详解:
| Reducer | 数学操作 | 适用场景 | 示例 |
|---|---|---|---|
operator.add |
幺半群(Monoid)追加 | 消息历史、日志 | messages |
lambda x, y: y |
替换(Replace) | 缓存结果、当前实体 | search_results |
operator.max |
取最大值 | 优先级、分数 | confidence_score |
lambda x, y: x + y |
数值累加 | 计数器、积分 | step_count |
| 自定义函数 | 复杂合并 | 去重、加权平均 | unique_tags |
Reducer设计:
python
from typing import List, Dict, Any
def merge_messages(old: List[AnyMessage], new: List[AnyMessage]) -> List[AnyMessage]:
"""
智能消息合并:去重 + 长度限制
防止上下文窗口溢出(Token爆炸)
"""
combined = old + new
# 去重:基于消息内容hash
seen = set()
unique = []
for msg in combined:
msg_hash = hash((msg.type, msg.content))
if msg_hash not in seen:
seen.add(msg_hash)
unique.append(msg)
# 保留最近20条(可配置)
max_history = 20
if len(unique) > max_history:
# 始终保留第一条system消息
system_msgs = [m for m in unique if isinstance(m, SystemMessage)]
other_msgs = [m for m in unique if not isinstance(m, SystemMessage)]
unique = system_msgs + other_msgs[-(max_history-len(system_msgs)):]
return unique
def merge_search_results(old: List[dict], new: List[dict]) -> List[dict]:
"""
检索结果合并:加权去重(基于相关性分数)
"""
if not new:
return old
# 合并并去重(基于title)
combined = {item["title"]: item for item in old}
for item in new:
title = item["title"]
if title in combined:
# 保留分数更高的
if item.get("score", 0) > combined[title].get("score", 0):
combined[title] = item
else:
combined[title] = item
# 按相关性排序
return sorted(combined.values(), key=lambda x: x.get("score", 0), reverse=True)[:10] # Top 10
# 应用自定义reducer
class LibraryState(TypedDict):
messages: Annotated[List[AnyMessage], merge_messages]
search_results: Annotated[List[dict], merge_search_results]
# ... 其他字段
状态膨胀防护:
"设置上限"具体机制,实际上需要双重防护:
python
def check_token_overflow(state: LibraryState) -> bool:
"""检查上下文是否即将溢出"""
from langchain_core.messages import convert_to_openai_messages
# 估算token数(1 token ≈ 4字符中文,3字符英文)
total_chars = sum(len(m.content) for m in state["messages"])
estimated_tokens = total_chars // 3
# GPT-4上下文限制8K,预留1K给输出
return estimated_tokens > 7000
def summarize_if_needed(state: LibraryState) -> LibraryState:
"""如果上下文过长,触发摘要"""
if check_token_overflow(state):
# 保留最近2轮,摘要更早的历史
recent = state["messages"][-2:]
older = state["messages"][:-2]
summary_prompt = f"总结以下对话的关键信息(100字内):{older}"
summary = llm.invoke(summary_prompt).content
new_messages = [
SystemMessage(content=f"历史摘要:{summary}"),
*recent
]
return {**state, "messages": new_messages}
return state
3.1.3 配置系统:多租户架构
ConfigSchema实现环境适配:
python
from langgraph.graph import StateGraph
from langchain_core.runnables import RunnableConfig
from typing import TypedDict
class LibraryConfig(TypedDict):
"""运行时配置模式"""
library_code: str # 图书馆代码(分馆标识)
max_loan_days: int # 最大借阅天数(分馆策略)
max_books_per_user: int # 借阅上限
model_name: str # LLM模型选择
enable_ai_recommendation: bool # 功能开关
language: str # 服务语言
# 默认配置
DEFAULT_CONFIG: LibraryConfig = {
"library_code": "MAIN",
"max_loan_days": 30,
"max_books_per_user": 10,
"model_name": "gpt-4",
"enable_ai_recommendation": True,
"language": "zh-CN"
}
def search_node(state: LibraryState, config: RunnableConfig) -> dict:
"""
节点接收config参数,实现多租户逻辑
"""
# 提取配置(带默认值)
library_config = config.get("configurable", {})
library_code = library_config.get("library_code", "MAIN")
lang = library_config.get("language", "zh-CN")
# 根据分馆选择数据库
db = get_library_db(library_code)
# 根据语言选择提示词
prompt_template = get_multilingual_prompt("search", lang)
# 执行检索
results = db.query(
state["query"],
limit=library_config.get("max_results", 5)
)
return {"search_results": results}
# 运行时动态配置
result = graph.invoke(
{"query": "Python编程"},
config={
"configurable": {
"library_code": "BRANCH_EAST", # 东区分馆
"max_loan_days": 15, # 分馆策略:更短借阅期
"language": "en-US" # 服务外籍读者
}
}
)
配置继承与覆盖机制:
⚙️ 配置加载优先级层级
📦 系统默认配置
DEFAULT_CONFIG
🌍 环境变量
ENV VARIABLES
🚀 运行时注入
invoke(config=...)
🎯 节点级覆盖
NODE SPECIFIC
✨ 最终生效配置
MERGED CONFIG
3.1.4 结构化输出:类型安全的业务数据
Pydantic与LangGraph的深度集成:
python
from pydantic import BaseModel, Field, validator
from typing import Literal
from langchain.output_parsers import PydanticOutputParser
class BookRecommendation(BaseModel):
"""图书推荐结构化输出"""
title: str = Field(description="书名(精确匹配馆藏)")
author: str = Field(description="作者(姓在前,名在后)")
call_number: str = Field(
description="索书号(遵循《中国图书馆分类法》)",
pattern=r"^[A-Z][0-9]+(\.[0-9]+)?/.+$" # 正则验证
)
isbn: str = Field(description="ISBN-13", pattern=r"^\d{13}$")
reason: str = Field(description="推荐理由(50-200字)")
availability: Literal["available", "loaned", "reserved", "unknown"] = Field(
description="在架状态"
)
shelf_location: str = Field(description="馆藏位置(楼层+区域)")
@validator('reason')
def validate_reason_length(cls, v):
if not 50 <= len(v) <= 200:
raise ValueError('推荐理由长度必须在50-200字之间')
return v
# 创建带验证的解析器
parser = PydanticOutputParser(pydantic_object=BookRecommendation)
def generate_recommendation(state: LibraryState, config: RunnableConfig) -> dict:
"""
生成结构化推荐,带错误恢复
"""
# 构建格式化的提示
format_instructions = parser.get_format_instructions()
prompt = f"""基于以下检索结果,为读者推荐一本最合适的图书。
检索结果:{state['search_results']}
读者偏好:{state.get('preferences', '无特殊偏好')}
必须严格按以下JSON格式返回:
{format_instructions}
注意:所有字段必须填写,不能为null"""
# 带重试的生成
max_retries = 3
for attempt in range(max_retries):
try:
response = llm.invoke(prompt)
structured = parser.parse(response.content)
# 验证通过
return {
"recommendation": structured.model_dump(),
"messages": [AIMessage(content=f"推荐图书:《{structured.title}》")]
}
except Exception as e:
if attempt == max_retries - 1:
# 最终失败,返回错误状态
return {
"error": f"结构化生成失败:{str(e)}",
"messages": [AIMessage(content="抱歉,推荐系统暂时故障,请稍后重试")]
}
# 修正提示词,要求更严格的格式
prompt += f"\n\n上次的输出格式有误:{str(e)},请修正后重新输出。"
错误处理节点模式:
成功
解析失败
重试<3次
重试耗尽
生成节点
验证通过
错误处理节点
降级输出
纯文本
继续流程
3.2 提示工程:咨询质量的系统性优化
3.2.1 动态模板系统:上下文感知的提示组装
分层模板架构:
python
from langchain_core.prompts import (
ChatPromptTemplate,
MessagesPlaceholder,
SystemMessagePromptTemplate,
HumanMessagePromptTemplate
)
from datetime import datetime
def create_dynamic_template(state: LibraryState, config: LibraryConfig) -> ChatPromptTemplate:
"""
根据当前状态和配置动态构建提示模板
"""
# 基础系统消息(所有场景)
base_system = SystemMessagePromptTemplate.from_template("""你是{library_name}的智能馆员。
当前时间:{current_time}
服务语言:{language}
读者ID:{borrower_id}
""")
# 动态添加角色专长(基于意图)
intent = state.get("intent")
expertise_map = {
"search": "精通图书检索和分类法",
"loan": "熟悉借阅规则和流程",
"reference": "擅长深度咨询和文献传递",
"technical": "了解图书馆IT系统和数据库"
}
role_expertise = expertise_map.get(intent, "通用咨询服务")
# 动态添加业务规则(基于分馆配置)
rules = []
if config.get("max_loan_days"):
rules.append(f"本馆借阅期限为{config['max_loan_days']}天")
if config.get("enable_ai_recommendation"):
rules.append("可使用AI推荐功能")
# 组装完整模板
template = ChatPromptTemplate.from_messages([
base_system,
("system", f"你的专长:{role_expertise}"),
("system", "业务规则:" + "\n".join(rules)) if rules else ("system", ""),
MessagesPlaceholder(variable_name="chat_history"),
("human", "{input}")
])
return template.partial(
library_name=config.get("library_code", "图书馆"),
current_time=datetime.now().strftime("%Y-%m-%d %H:%M"),
language=config.get("language", "zh-CN"),
borrower_id=state.get("borrower_id", "访客")
)
# 使用示例
template = create_dynamic_template(current_state, current_config)
prompt = template.format_messages(
chat_history=state["messages"][:-1],
input=state["messages"][-1].content
)
3.2.2 少样本学习的工程化实现
动态示例选择(基于语义相似度):
python
from langchain_core.example_selectors import SemanticSimilarityExampleSelector
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import FAISS
# 构建示例库(覆盖主要业务场景)
example_library = [
{
"query": "我想借《三体》",
"thought": "读者要借阅具体图书,需检查:1)馆藏是否存在 2)在架状态 3)读者资格",
"action": "check_availability",
"action_input": "三体"
},
{
"query": "推荐几本Python入门书",
"thought": "读者需要推荐服务,应使用AI推荐功能,考虑:1)初学者水平 2)实践导向 3)馆藏可得性",
"action": "ai_recommend",
"action_input": "Python programming, beginner, practical"
},
{
"query": "怎么续借?",
"thought": "读者咨询流程性问题,直接提供续借操作指引,无需检索",
"action": "provide_guidance",
"action_input": "renewal_process"
},
{
"query": "这个月的科技新书有哪些?",
"thought": "读者需要新书通报,查询本月到馆的新书,按学科分类展示",
"action": "new_arrivals",
"action_input": "current_month, technology"
}
]
# 创建语义选择器
example_selector = SemanticSimilarityExampleSelector.from_examples(
example_library,
OpenAIEmbeddings(),
FAISS,
k=2 # 只选最相关的2个示例
)
def create_few_shot_prompt(user_query: str) -> str:
"""动态选择示例并组装提示"""
selected = example_selector.select_examples({"query": user_query})
examples_text = "\n\n".join([
f"咨询:{ex['query']}\n"
f"思考:{ex['thought']}\n"
f"行动:{ex['action']}\n"
f"输入:{ex['action_input']}"
for ex in selected
])
return f"""以下是处理读者咨询的示例:
{examples_text}
现在处理新的咨询:
咨询:{user_query}
思考:"""
⚠️ 注意 :并不是建议"示例不超过5个",实际上应使用动态选择而非固定数量。通过语义相似度选择最相关的2-3个示例,比固定5个无关示例效果更好。
3.2.3 CoT与自洽性:推理质量的双重保障
思维链(Chain-of-Thought)的形式化:
CoT的本质是显式化隐式推理路径,通过增加token计算提升准确性:
python
cot_template = """你是一位经验丰富的参考咨询馆员。处理读者咨询时,请严格遵循以下推理框架:
【需求解析阶段】
1. 识别信息需求类型:事实型/主题型/文献型/导向型
2. 确定学科领域和深度要求
3. 识别任何隐含约束(时间、语言、可获取性)
【策略规划阶段】
4. 选择检索工具:馆藏目录/数据库/网络资源/专家咨询
5. 构建检索式:确定关键词、同义词、分类号
6. 设定评估标准:相关性、权威性、时效性
【执行与评估阶段】
7. 执行检索并评估结果质量
8. 若结果不足,调整策略(扩展/缩小/转换检索词)
9. 整理想法,准备回答
【回答组织阶段】
10. 结构化呈现:直接答案→详细解释→相关资源
11. 验证信息准确性(交叉验证)
12. 提供后续咨询路径
当前咨询:{question}
请展示完整思考过程:"""
# 在LangGraph节点中使用
def cot_reasoning_node(state: LibraryState) -> dict:
prompt = cot_template.format(question=state["messages"][-1].content)
response = llm.invoke(prompt)
# 提取最终答案(通常在最后部分)
thinking_process = response.content
return {
"cot_thinking": thinking_process,
"messages": [AIMessage(content=thinking_process)]
}
自洽性验证(Self-Consistency)的并行实现:
python
from concurrent.futures import ThreadPoolExecutor, as_completed
def self_consistency_node(state: LibraryState) -> dict:
"""
并行生成多个推理路径,通过投票选择最可靠的答案
"""
question = state["messages"][-1].content
num_samples = 3 # 采样次数
# 使用稍高的temperature增加多样性
diverse_llm = ChatOpenAI(model="gpt-4", temperature=0.7)
def single_reasoning(_):
"""单次推理"""
prompt = f"""回答读者咨询,先思考后回答:
咨询:{question}
思考过程:"""
return diverse_llm.invoke(prompt).content
# 并行执行(I/O密集型,适合多线程)
responses = []
with ThreadPoolExecutor(max_workers=num_samples) as executor:
futures = [executor.submit(single_reasoning, i) for i in range(num_samples)]
for future in as_completed(futures):
try:
responses.append(future.result())
except Exception as e:
print(f"推理路径失败:{e}")
if len(responses) < 2:
return {"final_answer": responses[0] if responses else "推理失败"}
# 答案聚类(基于嵌入相似度)
embeddings = OpenAIEmbeddings().embed_documents(responses)
# 简单投票:选择与其他答案平均相似度最高的
import numpy as np
similarity_matrix = np.inner(embeddings, embeddings)
avg_similarities = similarity_matrix.mean(axis=1)
best_idx = avg_similarities.argmax()
# 计算置信度(一致性比例)
confidence = avg_similarities[best_idx]
return {
"final_answer": responses[best_idx],
"confidence": float(confidence),
"all_responses": responses,
"messages": [AIMessage(content=f"[置信度{confidence:.2f}] {responses[best_idx]}")]
}
架构整合:
置信度<0.8
置信度≥0.8
用户咨询
CoT推理节点
详细思考
置信度检查
自洽性验证
多路径采样
直接输出
答案聚类
相似度计算
选择一致性最高答案
最终回答
用户反馈收集
优化示例库
3.3 上下文窗口管理:记忆压缩与检索
3.3.1 滑动窗口与摘要机制
Token预算管理:
python
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
import tiktoken
def count_tokens(messages: list, model: str = "gpt-4") -> int:
"""精确计算token数"""
encoding = tiktoken.encoding_for_model(model)
total = 0
for msg in messages:
total += len(encoding.encode(msg.content))
# 消息格式开销(角色标签等)
total += 4 # 近似开销
return total
def smart_context_window(state: LibraryState, max_tokens: int = 6000) -> dict:
"""
智能上下文管理:滑动窗口 + 摘要 + RAG检索
"""
messages = state["messages"]
current_tokens = count_tokens(messages)
if current_tokens <= max_tokens:
return {"messages": messages} # 无需处理
# 策略1:保留System消息和最近N轮
system_msgs = [m for m in messages if isinstance(m, SystemMessage)]
conversation = [m for m in messages if not isinstance(m, SystemMessage)]
# 保留最近4轮(8条消息)
recent = conversation[-8:] if len(conversation) > 8 else conversation
older = conversation[:-8] if len(conversation) > 8 else []
if not older:
# 仅保留system + recent
return {"messages": system_msgs + recent}
# 策略2:对历史生成摘要
summary_prompt = f"""将以下对话历史总结为关键信息(保留实体、意图、已确认的事实):
{older}
摘要(200字内):"""
summary = llm.invoke(summary_prompt).content
summary_msg = SystemMessage(content=f"[历史摘要] {summary}")
# 策略3:对当前查询相关的历史进行RAG检索(可选增强)
if state.get("enable_rag_memory"):
relevant = retrieve_relevant_history(older, recent[-1].content)
context_msgs = [SystemMessage(content=f"[相关历史] {r}") for r in relevant]
else:
context_msgs = []
new_messages = system_msgs + [summary_msg] + context_msgs + recent
# 验证最终token数
final_tokens = count_tokens(new_messages)
print(f"上下文压缩:{current_tokens} -> {final_tokens} tokens")
return {"messages": new_messages}
def retrieve_relevant_history(history: list, current_query: str, k: int = 2) -> list:
"""从长历史中检索与当前查询相关的片段"""
from langchain_community.vectorstores import Chroma
# 临时构建向量库(实际应持久化)
texts = [m.content for m in history]
metadatas = [{"index": i} for i in range(len(history))]
db = Chroma.from_texts(texts, OpenAIEmbeddings(), metadatas=metadatas)
results = db.similarity_search(current_query, k=k)
return [r.page_content for r in results]
3.3.2 分层记忆架构
记忆层级
摘要/溢出
持久化
检索
工作记忆
Working Memory
当前对话
~4K tokens
短期记忆
Short-term
会话历史
~100K tokens
长期记忆
Long-term
用户画像
无限
外部记忆
External
知识库/文档
RAG检索
3.4 持久化与检查点:可靠性工程
3.4.1 记忆持久化架构
PostgreSQL生产级配置:
python
from langgraph.checkpoint.postgres import PostgresSaver
from contextlib import contextmanager
@contextmanager
def get_checkpointer():
"""数据库连接池管理"""
connection_kwargs = {
"autocommit": True,
"prepare_threshold": 0, # 禁用预处理语句缓存(兼容PgBouncer)
}
checkpointer = PostgresSaver(
conninfo="postgresql://user:pass@localhost:5432/library_db",
connection_kwargs=connection_kwargs
)
# 初始化表结构(首次运行)
checkpointer.setup()
try:
yield checkpointer
finally:
checkpointer.conn.close()
# 使用
with get_checkpointer() as checkpointer:
graph = builder.compile(checkpointer=checkpointer)
result = graph.invoke(initial_state, config={"configurable": {"thread_id": "user_123"}})
SQLite轻量级方案:
python
from langgraph.checkpoint.sqlite import SqliteSaver
# 内存模式(测试)
memory_saver = SqliteSaver.from_conn_string(":memory:")
# 文件模式(小型生产)
disk_saver = SqliteSaver.from_conn_string("/data/library_checkpoints.db")
graph = builder.compile(checkpointer=disk_saver)
3.4.2 检查点机制:容错与恢复
检查点的数据结构:
python
# LangGraph自动保存的检查点包含:
checkpoint = {
"v": 1, # 版本
"ts": "2024-01-15T10:30:00Z", # 时间戳
"id": "checkpoint_id", # 唯一ID
"channel_values": { # 状态通道值
"messages": [...],
"search_results": [...],
# ... 所有state字段
},
"channel_versions": { # 版本向量(用于并发控制)
"messages": 5,
"search_results": 3
},
"versions_seen": {}, # 已见的父版本
"pending_sends": [] # 待处理的异步消息
}
断点续传与人工介入:
python
# 配置检查点命名空间
config = {
"configurable": {
"thread_id": "loan_application_001", # 业务ID
"checkpoint_ns": "circulation", # 命名空间
"checkpoint_id": None # 首次运行为None
}
}
# 场景1:流程中断恢复
def resume_workflow(thread_id: str):
"""从最后检查点恢复流程"""
# 获取最新检查点
checkpoint = checkpointer.get({
"configurable": {"thread_id": thread_id}
})
if not checkpoint:
return "无历史记录"
# 继续执行
for event in graph.stream(None, config, stream_mode="values"):
print(event)
return "流程已恢复"
# 场景2:人工审核节点(Human-in-the-loop)
def human_approval_node(state: LibraryState) -> dict:
"""暂停等待人工确认"""
# 实际实现:抛出特殊异常或设置等待标志
# LangGraph会在检查点保存状态,等待外部触发
raise NodeInterrupt("等待管理员确认借阅申请")
# 编译时配置中断点
graph = builder.compile(
checkpointer=checkpointer,
interrupt_before=["human_approval"] # 在这些节点前暂停
)
# 外部恢复(管理员审核后)
def approve_and_continue(thread_id: str, approval: bool):
"""管理员审核后继续"""
config = {"configurable": {"thread_id": thread_id}}
# 注入人工决策
human_response = "approved" if approval else "rejected"
# 恢复执行
for event in graph.stream(
Command(resume=human_response), # 新版LangGraph语法
config
):
print(event)
时间旅行调试(Time Travel):
python
# 查看历史检查点
history = list(graph.get_state_history(config))
for checkpoint in history:
print(f"时间:{checkpoint.ts}, 节点:{checkpoint.metadata['step']}")
# 回滚到特定检查点
target_checkpoint = history[3] # 回到第3步
graph.update_state(
config,
target_checkpoint.config, # 使用历史配置
as_node="manual_rollback"
)
# 从该点重新执行(不同分支)
for event in graph.stream(None, config):
print(event)
3.5 本章小结
关键工程原则:
- 状态是单一事实源:所有业务逻辑通过状态转换实现,避免副作用
- Reducer定义业务语义:选择合适的合并策略(追加/替换/聚合)
- 检查点即服务:将持久化视为一等公民,确保流程可恢复
- 配置驱动多租户:同一图实例服务不同分馆,通过config隔离
- 防御性提示工程:结构化输出+验证+重试,确保可靠性