• 微信:WANCOME
  • 扫码加微信,提供专业咨询
  • 服务热线
  • 13215191218
    13027920428

  • 微信扫码访问本页
desc-2
环企首页

LangGraph 中状态与多重状态详解

📦 一、状态(State)的基本概念

在 LangGraph 中,状态(State) 是一个在所有节点之间共享的、可变的数据结构。它会在图执行的每个步骤中依次传递、更新和扩展。

from typing import TypedDict, Annotated, List
from langgraph.graph import add_messages
from langchain_core.messages import BaseMessage

class MyState(TypedDict):
    messages: Annotated[List[BaseMessage], add_messages]  # 使用 reducer
    user_name: str                                         # 普通字段
    step_count: int                                        # 普通字段

状态的核心特点

特性 说明
共享性 所有节点都能读取和修改状态的任意字段
不可变性 节点返回的更新是增量更新,不是完全替换
Reducer 函数 决定如何合并新旧值(如追加、累加、覆盖等)
类型安全 通过 TypedDict 定义结构,IDE 有类型提示

🧩 二、Reducer 机制(归约函数)

Reducer 是 LangGraph 最核心的设计之一。当你认为“节点返回的更新应该如何合并到现有状态”时,就需要 Reducer。

1. 内置 Reducer:add_messages

from typing import Annotated
from langgraph.graph.message import add_messages

class State(TypedDict):
    messages: Annotated[list, add_messages]   # 消息会追加,不会覆盖

效果

  • 节点1 返回 {"messages": [msg1]}
  • 节点2 返回 {"messages": [msg2]}
  • 最终状态 messages = [msg1, msg2](追加)

2. 无 Reducer(默认覆盖)

class State(TypedDict):
    user_name: str     # 没有 Annotated,默认覆盖

# 节点1 返回 {"user_name": "Alice"}
# 节点2 返回 {"user_name": "Bob"}
# 最终: user_name = "Bob"(覆盖)

3. 自定义 Reducer

from typing import Annotated

def sum_reducer(left: int, right: int) -> int:
    """累加 reducer"""
    return left + right

class State(TypedDict):
    total: Annotated[int, sum_reducer]  # 每次更新都会累加

# 节点1 返回 {"total": 10} → total = 10
# 节点2 返回 {"total": 5}  → total = 15

4. 更复杂的自定义 Reducer(合并列表)

def merge_lists(left: list, right: list) -> list:
    """合并列表,去重保留顺序"""
    seen = set()
    result = []
    for item in left + right:
        if item not in seen:
            seen.add(item)
            result.append(item)
    return result

class State(TypedDict):
    visited_nodes: Annotated[list, merge_lists]

🔄 三、节点的状态更新模式

节点必须返回一个字典,代表要对状态进行的增量更新

模式1:更新单个字段

def node_a(state: State) -> dict:
    return {"user_name": "Alice"}   # 只更新 user_name

模式2:更新多个字段

def node_b(state: State) -> dict:
    return {
        "user_name": "Bob",
        "step_count": state["step_count"] + 1
    }

模式3:更新时依赖当前状态

def node_c(state: State) -> dict:
    messages = state["messages"]
    last_message = messages[-1] if messages else None
    response = process(last_message)
    return {"messages": [response]}   # 追加到 messages 列表

模式4:不更新任何状态(但可以执行副作用)

def node_d(state: State) -> dict:
    print(f"Current user: {state['user_name']}")
    return {}   # 返回空字典,不修改状态

🎯 四、多层级状态(嵌套状态)

LangGraph 支持复杂的嵌套状态结构,适合大型应用。

1. 简单嵌套

class UserState(TypedDict):
    name: str
    age: int

class AppState(TypedDict):
    user: UserState          # 嵌套
    messages: Annotated[list, add_messages]
def update_user(state: AppState) -> dict:
    return {"user": {"name": "Alice", "age": 25}}   # 整体替换 user

def increment_age(state: AppState) -> dict:
    new_age = state["user"]["age"] + 1
    return {"user": {"age": new_age}}   # 只更新 age 字段(合并)

2. 深度嵌套 + Reducer

from typing import Annotated, TypedDict

class Metrics(TypedDict):
    tokens: Annotated[int, sum_reducer]      # 累加 token
    steps: Annotated[int, sum_reducer]       # 累加 step

class NodeMetrics(TypedDict):
    node_name: str
    metrics: Metrics

class State(TypedDict):
    overall_metrics: Annotated[Metrics, lambda l, r: Metrics(
        tokens=l["tokens"] + r["tokens"],
        steps=l["steps"] + r["steps"]
    )]

🧠 五、子图状态(Subgraph State)

当图中包含子图时,父状态和子状态可以映射转换

子图定义

class SubgraphState(TypedDict):
    query: str
    result: str

def subgraph_node(state: SubgraphState) -> dict:
    return {"result": f"Processed: {state['query']}"}

父图中调用子图

from langgraph.graph import StateGraph

# 父状态定义
class ParentState(TypedDict):
    user_input: str
    final_output: str

# 构建子图
subgraph = StateGraph(SubgraphState)
subgraph.add_node("process", subgraph_node)
subgraph.set_entry_point("process")
subgraph.set_finish_point("process")
compiled_subgraph = subgraph.compile()

# 在父图中使用子图(需要做状态映射)
def call_subgraph(state: ParentState) -> dict:
    # 将父状态映射为子图输入
    sub_input = {"query": state["user_input"]}
    sub_result = compiled_subgraph.invoke(sub_input)
    # 将子图输出映射回父状态
    return {"final_output": sub_result["result"]}

📊 六、状态 Schema 的三种定义方式

方式 代码示例 适用场景
TypedDict class State(TypedDict): 最常见,类型提示好
Dataclass @dataclass class State: 需要默认值、方法时
Pydantic BaseModel class State(BaseModel): 需要数据验证时
# 方式1: TypedDict
from typing import TypedDict
class State(TypedDict):
    messages: list

# 方式2: Dataclass
from dataclasses import dataclass, field
@dataclass
class State:
    messages: list = field(default_factory=list)

# 方式3: Pydantic
from pydantic import BaseModel
class State(BaseModel):
    messages: list = []

🎬 七、完整示例:带多重状态的 Agent

from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, add_messages
from langchain_core.messages import HumanMessage, AIMessage

# 定义状态(包含多个字段)
class AgentState(TypedDict):
    messages: Annotated[list, add_messages]   # 对话历史
    iteration: int                             # 迭代次数
    intermediate_results: list                 # 中间结果
    final_answer: str                          # 最终答案

# 处理节点
def assistant(state: AgentState) -> dict:
    print(f"迭代 {state['iteration'] + 1}: 处理中...")
    
    # 模拟处理逻辑
    last_msg = state["messages"][-1].content
    result = f"处理结果: {last_msg}"
    
    # 返回更新
    return {
        "messages": [AIMessage(content=result)],
        "iteration": state["iteration"] + 1,
        "intermediate_results": [result]
    }

def finalizer(state: AgentState) -> dict:
    # 汇总所有中间结果
    summary = " → ".join(state["intermediate_results"])
    return {"final_answer": summary}

# 构建图
builder = StateGraph(AgentState)
builder.add_node("assistant", assistant)
builder.add_node("finalize", finalizer)
builder.set_entry_point("assistant")
builder.add_edge("assistant", "finalize")
builder.set_finish_point("finalize")
graph = builder.compile()

# 运行
result = graph.invoke({
    "messages": [HumanMessage(content="计算 3+5")],
    "iteration": 0,
    "intermediate_results": [],
    "final_answer": ""
})

print("最终状态:")
print(f"- 消息数: {len(result['messages'])}")
print(f"- 迭代次数: {result['iteration']}")
print(f"- 中间结果: {result['intermediate_results']}")
print(f"- 最终答案: {result['final_answer']}")

输出

迭代 1: 处理中...
最终状态:
- 消息数: 2
- 迭代次数: 1
- 中间结果: ['处理结果: 计算 3+5']
- 最终答案: 处理结果: 计算 3+5

💎 总结

概念 核心要点
状态 节点间共享的 TypedDict,通过返回值增量更新
Reducer 决定如何合并新旧值(追加/覆盖/累加/自定义)
add_messages 最常用的 reducer,自动追加消息列表
多字段状态 可包含多个独立字段,不同字段可同时更新
嵌套状态 支持嵌套 TypedDict / List / Dict 等复杂结构
子图状态 子图有独立状态,通过映射与父状态传递数据

最佳实践

  1. 对消息列表始终使用 Annotated[list, add_messages]
  2. 计数器、累加器使用自定义 reducer
  3. 简单覆盖用普通字段
  4. 大状态拆分为多个字段,减少碰撞和冗余