langgraph的 MessagesState 解读

MessagesState 是 LangGraph 预置的状态类,专门为"对话历史 + 工具调用"这类场景设计。它的本质就是一个内置了 messages 字段、且该字段用 add_messages 作为 reducer 的 TypedDict

python 复制代码
# MessagesState 在源码中大致等价于
class MessagesState(TypedDict):
    messages: Annotated[list[AnyMessage], add_messages]

文章目录

    • [一、最基本用法:作为 StateGraph 的状态](#一、最基本用法:作为 StateGraph 的状态)
    • [二、add_messages 的三个核心行为](#二、add_messages 的三个核心行为)
    • [三、继承 MessagesState 扩展自定义字段](#三、继承 MessagesState 扩展自定义字段)
    • [四、多轮对话 + 记忆持久化](#四、多轮对话 + 记忆持久化)
    • [五、⚠️ 三个最容易踩的坑](#五、⚠️ 三个最容易踩的坑)

这意味着你不用自己写 Annotated[...] 那串样板,直接拿来用就行 。下面把关键用法拆开讲。

一、最基本用法:作为 StateGraph 的状态

python 复制代码
from langgraph.graph import StateGraph, START, END, MessagesState
from langchain_core.messages import HumanMessage, AIMessage

def chatbot_node(state: MessagesState):
    # 节点内通过 state["messages"] 读取完整历史
    last_user_msg = state["messages"][-1]
    reply = f"你说了:{last_user_msg.content}"
    # 只返回"新增的部分",框架会用 add_messages 合并
    return {"messages": [AIMessage(content=reply)]}

builder = StateGraph(MessagesState)
builder.add_node("chatbot", chatbot_node)
builder.add_edge(START, "chatbot")
builder.add_edge("chatbot", END)

graph = builder.compile()
result = graph.invoke({
    "messages": [HumanMessage(content="你好")]
})

📌 注意:MessagesState 是状态 schema,不是普通的 class 实例。不要写成 MessagesState(messages=[...]),而是在调用图时传 {"messages": [...]},或继承它来扩展字段 。

二、add_messages 的三个核心行为

add_messages 这个 reducer 决定了 messages 字段怎么合并,它比简单的 list + list 聪明得多 :

  1. 追加新消息(没有 id 或 id 不匹配时)
python 复制代码
# 旧: [msg1, msg2]  +  节点返回 [msg3]
# 结果: [msg1, msg2, msg3]
  1. 按 id 替换已有消息(流式输出场景必备)
python 复制代码
from langchain_core.messages import AIMessage
from langgraph.graph.message import add_messages

existing = [AIMessage(content="它是5", id="msg-2")]
correction = [AIMessage(content="它是4", id="msg-2")]  # 同 id
result = add_messages(existing, correction)
# 结果: [AIMessage(content="它是4", id="msg-2")]
# 长度还是 1,没有重复

如果换成 operator.add,错误答案和正确答案会并排在列表里 。

  1. RemoveMessage 删除消息(裁剪历史以控制 token)
python 复制代码
from langchain_core.messages import RemoveMessage
# 节点返回这个,id 对应的消息会从列表中移除
return {"messages": [RemoveMessage(id="msg-2")]}

三、继承 MessagesState 扩展自定义字段

如果你的 Agent 还需要追踪其他状态(当前工具、迭代次数、用户信息等),直接继承:

python 复制代码
from langgraph.graph import MessagesState

class AgentState(MessagesState):
    current_tool: str
    iteration: int
    user_info: dict

def chatbot(state: AgentState):
    # messages 字段依然享受 add_messages 的全部能力
    reply = AIMessage(content="处理中...")
    return {
        "messages": [reply],
        "current_tool": "search",
        "iteration": state["iteration"] + 1
    }

这是多智能体协作、RAG、带业务上下文的对话 Agent 最常用的模式 。

四、多轮对话 + 记忆持久化

配合 MemorySaverSqliteSaver 做 checkpoint,用 thread_id 区分不同会话:

python 复制代码
from langgraph.checkpoint.memory import MemorySaver

checkpointer = MemorySaver()
graph = builder.compile(checkpointer=checkpointer)

config = {"configurable": {"thread_id": "conversation-1"}}

# 第一轮
graph.stream(
    {"messages": [{"role": "user", "content": "广州天气怎么样?"}]},
    config, stream_mode="values"
)

# 第二轮:不需要手动拼历史,框架自动从 checkpoint 恢复
graph.stream(
    {"messages": [{"role": "user", "content": "那明天呢?"}]},
    config, stream_mode="values"
)

messages 字段会自动累积,thread_id 不同的会话互不干扰 。

五、⚠️ 三个最容易踩的坑

坑 1:原地修改 state["messages"]

python 复制代码
# ❌ 错误:直接 append 会破坏 LangGraph 的不可变更新契约
def bad_node(state):
    state["messages"].append(AIMessage(content="hi"))
    return {}

# ✅ 正确:返回增量,让 reducer 合并
def good_node(state):
    return {"messages": [AIMessage(content="hi")]}

原地修改会导致浅拷贝下多节点共享引用,引发竞态丢失 。

坑 2:自定义 TypedDict 忘了加 add_messages reducer

python 复制代码
# ❌ 错误:没有 reducer,新值会直接覆盖整个列表,历史全丢
class BadState(TypedDict):
    messages: list[AnyMessage]

# ✅ 正确
class GoodState(TypedDict):
    messages: Annotated[list[AnyMessage], add_messages]

这也正是为什么直接用 MessagesState 更安全 。

坑 3:条件分支里有的路径返回 messages、有的不返回

如果 route_to_tool 分支里,工具路径返回了 {"messages": [...]},而直接响应路径只返回 {"messages": [ai_msg]},会造成历史断裂。确保所有分支都按统一方式返回消息增量 。


总的来说,MessagesState 就是 LangGraph 给你的一把"对话状态瑞士军刀":开箱即用管理消息历史、自动处理追加/更新/删除、还能继承扩展。只要是涉及聊天、工具调用、多轮对话的 Agent,直接用它就对了;只有当你需要非常特殊的合并逻辑(比如滑动窗口只保留最近 N 条)时,才需要回到自定义的 TypedDict + 自定义 reducer 。

如果你是在具体的 Agent 架构里用(比如 ReAct Agent、多智能体协作、或者带 RAG 的对话),告诉我场景,我可以给到更贴合的代码示例。

相关推荐
达达尼昂21 小时前
Flutter AI Harness 如何让 Agent 参与软件开发全流程
android·人工智能·后端
人工智能研究所21 小时前
GitHub 10k+ Star:用自然语言描述系统,AI 帮你生成可交互架构图
人工智能·github·交互·自然语言·系统架构图·archify
王六米。21 小时前
武汉自动意志科技有限公司:人工智能应用软件开发:OpenClaw 企业部署怎样配置权限、日志和回滚
人工智能·科技·企业ai落地·ai智能体开发·openclaw部署·武汉自动意志科技·智钳claw
yangmu320321 小时前
脑机接口:当意识越过肉身的边界
人工智能·科技
NGINX开源社区21 小时前
NGINX Ingress Controller 5.5:安全性与性能提升,迁移更轻松
java·服务器·nginx
Warren2Lynch21 小时前
从文本到架构:Visual Paradigm AI Chatbot V2 深度评测与实战指南引言
人工智能·架构
k4m7v2pz21 小时前
Rust 长跑守护进程日志治理:切分、时区与结构化
开发语言·后端·rust·日志系统·日志轮转·ndjson
FfHUCisI21 小时前
Golang RESTful API 设计原则
开发语言·golang·restful
breeze jiang21 小时前
ESLint flat config 配置实战:五大字段、规则严重级别与 --fix 能力边界详解
开发语言·前端·javascript
云烟成雨TD21 小时前
Micrometer 系列【25】Spring Boot Actuator | Spring MVC、Spring WebFlux 指标
java·spring boot·micrometer