- LangGraph 入门基础全解析
- LangGraph中的Reducer是什么
- LangGraph 核心概念详解:从编译到可视化
- LangGraph实战教程:一文搞懂图的状态(State)管理
- LangGraph实战教程:状态管理与
graph.invoke入参深度解析 - LangGraph高级教程:Multi Schema多状态管理详解
在构建与LLM交互的LangGraph应用时,消息列表的管理是一个非常常见的需求。每次调用模型都需要传入历史消息,而模型返回的新消息又需要追加到列表中。如果每次都手动处理消息的合并逻辑,不仅繁琐,而且容易出错。
LangGraph官方为此提供了预定义状态类型,帮助开发者快速搭建基于消息的对话流程。
一、MessagesState:消息管理的开箱即用方案
1.1 什么是MessagesState?
MessagesState是 langgraph.graph.message模块中预定义的一个状态类型,专门用于管理LLM交互过程中的消息列表。
源码定义:
py
class MessagesState(TypedDict):
messages: Annotated[list[AnyMessage], add_messages]
可以看到,MessagesState只有一个字段 messages:
- 类型 :
list[AnyMessage],可以存储任意类型的消息(HumanMessage、AIMessage、SystemMessage等) - Reducer :绑定了
add_messages,这是一个内置的归约器,负责将新消息智能地合并到已有消息列表中
1.2 add_messages的工作机制
add_messages并不是简单的列表拼接,它具备以下智能行为:
- 追加新消息:默认将新消息追加到列表末尾
- 去重 :如果新消息的
id与列表中某条消息的id相同,则会覆盖旧消息,而不是重复添加 - 删除消息 :如果新消息的内容为
None,则会从列表中移除该id对应的消息
这种机制非常适合多轮对话场景,既能保证消息顺序,又能避免重复。
1.3 继承MessagesState扩展自定义字段
开发者可以直接继承 MessagesState,在其基础上添加业务所需的字段。
py
from langgraph.graph.message import MessagesState
from typing import TypedDict
class OverAllState(MessagesState):
username: str
output: str
此时 OverAllState拥有三个字段:
messages:来自父类,管理对话消息username:自定义字段,存储用户名output:自定义字段,存储模型输出的文本内容
1.4 完整案例:集成ChatDeepSeek
py
from langchain_deepseek import ChatDeepSeek
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import MessagesState
from langchain.messages import HumanMessage
from dotenv import load_dotenv
load_dotenv(override=True)
model = ChatDeepSeek(
model='deepseek-v4-flash',
extra_body={"thinking": {"type": "disabled"}}
)
class OverAllState(MessagesState):
username: str
output: str
def node_a(state: OverAllState) -> OverAllState:
"""构造用户消息"""
return {
"messages": [HumanMessage("你好,我是 " + state["username"])]
}
def llm_node(state: OverAllState) -> OverAllState:
"""调用LLM并返回结果"""
res = model.invoke(state["messages"])
return {
"messages": [res],
"output": res.content
}
builder = StateGraph(state_schema=OverAllState)
builder.add_node("node_a", node_a)
builder.add_node("llm_node", llm_node)
builder.add_edge(START, "node_a")
builder.add_edge("node_a", "llm_node")
builder.add_edge("llm_node", END)
graph = builder.compile()
response = graph.invoke({"username": "小黄"})
print(response)
输出结果:
css
{
"messages": [
HumanMessage(content="你好,我是 小黄", ...),
AIMessage(content="你好呀,小黄!😊 我是DeepSeek,很高兴认识你!...", ...)
],
"username": "小黄",
"output": "你好呀,小黄!😊 我是DeepSeek,很高兴认识你!..."
}
1.5 执行流程分析
node_a执行 :返回一条HumanMessage,内容为"你好,我是 小黄"add_messages合并 :将该消息追加到messages列表中llm_node执行 :从state["messages"]读取完整消息列表,调用模型- 模型返回 :生成
AIMessage add_messages再次合并 :将AIMessage追加到messages列表中- 最终状态 :
messages包含完整的对话历史
整个过程无需手动处理消息的追加逻辑,MessagesState和 add_messages自动完成了所有管理工作。
二、AgentState:LangChain Agent的内部状态
2.1 什么是AgentState?
AgentState是 LangChain Agent 内部使用的状态类型,位于 langchain.agents.middleware.types.AgentState。由于 LangChain Agent 底层也是基于 LangGraph 构建的,所以 AgentState本质上也是一个 LangGraph 状态类型。
源码定义:
py
class AgentState(TypedDict, Generic[ResponseT]):
"""State schema for the agent."""
messages: Required[Annotated[list[AnyMessage], add_messages]]
jump_to: NotRequired[Annotated[JumpTo | None, EphemeralValue, PrivateStateAttr]]
structured_response: NotRequired[Annotated[ResponseT, OmitFromInput]]
2.2 三个核心字段
字段一:messages
ini
messages: Required[Annotated[list[AnyMessage], add_messages]]
与 MessagesState中的 messages字段类似,用于存储 Agent 运行过程中的消息列表,同样使用 add_messages作为 Reducer。
字段二:jump_to
ini
jump_to: NotRequired[Annotated[JumpTo | None, EphemeralValue, PrivateStateAttr]]
jump_to是 Agent 内部使用的控制字段,主要用于 Agent 中间件体系,表示运行流程的跳转意图。
重要提示 :在自定义的普通 StateGraph中,即使状态中定义了 jump_to字段,LangGraph 也不会自动根据该字段的值跳转到某个节点。如果需要控制图的分支流向,应使用:
ini
Command(goto="node_name")
字段三:structured_response
ini
structured_response: NotRequired[Annotated[ResponseT, OmitFromInput]]
structured_response用于存储 Agent 最终生成的结构化输出。当使用 LangChain Agent 的结构化输出能力时(例如指定 response_format),结果会被写入该字段。
其中 OmitFromInput表示该字段不应作为外部输入暴露给调用方,而是由 Agent 运行过程中内部生成。
2.3 使用建议
AgentState是专门为 LangChain Agent 运行时设计的状态类型。在普通自定义 LangGraph 项目中,一般不建议直接基于 AgentState扩展图状态。
如果你只是构建一个与 LLM 交互的图,推荐使用 MessagesState作为基类;如果你在使用 LangChain Agent 的高级功能,Agent 内部自然会使用 AgentState。
三、总结
| 特性 | MessagesState | AgentState |
|---|---|---|
| 所属模块 | langgraph.graph.message |
langchain.agents.middleware.types |
| 核心字段 | messages |
messages、jump_to、structured_response |
| 消息Reducer | add_messages |
add_messages |
| 适用场景 | 通用的LLM对话图 | LangChain Agent内部运行 |
| 推荐使用 | ✅ 推荐作为自定义状态的基类 | ❌ 不推荐在普通项目中使用 |
一句话总结:MessagesState是构建LLM对话应用的利器,它帮你自动管理消息列表,让你专注于业务逻辑;而 AgentState是 LangChain Agent 的内部实现细节,了解即可,普通项目无需直接使用。
四、相关面试题
面试题1:MessagesState中的 add_messagesReducer 和普通的 addReducer 有什么区别?
参考答案:
| 特性 | add |
add_messages |
|---|---|---|
| 合并方式 | 简单的列表拼接 | 基于消息ID的智能合并 |
| 去重能力 | 无 | 有(相同ID的消息会覆盖) |
| 删除能力 | 无 | 有(消息内容为None时删除) |
| 适用类型 | 任意列表 | AnyMessage消息列表 |
add_messages比 add更适合消息管理场景,因为它能正确处理消息的去重和更新,避免在多轮对话中出现重复消息。
面试题2:如果在一个节点中返回了两条 HumanMessage,add_messages会如何处理?
参考答案:
两条消息都会追加到 messages列表末尾。add_messages会遍历节点返回的消息列表,逐条处理每条消息:
- 如果消息的
id在列表中不存在,则追加 - 如果消息的
id已存在,则覆盖
因此,返回多条不同 id的消息等同于连续多次追加。
面试题3:如何删除 MessagesState中的某条历史消息?
参考答案:
可以利用 add_messages的删除机制:返回一条与被删除消息具有相同 id、但内容为 None的消息。
python
from langchain.messages import AIMessage, RemoveMessage
def delete_node(state: OverAllState) -> OverAllState:
# 删除最后一条消息
last_message = state["messages"][-1]
return {
"messages": [RemoveMessage(id=last_message.id)]
}
LangGraph 提供了 RemoveMessage工具类,专门用于标记要删除的消息。
面试题4:AgentState中的 jump_to字段在普通 StateGraph中能用吗?
参考答案:
不能直接使用 。jump_to是 LangChain Agent 中间件体系内部使用的控制字段,普通 StateGraph并不会识别或处理该字段。
在自定义的 StateGraph中,如果需要控制图的分支跳转,应使用以下方式:
python
# 方式一:条件边
builder.add_conditional_edges("node_a", routing_function)
# 方式二:Command方式
from langgraph.types import Command
def node_with_routing(state) -> Command:
# 处理逻辑
return Command(goto="next_node")
面试题5:为什么不推荐在普通项目中使用 AgentState?
参考答案:
主要原因有三点:
- 设计目的不同 :
AgentState是为 LangChain Agent 的中间件体系设计的,包含了许多 Agent 内部专用的字段(如jump_to、structured_response),普通项目用不到这些字段。 - 依赖关系 :引入
AgentState意味着引入了langchain.agents模块的依赖,增加了项目的复杂度。 - 语义混淆 :使用
AgentState会让阅读代码的人误以为这个图是一个 Agent,但实际上它可能只是一个简单的 LLM 调用链。
推荐做法 :如果是构建与 LLM 交互的普通图,使用 MessagesState作为基类就足够了。
面试题6:MessagesState中的 messages字段为什么是 list[AnyMessage]而不是 list[BaseMessage]?
参考答案:
AnyMessage是 LangChain 中所有消息类型的联合类型(Union Type),包括 HumanMessage、AIMessage、SystemMessage、ToolMessage等。使用 AnyMessage比 BaseMessage更精确地表达了"这个列表可以包含任意类型的消息"这一语义。
从功能上看,两者都能正常工作,但 AnyMessage在类型提示和静态检查方面更为严谨。
面试题7:如果我想在 MessagesState的基础上增加一个 message_count字段,自动统计消息数量,应该如何实现?
参考答案:
可以使用自定义 Reducer 来实现自动计数:
py
from typing import TypedDict, Annotated
from langgraph.graph.message import MessagesState, add_messages
from langchain.messages import AnyMessage
def count_messages(current_count: int, new_messages: list[AnyMessage]) -> int:
"""自定义Reducer:统计消息总数"""
return current_count + len(new_messages)
class OverAllState(MessagesState):
message_count: Annotated[int, count_messages]
username: str
或者在节点中手动更新:
py
def log_node(state: OverAllState) -> OverAllState:
return {
"message_count": len(state["messages"])
}
第一种方式更自动化,第二种方式更直观。根据实际需求选择即可。
希望这篇教程能帮助你掌握 LangGraph 的预定义状态机制,让你的 LLM 应用开发更加高效!