目录
[5. LangGraph 的其他特性](#5. LangGraph 的其他特性)
[5.1 使用 Overwrite 绕过 reducer](#5.1 使用 Overwrite 绕过 reducer)
[为什么需要 Overwrite?](#为什么需要 Overwrite?)
[使用 Overwrite 的应用场景](#使用 Overwrite 的应用场景)
[5.2 定义输入输出模式](#5.2 定义输入输出模式)
[StateGraph 初始化参数说明](#StateGraph 初始化参数说明)
[5.3 在节点间传递私有状态](#5.3 在节点间传递私有状态)
5. LangGraph 的其他特性
5.1 使用 Overwrite 绕过 reducer
概念解释
在 LangGraph 中,reducers(归约器) 用于控制状态更新的处理方式。默认情况下,每个状态键都有其独立的 reducer 函数,用于决定如何合并节点返回的更新。但有时我们需要完全覆盖状态值而不是合并 ,这时候就需要使用 Overwrite。
为什么需要 Overwrite?
想象一下开发一个聊天应用时:
-
正常情况下,新消息会追加到消息列表中
-
但有时需要清空聊天记录并重新开始 ,这时候就需要绕过追加逻辑,直接覆盖整个消息列表
涉及的核心类和函数
| 名称 | 来源 | 作用 |
|---|---|---|
StateGraph |
langgraph.graph |
状态图构建器,用于定义工作流 |
START / END |
langgraph.graph |
特殊节点,表示工作流的开始和结束 |
Overwrite |
langgraph.types |
类型包装器,用于绕过 reducer 直接覆盖状态 |
TypedDict |
typing_extensions |
定义状态结构的类型字典 |
Annotated |
typing_extensions |
类型注解,用于指定 reducer 函数 |
operator.add |
operator |
Python 内置模块,用作列表追加的 reducer |
代码实现
python
from langgraph.graph import StateGraph, START, END # 核心图构建组件
from langgraph.types import Overwrite # 用于绕过 reducer 的特殊类型
from typing_extensions import Annotated, TypedDict # 类型注解工具
import operator # 用于定义 reducer 函数
class State(TypedDict):
"""定义工作流的状态结构
messages: 消息列表,使用 operator.add 作为 reducer
- 正常情况下,新消息会追加到列表末尾
- 使用 Overwrite 可以绕过这个行为,直接替换整个列表
"""
messages: Annotated[list, operator.add]
# Annotated[list, operator.add] 的含义:
# - list: 字段类型为列表
# - operator.add: reducer 函数,当状态更新时,新值会与旧值相加(列表追加)
def add_message(state: State):
"""节点1:添加消息 - 正常返回新消息,会触发 reducer 的追加行为"""
return {"messages": ["first message"]}
def replace_messages(state: State):
"""节点2:替换消息列表 - 使用 Overwrite 包装返回值,绕过 reducer 的追加逻辑"""
return {"messages": Overwrite(["replacement message"])}
# 创建 StateGraph 实例,传入状态类型定义
builder = StateGraph(State)
# 添加节点到图中
builder.add_node("add_message", add_message) # 添加消息节点
builder.add_node("replace_messages", replace_messages) # 替换消息节点
# 定义节点之间的连接关系(边)
builder.add_edge(START, "add_message") # 开始 → 添加消息
builder.add_edge("add_message", "replace_messages") # 添加消息 → 替换消息
builder.add_edge("replace_messages", END) # 替换消息 → 结束
# 编译图,生成可执行的工作流
graph = builder.compile()
# 调用工作流,传入初始状态
result = graph.invoke({"messages": ["initial"]})
print(result["messages"])
# 输出: ['replacement message']
# 注意:不是 ['initial message', 'first message', 'replacement message']
# 因为 Overwrite 绕过了 reducer,直接替换了整个列表
使用 Overwrite 的应用场景
| 场景 | 说明 |
|---|---|
| 重置对话 | 清空聊天历史,开始新对话 |
| 状态重置 | 在错误恢复后重置应用状态 |
| 数据清理 | 替换损坏或过时的数据 |
5.2 定义输入输出模式
概念解释
默认情况下,LangGraph 使用单一的状态模式 。但我们可以定义独立的输入和输出模式:
| 模式类型 | 说明 |
|---|---|
| 独立的输入模式 | 验证输入数据的结构 |
| 独立的输出模式 | 过滤输出数据,只返回需要的信息 |
| 内部模式 | 节点间通信使用的完整状态 |
为什么需要独立的内部模式?
考虑一个问答系统:
-
输入:用户的问题(字符串)
-
输出:AI 的答案(字符串)
-
内部:可能需要存储中间结果、上下文等信息
我们不想把内部状态都暴露给用户!
涉及的核心类和函数
| 名称 | 来源 | 作用 |
|---|---|---|
StateGraph |
langgraph.graph |
状态图构建器 |
input_schema |
StateGraph 参数 | 定义输入的 State 类 |
output_schema |
StateGraph 参数 | 定义输出的 State 类 |
state_schema |
StateGraph 参数 | 定义完整的 State 类(内部使用) |
StateGraph 初始化参数说明
| 参数名 | 类型 | 描述 |
|---|---|---|
input_schema |
type[InputT] 或 None |
定义 StateGraph 输入的 State 类 |
output_schema |
type[OutputT] 或 None |
定义 StateGraph 输出的 State 类 |
state_schema |
type[StateT] |
定义 StateGraph 的 State 类 |
代码实现
python
from langgraph.graph import StateGraph, START, END
from typing_extensions import TypedDict
# 1. 定义输入模式 - 只包含用户问题
class InputState(TypedDict):
"""输入状态:只暴露给用户的问题字段"""
question: str
# 2. 定义输出模式 - 只包含AI答案
class OutputState(TypedDict):
"""输出状态:只暴露给用户的答案字段"""
answer: str
# 3. 定义完整状态模式(内部使用)
class OverallState(InputState, OutputState):
"""完整状态:继承自 InputState 和 OutputState
用于节点间通信,包含所有必要的字段
但在最终输出时,只会返回 OutputState 中定义的字段
"""
pass
def answer_node(state: InputState):
"""处理输入并生成答案
参数: state - InputState 类型,只包含 question 字段
返回: dict - 包含 answer 和 question 的字典
注意:虽然返回了 question,但由于 OutputState 的定义,最终输出中不会包含 question 字段
"""
return {
"answer": f"Answer to: {state['question']}",
"question": state["question"] # 保留问题用于内部追踪
}
# 构建图时指定输入输出模式
builder = StateGraph(
OverallState, # 完整状态类型(节点间通信使用)
input_schema=InputState, # 输入验证:只接受 question 字段
output_schema=OutputState # 输出过滤:只返回 answer 字段
)
# 添加节点
builder.add_node("answer_node", answer_node)
builder.add_edge(START, "answer_node")
builder.add_edge("answer_node", END)
# 编译图
graph = builder.compile()
# 调用工作流
result = graph.invoke({"question": "What is LangGraph?"})
print(result)
# 输出: {'answer': 'Answer to: What is LangGraph?'}
# 注意:question 字段被过滤掉了,不在输出中
独立输入和输出的实际应用场景
| 场景 | 说明 |
|---|---|
| API 开发 | 定义清晰的请求/响应格式 |
| 微服务 | 服务间明确的数据契约 |
| 数据管道 | 明确的输入输出规范 |
5.3 在节点间传递私有状态
概念解释
有时节点间需要传递临时数据 ,这些数据对中间逻辑很重要,但不应该出现在最终输出中 ,只在特定节点间共享,这就是私有状态的概念。
应用场景举例
举个例子,现在我们需要根据数据库中的信息,生成相关数据报告,因此可以设置三个节点:
-
节点1:从数据库获取原始数据(包含敏感信息)
-
节点2:处理数据,过滤掉敏感信息
-
节点3:生成最终报告
此时,节点1 和 节点2 需要共享原始数据 ,但 节点3 不应该看到敏感信息。
涉及的核心类和函数
| 名称 | 来源 | 作用 |
|---|---|---|
StateGraph |
langgraph.graph |
状态图构建器 |
TypedDict |
typing_extensions |
定义状态结构 |
add_sequence |
builder 方法 |
添加一系列节点,按顺序执行 |
代码实现
python
from langgraph.graph import StateGraph, START, END
from typing_extensions import TypedDict
# 公共状态(最终输出中可见)
class OverallState(TypedDict):
"""公共状态:包含最终输出需要的字段"""
final_result: str
# 节点1的私有输出
class Node1Output(TypedDict):
"""节点1的私有输出状态 - sensitive_data 字段不会出现在最终状态中"""
sensitive_data: str
# 节点2需要的输入(包含私有数据)
class Node2Input(TypedDict):
"""节点2的输入状态 - 从节点1接收敏感数据"""
sensitive_data: str
def node_1(state: OverallState) -> Node1Output:
"""第一步:获取包含敏感信息的原始数据
返回 Node1Output 类型,确保 sensitive_data 只在节点间传递,不会泄露到最终输出
"""
private_data = "这是敏感信息"
print(f"Node1: 获取到敏感数据,但不会暴露给最终输出")
return {"sensitive_data": private_data}
def node_2(state: Node2Input) -> OverallState:
"""第二步:处理数据,移除敏感信息
接收 sensitive_data 作为输入,返回时只输出 final_result,敏感数据被过滤掉
"""
print(f"Node2: 处理敏感数据:{state['sensitive_data']}")
return {"final_result": "清理后的处理结果"}
def node_3(state: OverallState) -> OverallState:
"""第三步:只看到清理后的数据
无法访问 sensitive_data,只能看到 final_result
"""
print(f"Node3: 只能看到最终结果:{state['final_result']}")
return {"final_result": state["final_result"] + " - 完成"}
# 构建图
builder = StateGraph(OverallState)
# add_sequence: 支持添加一系列节点,按所给的顺序执行
builder.add_sequence([node_1, node_2, node_3])
builder.add_edge(START, "node_1")
graph = builder.compile()
# 调用工作流
response = graph.invoke({"final_result": "initial"})
print(f"\n最终输出:{response}")
# 输出:{'final_result': '清理后的处理结果 - 完成'}
# 注意:sensitive_data 字段没有出现在最终输出中
运行结果说明
python
Node1: 获取到敏感数据,但不会暴露给最终输出
Node2: 处理敏感数据:这是敏感信息
Node3: 只能看到最终结果:清理后的处理结果
最终输出:{'final_result': '清理后的处理结果 - 完成'}
节点间传递私有状态的实际应用场景
| 场景 | 说明 |
|---|---|
| 数据处理 | 中间处理步骤的临时数据 |
| 认证流程 | 令牌等敏感信息的传递 |
| 复杂计算 | 中间计算结果 |
| 错误处理 | 错误详情在内部传递,但对外提供友好消息 |
总结
以上三个特性让 LangGraph 能够处理复杂的企业级应用场景,同时保持代码的清晰和安全性:
-
Overwrite:灵活控制状态更新策略
-
输入输出模式:清晰的数据边界和接口定义
-
私有状态:安全的节点间数据传递
这些特性共同构成了 LangGraph 强大的状态管理能力,使其能够构建生产级的 AI 应用。